From: Denis Vlasenko Date: Wed, 27 Dec 2006 04:35:09 +0000 (-0000) Subject: Correcting tag name to be like previous ones X-Git-Tag: 1_3_0^0 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=7b76233290bd9dead1848f28ed6d0edfcceb8e09;p=oweals%2Fbusybox.git Correcting tag name to be like previous ones --- 7b76233290bd9dead1848f28ed6d0edfcceb8e09 diff --git a/.indent.pro b/.indent.pro new file mode 100644 index 000000000..492ecf1c7 --- /dev/null +++ b/.indent.pro @@ -0,0 +1,33 @@ +--blank-lines-after-declarations +--blank-lines-after-procedures +--break-before-boolean-operator +--no-blank-lines-after-commas +--braces-on-if-line +--braces-on-struct-decl-line +--comment-indentation25 +--declaration-comment-column25 +--no-comment-delimiters-on-blank-lines +--cuddle-else +--continuation-indentation4 +--case-indentation0 +--else-endif-column33 +--space-after-cast +--line-comments-indentation0 +--declaration-indentation1 +--dont-format-first-column-comments +--dont-format-comments +--honour-newlines +--indent-level4 +/* changed from 0 to 4 */ +--parameter-indentation4 +--line-length78 /* changed from 75 */ +--continue-at-parentheses +--no-space-after-function-call-names +--dont-break-procedure-type +--dont-star-comments +--leave-optional-blank-lines +--dont-space-special-semicolon +--tab-size4 +/* additions by Mark */ +--case-brace-indentation0 +--leave-preprocessor-space diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..8f6498b98 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,173 @@ +List of the authors of code contained in BusyBox. + +If you have code in BusyBox, you should be listed here. If you should be +listed, or the description of what you have done needs more detail, or is +incorrect, _please_ let me know. + + -Erik + +----------- + +Peter Willis + eject + +Emanuele Aina + run-parts + +Erik Andersen + Tons of new stuff, major rewrite of most of the + core apps, tons of new apps as noted in header files. + Lots of tedious effort writing these boring docs that + nobody is going to actually read. + +Laurence Anderson + rpm2cpio, unzip, get_header_cpio, read_gz interface, rpm + +Jeff Angielski + ftpput, ftpget + +Enrik Berkhan + setconsole + +Jim Bauer + modprobe shell dependency + +Edward Betts + expr, hostid, logname, whoami + +John Beppu + du, nslookup, sort + +David Brownell + zcip + +Brian Candler + tiny-ls(ls) + +Randolph Chung + fbset, ping, hostname + +Dave Cinege + more(v2), makedevs, dutmp, modularization, auto links file, + various fixes, Linux Router Project maintenance + +Jordan Crouse + ipcalc + +Magnus Damm + tftp client + insmod powerpc support + +Larry Doolittle + pristine source directory compilation, lots of patches and fixes. + +Glenn Engel + httpd + +Gennady Feldman + Sysklogd (single threaded syslogd, IPC Circular buffer support, + logread), various fixes. + +Robert Griebl + modprobe, hwclock, suid/sgid handling, tinylogin integration + many bugfixes and enhancements + +Karl M. Hegbloom + cp_mv.c, the test suite, various fixes to utility.c, &c. + +Daniel Jacobowitz + mktemp.c + +Matt Kraai + documentation, bugfixes, test suite + +Rob Landley + Became busybox maintainer in 2006. + + sed (major rewrite in 2003, and I now maintain the thing) + bunzip2 (complete from-scratch rewrite, then mjn3 optimized the result) + sort (more or less from scratch rewrite in 2004, I now maintain it) + mount (rewrite in 2005, I maintain the new one) + +Stephan Linz + ipcalc, Red Hat equivalence + +John Lombardo + tr + +Glenn McGrath + Common unarchiving code and unarchiving applets, ifupdown, ftpgetput, + nameif, sed, patch, fold, install, uudecode. + Various bugfixes, review and apply numerous patches. + +Manuel Novoa III + cat, head, mkfifo, mknod, rmdir, sleep, tee, tty, uniq, usleep, wc, yes, + mesg, vconfig, nice, renice, + make_directory, parse_mode, dirname, mode_string, + get_last_path_component, simplify_path, and a number trivial libbb routines + + also bug fixes, partial rewrites, and size optimizations in + ash, basename, cal, cmp, cp, df, du, echo, env, ln, logname, md5sum, mkdir, + mv, realpath, rm, sort, tail, touch, uname, watch, arith, human_readable, + interface, dutmp, ifconfig, route + +Vladimir Oleynik + cmdedit; bb_mkdep, xargs(current), httpd(current); + ports: ash, crond, fdisk (initial, unmaintained now), inetd, stty, traceroute, + top; + locale, various fixes + and irreconcilable critic of everything not perfect. + +Bruce Perens + Original author of BusyBox in 1995, 1996. Some of his code can + still be found hiding here and there... + +Rodney Radford + ipcs, ipcrm + +Tim Riker + bug fixes, member of fan club + +Kent Robotti + reset, tons and tons of bug reports and patches. + +Chip Rosenthal , + wget - Contributed by permission of Covad Communications + +Pavel Roskin + Lots of bugs fixes and patches. + +Gyepi Sam + Remote logging feature for syslogd + +Rob Sullivan + comm + +Linus Torvalds + mkswap, fsck.minix, mkfs.minix + +Mark Whitley + grep, sed, cut, xargs(previous), + style-guide, new-applet-HOWTO, bug fixes, etc. + +Charles P. Wright + gzip, mini-netcat(nc) + +Enrique Zanardi + tarcat (since removed), loadkmap, various fixes, Debian maintenance + +Tito Ragusa + devfsd and size optimizations in strings, openvt, chvt, deallocvt, hdparm, + fdformat, lsattr, chattr, id and eject. + +Paul Fox + vi editing mode for ash, various other patches/fixes + +Roberto A. Foglietta + port: dnsd + +Bernhard Fischer + misc + +Mike Frysinger + initial e2fsprogs, printenv, setarch, sum, misc diff --git a/Config.in b/Config.in new file mode 100644 index 000000000..fb07c681b --- /dev/null +++ b/Config.in @@ -0,0 +1,479 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +mainmenu "BusyBox Configuration" + +config HAVE_DOT_CONFIG + bool + default y + +menu "Busybox Settings" + +menu "General Configuration" + +config NITPICK + bool "See lots more (probably unnecessary) configuration options." + default n + help + Some BusyBox applets have more configuration options than anyone + will ever care about. To avoid drowining people in complexity, most + of the applet features that can be set to a sane default value are + hidden, unless you hit the above switch. + + This is better than to telling people to edit the busybox source + code, but not by much. + + See http://en.wikipedia.org/wiki/Fibber_McGee_and_Molly#The_Closet + + You have been warned. + +config DESKTOP + bool "Enable options for full-blown desktop systems" + default n + help + Enable options and features which are not essential. + Select this only if you plan to use busybox on full-blown + desktop machine with common Linux distro, not on an embedded box. + +choice + prompt "Buffer allocation policy" + default FEATURE_BUFFERS_USE_MALLOC + depends on NITPICK + help + There are 3 ways BusyBox can handle buffer allocations: + - Use malloc. This costs code size for the call to xmalloc. + - Put them on stack. For some very small machines with limited stack + space, this can be deadly. For most folks, this works just fine. + - Put them in BSS. This works beautifully for computers with a real + MMU (and OS support), but wastes runtime RAM for uCLinux. This + behavior was the only one available for BusyBox versions 0.48 and + earlier. + +config FEATURE_BUFFERS_USE_MALLOC + bool "Allocate with Malloc" + +config FEATURE_BUFFERS_GO_ON_STACK + bool "Allocate on the Stack" + +config FEATURE_BUFFERS_GO_IN_BSS + bool "Allocate in the .bss section" + +endchoice + +config SHOW_USAGE + bool "Show terse applet usage messages" + default y + help + All BusyBox applets will show help messages when invoked with + wrong arguments. You can turn off printing these terse usage + messages if you say no here. + This will save you up to 7k. + +config FEATURE_VERBOSE_USAGE + bool "Show verbose applet usage messages" + default n + select SHOW_USAGE + help + All BusyBox applets will show more verbose help messages when + busybox is invoked with --help. This will add a lot of text to the + busybox binary. In the default configuration, this will add about + 13k, but it can add much more depending on your configuration. + +config FEATURE_COMPRESS_USAGE + bool "Store applet usage messages in compressed form" + default y + depends on SHOW_USAGE + help + Store usage messages in compressed form, uncompress them on-the-fly + when --help is called. + + If you have a really tiny busybox with few applets enabled (and + bunzip2 isn't one of them), the overhead of the decompressor might + be noticeable. Also, if you run executables directly from ROM + and have very little memory, this might not be a win. Otherwise, + you probably want this. + +config FEATURE_INSTALLER + bool "Support --install [-s] to install applet links at runtime" + default n + help + Enable 'busybox --install [-s]' support. This will allow you to use + busybox at runtime to create hard links or symlinks for all the + applets that are compiled into busybox. This feature requires the + /proc filesystem. + +config LOCALE_SUPPORT + bool "Enable locale support (system needs locale for this to work)" + default n + help + Enable this if your system has locale support and you would like + busybox to support locale settings. + +config GETOPT_LONG + bool "Enable support for --long-options" + default y + help + Enable this if you want busybox applets to use the gnu --long-option + style, in addition to single character -a -b -c style options. + +config FEATURE_DEVPTS + bool "Use the devpts filesystem for Unix98 PTYs" + default y + help + Enable if you want BusyBox to use Unix98 PTY support. If enabled, + busybox will use /dev/ptmx for the master side of the pseudoterminal + and /dev/pts/ for the slave side. Otherwise, BSD style + /dev/ttyp will be used. To use this option, you should have + devpts mounted. + +config FEATURE_CLEAN_UP + bool "Clean up all memory before exiting (usually not needed)" + default n + depends on NITPICK + help + As a size optimization, busybox normally exits without explicitly + freeing dynamically allocated memory or closing files. This saves + space since the OS will clean up for us, but it can confuse debuggers + like valgrind, which report tons of memory and resource leaks. + + Don't enable this unless you have a really good reason to clean + things up manually. + +config FEATURE_SUID + bool "Support for SUID/SGID handling" + default n + help + With this option you can install the busybox binary belonging + to root with the suid bit set, and it'll and it'll automatically drop + priviledges for applets that don't need root access. + + If you're really paranoid and don't want to do this, build two + busybox binaries with different applets in them (and the appropriate + symlinks pointing to each binary), and only set the suid bit on the + one that needs it. The applets currently marked to need the suid bit + are login, passwd, su, ping, traceroute, crontab, dnsd, ipcrm, ipcs, + and vlock. + +config FEATURE_SYSLOG + bool "Support for syslog" + default n + help + This option is auto-selected when you select any applet which may + send its output to syslog. You do not need to select it manually. + +config FEATURE_SUID_CONFIG + bool "Runtime SUID/SGID configuration via /etc/busybox.conf" + default n if FEATURE_SUID + depends on FEATURE_SUID + help + Allow the SUID / SGID state of an applet to be determined at runtime + by checking /etc/busybox.conf. (This is sort of a poor man's sudo.) + The format of this file is as follows: + + = [Ssx-][Ssx-][x-] (|).(|) + + An example might help: + + [SUID] + su = ssx root.0 # applet su can be run by anyone and runs with euid=0/egid=0 + su = ssx # exactly the same + + mount = sx- root.disk # applet mount can be run by root and members of group disk + # and runs with euid=0 + + cp = --- # disable applet cp for everyone + + The file has to be owned by user root, group root and has to be + writeable only by root: + (chown 0.0 /etc/busybox.conf; chmod 600 /etc/busybox.conf) + The busybox executable has to be owned by user root, group + root and has to be setuid root for this to work: + (chown 0.0 /bin/busybox; chmod 4755 /bin/busybox) + + Robert 'sandman' Griebl has more information here: + . + +config FEATURE_SUID_CONFIG_QUIET + bool "Suppress warning message if /etc/busybox.conf is not readable" + default y + depends on FEATURE_SUID_CONFIG + help + /etc/busybox.conf should be readable by the user needing the SUID, check + this option to avoid users to be notified about missing permissions. + +config FEATURE_HAVE_RPC + bool "RPC support" + default y + help + Select this if you have rpc support. + This automatically turns off all configuration options that rely + on RPC. + +config SELINUX + bool "Support NSA Security Enhanced Linux" + default n + help + Enable support for SELinux in applets ls, ps, and id. Also provide + the option of compiling in SELinux applets. + + If you do not have a complete SELinux userland installed, this stuff + will not compile. Go visit + http://www.nsa.gov/selinux/index.html + to download the necessary stuff to allow busybox to compile with + this option enabled. Specifially, libselinux 1.28 or better is + directly required by busybox. If the installation is located in a + non-standard directory, provide it by invoking make as follows: + CFLAGS=-I \ + LDFLAGS=-L \ + make + + Most people will leave this set to 'N'. + +config BUSYBOX_EXEC_PATH + string "Path to BusyBox executable" + default "/proc/self/exe" + help + When Busybox applets need to run other busybox applets, BusyBox + sometimes needs to exec() itself. When the /proc filesystem is + mounted, /proc/self/exe always points to the currently running + executable. If you haven't got /proc, set this to wherever you + want to run BusyBox from. + +endmenu + +menu 'Build Options' + +config STATIC + bool "Build BusyBox as a static binary (no shared libs)" + default n + help + If you want to build a static BusyBox binary, which does not + use or require any shared libraries, then enable this option. + This can cause BusyBox to be considerably larger, so you should + leave this option false unless you have a good reason (i.e. + your target platform does not support shared libraries, or + you are building an initrd which doesn't need anything but + BusyBox, etc). + + Most people will leave this set to 'N'. + +config BUILD_LIBBUSYBOX + bool "Build shared libbusybox" + default n + help + Build a shared library libbusybox.so which contains all + libraries used inside busybox. + + This is an experimental feature intended to support the upcoming + "make standalone" mode. Enabling it against the one big busybox + binary serves no purpose (and increases the size). You should + almost certainly say "no" to this right now. + +config FEATURE_FULL_LIBBUSYBOX + bool "Feature-complete libbusybox" + default n if !FEATURE_SHARED_BUSYBOX + depends on BUILD_LIBBUSYBOX + help + Build a libbusybox with the complete feature-set, disregarding + the actually selected config. + + Normally, libbusybox will only contain the features which are + used by busybox itself. If you plan to write a separate + standalone application which uses libbusybox say 'Y'. + + Note: libbusybox is GPL, not LGPL, and exports no stable API that + might act as a copyright barrier. We can and will modify the + exported function set between releases (even minor version number + changes), and happily break out-of-tree features. + + Say 'N' if in doubt. + +config FEATURE_SHARED_BUSYBOX + bool "Use shared libbusybox for busybox" + default y if BUILD_LIBBUSYBOX + depends on !STATIC && BUILD_LIBBUSYBOX + help + Use libbusybox.so also for busybox itself. + You need to have a working dynamic linker to use this variant. + +config LFS + bool "Build with Large File Support (for accessing files > 2 GB)" + default n + select FDISK_SUPPORT_LARGE_DISKS + help + If you want to build BusyBox with large file support, then enable + this option. This will have no effect if your kernel or your C + library lacks large file support for large files. Some of the + programs that can benefit from large file support include dd, gzip, + cp, mount, tar, and many others. If you want to access files larger + than 2 Gigabytes, enable this option. Otherwise, leave it set to 'N'. + +config BUILD_AT_ONCE + bool "Compile all sources at once" + default n + help + Normally each source-file is compiled with one invocation of + the compiler. + If you set this option, all sources are compiled at once. + This gives the compiler more opportunities to optimize which can + result in smaller and/or faster binaries. + + Setting this option will consume alot of memory, e.g. if you + enable all applets with all features, gcc uses more than 300MB + RAM during compilation of busybox. + + This option is most likely only beneficial for newer compilers + such as gcc-4.1 and above. + + Say 'N' unless you know what you are doing. + +endmenu + +menu 'Debugging Options' + +config DEBUG + bool "Build BusyBox with extra Debugging symbols" + default n + help + Say Y here if you wish to examine BusyBox internals while applets are + running. This increases the size of the binary considerably, and + should only be used when doing development. If you are doing + development and want to debug BusyBox, answer Y. + + Most people should answer N. + +config DEBUG_PESSIMIZE + bool "Disable compiler optimizations." + default n + depends on DEBUG + help + The compiler's optimization of source code can eliminate and reorder + code, resulting in an executable that's hard to understand when + stepping through it with a debugger. This switches it off, resulting + in a much bigger executable that more closely matches the source + code. + +choice + prompt "Additional debugging library" + default NO_DEBUG_LIB + depends on DEBUG + help + Using an additional debugging library will make BusyBox become + considerable larger and will cause it to run more slowly. You + should always leave this option disabled for production use. + + dmalloc support: + ---------------- + This enables compiling with dmalloc ( http://dmalloc.com/ ) + which is an excellent public domain mem leak and malloc problem + detector. To enable dmalloc, before running busybox you will + want to properly set your environment, for example: + export DMALLOC_OPTIONS=debug=0x34f47d83,inter=100,log=logfile + The 'debug=' value is generated using the following command + dmalloc -p log-stats -p log-non-free -p log-bad-space -p log-elapsed-time \ + -p check-fence -p check-heap -p check-lists -p check-blank \ + -p check-funcs -p realloc-copy -p allow-free-null + + Electric-fence support: + ----------------------- + This enables compiling with Electric-fence support. Electric + fence is another very useful malloc debugging library which uses + your computer's virtual memory hardware to detect illegal memory + accesses. This support will make BusyBox be considerable larger + and run slower, so you should leave this option disabled unless + you are hunting a hard to find memory problem. + + +config NO_DEBUG_LIB + bool "None" + +config DMALLOC + bool "Dmalloc" + +config EFENCE + bool "Electric-fence" + +endchoice + +config DEBUG_YANK_SUSv2 + bool "Disable obsolete features removed before SUSv3?" + default y + help + This option will disable backwards compatibility with SuSv2, + specifically, old-style numeric options ('command -1 ') + will not be supported in head, tail, and fold. (Note: should + yank from renice too.) + +endmenu + +menu 'Installation Options' + +config INSTALL_NO_USR + bool "Don't use /usr" + default n + help + Disable use of /usr. Don't activate this option if you don't know + that you really want this behaviour. + +choice + prompt "Applets links" + default INSTALL_APPLET_SYMLINKS + help + Choose how you install applets links. + +config INSTALL_APPLET_SYMLINKS + bool "as soft-links" + help + Install applets as soft-links to the busybox binary. This needs some + free inodes on the filesystem, but might help with filesystem + generators that can't cope with hard-links. + +config INSTALL_APPLET_HARDLINKS + bool "as hard-links" + help + Install applets as hard-links to the busybox binary. This might count + on a filesystem with few inodes. + +config INSTALL_APPLET_DONT + bool + prompt "not installed" + depends on FEATURE_INSTALLER || FEATURE_SH_STANDALONE_SHELL + help + Do not install applets links. Usefull when using the -install feature + or a standalone shell for rescue pruposes. + +endchoice + +config PREFIX + string "BusyBox installation prefix" + default "./_install" + help + Define your directory to install BusyBox files/subdirs in. + +endmenu + +source libbb/Config.in + +endmenu + +comment "Applets" + +source archival/Config.in +source coreutils/Config.in +source console-tools/Config.in +source debianutils/Config.in +source editors/Config.in +source findutils/Config.in +source init/Config.in +source loginutils/Config.in +source e2fsprogs/Config.in +source modutils/Config.in +source util-linux/Config.in +source miscutils/Config.in +source networking/Config.in +source procps/Config.in +source shell/Config.in +source sysklogd/Config.in +source runit/Config.in diff --git a/INSTALL b/INSTALL new file mode 100644 index 000000000..6644481a3 --- /dev/null +++ b/INSTALL @@ -0,0 +1,125 @@ +Building: +========= + +The BusyBox build process is similar to the Linux kernel build: + + make menuconfig # This creates a file called ".config" + make # This creates the "busybox" executable + make install # or make PREFIX=/path/from/root install + +The full list of configuration and install options is available by typing: + + make help + +Quick Start: +============ + +The easy way to try out BusyBox for the first time, without having to install +it, is to enable all features and then use "standalone shell" mode with a +blank command $PATH. + +To enable all features, use "make defconfig", which produces the largest +general-purpose configuration. (It's allyesconfig minus debugging options, +optional packaging choices, and a few special-purpose features requiring +extra configuration to use.) + + make defconfig + make + PATH= ./busybox ash + +Standalone shell mode causes busybox's built-in command shell to run +any built-in busybox applets directly, without looking for external +programs by that name. Supplying an empty command path (as above) means +the only commands busybox can find are the built-in ones. + +Note that the standalone shell requires CONFIG_BUSYBOX_EXEC_PATH +to be set appropriately, depending on whether or not /proc/self/exe is +available or not. If you do not have /proc, then point that config option +to the location of your busybox binary, usually /bin/busybox. + +Configuring Busybox: +==================== + +Busybox is optimized for size, but enabling the full set of functionality +still results in a fairly large executable -- more than 1 megabyte when +statically linked. To save space, busybox can be configured with only the +set of applets needed for each environment. The minimal configuration, with +all applets disabled, produces a 4k executable. (It's useless, but very small.) + +The manual configurator "make menuconfig" modifies the existing configuration. +(For systems without ncurses, try "make config" instead.) The two most +interesting starting configurations are "make allnoconfig" (to start with +everything disabled and add just what you need), and "make defconfig" (to +start with everything enabled and remove what you don't need). If menuconfig +is run without an existing configuration, make defconfig will run first to +create a known starting point. + +Other starting configurations (mostly used for testing purposes) include +"make allbareconfig" (enables all applets but disables all optional features), +"make allyesconfig" (enables absolutely everything including debug features), +and "make randconfig" (produce a random configuration). + +Configuring BusyBox produces a file ".config", which can be saved for future +use. Run "make oldconfig" to bring a .config file from an older version of +busybox up to date. + +Installing Busybox: +=================== + +Busybox is a single executable that can behave like many different commands, +and BusyBox uses the name it was invoked under to determine the desired +behavior. (Try "mv busybox ls" and then "./ls -l".) + +Installing busybox consists of creating symlinks (or hardlinks) to the busybox +binary for each applet enabled in busybox, and making sure these symlinks are +in the shell's command $PATH. Running "make install" creates these symlinks, +or "make install-hardlinks" creates hardlinks instead (useful on systems with +a limited number of inodes). This install process uses the file +"busybox.links" (created by make), which contains the list of enabled applets +and the path at which to install them. + +Installing links to busybox is not always necessary. The special applet name +"busybox" (or with any optional suffix, such as "busybox-static") uses the +first argument to determine which applet to behave as, for example +"./busybox cat LICENSE". (Running the busybox applet with no arguments gives +a list of all enabled applets.) The standalone shell can also call busybox +applets without links to busybox under other names in the filesystem. You can +also configure a standaone install capability into the busybox base applet, +and then install such links at runtime with one of "busybox --install" (for +hardlinks) or "busybox --install -s" (for symlinks). + +If you enabled the busybox shared library feature (libbusybox.so) and want +to run tests without installing, set your LD_LIBRARY_PATH accordingly when +running the executable: + + LD_LIBRARY_PATH=`pwd` ./busybox + +Building out-of-tree: +===================== + +By default, the BusyBox build puts its temporary files in the source tree. +Building from a read-only source tree, or building multiple configurations from +the same source directory, requires the ability to put the temporary files +somewhere else. + +To build out of tree, cd to an empty directory and configure busybox from there: + + make -f /path/to/source/Makefile defconfig + make + make install + +Alternately, use the O=$BUILDPATH option (with an absolute path) during the +configuration step, as in: + + make O=/some/empty/directory allyesconfig + cd /some/empty/directory + make + make PREFIX=. install + +More Information: +================= + +Se also the busybox FAQ, under the questions "How can I get started using +BusyBox" and "How do I build a BusyBox-based system?" The BusyBox FAQ is +available from http://www.busybox.net/FAQ.html or as the file +docs/busybox.net/FAQ.html in this tarball. diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..9d9bdc7e3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,348 @@ +--- A note on GPL versions + +BusyBox is distributed under version 2 of the General Public License (included +in its entirety, below). Version 2 is the only version of this license which +this version of BusyBox (or modified versions derived from this one) may be +distributed under. + +------------------------------------------------------------------------ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..e05fad166 --- /dev/null +++ b/Makefile @@ -0,0 +1,1289 @@ +VERSION = 1 +PATCHLEVEL = 3 +SUBLEVEL = 0 +EXTRAVERSION = +NAME = Unnamed + +# *DOCUMENTATION* +# To see a list of typical targets execute "make help" +# More info can be located in ./README +# Comments in this file are targeted only to the developer, do not +# expect to learn how to build the kernel reading this file. + +# Do not print "Entering directory ..." +MAKEFLAGS += --no-print-directory + +# We are using a recursive build, so we need to do a little thinking +# to get the ordering right. +# +# Most importantly: sub-Makefiles should only ever modify files in +# their own directory. If in some directory we have a dependency on +# a file in another dir (which doesn't happen often, but it's often +# unavoidable when linking the built-in.o targets which finally +# turn into busybox), we will call a sub make in that other dir, and +# after that we are sure that everything which is in that other dir +# is now up to date. +# +# The only cases where we need to modify files which have global +# effects are thus separated out and done before the recursive +# descending is started. They are now explicitly listed as the +# prepare rule. + +# To put more focus on warnings, be less verbose as default +# Use 'make V=1' to see the full commands + +ifdef V + ifeq ("$(origin V)", "command line") + KBUILD_VERBOSE = $(V) + endif +endif +ifndef KBUILD_VERBOSE + KBUILD_VERBOSE = 0 +endif + +# Call sparse as part of compilation of C files +# Use 'make C=1' to enable sparse checking + +ifdef C + ifeq ("$(origin C)", "command line") + KBUILD_CHECKSRC = $(C) + endif +endif +ifndef KBUILD_CHECKSRC + KBUILD_CHECKSRC = 0 +endif + +# Use make M=dir to specify directory of external module to build +# Old syntax make ... SUBDIRS=$PWD is still supported +# Setting the environment variable KBUILD_EXTMOD take precedence +ifdef SUBDIRS + KBUILD_EXTMOD ?= $(SUBDIRS) +endif +ifdef M + ifeq ("$(origin M)", "command line") + KBUILD_EXTMOD := $(M) + endif +endif + + +# kbuild supports saving output files in a separate directory. +# To locate output files in a separate directory two syntaxes are supported. +# In both cases the working directory must be the root of the kernel src. +# 1) O= +# Use "make O=dir/to/store/output/files/" +# +# 2) Set KBUILD_OUTPUT +# Set the environment variable KBUILD_OUTPUT to point to the directory +# where the output files shall be placed. +# export KBUILD_OUTPUT=dir/to/store/output/files/ +# make +# +# The O= assignment takes precedence over the KBUILD_OUTPUT environment +# variable. + + +# KBUILD_SRC is set on invocation of make in OBJ directory +# KBUILD_SRC is not intended to be used by the regular user (for now) +ifeq ($(KBUILD_SRC),) + +# OK, Make called in directory where kernel src resides +# Do we want to locate output files in a separate directory? +ifdef O + ifeq ("$(origin O)", "command line") + KBUILD_OUTPUT := $(O) + endif +endif + +# That's our default target when none is given on the command line +PHONY := _all +_all: + +ifneq ($(KBUILD_OUTPUT),) +# Invoke a second make in the output directory, passing relevant variables +# check that the output directory actually exists +saved-output := $(KBUILD_OUTPUT) +KBUILD_OUTPUT := $(shell cd $(KBUILD_OUTPUT) && /bin/pwd) +$(if $(KBUILD_OUTPUT),, \ + $(error output directory "$(saved-output)" does not exist)) + +PHONY += $(MAKECMDGOALS) + +$(filter-out _all,$(MAKECMDGOALS)) _all: + $(if $(KBUILD_VERBOSE:1=),@)$(MAKE) -C $(KBUILD_OUTPUT) \ + KBUILD_SRC=$(CURDIR) \ + KBUILD_EXTMOD="$(KBUILD_EXTMOD)" -f $(CURDIR)/Makefile $@ + +# Leave processing to above invocation of make +skip-makefile := 1 +endif # ifneq ($(KBUILD_OUTPUT),) +endif # ifeq ($(KBUILD_SRC),) + +# We process the rest of the Makefile if this is the final invocation of make +ifeq ($(skip-makefile),) + +# If building an external module we do not care about the all: rule +# but instead _all depend on modules +PHONY += all +ifeq ($(KBUILD_EXTMOD),) +_all: all +else +_all: modules +endif + +srctree := $(if $(KBUILD_SRC),$(KBUILD_SRC),$(CURDIR)) +TOPDIR := $(srctree) +# FIXME - TOPDIR is obsolete, use srctree/objtree +objtree := $(CURDIR) +src := $(srctree) +obj := $(objtree) + +VPATH := $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD)) + +export srctree objtree VPATH TOPDIR + + +# SUBARCH tells the usermode build what the underlying arch is. That is set +# first, and if a usermode build is happening, the "ARCH=um" on the command +# line overrides the setting of ARCH below. If a native build is happening, +# then ARCH is assigned, getting whatever value it gets normally, and +# SUBARCH is subsequently ignored. + +SUBARCH := $(shell uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ \ + -e s/arm.*/arm/ -e s/sa110/arm/ \ + -e s/s390x/s390/ -e s/parisc64/parisc/ \ + -e s/ppc.*/powerpc/ -e s/mips.*/mips/ ) + +# Cross compiling and selecting different set of gcc/bin-utils +# --------------------------------------------------------------------------- +# +# When performing cross compilation for other architectures ARCH shall be set +# to the target architecture. (See arch/* for the possibilities). +# ARCH can be set during invocation of make: +# make ARCH=ia64 +# Another way is to have ARCH set in the environment. +# The default ARCH is the host where make is executed. + +# CROSS_COMPILE specify the prefix used for all executables used +# during compilation. Only gcc and related bin-utils executables +# are prefixed with $(CROSS_COMPILE). +# CROSS_COMPILE can be set on the command line +# make CROSS_COMPILE=ia64-linux- +# Alternatively CROSS_COMPILE can be set in the environment. +# Default value for CROSS_COMPILE is not to prefix executables +# Note: Some architectures assign CROSS_COMPILE in their arch/*/Makefile + +ARCH ?= $(SUBARCH) +CROSS_COMPILE ?= + +# Architecture as present in compile.h +UTS_MACHINE := $(ARCH) + +# SHELL used by kbuild +CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \ + else if [ -x /bin/bash ]; then echo /bin/bash; \ + else echo sh; fi ; fi) + +HOSTCC = gcc +HOSTCXX = g++ +HOSTCFLAGS = -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer +HOSTCXXFLAGS = -O2 + +# Decide whether to build built-in, modular, or both. +# Normally, just do built-in. + +KBUILD_MODULES := +KBUILD_BUILTIN := 1 + +# If we have only "make modules", don't compile built-in objects. +# When we're building modules with modversions, we need to consider +# the built-in objects during the descend as well, in order to +# make sure the checksums are uptodate before we record them. + +ifeq ($(MAKECMDGOALS),modules) + KBUILD_BUILTIN := $(if $(CONFIG_MODVERSIONS),1) +endif + +# If we have "make modules", compile modules +# in addition to whatever we do anyway. +# Just "make" or "make all" shall build modules as well + +ifneq ($(filter all _all modules,$(MAKECMDGOALS)),) + KBUILD_MODULES := 1 +endif + +ifeq ($(MAKECMDGOALS),) + KBUILD_MODULES := 1 +endif + +export KBUILD_MODULES KBUILD_BUILTIN +export KBUILD_CHECKSRC KBUILD_SRC KBUILD_EXTMOD + +# Beautify output +# --------------------------------------------------------------------------- +# +# Normally, we echo the whole command before executing it. By making +# that echo $($(quiet)$(cmd)), we now have the possibility to set +# $(quiet) to choose other forms of output instead, e.g. +# +# quiet_cmd_cc_o_c = Compiling $(RELDIR)/$@ +# cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< +# +# If $(quiet) is empty, the whole command will be printed. +# If it is set to "quiet_", only the short version will be printed. +# If it is set to "silent_", nothing wil be printed at all, since +# the variable $(silent_cmd_cc_o_c) doesn't exist. +# +# A simple variant is to prefix commands with $(Q) - that's useful +# for commands that shall be hidden in non-verbose mode. +# +# $(Q)ln $@ :< +# +# If KBUILD_VERBOSE equals 0 then the above command will be hidden. +# If KBUILD_VERBOSE equals 1 then the above command is displayed. + +ifeq ($(KBUILD_VERBOSE),1) + quiet = + Q = +else + quiet=quiet_ + Q = @ +endif + +# If the user is running make -s (silent mode), suppress echoing of +# commands + +ifneq ($(findstring s,$(MAKEFLAGS)),) + quiet=silent_ +endif + +export quiet Q KBUILD_VERBOSE + + +# Look for make include files relative to root of kernel src +MAKEFLAGS += --include-dir=$(srctree) + +# We need some generic definitions +include $(srctree)/scripts/Kbuild.include + +# For maximum performance (+ possibly random breakage, uncomment +# the following) + +MAKEFLAGS += -rR + +# Make variables (CC, etc...) + +AS = $(CROSS_COMPILE)as +LD = $(CROSS_COMPILE)ld +CC = $(CROSS_COMPILE)gcc +CPP = $(CC) -E +AR = $(CROSS_COMPILE)ar +NM = $(CROSS_COMPILE)nm +STRIP = $(CROSS_COMPILE)strip +OBJCOPY = $(CROSS_COMPILE)objcopy +OBJDUMP = $(CROSS_COMPILE)objdump +AWK = awk +GENKSYMS = scripts/genksyms/genksyms +DEPMOD = /sbin/depmod +KALLSYMS = scripts/kallsyms +PERL = perl +CHECK = sparse + +CHECKFLAGS := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ -Wbitwise $(CF) +MODFLAGS = -DMODULE +CFLAGS_MODULE = $(MODFLAGS) +AFLAGS_MODULE = $(MODFLAGS) +LDFLAGS_MODULE = -r +CFLAGS_KERNEL = +AFLAGS_KERNEL = + + +# Use LINUXINCLUDE when you must reference the include/ directory. +# Needed to be compatible with the O= option +CFLAGS := +CPPFLAGS := +AFLAGS := + +# Read KERNELRELEASE from .kernelrelease (if it exists) +KERNELRELEASE = $(shell cat .kernelrelease 2> /dev/null) +KERNELVERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION) + +export VERSION PATCHLEVEL SUBLEVEL KERNELRELEASE KERNELVERSION \ + ARCH CONFIG_SHELL HOSTCC HOSTCFLAGS CROSS_COMPILE AS LD CC \ + CPP AR NM STRIP OBJCOPY OBJDUMP MAKE AWK GENKSYMS PERL UTS_MACHINE \ + HOSTCXX HOSTCXXFLAGS LDFLAGS_MODULE CHECK CHECKFLAGS + +export CPPFLAGS NOSTDINC_FLAGS LINUXINCLUDE OBJCOPYFLAGS LDFLAGS +export CFLAGS CFLAGS_KERNEL CFLAGS_MODULE +export AFLAGS AFLAGS_KERNEL AFLAGS_MODULE + +# When compiling out-of-tree modules, put MODVERDIR in the module +# tree rather than in the kernel tree. The kernel tree might +# even be read-only. +export MODVERDIR := $(if $(KBUILD_EXTMOD),$(firstword $(KBUILD_EXTMOD))/).tmp_versions + +# Files to ignore in find ... statements + +RCS_FIND_IGNORE := \( -name SCCS -o -name BitKeeper -o -name .svn -o -name CVS -o -name .pc -o -name .hg -o -name .git \) -prune -o +export RCS_TAR_IGNORE := --exclude SCCS --exclude BitKeeper --exclude .svn --exclude CVS --exclude .pc --exclude .hg --exclude .git + +# =========================================================================== +# Rules shared between *config targets and build targets + +# Basic helpers built in scripts/ +PHONY += scripts_basic +scripts_basic: + $(Q)$(MAKE) $(build)=scripts/basic + +# To avoid any implicit rule to kick in, define an empty command. +scripts/basic/%: scripts_basic ; + +PHONY += outputmakefile +# outputmakefile generates a Makefile in the output directory, if using a +# separate output directory. This allows convenient use of make in the +# output directory. +outputmakefile: +ifneq ($(KBUILD_SRC),) + $(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \ + $(srctree) $(objtree) $(VERSION) $(PATCHLEVEL) +endif + +# To make sure we do not include .config for any of the *config targets +# catch them early, and hand them over to scripts/kconfig/Makefile +# It is allowed to specify more targets when calling make, including +# mixing *config targets and build targets. +# For example 'make oldconfig all'. +# Detect when mixed targets is specified, and make a second invocation +# of make so .config is not included in this case either (for *config). + +no-dot-config-targets := clean mrproper distclean \ + cscope TAGS tags help %docs check% + +config-targets := 0 +mixed-targets := 0 +dot-config := 1 + +ifneq ($(filter $(no-dot-config-targets), $(MAKECMDGOALS)),) + ifeq ($(filter-out $(no-dot-config-targets), $(MAKECMDGOALS)),) + dot-config := 0 + endif +endif + +ifeq ($(KBUILD_EXTMOD),) + ifneq ($(filter config %config,$(MAKECMDGOALS)),) + config-targets := 1 + ifneq ($(filter-out config %config,$(MAKECMDGOALS)),) + mixed-targets := 1 + endif + endif +endif + +ifeq ($(mixed-targets),1) +# =========================================================================== +# We're called with mixed targets (*config and build targets). +# Handle them one by one. + +%:: FORCE + $(Q)$(MAKE) -C $(srctree) KBUILD_SRC= $@ + +else +ifeq ($(config-targets),1) +# =========================================================================== +# *config targets only - make sure prerequisites are updated, and descend +# in scripts/kconfig to make the *config target + +# Read arch specific Makefile to set KBUILD_DEFCONFIG as needed. +# KBUILD_DEFCONFIG may point out an alternative default configuration +# used for 'make defconfig' +-include $(srctree)/arch/$(ARCH)/Makefile +export KBUILD_DEFCONFIG + +config %config: scripts_basic outputmakefile FORCE + $(Q)mkdir -p include + $(Q)$(MAKE) $(build)=scripts/kconfig $@ + $(Q)$(MAKE) -C $(srctree) KBUILD_SRC= .kernelrelease + +else +# =========================================================================== +# Build targets only - this includes busybox, arch specific targets, clean +# targets and others. In general all targets except *config targets. + +ifeq ($(KBUILD_EXTMOD),) +# Additional helpers built in scripts/ +# Carefully list dependencies so we do not try to build scripts twice +# in parrallel +PHONY += scripts +scripts: scripts_basic include/config/MARKER + $(Q)$(MAKE) $(build)=$(@) + +scripts_basic: include/autoconf.h + +# Objects we will link into busybox / subdirs we need to visit +core-y := \ + applets/ \ + +libs-y := \ + archival/ \ + archival/libunarchive/ \ + console-tools/ \ + coreutils/ \ + coreutils/libcoreutils/ \ + debianutils/ \ + e2fsprogs/ \ + e2fsprogs/blkid/ \ + e2fsprogs/e2p/ \ + e2fsprogs/ext2fs/ \ + e2fsprogs/uuid/ \ + editors/ \ + findutils/ \ + init/ \ + libbb/ \ + libpwdgrp/ \ + loginutils/ \ + miscutils/ \ + modutils/ \ + networking/ \ + networking/libiproute/ \ + networking/udhcp/ \ + procps/ \ + runit/ \ + shell/ \ + sysklogd/ \ + util-linux/ \ + +endif # KBUILD_EXTMOD + +ifeq ($(dot-config),1) +# In this section, we need .config + +# Read in dependencies to all Kconfig* files, make sure to run +# oldconfig if changes are detected. +-include .kconfig.d + +-include .config + +# If .config needs to be updated, it will be done via the dependency +# that autoconf has on .config. +# To avoid any implicit rule to kick in, define an empty command +.config .kconfig.d: ; + +# Now we can define CFLAGS etc according to .config +include $(srctree)/Makefile.flags + +# If .config is newer than include/autoconf.h, someone tinkered +# with it and forgot to run make oldconfig. +# If kconfig.d is missing then we are probarly in a cleaned tree so +# we execute the config step to be sure to catch updated Kconfig files +include/autoconf.h: .kconfig.d .config + $(Q)$(MAKE) -f $(srctree)/Makefile silentoldconfig + +else +# Dummy target needed, because used as prerequisite +include/autoconf.h: ; +endif + +# The all: target is the default when no target is given on the +# command line. +# This allow a user to issue only 'make' to build a kernel including modules +# Defaults busybox but it is usually overriden in the arch makefile +all: busybox + +-include $(srctree)/arch/$(ARCH)/Makefile + +# arch Makefile may override CC so keep this after arch Makefile is included +#bbox# NOSTDINC_FLAGS += -nostdinc -isystem $(shell $(CC) -print-file-name=include) +CHECKFLAGS += $(NOSTDINC_FLAGS) + +# warn about C99 declaration after statement +CFLAGS += $(call cc-option,-Wdeclaration-after-statement,) + +# disable pointer signedness warnings in gcc 4.0 +CFLAGS += $(call cc-option,-Wno-pointer-sign,) + +# Default kernel image to build when no specific target is given. +# KBUILD_IMAGE may be overruled on the commandline or +# set in the environment +# Also any assignments in arch/$(ARCH)/Makefile take precedence over +# this default value +export KBUILD_IMAGE ?= busybox + +# +# INSTALL_PATH specifies where to place the updated kernel and system map +# images. Default is /boot, but you can set it to other values +export INSTALL_PATH ?= /boot + +# +# INSTALL_MOD_PATH specifies a prefix to MODLIB for module directory +# relocations required by build roots. This is not defined in the +# makefile but the arguement can be passed to make if needed. +# + +MODLIB = $(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE) +export MODLIB + + +ifeq ($(KBUILD_EXTMOD),) +busybox-dirs := $(patsubst %/,%,$(filter %/, $(core-y) $(core-m) $(libs-y) $(libs-m))) + +busybox-alldirs := $(sort $(busybox-dirs) $(patsubst %/,%,$(filter %/, \ + $(core-n) $(core-) $(libs-n) $(libs-) \ + ))) + +core-y := $(patsubst %/, %/built-in.o, $(core-y)) +libs-y1 := $(patsubst %/, %/lib.a, $(libs-y)) +libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y)) +libs-y := $(libs-y1) $(libs-y2) + + +# Build busybox +# --------------------------------------------------------------------------- +# busybox is build from the objects selected by $(busybox-init) and +# $(busybox-main). Most are built-in.o files from top-level directories +# in the kernel tree, others are specified in arch/$(ARCH)Makefile. +# Ordering when linking is important, and $(busybox-init) must be first. +# +# busybox +# ^ +# | +# +-< $(busybox-init) +# | +--< init/version.o + more +# | +# +--< $(busybox-main) +# | +--< driver/built-in.o mm/built-in.o + more +# | +# +-< kallsyms.o (see description in CONFIG_KALLSYMS section) +# +# busybox version (uname -v) cannot be updated during normal +# descending-into-subdirs phase since we do not yet know if we need to +# update busybox. +# Therefore this step is delayed until just before final link of busybox - +# except in the kallsyms case where it is done just before adding the +# symbols to the kernel. +# +# System.map is generated to document addresses of all kernel symbols + +busybox-all := $(core-y) $(libs-y) + +# Rule to link busybox - also used during CONFIG_KALLSYMS +# May be overridden by arch/$(ARCH)/Makefile +quiet_cmd_busybox__ ?= LINK $@ +ifdef CONFIG_STATIC + cmd_busybox__ ?= $(srctree)/scripts/trylink $(CC) $(LDFLAGS) \ + -static \ + -o $@ \ + -Wl,--warn-common -Wl,--sort-common -Wl,--gc-sections \ + -Wl,--start-group $(busybox-all) -Wl,--end-group +else + cmd_busybox__ ?= $(srctree)/scripts/trylink $(CC) -o $@ $(LDFLAGS) \ + -Wl,--warn-common -Wl,--sort-common -Wl,--gc-sections \ + -Wl,--start-group $(busybox-all) -Wl,--end-group +endif + +# Generate System.map +quiet_cmd_sysmap = SYSMAP + cmd_sysmap = $(CONFIG_SHELL) $(srctree)/scripts/mksysmap + +# Link of busybox +# If CONFIG_KALLSYMS is set .version is already updated +# Generate System.map and verify that the content is consistent +# Use + in front of the busybox_version rule to silent warning with make -j2 +# First command is ':' to allow us to use + in front of the rule +define rule_busybox__ + : + $(call cmd,busybox__) + $(Q)echo 'cmd_$@ := $(cmd_busybox__)' > $(@D)/.$(@F).cmd +endef + + +ifdef CONFIG_KALLSYMS +# Generate section listing all symbols and add it into busybox $(kallsyms.o) +# It's a three stage process: +# o .tmp_busybox1 has all symbols and sections, but __kallsyms is +# empty +# Running kallsyms on that gives us .tmp_kallsyms1.o with +# the right size - busybox version (uname -v) is updated during this step +# o .tmp_busybox2 now has a __kallsyms section of the right size, +# but due to the added section, some addresses have shifted. +# From here, we generate a correct .tmp_kallsyms2.o +# o The correct .tmp_kallsyms2.o is linked into the final busybox. +# o Verify that the System.map from busybox matches the map from +# .tmp_busybox2, just in case we did not generate kallsyms correctly. +# o If CONFIG_KALLSYMS_EXTRA_PASS is set, do an extra pass using +# .tmp_busybox3 and .tmp_kallsyms3.o. This is only meant as a +# temporary bypass to allow the kernel to be built while the +# maintainers work out what went wrong with kallsyms. + +ifdef CONFIG_KALLSYMS_EXTRA_PASS +last_kallsyms := 3 +else +last_kallsyms := 2 +endif + +kallsyms.o := .tmp_kallsyms$(last_kallsyms).o + +define verify_kallsyms + $(Q)$(if $($(quiet)cmd_sysmap), \ + echo ' $($(quiet)cmd_sysmap) .tmp_System.map' &&) \ + $(cmd_sysmap) .tmp_busybox$(last_kallsyms) .tmp_System.map + $(Q)cmp -s System.map .tmp_System.map || \ + (echo Inconsistent kallsyms data; \ + echo Try setting CONFIG_KALLSYMS_EXTRA_PASS; \ + rm .tmp_kallsyms* ; /bin/false ) +endef + +# Update busybox version before link +# Use + in front of this rule to silent warning about make -j1 +# First command is ':' to allow us to use + in front of this rule +cmd_ksym_ld = $(cmd_busybox__) +define rule_ksym_ld + : + +$(call cmd,busybox_version) + $(call cmd,busybox__) + $(Q)echo 'cmd_$@ := $(cmd_busybox__)' > $(@D)/.$(@F).cmd +endef + +# Generate .S file with all kernel symbols +quiet_cmd_kallsyms = KSYM $@ + cmd_kallsyms = $(NM) -n $< | $(KALLSYMS) \ + $(if $(CONFIG_KALLSYMS_ALL),--all-symbols) > $@ + +.tmp_kallsyms1.o .tmp_kallsyms2.o .tmp_kallsyms3.o: %.o: %.S scripts FORCE + $(call if_changed_dep,as_o_S) + +.tmp_kallsyms%.S: .tmp_busybox% $(KALLSYMS) + $(call cmd,kallsyms) + +# .tmp_busybox1 must be complete except kallsyms, so update busybox version +.tmp_busybox1: $(busybox-lds) $(busybox-all) FORCE + $(call if_changed_rule,ksym_ld) + +.tmp_busybox2: $(busybox-lds) $(busybox-all) .tmp_kallsyms1.o FORCE + $(call if_changed,busybox__) + +.tmp_busybox3: $(busybox-lds) $(busybox-all) .tmp_kallsyms2.o FORCE + $(call if_changed,busybox__) + +# Needs to visit scripts/ before $(KALLSYMS) can be used. +$(KALLSYMS): scripts ; + +# Generate some data for debugging strange kallsyms problems +debug_kallsyms: .tmp_map$(last_kallsyms) + +.tmp_map%: .tmp_busybox% FORCE + ($(OBJDUMP) -h $< | $(AWK) '/^ +[0-9]/{print $$4 " 0 " $$2}'; $(NM) $<) | sort > $@ + +.tmp_map3: .tmp_map2 + +.tmp_map2: .tmp_map1 + +endif # ifdef CONFIG_KALLSYMS + +# busybox image - including updated kernel symbols +busybox_unstripped: $(busybox-all) FORCE + $(call if_changed_rule,busybox__) + $(Q)rm -f .old_version + +busybox: busybox_unstripped + $(Q)$(STRIP) -s --remove-section=.note --remove-section=.comment \ + busybox_unstripped -o $@ + +# The actual objects are generated when descending, +# make sure no implicit rule kicks in +$(sort $(busybox-all)): $(busybox-dirs) ; + +# Handle descending into subdirectories listed in $(busybox-dirs) +# Preset locale variables to speed up the build process. Limit locale +# tweaks to this spot to avoid wrong language settings when running +# make menuconfig etc. +# Error messages still appears in the original language + +PHONY += $(busybox-dirs) +$(busybox-dirs): prepare scripts + $(Q)$(MAKE) $(build)=$@ + +# Build the kernel release string +# The KERNELRELEASE is stored in a file named .kernelrelease +# to be used when executing for example make install or make modules_install +# +# Take the contents of any files called localversion* and the config +# variable CONFIG_LOCALVERSION and append them to KERNELRELEASE. +# LOCALVERSION from the command line override all of this + +nullstring := +space := $(nullstring) # end of line + +___localver = $(objtree)/localversion* $(srctree)/localversion* +__localver = $(sort $(wildcard $(___localver))) +# skip backup files (containing '~') +_localver = $(foreach f, $(__localver), $(if $(findstring ~, $(f)),,$(f))) + +localver = $(subst $(space),, \ + $(shell cat /dev/null $(_localver)) \ + $(patsubst "%",%,$(CONFIG_LOCALVERSION))) + +# If CONFIG_LOCALVERSION_AUTO is set scripts/setlocalversion is called +# and if the SCM is know a tag from the SCM is appended. +# The appended tag is determinded by the SCM used. +# +# Currently, only git is supported. +# Other SCMs can edit scripts/setlocalversion and add the appropriate +# checks as needed. +ifdef CONFIG_LOCALVERSION_AUTO + _localver-auto = $(shell $(CONFIG_SHELL) \ + $(srctree)/scripts/setlocalversion $(srctree)) + localver-auto = $(LOCALVERSION)$(_localver-auto) +endif + +localver-full = $(localver)$(localver-auto) + +# Store (new) KERNELRELASE string in .kernelrelease +kernelrelease = $(KERNELVERSION)$(localver-full) +.kernelrelease: FORCE + $(Q)rm -f $@ + $(Q)echo $(kernelrelease) > $@ + + +# Things we need to do before we recursively start building the kernel +# or the modules are listed in "prepare". +# A multi level approach is used. prepareN is processed before prepareN-1. +# archprepare is used in arch Makefiles and when processed asm symlink, +# version.h and scripts_basic is processed / created. + +# Listed in dependency order +PHONY += prepare archprepare prepare0 prepare1 prepare2 prepare3 + +# prepare-all is deprecated, use prepare as valid replacement +PHONY += prepare-all + +# prepare3 is used to check if we are building in a separate output directory, +# and if so do: +# 1) Check that make has not been executed in the kernel src $(srctree) +# 2) Create the include2 directory, used for the second asm symlink +prepare3: .kernelrelease +ifneq ($(KBUILD_SRC),) + @echo ' Using $(srctree) as source for kernel' + $(Q)if [ -f $(srctree)/.config ]; then \ + echo " $(srctree) is not clean, please run 'make mrproper'";\ + echo " in the '$(srctree)' directory.";\ + /bin/false; \ + fi; + $(Q)if [ ! -d include2 ]; then mkdir -p include2; fi; + $(Q)ln -fsn $(srctree)/include/asm-$(ARCH) include2/asm +endif + +# prepare2 creates a makefile if using a separate output directory +prepare2: prepare3 outputmakefile + +prepare1: prepare2 include/config/MARKER +ifneq ($(KBUILD_MODULES),) + $(Q)mkdir -p $(MODVERDIR) + $(Q)rm -f $(MODVERDIR)/* +endif + +archprepare: prepare1 scripts_basic + +prepare0: archprepare FORCE + $(Q)$(MAKE) $(build)=. + +# All the preparing.. +prepare prepare-all: prepare0 + +# Leave this as default for preprocessing busybox.lds.S, which is now +# done in arch/$(ARCH)/kernel/Makefile + +export CPPFLAGS_busybox.lds += -P -C -U$(ARCH) + +# FIXME: The asm symlink changes when $(ARCH) changes. That's +# hard to detect, but I suppose "make mrproper" is a good idea +# before switching between archs anyway. + +#bbox# include/asm: +#bbox# @echo ' SYMLINK $@ -> include/asm-$(ARCH)' +#bbox# $(Q)if [ ! -d include ]; then mkdir -p include; fi; +#bbox# @ln -fsn asm-$(ARCH) $@ + +# Split autoconf.h into include/linux/config/* +#bbox# piggybacked generation of few .h files +include/config/MARKER: scripts/basic/split-include include/autoconf.h + @echo ' SPLIT include/autoconf.h -> include/config/*' + @scripts/basic/split-include include/autoconf.h include/config + @echo ' GEN include/bbconfigopts.h' + @$(srctree)/scripts/mkconfigs >include/bbconfigopts.h + @touch $@ + +# Generate some files +# --------------------------------------------------------------------------- + +# KERNELRELEASE can change from a few different places, meaning version.h +# needs to be updated, so this check is forced on all builds + +uts_len := 64 + +define filechk_version.h + if [ `echo -n "$(KERNELRELEASE)" | wc -c ` -gt $(uts_len) ]; then \ + echo '"$(KERNELRELEASE)" exceeds $(uts_len) characters' >&2; \ + exit 1; \ + fi; \ + (echo \#define UTS_RELEASE \"$(KERNELRELEASE)\"; \ + echo \#define LINUX_VERSION_CODE `expr $(VERSION) \\* 65536 + $(PATCHLEVEL) \\* 256 + $(SUBLEVEL)`; \ + echo '#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))'; \ + ) +endef + +# --------------------------------------------------------------------------- + +PHONY += depend dep +depend dep: + @echo '*** Warning: make $@ is unnecessary now.' + +# --------------------------------------------------------------------------- +# Modules + +ifdef CONFIG_MODULES + +# By default, build modules as well + +all: modules + +# Build modules + +PHONY += modules +modules: $(busybox-dirs) $(if $(KBUILD_BUILTIN),busybox) + @echo ' Building modules, stage 2.'; + $(Q)$(MAKE) -rR -f $(srctree)/scripts/Makefile.modpost + + +# Target to prepare building external modules +PHONY += modules_prepare +modules_prepare: prepare scripts + +# Target to install modules +PHONY += modules_install +modules_install: _modinst_ _modinst_post + +PHONY += _modinst_ +_modinst_: + @if [ -z "`$(DEPMOD) -V 2>/dev/null | grep module-init-tools`" ]; then \ + echo "Warning: you may need to install module-init-tools"; \ + echo "See http://www.codemonkey.org.uk/docs/post-halloween-2.6.txt";\ + sleep 1; \ + fi + @rm -rf $(MODLIB)/kernel + @rm -f $(MODLIB)/source + @mkdir -p $(MODLIB)/kernel + @ln -s $(srctree) $(MODLIB)/source + @if [ ! $(objtree) -ef $(MODLIB)/build ]; then \ + rm -f $(MODLIB)/build ; \ + ln -s $(objtree) $(MODLIB)/build ; \ + fi + $(Q)$(MAKE) -rR -f $(srctree)/scripts/Makefile.modinst + +# If System.map exists, run depmod. This deliberately does not have a +# dependency on System.map since that would run the dependency tree on +# busybox. This depmod is only for convenience to give the initial +# boot a modules.dep even before / is mounted read-write. However the +# boot script depmod is the master version. +ifeq "$(strip $(INSTALL_MOD_PATH))" "" +depmod_opts := +else +depmod_opts := -b $(INSTALL_MOD_PATH) -r +endif +PHONY += _modinst_post +_modinst_post: _modinst_ + if [ -r System.map -a -x $(DEPMOD) ]; then $(DEPMOD) -ae -F System.map $(depmod_opts) $(KERNELRELEASE); fi + +else # CONFIG_MODULES + +# Modules not configured +# --------------------------------------------------------------------------- + +modules modules_install: FORCE + @echo + @echo "The present busybox configuration has modules disabled." + @echo "Type 'make config' and enable loadable module support." + @echo "Then build a kernel with module support enabled." + @echo + @exit 1 + +endif # CONFIG_MODULES + +### +# Cleaning is done on three levels. +# make clean Delete most generated files +# Leave enough to build external modules +# make mrproper Delete the current configuration, and all generated files +# make distclean Remove editor backup files, patch leftover files and the like + +# Directories & files removed with 'make clean' +CLEAN_DIRS += $(MODVERDIR) +CLEAN_FILES += busybox* System.map \ + .tmp_kallsyms* .tmp_version .tmp_busybox* .tmp_System.map + +# Directories & files removed with 'make mrproper' +MRPROPER_DIRS += include/config include2 +MRPROPER_FILES += .config .config.old include/asm .version .old_version \ + include/autoconf.h \ + include/bbconfigopts.h \ + include/usage_compressed.h \ + .kernelrelease Module.symvers tags TAGS cscope* + +# clean - Delete most, but leave enough to build external modules +# +clean: rm-dirs := $(CLEAN_DIRS) +clean: rm-files := $(CLEAN_FILES) +clean-dirs := $(addprefix _clean_,$(srctree) $(busybox-alldirs)) + +PHONY += $(clean-dirs) clean archclean +$(clean-dirs): + $(Q)$(MAKE) $(clean)=$(patsubst _clean_%,%,$@) + +clean: archclean $(clean-dirs) + $(call cmd,rmdirs) + $(call cmd,rmfiles) + @find . $(RCS_FIND_IGNORE) \ + \( -name '*.[oas]' -o -name '*.ko' -o -name '.*.cmd' \ + -o -name '.*.d' -o -name '.*.tmp' -o -name '*.mod.c' \) \ + -type f -print | xargs rm -f + +# mrproper - Delete all generated files, including .config +# +mrproper: rm-dirs := $(wildcard $(MRPROPER_DIRS)) +mrproper: rm-files := $(wildcard $(MRPROPER_FILES)) +mrproper-dirs := $(addprefix _mrproper_,scripts) + +PHONY += $(mrproper-dirs) mrproper archmrproper +$(mrproper-dirs): + $(Q)$(MAKE) $(clean)=$(patsubst _mrproper_%,%,$@) + +mrproper: clean archmrproper $(mrproper-dirs) + $(call cmd,rmdirs) + $(call cmd,rmfiles) + +# distclean +# +PHONY += distclean + +distclean: mrproper + @find $(srctree) $(RCS_FIND_IGNORE) \ + \( -name '*.orig' -o -name '*.rej' -o -name '*~' \ + -o -name '*.bak' -o -name '#*#' -o -name '.*.orig' \ + -o -name '.*.rej' -o -size 0 \ + -o -name '*%' -o -name '.*.cmd' -o -name 'core' \) \ + -type f -print | xargs rm -f + + +# Packaging of the kernel to various formats +# --------------------------------------------------------------------------- +# rpm target kept for backward compatibility +package-dir := $(srctree)/scripts/package + +%pkg: FORCE + $(Q)$(MAKE) $(build)=$(package-dir) $@ +rpm: FORCE + $(Q)$(MAKE) $(build)=$(package-dir) $@ + + +# Brief documentation of the typical targets used +# --------------------------------------------------------------------------- + +boards := $(wildcard $(srctree)/arch/$(ARCH)/configs/*_defconfig) +boards := $(notdir $(boards)) + +-include $(srctree)/Makefile.help + +# Documentation targets +# --------------------------------------------------------------------------- +%docs: scripts_basic FORCE + $(Q)$(MAKE) $(build)=Documentation/DocBook $@ + +else # KBUILD_EXTMOD + +### +# External module support. +# When building external modules the kernel used as basis is considered +# read-only, and no consistency checks are made and the make +# system is not used on the basis kernel. If updates are required +# in the basis kernel ordinary make commands (without M=...) must +# be used. +# +# The following are the only valid targets when building external +# modules. +# make M=dir clean Delete all automatically generated files +# make M=dir modules Make all modules in specified dir +# make M=dir Same as 'make M=dir modules' +# make M=dir modules_install +# Install the modules build in the module directory +# Assumes install directory is already created + +# We are always building modules +KBUILD_MODULES := 1 +PHONY += crmodverdir +crmodverdir: + $(Q)mkdir -p $(MODVERDIR) + $(Q)rm -f $(MODVERDIR)/* + +PHONY += $(objtree)/Module.symvers +$(objtree)/Module.symvers: + @test -e $(objtree)/Module.symvers || ( \ + echo; \ + echo " WARNING: Symbol version dump $(objtree)/Module.symvers"; \ + echo " is missing; modules will have no dependencies and modversions."; \ + echo ) + +module-dirs := $(addprefix _module_,$(KBUILD_EXTMOD)) +PHONY += $(module-dirs) modules +$(module-dirs): crmodverdir $(objtree)/Module.symvers + $(Q)$(MAKE) $(build)=$(patsubst _module_%,%,$@) + +modules: $(module-dirs) + @echo ' Building modules, stage 2.'; + $(Q)$(MAKE) -rR -f $(srctree)/scripts/Makefile.modpost + +PHONY += modules_install +modules_install: _emodinst_ _emodinst_post + +install-dir := $(if $(INSTALL_MOD_DIR),$(INSTALL_MOD_DIR),extra) +PHONY += _emodinst_ +_emodinst_: + $(Q)mkdir -p $(MODLIB)/$(install-dir) + $(Q)$(MAKE) -rR -f $(srctree)/scripts/Makefile.modinst + +# Run depmod only is we have System.map and depmod is executable +quiet_cmd_depmod = DEPMOD $(KERNELRELEASE) + cmd_depmod = if [ -r System.map -a -x $(DEPMOD) ]; then \ + $(DEPMOD) -ae -F System.map \ + $(if $(strip $(INSTALL_MOD_PATH)), \ + -b $(INSTALL_MOD_PATH) -r) \ + $(KERNELRELEASE); \ + fi + +PHONY += _emodinst_post +_emodinst_post: _emodinst_ + $(call cmd,depmod) + +clean-dirs := $(addprefix _clean_,$(KBUILD_EXTMOD)) + +PHONY += $(clean-dirs) clean +$(clean-dirs): + $(Q)$(MAKE) $(clean)=$(patsubst _clean_%,%,$@) + +clean: rm-dirs := $(MODVERDIR) +clean: $(clean-dirs) + $(call cmd,rmdirs) + @find $(KBUILD_EXTMOD) $(RCS_FIND_IGNORE) \ + \( -name '*.[oas]' -o -name '*.ko' -o -name '.*.cmd' \ + -o -name '.*.d' -o -name '.*.tmp' -o -name '*.mod.c' \) \ + -type f -print | xargs rm -f + +help: + @echo ' Building external modules.' + @echo ' Syntax: make -C path/to/kernel/src M=$$PWD target' + @echo '' + @echo ' modules - default target, build the module(s)' + @echo ' modules_install - install the module' + @echo ' clean - remove generated files in module directory only' + @echo '' + +# Dummies... +PHONY += prepare scripts +prepare: ; +scripts: ; +endif # KBUILD_EXTMOD + +# Generate tags for editors +# --------------------------------------------------------------------------- + +#We want __srctree to totally vanish out when KBUILD_OUTPUT is not set +#(which is the most common case IMHO) to avoid unneeded clutter in the big tags file. +#Adding $(srctree) adds about 20M on i386 to the size of the output file! + +ifeq ($(src),$(obj)) +__srctree = +else +__srctree = $(srctree)/ +endif + +ifeq ($(ALLSOURCE_ARCHS),) +ifeq ($(ARCH),um) +ALLINCLUDE_ARCHS := $(ARCH) $(SUBARCH) +else +ALLINCLUDE_ARCHS := $(ARCH) +endif +else +#Allow user to specify only ALLSOURCE_PATHS on the command line, keeping existing behaviour. +ALLINCLUDE_ARCHS := $(ALLSOURCE_ARCHS) +endif + +ALLSOURCE_ARCHS := $(ARCH) + +define all-sources + ( find $(__srctree) $(RCS_FIND_IGNORE) \ + \( -name include -o -name arch \) -prune -o \ + -name '*.[chS]' -print; \ + for ARCH in $(ALLSOURCE_ARCHS) ; do \ + find $(__srctree)arch/$${ARCH} $(RCS_FIND_IGNORE) \ + -name '*.[chS]' -print; \ + done ; \ + find $(__srctree)security/selinux/include $(RCS_FIND_IGNORE) \ + -name '*.[chS]' -print; \ + find $(__srctree)include $(RCS_FIND_IGNORE) \ + \( -name config -o -name 'asm-*' \) -prune \ + -o -name '*.[chS]' -print; \ + for ARCH in $(ALLINCLUDE_ARCHS) ; do \ + find $(__srctree)include/asm-$${ARCH} $(RCS_FIND_IGNORE) \ + -name '*.[chS]' -print; \ + done ; \ + find $(__srctree)include/asm-generic $(RCS_FIND_IGNORE) \ + -name '*.[chS]' -print ) +endef + +quiet_cmd_cscope-file = FILELST cscope.files + cmd_cscope-file = (echo \-k; echo \-q; $(all-sources)) > cscope.files + +quiet_cmd_cscope = MAKE cscope.out + cmd_cscope = cscope -b + +cscope: FORCE + $(call cmd,cscope-file) + $(call cmd,cscope) + +quiet_cmd_TAGS = MAKE $@ +define cmd_TAGS + rm -f $@; \ + ETAGSF=`etags --version | grep -i exuberant >/dev/null && \ + echo "-I __initdata,__exitdata,__acquires,__releases \ + -I EXPORT_SYMBOL,EXPORT_SYMBOL_GPL \ + --extra=+f --c-kinds=+px"`; \ + $(all-sources) | xargs etags $$ETAGSF -a +endef + +TAGS: FORCE + $(call cmd,TAGS) + + +quiet_cmd_tags = MAKE $@ +define cmd_tags + rm -f $@; \ + CTAGSF=`ctags --version | grep -i exuberant >/dev/null && \ + echo "-I __initdata,__exitdata,__acquires,__releases \ + -I EXPORT_SYMBOL,EXPORT_SYMBOL_GPL \ + --extra=+f --c-kinds=+px"`; \ + $(all-sources) | xargs ctags $$CTAGSF -a +endef + +tags: FORCE + $(call cmd,tags) + + +# Scripts to check various things for consistency +# --------------------------------------------------------------------------- + +includecheck: + find * $(RCS_FIND_IGNORE) \ + -name '*.[hcS]' -type f -print | sort \ + | xargs $(PERL) -w scripts/checkincludes.pl + +versioncheck: + find * $(RCS_FIND_IGNORE) \ + -name '*.[hcS]' -type f -print | sort \ + | xargs $(PERL) -w scripts/checkversion.pl + +namespacecheck: + $(PERL) $(srctree)/scripts/namespace.pl + +endif #ifeq ($(config-targets),1) +endif #ifeq ($(mixed-targets),1) + +PHONY += checkstack +checkstack: + $(OBJDUMP) -d busybox $$(find . -name '*.ko') | \ + $(PERL) $(src)/scripts/checkstack.pl $(ARCH) + +kernelrelease: + $(if $(wildcard .kernelrelease), $(Q)echo $(KERNELRELEASE), \ + $(error kernelrelease not valid - run 'make *config' to update it)) +kernelversion: + @echo $(KERNELVERSION) + +# Single targets +# --------------------------------------------------------------------------- +# Single targets are compatible with: +# - build whith mixed source and output +# - build with separate output dir 'make O=...' +# - external modules +# +# target-dir => where to store outputfile +# build-dir => directory in kernel source tree to use + +ifeq ($(KBUILD_EXTMOD),) + build-dir = $(patsubst %/,%,$(dir $@)) + target-dir = $(dir $@) +else + zap-slash=$(filter-out .,$(patsubst %/,%,$(dir $@))) + build-dir = $(KBUILD_EXTMOD)$(if $(zap-slash),/$(zap-slash)) + target-dir = $(if $(KBUILD_EXTMOD),$(dir $<),$(dir $@)) +endif + +%.s: %.c prepare scripts FORCE + $(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@) +%.i: %.c prepare scripts FORCE + $(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@) +%.o: %.c prepare scripts FORCE + $(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@) +%.lst: %.c prepare scripts FORCE + $(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@) +%.s: %.S prepare scripts FORCE + $(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@) +%.o: %.S prepare scripts FORCE + $(Q)$(MAKE) $(build)=$(build-dir) $(target-dir)$(notdir $@) + +# Modules +/ %/: prepare scripts FORCE + $(Q)$(MAKE) KBUILD_MODULES=$(if $(CONFIG_MODULES),1) \ + $(build)=$(build-dir) +%.ko: prepare scripts FORCE + $(Q)$(MAKE) KBUILD_MODULES=$(if $(CONFIG_MODULES),1) \ + $(build)=$(build-dir) $(@:.ko=.o) + $(Q)$(MAKE) -rR -f $(srctree)/scripts/Makefile.modpost + +# FIXME Should go into a make.lib or something +# =========================================================================== + +quiet_cmd_rmdirs = $(if $(wildcard $(rm-dirs)),CLEAN $(wildcard $(rm-dirs))) + cmd_rmdirs = rm -rf $(rm-dirs) + +quiet_cmd_rmfiles = $(if $(wildcard $(rm-files)),CLEAN $(wildcard $(rm-files))) + cmd_rmfiles = rm -f $(rm-files) + + +a_flags = -Wp,-MD,$(depfile) $(AFLAGS) $(AFLAGS_KERNEL) \ + $(NOSTDINC_FLAGS) $(CPPFLAGS) \ + $(modkern_aflags) $(EXTRA_AFLAGS) $(AFLAGS_$(*F).o) + +quiet_cmd_as_o_S = AS $@ +cmd_as_o_S = $(CC) $(a_flags) -c -o $@ $< + +# read all saved command lines + +targets := $(wildcard $(sort $(targets))) +cmd_files := $(wildcard .*.cmd $(foreach f,$(targets),$(dir $(f)).$(notdir $(f)).cmd)) + +ifneq ($(cmd_files),) + $(cmd_files): ; # Do not try to update included dependency files + include $(cmd_files) +endif + +# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.clean obj=dir +# Usage: +# $(Q)$(MAKE) $(clean)=dir +clean := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.clean obj + +endif # skip-makefile + +PHONY += FORCE +FORCE: + +-include $(srctree)/Makefile.custom + +# Declare the contents of the .PHONY variable as phony. We keep that +# information in a variable se we can use it in if_changed and friends. +.PHONY: $(PHONY) diff --git a/Makefile.custom b/Makefile.custom new file mode 100644 index 000000000..e976e739e --- /dev/null +++ b/Makefile.custom @@ -0,0 +1,145 @@ +# ========================================================================== +# Build system +# ========================================================================== + +%.bflt: %_unstripped + $(CROSS_COMPILE)elf2flt $(ELF2FLTFLAGS) $< -o $@ + +busybox.links: $(srctree)/applets/busybox.mkll $(objtree)/include/autoconf.h $(srctree)/include/applets.h + $(Q)-$(SHELL) $^ >$@ + +.PHONY: install +ifeq ($(CONFIG_INSTALL_APPLET_SYMLINKS),y) +INSTALL_OPTS:= --symlinks +endif +ifeq ($(CONFIG_INSTALL_APPLET_HARDLINKS),y) +INSTALL_OPTS:= --hardlinks +endif +install: $(srctree)/applets/install.sh busybox busybox.links + $(Q)DO_INSTALL_LIBS="$(strip $(LIBBUSYBOX_SONAME) $(DO_INSTALL_LIBS))" \ + $(SHELL) $< $(CONFIG_PREFIX) $(INSTALL_OPTS) +ifeq ($(strip $(CONFIG_FEATURE_SUID)),y) + @echo + @echo + @echo -------------------------------------------------- + @echo You will probably need to make your busybox binary + @echo setuid root to ensure all configured applets will + @echo work properly. + @echo -------------------------------------------------- + @echo +endif + +uninstall: busybox.links + rm -f $(CONFIG_PREFIX)/bin/busybox + for i in `cat busybox.links` ; do rm -f $(CONFIG_PREFIX)$$i; done +ifneq ($(strip $(DO_INSTALL_LIBS)),n) + for i in $(LIBBUSYBOX_SONAME) $(DO_INSTALL_LIBS); do \ + rm -f $(CONFIG_PREFIX)$$i; \ + done +endif + +check test: busybox + bindir=$(objtree) srcdir=$(srctree)/testsuite SED="$(SED)" \ + $(SHELL) $(srctree)/testsuite/runtest $(if $(KBUILD_VERBOSE:1=),-v) + +.PHONY: release +release: distclean + cd ..; \ + rm -r -f busybox-$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION); \ + cp -a busybox busybox-$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION) && { \ + find busybox-$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)/ -type d \ + -name .svn \ + -print \ + -exec rm -r -f {} \; ; \ + find busybox-$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)/ -type f \ + -name .\#* \ + -print \ + -exec rm -f {} \; ; \ + tar -czf busybox-$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION).tar.gz \ + busybox-$(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)/ ; } + +.PHONY: checkhelp +checkhelp: + $(Q)$(srctree)/scripts/checkhelp.awk \ + $(patsubst %,$(srctree)/%,$(wildcard $(patsubst %,%/Config.in,$(busybox-dirs) ./))) + +.PHONY: sizes +sizes: busybox_unstripped + $(NM) --size-sort $(<) + +.PHONY: bloatcheck +bloatcheck: busybox_old busybox_unstripped + @$(srctree)/scripts/bloat-o-meter busybox_old busybox_unstripped + +.PHONY: baseline +baseline: busybox_unstripped + @mv busybox_unstripped busybox_old + +.PHONY: objsizes +objsizes: busybox_unstripped + $(srctree)/scripts/objsizes + +.PHONY: bigdata +bigdata: busybox_unstripped + nm --size-sort busybox_unstripped | grep -vi ' [tr] ' | tail -20 + +# Documentation Targets +.PHONY: doc +doc: docs/busybox.pod docs/BusyBox.txt docs/BusyBox.1 docs/BusyBox.html + +docs/busybox.pod: $(srctree)/docs/busybox_header.pod \ + $(srctree)/include/usage.h \ + $(srctree)/docs/busybox_footer.pod \ + $(srctree)/docs/autodocifier.pl + $(disp_doc) + $(Q)-mkdir -p docs + $(Q)-( cat $(srctree)/docs/busybox_header.pod ; \ + $(srctree)/docs/autodocifier.pl $(srctree)/include/usage.h ; \ + cat $(srctree)/docs/busybox_footer.pod ; ) > docs/busybox.pod + +docs/BusyBox.txt: docs/busybox.pod + $(disp_doc) + $(Q)-mkdir -p docs + $(Q)-pod2text $< > $@ + +docs/BusyBox.1: docs/busybox.pod + $(disp_doc) + $(Q)-mkdir -p docs + $(Q)-pod2man --center=BusyBox --release="version $(VERSION)" \ + $< > $@ + +docs/BusyBox.html: docs/busybox.net/BusyBox.html + $(disp_doc) + $(Q)-mkdir -p docs + $(Q)-rm -f docs/BusyBox.html + $(Q)-cp docs/busybox.net/BusyBox.html docs/BusyBox.html + +docs/busybox.net/BusyBox.html: docs/busybox.pod + $(Q)-mkdir -p docs/busybox.net + $(Q)-pod2html --noindex $< > \ + docs/busybox.net/BusyBox.html + $(Q)-rm -f pod2htm* + +# documentation, cross-reference +# Modern distributions already ship synopsis packages (e.g. debian) +# If you have an old distribution go to http://synopsis.fresco.org/ +syn_tgt = $(wildcard $(patsubst %,%/*.c,$(busybox-alldirs))) +syn = $(patsubst %.c, %.syn, $(syn_tgt)) + +comma:= , +brace_open:= ( +brace_close:= ) + +SYN_CPPFLAGS := $(strip $(CPPFLAGS) $(EXTRA_CPPFLAGS)) +SYN_CPPFLAGS := $(subst $(brace_open),\$(brace_open),$(SYN_CPPFLAGS)) +SYN_CPPFLAGS := $(subst $(brace_close),\$(brace_close),$(SYN_CPPFLAGS)) +#SYN_CPPFLAGS := $(subst ",\",$(SYN_CPPFLAGS)) +#") +#SYN_CPPFLAGS := [$(patsubst %,'%'$(comma),$(SYN_CPPFLAGS))''] + +%.syn: %.c + synopsis -p C -l Comments.SSDFilter,Comments.Previous -Wp,preprocess=True,cppflags="'$(SYN_CPPFLAGS)'" -o $@ $< + +.PHONY: html +html: $(syn) + synopsis -f HTML -Wf,title="'BusyBox Documentation'" -o $@ $^ diff --git a/Makefile.flags b/Makefile.flags new file mode 100644 index 000000000..0261d34df --- /dev/null +++ b/Makefile.flags @@ -0,0 +1,28 @@ +# ========================================================================== +# Build system +# ========================================================================== + +BB_VER = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION) + +# -std=gnu99 needed for [U]LLONG_MAX on some systems + +CPPFLAGS += \ + -std=gnu99 \ + -Iinclude -Ilibbb \ + $(if $(KBUILD_SRC),-Iinclude2 -I$(srctree)/include) -I$(srctree)/libbb \ + -include include/autoconf.h \ + -D_GNU_SOURCE -DNDEBUG \ + $(if $(CONFIG_LFS),-D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64) \ + -D"BB_VER=KBUILD_STR($(BB_VER))" -DBB_BT=AUTOCONF_TIMESTAMP + +CFLAGS += \ + -Wall -Wstrict-prototypes -Wshadow -Werror -Wundef \ + -funsigned-char -fno-builtin-strlen -finline-limit=0 -static-libgcc \ + -Os -falign-functions=1 -falign-jumps=1 -falign-loops=1 \ + -fomit-frame-pointer -ffunction-sections -fdata-sections + +ifeq ($(CONFIG_DEBUG),y) +CFLAGS += -g +LDFLAGS += -g +endif + diff --git a/Makefile.help b/Makefile.help new file mode 100644 index 000000000..a1ff4fcb8 --- /dev/null +++ b/Makefile.help @@ -0,0 +1,42 @@ +# ========================================================================== +# Build system +# ========================================================================== + +help: + @echo 'Cleaning:' + @echo ' clean - delete temporary files created by build' + @echo ' distclean - delete all non-source files (including .config)' + @echo + @echo 'Build:' + @echo ' all - Executable and documentation' + @echo ' busybox - the swiss-army executable' + @echo ' doc - docs/BusyBox.{txt,html,1}' + @echo ' html - create html-based cross-reference' + @echo + @echo 'Configuration:' + @echo ' allnoconfig - disable all symbols in .config' + @echo ' allyesconfig - enable all symbols in .config (see defconfig)' + @echo ' allbareconfig - enable all applets without any sub-features' + @echo ' config - text based configurator (of last resort)' + @echo ' defconfig - set .config to largest generic configuration' + @echo ' menuconfig - interactive curses-based configurator' + @echo ' oldconfig - resolve any unresolved symbols in .config' + @echo ' hosttools - build sed for the host.' + @echo ' You can use these commands if the commands on the host' + @echo ' is unusable. Afterwards use it like:' + @echo ' make SED="$(objtree)/sed"' + @echo + @echo 'Installation:' + @echo ' install - install busybox into $(PREFIX)' + @echo ' uninstall' + @echo + @echo 'Development:' + @echo ' baseline - create busybox_old for bloatcheck.' + @echo ' bloatcheck - show size difference between old and new versions' + @echo ' check - run the test suite for all applets' + @echo ' checkhelp - check for missing help-entries in Config.in' + @echo ' randconfig - generate a random configuration' + @echo ' release - create a distribution tarball' + @echo ' sizes - show size of all enabled busybox symbols' + @echo ' objsizes - show size of each .o object built' + @echo diff --git a/README b/README new file mode 100644 index 000000000..0ed196a53 --- /dev/null +++ b/README @@ -0,0 +1,198 @@ +Please see the LICENSE file for details on copying and usage. +Please refer to the INSTALL file for instructions on how to build. + +What is busybox: + + BusyBox combines tiny versions of many common UNIX utilities into a single + small executable. It provides minimalist replacements for most of the + utilities you usually find in bzip2, coreutils, dhcp, diffutils, e2fsprogs, + file, findutils, gawk, grep, inetutils, less, modutils, net-tools, procps, + sed, shadow, sysklogd, sysvinit, tar, util-linux, and vim. The utilities + in BusyBox often have fewer options than their full-featured cousins; + however, the options that are included provide the expected functionality + and behave very much like their larger counterparts. + + BusyBox has been written with size-optimization and limited resources in + mind, both to produce small binaries and to reduce run-time memory usage. + Busybox is also extremely modular so you can easily include or exclude + commands (or features) at compile time. This makes it easy to customize + embedded systems; to create a working system, just add /dev, /etc, and a + Linux kernel. Busybox (usually together with uClibc) has also been used as + a component of "thin client" desktop systems, live-CD distributions, rescue + disks, installers, and so on. + + BusyBox provides a fairly complete POSIX environment for any small system, + both embedded environments and more full featured systems concerned about + space. Busybox is slowly working towards implementing the full Single Unix + Specification V3 (http://www.opengroup.org/onlinepubs/009695399/), but isn't + there yet (and for size reasons will probably support at most UTF-8 for + internationalization). We are also interested in passing the Linux Test + Project (http://ltp.sourceforge.net). + +---------------- + +Using busybox: + + BusyBox is extremely configurable. This allows you to include only the + components and options you need, thereby reducing binary size. Run 'make + config' or 'make menuconfig' to select the functionality that you wish to + enable. (See 'make help' for more commands.) + + The behavior of busybox is determined by the name it's called under: as + "cp" it behaves like cp, as "sed" it behaves like sed, and so on. Called + as "busybox" it takes the second argument as the name of the applet to + run (I.E. "./busybox ls -l /proc"). + + The "standalone shell" mode is an easy way to try out busybox; this is a + command shell that calls the builtin applets without needing them to be + installed in the path. (Note that this requires /proc to be mounted, if + testing from a boot floppy or in a chroot environment.) + + The build automatically generates a file "busybox.links", which is used by + 'make install' to create symlinks to the BusyBox binary for all compiled in + commands. This uses the PREFIX environment variable to specify where to + install, and installs hardlinks or symlinks depending on the configuration + preferences. (You can also manually run the install script at + "applets/install.sh"). + +---------------- + +Downloading the current source code: + + Source for the latest released version, as well as daily snapshots, can always + be downloaded from + + http://busybox.net/downloads/ + + You can browse the up to the minute source code and change history online. + + http://www.busybox.net/cgi-bin/viewcvs.cgi/trunk/busybox/ + + Anonymous SVN access is available. For instructions, check out: + + http://busybox.net/subversion.html + + For those that are actively contributing and would like to check files in, + see: + + http://busybox.net/developer.html + + The developers also have a bug and patch tracking system + (http://bugs.busybox.net) although posting a bug/patch to the mailing list + is generally a faster way of getting it fixed, and the complete archive of + what happened is the subversion changelog. + +---------------- + +getting help: + + when you find you need help, you can check out the busybox mailing list + archives at http://busybox.net/lists/busybox/ or even join + the mailing list if you are interested. + +---------------- + +bugs: + + if you find bugs, please submit a detailed bug report to the busybox mailing + list at busybox@busybox.net. a well-written bug report should include a + transcript of a shell session that demonstrates the bad behavior and enables + anyone else to duplicate the bug on their own machine. the following is such + an example: + + to: busybox@busybox.net + from: diligent@testing.linux.org + subject: /bin/date doesn't work + + package: busybox + version: 1.00 + + when i execute busybox 'date' it produces unexpected results. + with gnu date i get the following output: + + $ date + fri oct 8 14:19:41 mdt 2004 + + but when i use busybox date i get this instead: + + $ date + illegal instruction + + i am using debian unstable, kernel version 2.4.25-vrs2 on a netwinder, + and the latest uclibc from cvs. thanks for the wonderful program! + + -diligent + + note the careful description and use of examples showing not only what + busybox does, but also a counter example showing what an equivalent app + does (or pointing to the text of a relevant standard). Bug reports lacking + such detail may never be fixed... Thanks for understanding. + +---------------- + +Portability: + + Busybox is developed and tested on Linux 2.4 and 2.6 kernels, compiled + with gcc (the unit-at-a-time optimizations in version 3.4 and later are + worth upgrading to get, but older versions should work), and linked against + uClibc (0.9.27 or greater) or glibc (2.2 or greater). In such an + environment, the full set of busybox features should work, and if + anything doesn't we want to know about it so we can fix it. + + There are many other environments out there, in which busybox may build + and run just fine. We just don't test them. Since busybox consists of a + large number of more or less independent applets, portability is a question + of which features work where. Some busybox applets (such as cat and rm) are + highly portable and likely to work just about anywhere, while others (such as + insmod and losetup) require recent Linux kernels with recent C libraries. + + Earlier versions of Linux and glibc may or may not work, for any given + configuration. Linux 2.2 or earlier should mostly work (there's still + some support code in things like mount.c) but this is no longer regularly + tested, and inherently won't support certain features (such as long files + and --bind mounts). The same is true for glibc 2.0 and 2.1: expect a higher + testing and debugging burden using such old infrastructure. (The busybox + developers are not very interested in supporting these older versions, but + will probably accept small self-contained patches to fix simple problems.) + + Some environments are not recommended. Early versions of uClibc were buggy + and missing many features: upgrade. Linking against libc5 or dietlibc is + not supported and not interesting to the busybox developers. (The first is + obsolete and has no known size or feature advantages over uClibc, the second + has known bugs that its developers have actively refused to fix.) Ancient + Linux kernels (2.0.x and earlier) are similarly uninteresting. + + In theory it's possible to use Busybox under other operating systems (such as + MacOS X, Solaris, Cygwin, or the BSD Fork Du Jour). This generally involves + a different kernel and a different C library at the same time. While it + should be possible to port the majority of the code to work in one of + these environments, don't be suprised if it doesn't work out of the box. If + you're into that sort of thing, start small (selecting just a few applets) + and work your way up. + + Shaun Jackman has recently (2005) ported busybox to a combination of newlib + and libgloss, and some of his patches have been integrated. This platform + may join glibc/uclibc and Linux as a supported combination with the 1.1 + release, but is not supported in 1.0. + +Supported hardware: + + BusyBox in general will build on any architecture supported by gcc. We + support both 32 and 64 bit platforms, and both big and little endian + systems. + + Under 2.4 Linux kernels, kernel module loading was implemented in a + platform-specific manner. Busybox's insmod utility has been reported to + work under ARM, CRIS, H8/300, x86, ia64, x86_64, m68k, MIPS, PowerPC, S390, + SH3/4/5, Sparc, v850e, and x86_64. Anything else probably won't work. + + The module loading mechanism for the 2.6 kernel is much more generic, and + we believe 2.6.x kernel module loading support should work on all + architectures supported by the kernel. + +---------------- + +Please feed suggestions, bug reports, insults, and bribes back to the busybox +maintainer: + Denis Vlasenko + diff --git a/TODO b/TODO new file mode 100644 index 000000000..f0b720ba4 --- /dev/null +++ b/TODO @@ -0,0 +1,309 @@ +Busybox TODO + +Stuff that needs to be done. This is organized by who plans to get around to +doing it eventually, but that doesn't mean they "own" the item. If you want to +do one of these bounce an email off the person it's listed under to see if they +have any suggestions how they plan to go about it, and to minimize conflicts +between your work and theirs. But otherwise, all of these are fair game. + +Rob Landley : + Add BB_NOMMU to platform.h and migrate __uClinux__ tests to that. + #if defined __UCLIBC__ && !defined __ARCH_USE_MMU__ + Add a libbb/platform.c + Implement fdprintf() for platforms that haven't got one. + Implement bb_realpath() that can handle NULL on non-glibc. + Cleanup bb_asprintf() + + Migrate calloc() and bb_calloc() occurrences to bb_xzalloc(). + Remove obsolete _() wrapper crud for internationalization we don't do. + Figure out where we need utf8 support, and add it. + + sh + The command shell situation is a big mess. We have three or four different + shells that don't really share any code, and the "standalone shell" doesn't + work all that well (especially not in a chroot environment), due to apps not + being reentrant. I'm writing a new shell (bbsh) to unify the various + shells and configurably add the minimal set of bash features people + actually use. The hardest part is it has to configure down as small as + lash while providing lash's features. The rest is easy in comparison. + bzip2 + Compression-side support. + init + General cleanup (should use ENABLE_FEATURE_INIT_SYSLOG and ENABLE_FEATURE_INIT_DEBUG). + depmod + busybox lacks a way to update module deps when running from firmware without the + use of the depmod.pl (perl is to bloated for most embedded setups) and or orig + modutils. The orig depmod is rather pointless to have to add to a firmware image + in when we already have a insmod/rmmod and friends. + Unify base64 handling. + There's base64 encoding and decoding going on in: + networking/wget.c:base64enc() + coreutils/uudecode.c:read_base64() + coreutils/uuencode.c:tbl_base64[] + networking/httpd.c:decodeBase64() + And probably elsewhere. That needs to be unified into libbb functions. + Do a SUSv3 audit + Look at the full Single Unix Specification version 3 (available online at + "http://www.opengroup.org/onlinepubs/009695399/nfindex.html") and + figure out which of our apps are compliant, and what we're missing that + we might actually care about. + + Even better would be some kind of automated compliance test harness that + exercises each command line option and the various corner cases. + Internationalization + How much internationalization should we do? + + The low hanging fruit is UTF-8 character set support. We should do this. + (Vodz pointed out the shell's cmdedit as needing work here. What else?) + + We also have lots of hardwired english text messages. Consolidating this + into some kind of message table not only makes translation easier, but + also allows us to consolidate redundant (or close) strings. + + We probably don't want to be bloated with locale support. (Not unless we + can cleanly export it from our underlying C library without having to + concern ourselves with it directly. Perhaps a few specific things like a + config option for "date" are low hanging fruit here?) + + What level should things happen at? How much do we care about + internationalizing the text console when X11 and xterms are so much better + at it? (There's some infrastructure here we don't implement: The + "unicode_start" and "unicode_stop" shell scripts need "vt-is-UTF8" and a + --unicode option to loadkeys. That implies a real loadkeys/dumpkeys + implementation to replace loadkmap/dumpkmap. Plus messing with console font + loading. Is it worth it, or do we just say "use X"?) + + Individual compilation of applets. + It would be nice if busybox had the option to compile to individual applets, + for people who want an alternate implementation less bloated than the gnu + utils (or simply with less political baggage), but without it being one big + executable. + + Turning libbb into a real dll is another possibility, especially if libbb + could export some of the other library interfaces we've already more or less + got the code for (like zlib). + buildroot - Make a "dogfood" option + Busybox 1.1 will be capable of replacing most gnu packages for real world + use, such as developing software or in a live CD. It needs wider testing. + + Busybox should now be able to replace bzip2, coreutils, e2fsprogs, file, + findutils, gawk, grep, inetutils, less, modutils, net-tools, patch, procps, + sed, shadow, sysklogd, sysvinit, tar, util-linux, and vim. The resulting + system should be self-hosting (I.E. able to rebuild itself from source + code). This means it would need (at least) binutils, gcc, and make, or + equivalents. + + It would be a good "eating our own dogfood" test if buildroot had the option + of using a "make allyesconfig" busybox instead of the all of the above + packages. Anything that's wrong with the resulting system, we can fix. (It + would be nice to be able to upgrade busybox to be able to replace bash and + diffutils as well, but we're not there yet.) + + One example of an existing system that does this already is Firmware Linux: + http://www.landley.net/code/firmware + initramfs + Busybox should have a sample initramfs build script. This depends on + bbsh, mdev, and switch_root. + mkdep + Write a mkdep that doesn't segfault if there's a directory it doesn't + have permission to read, isn't based on manually editing the output of + lexx and yacc, doesn't make such a mess under include/config, etc. + Group globals into unions of structures. + Go through and turn all the global and static variables into structures, + and have all those structures be in a big union shared between processes, + so busybox uses less bss. (This is a big win on nommu machines.) See + sed.c and mdev.c for examples. + Go through bugs.busybox.net and close out all of that somehow. + This one's open to everybody, but I'll wind up doing it... + + +Bernhard Fischer suggests to look at these: + New debug options: + -Wlarger-than-127 + Cleanup any big users + -Wunused-parameter + Facilitate applet PROTOTYPES to provide means for having applets that + do a) not take any arguments b) need only one of argc or argv c) need + both argc and argv. All of these three options should go for the most + feature complete denominator. + Collate BUFSIZ IOBUF_SIZE MY_BUF_SIZE PIPE_PROGRESS_SIZE BUFSIZE PIPESIZE + make bb_common_bufsiz1 configurable, size wise. + make pipesize configurable, size wise. + Use bb_common_bufsiz1 throughout applets! + Add chrt applet. Please CC Bernhard if you suggest a patch. + +As yet unclaimed: + +---- +find + doesn't understand (), lots of susv3 stuff. +---- +diff + Make sure we handle empty files properly: + From the patch man page: + + you can remove a file by sending out a context diff that compares + the file to be deleted with an empty file dated the Epoch. The + file will be removed unless patch is conforming to POSIX and the + -E or --remove-empty-files option is not given. +--- +patch + Should have simple fuzz factor support to apply patches at an offset which + shouldn't take up too much space. + + And while we're at it, a new patch filename quoting format is apparently + coming soon: http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2 +--- +man + It would be nice to have a man command. Not one that handles troff or + anything, just one that can handle preformatted ascii man pages, possibly + compressed. This could probably be a script in the extras directory that + calls cat/zcat/bzcat | less + + (How doclifter might work into this is anybody's guess.) +--- +ar + Write support? +--- + +Architectural issues: + +bb_close() with fsync() + We should have a bb_close() in place of normal close, with a CONFIG_ option + to not just check the return value of close() for an error, but fsync(). + Close can't reliably report anything useful because if write() accepted the + data then it either went out to the network or it's in cache or a pipe + buffer. Either way, there's no guarantee it'll make it to its final + destination before close() gets called, so there's no guarantee that any + error will be reported. + + You need to call fsync() if you care about errors that occur after write(), + but that can have a big performance impact. So make it a config option. +--- +Unify archivers + Lots of archivers have the same general infrastructure. The directory + traversal code should be factored out, and the guts of each archiver could + be some setup code and a series of callbacks for "add this file", + "add this directory", "add this symlink" and so on. + + This could clean up tar and zip, and make it cheaper to add cpio and ar + write support, and possibly even cheaply add things like mkisofs or + mksquashfs someday, if they become relevant. +--- +Text buffer support. + Several existing applets (sort, vi, less...) read + a whole file into memory and act on it. There might be an opportunity + for shared code in there that could be moved into libbb... +--- +Memory Allocation + We have a CONFIG_BUFFER mechanism that lets us select whether to do memory + allocation on the stack or the heap. Unfortunately, we're not using it much. + We need to audit our memory allocations and turn a lot of malloc/free calls + into RESERVE_CONFIG_BUFFER/RELEASE_CONFIG_BUFFER. + For a start, see e.g. make EXTRA_CFLAGS=-Wlarger-than-64 + + And while we're at it, many of the CONFIG_FEATURE_CLEAN_UP #ifdefs will be + optimized out by the compiler in the stack allocation case (since there's no + free for an alloca()), and this means that various cleanup loops that just + call free might also be optimized out by the compiler if written right, so + we can yank those #ifdefs too, and generally clean up the code. +--- +Switch CONFIG_SYMBOLS to ENABLE_SYMBOLS + + In busybox 1.0 and earlier, configuration was done by CONFIG_SYMBOLS + that were either defined or undefined to indicate whether the symbol was + selected in the .config file. They were used with #ifdefs, ala: + + #ifdef CONFIG_SYMBOL + if (other_test) { + do_code(); + } + #endif + + In 1.1, we have new ENABLE_SYMBOLS which are always defined (as 0 or 1), + meaning you can still use them for preprocessor tests by replacing + "#ifdef CONFIG_SYMBOL" with "#if ENABLE_SYMBOL". But more importantly, we + can use them as a true or false test in normal C code: + + if (ENABLE_SYMBOL && other_test) { + do_code(); + } + + (Optimizing away if() statements that resolve to a constant value + is known as "dead code elimination", an optimization so old and simple that + Turbo Pascal for DOS did it twenty years ago. Even modern mini-compilers + like the Tiny C Compiler (tcc) and the Small Device C Compiler (SDCC) + perform dead code elimination.) + + Right now, busybox.h is #including both "config.h" (defining the + CONFIG_SYMBOLS) and "bb_config.h" (defining the ENABLE_SYMBOLS). At some + point in the future, it would be nice to wean ourselves off of the + CONFIG versions. (Among other things, some defective build environments + leak the Linux kernel's CONFIG_SYMBOLS into the system's standard #include + files. We've experienced collisions before.) +--- +FEATURE_CLEAN_UP + This is more an unresolved issue than a to-do item. More thought is needed. + + Normally we rely on exit() to free memory, close files, and unmap segments + for us. This makes most calls to free(), close(), and unmap() optional in + busybox applets that don't intend to run for very long, and optional stuff + can be omitted to save size. + + The idea was raised that we could simulate fork/exit with setjmp/longjmp + for _really_ brainless embedded systems, or speed up the standalone shell + by not forking. Doing so would require a reliable FEATURE_CLEAN_UP. + Unfortunately, this isn't as easy as it sounds. + + The problem is, lots of things exit(), sometimes unexpectedly (xmalloc()) + and sometimes reliably (bb_perror_msg_and_die() or show_usage()). This + jumps out of the normal flow control and bypasses any cleanup code we + put at the end of our applets. + + It's possible to add hooks to libbb functions like xmalloc() and xopen() + to add their entries to a linked list, which could be traversed and + freed/closed automatically. (This would need to be able to free just the + entries after a checkpoint to be usable for a forkless standalone shell. + You don't want to free the shell's own resources.) + + Right now, FEATURE_CLEAN_UP is more or less a debugging aid, to make things + like valgrind happy. It's also documentation of _what_ we're trusting + exit() to clean up for us. But new infrastructure to auto-free stuff would + render the existing FEATURE_CLEAN_UP code redundant. + + For right now, exit() handles it just fine. + + + +Minor stuff: + watchdog.c could autodetect the timer duration via: + if(!ioctl (fd, WDIOC_GETTIMEOUT, &tmo)) timer_duration = 1 + (tmo / 2); + Unfortunately, that needs linux/watchdog.h and that contains unfiltered + kernel types on some distros, which breaks the build. +--- + use bb_error_msg where appropriate: See + egrep "(printf.*\([[:space:]]*(stderr|2)|[^_]write.*\([[:space:]]*(stderr|2))" +--- + use bb_perror_msg where appropriate: See + egrep "[^_]perror" +--- + Remove superfluous fmt occurances: e.g. + fprintf(stderr, "%s: %s not found\n", "unalias", *argptr); + -> fprintf(stderr, "unalias: %s not found\n", *argptr); +--- + possible code duplication ingroup() and is_a_group_member() +--- + Move __get_hz() to a better place and (re)use it in route.c, ash.c, msh.c +--- + + +Code cleanup: + +Replace deprecated functions. + +bzero() -> memset() +--- +sigblock(), siggetmask(), sigsetmask(), sigmask() -> sigprocmask et al +--- +vdprintf() -> similar sized functionality +--- diff --git a/applets/Kbuild b/applets/Kbuild new file mode 100644 index 000000000..c95c09d97 --- /dev/null +++ b/applets/Kbuild @@ -0,0 +1,21 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +obj-y:= +obj-y += applets.o +obj-y += busybox.o + +# Generated file needs additional love + +applets/applets.o: include/usage_compressed.h + +hostprogs-y += usage +always := $(hostprogs-y) +HOSTCFLAGS_usage.o = -I$(srctree)/include + +include/usage_compressed.h: $(srctree)/include/usage.h applets/usage + @echo ' GEN include/usage_compressed.h' + @$(srctree)/applets/usage_compressed include/usage_compressed.h applets diff --git a/applets/applets.c b/applets/applets.c new file mode 100644 index 000000000..23f9e4f92 --- /dev/null +++ b/applets/applets.c @@ -0,0 +1,483 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) tons of folks. Tracking down who wrote what + * isn't something I'm going to worry about... If you wrote something + * here, please feel free to acknowledge your work. + * + * Based in part on code from sash, Copyright (c) 1999 by David I. Bell + * Permission has been granted to redistribute this code under the GPL. + * + * Licensed under GPLv2 or later, see file License in this tarball for details. + */ + +#include "busybox.h" +#include +#include +#include + +/* Apparently uclibc defines __GLIBC__ (compat trick?). Oh well. */ +#if ENABLE_STATIC && defined(__GLIBC__) && !defined(__UCLIBC__) +#warning Static linking against glibc produces buggy executables +#warning (glibc does not cope well with ld --gc-sections). +#warning See sources.redhat.com/bugzilla/show_bug.cgi?id=3400 +#warning Note that glibc is utterly unsuitable for static linking anyway. +#endif + +#if ENABLE_SHOW_USAGE && !ENABLE_FEATURE_COMPRESS_USAGE +static const char usage_messages[] = +#define MAKE_USAGE +#include "usage.h" +#include "applets.h" +; +#undef MAKE_USAGE +#else +#define usage_messages 0 +#endif /* ENABLE_SHOW_USAGE */ + +#undef APPLET +#undef APPLET_NOUSAGE +#undef PROTOTYPES +#include "applets.h" + +static struct BB_applet *applet_using; + +/* The -1 arises because of the {0,NULL,0,-1} entry above. */ +const size_t NUM_APPLETS = (sizeof (applets) / sizeof (struct BB_applet) - 1); + + +#ifdef CONFIG_FEATURE_SUID_CONFIG + +#include + +#define CONFIG_FILE "/etc/busybox.conf" + +/* applets [] is const, so we have to define this "override" structure */ +static struct BB_suid_config +{ + struct BB_applet *m_applet; + + uid_t m_uid; + gid_t m_gid; + mode_t m_mode; + + struct BB_suid_config *m_next; +} *suid_config; + +static int suid_cfg_readable; + +/* check if u is member of group g */ +static int ingroup(uid_t u, gid_t g) +{ + struct group *grp = getgrgid(g); + + if (grp) { + char **mem; + + for (mem = grp->gr_mem; *mem; mem++) { + struct passwd *pwd = getpwnam(*mem); + + if (pwd && (pwd->pw_uid == u)) + return 1; + } + } + return 0; +} + +/* This should probably be a libbb routine. In that case, + * I'd probably rename it to something like bb_trimmed_slice. + */ +static char *get_trimmed_slice(char *s, char *e) +{ + /* First, consider the value at e to be nul and back up until we + * reach a non-space char. Set the char after that (possibly at + * the original e) to nul. */ + while (e-- > s) { + if (!isspace(*e)) { + break; + } + } + e[1] = 0; + + /* Next, advance past all leading space and return a ptr to the + * first non-space char; possibly the terminating nul. */ + return skip_whitespace(s); +} + + +#define parse_error(x) { err=x; goto pe_label; } + +/* Don't depend on the tools to combine strings. */ +static const char config_file[] = CONFIG_FILE; + +/* There are 4 chars + 1 nul for each of user/group/other. */ +static const char mode_chars[] = "Ssx-\0Ssx-\0Ttx-"; + +/* We don't supply a value for the nul, so an index adjustment is + * necessary below. Also, we use unsigned short here to save some + * space even though these are really mode_t values. */ +static const unsigned short mode_mask[] = { + /* SST sst xxx --- */ + S_ISUID, S_ISUID|S_IXUSR, S_IXUSR, 0, /* user */ + S_ISGID, S_ISGID|S_IXGRP, S_IXGRP, 0, /* group */ + 0, S_IXOTH, S_IXOTH, 0 /* other */ +}; + +static void parse_config_file(void) +{ + struct BB_suid_config *sct_head; + struct BB_suid_config *sct; + struct BB_applet *applet; + FILE *f; + char *err; + char *s; + char *e; + int i, lc, section; + char buffer[256]; + struct stat st; + + assert(!suid_config); /* Should be set to NULL by bss init. */ + + if ((stat(config_file, &st) != 0) /* No config file? */ + || !S_ISREG(st.st_mode) /* Not a regular file? */ + || (st.st_uid != 0) /* Not owned by root? */ + || (st.st_mode & (S_IWGRP | S_IWOTH)) /* Writable by non-root? */ + || !(f = fopen(config_file, "r")) /* Cannot open? */ + ) { + return; + } + + suid_cfg_readable = 1; + sct_head = NULL; + section = lc = 0; + + do { + s = buffer; + + if (!fgets(s, sizeof(buffer), f)) { /* Are we done? */ + if (ferror(f)) { /* Make sure it wasn't a read error. */ + parse_error("reading"); + } + fclose(f); + suid_config = sct_head; /* Success, so set the pointer. */ + return; + } + + lc++; /* Got a (partial) line. */ + + /* If a line is too long for our buffer, we consider it an error. + * The following test does mistreat one corner case though. + * If the final line of the file does not end with a newline and + * yet exactly fills the buffer, it will be treated as too long + * even though there isn't really a problem. But it isn't really + * worth adding code to deal with such an unlikely situation, and + * we do err on the side of caution. Besides, the line would be + * too long if it did end with a newline. */ + if (!strchr(s, '\n') && !feof(f)) { + parse_error("line too long"); + } + + /* Trim leading and trailing whitespace, ignoring comments, and + * check if the resulting string is empty. */ + if (!*(s = get_trimmed_slice(s, strchrnul(s, '#')))) { + continue; + } + + /* Check for a section header. */ + + if (*s == '[') { + /* Unlike the old code, we ignore leading and trailing + * whitespace for the section name. We also require that + * there are no stray characters after the closing bracket. */ + if (!(e = strchr(s, ']')) /* Missing right bracket? */ + || e[1] /* Trailing characters? */ + || !*(s = get_trimmed_slice(s+1, e)) /* Missing name? */ + ) { + parse_error("section header"); + } + /* Right now we only have one section so just check it. + * If more sections are added in the future, please don't + * resort to cascading ifs with multiple strcasecmp calls. + * That kind of bloated code is all too common. A loop + * and a string table would be a better choice unless the + * number of sections is very small. */ + if (strcasecmp(s, "SUID") == 0) { + section = 1; + continue; + } + section = -1; /* Unknown section so set to skip. */ + continue; + } + + /* Process sections. */ + + if (section == 1) { /* SUID */ + /* Since we trimmed leading and trailing space above, we're + * now looking for strings of the form + * [::space::]*=[::space::]* + * where both key and value could contain inner whitespace. */ + + /* First get the key (an applet name in our case). */ + if (!!(e = strchr(s, '='))) { + s = get_trimmed_slice(s, e); + } + if (!e || !*s) { /* Missing '=' or empty key. */ + parse_error("keyword"); + } + + /* Ok, we have an applet name. Process the rhs if this + * applet is currently built in and ignore it otherwise. + * Note: This can hide config file bugs which only pop + * up when the busybox configuration is changed. */ + if ((applet = find_applet_by_name(s))) { + /* Note: We currently don't check for duplicates! + * The last config line for each applet will be the + * one used since we insert at the head of the list. + * I suppose this could be considered a feature. */ + sct = xmalloc(sizeof(struct BB_suid_config)); + sct->m_applet = applet; + sct->m_mode = 0; + sct->m_next = sct_head; + sct_head = sct; + + /* Get the specified mode. */ + + e = skip_whitespace(e+1); + + for (i=0 ; i < 3 ; i++) { + const char *q; + if (!*(q = strchrnul(mode_chars + 5*i, *e++))) { + parse_error("mode"); + } + /* Adjust by -i to account for nul. */ + sct->m_mode |= mode_mask[(q - mode_chars) - i]; + } + + /* Now get the the user/group info. */ + + s = skip_whitespace(e); + + /* Note: We require whitespace between the mode and the + * user/group info. */ + if ((s == e) || !(e = strchr(s, '.'))) { + parse_error("."); + } + *e++ = 0; + + /* We can't use get_ug_id here since it would exit() + * if a uid or gid was not found. Oh well... */ + { + char *e2; + + sct->m_uid = strtoul(s, &e2, 10); + if (*e2 || (s == e2)) { + struct passwd *pwd = getpwnam(s); + if (!pwd) { + parse_error("user"); + } + sct->m_uid = pwd->pw_uid; + } + + sct->m_gid = strtoul(e, &e2, 10); + if (*e2 || (e == e2)) { + struct group *grp; + if (!(grp = getgrnam(e))) { + parse_error("group"); + } + sct->m_gid = grp->gr_gid; + } + } + } + continue; + } + + /* Unknown sections are ignored. */ + + /* Encountering configuration lines prior to seeing a + * section header is treated as an error. This is how + * the old code worked, but it may not be desirable. + * We may want to simply ignore such lines in case they + * are used in some future version of busybox. */ + if (!section) { + parse_error("keyword outside section"); + } + + } while (1); + + pe_label: + fprintf(stderr, "Parse error in %s, line %d: %s\n", + config_file, lc, err); + + fclose(f); + /* Release any allocated memory before returning. */ + while (sct_head) { + sct = sct_head->m_next; + free(sct_head); + sct_head = sct; + } + return; +} + +#else +#define parse_config_file() +#endif /* CONFIG_FEATURE_SUID_CONFIG */ + +#ifdef CONFIG_FEATURE_SUID +static void check_suid(struct BB_applet *applet) +{ + uid_t ruid = getuid(); /* real [ug]id */ + uid_t rgid = getgid(); + +#ifdef CONFIG_FEATURE_SUID_CONFIG + if (suid_cfg_readable) { + struct BB_suid_config *sct; + + for (sct = suid_config; sct; sct = sct->m_next) { + if (sct->m_applet == applet) + break; + } + if (sct) { + mode_t m = sct->m_mode; + + if (sct->m_uid == ruid) /* same uid */ + m >>= 6; + else if ((sct->m_gid == rgid) || ingroup(ruid, sct->m_gid)) /* same group / in group */ + m >>= 3; + + if (!(m & S_IXOTH)) /* is x bit not set ? */ + bb_error_msg_and_die("you have no permission to run this applet!"); + + if ((sct->m_mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) { /* *both* have to be set for sgid */ + xsetgid(sct->m_gid); + } else xsetgid(rgid); /* no sgid -> drop */ + + if (sct->m_mode & S_ISUID) xsetuid(sct->m_uid); + else xsetuid(ruid); /* no suid -> drop */ + } else { + /* default: drop all privileges */ + xsetgid(rgid); + xsetuid(ruid); + } + return; + } else { +#ifndef CONFIG_FEATURE_SUID_CONFIG_QUIET + static int onetime = 0; + + if (!onetime) { + onetime = 1; + fprintf(stderr, "Using fallback suid method\n"); + } +#endif + } +#endif + + if (applet->need_suid == _BB_SUID_ALWAYS) { + if (geteuid()) bb_error_msg_and_die("applet requires root privileges!"); + } else if (applet->need_suid == _BB_SUID_NEVER) { + xsetgid(rgid); /* drop all privileges */ + xsetuid(ruid); + } +} +#else +#define check_suid(x) +#endif /* CONFIG_FEATURE_SUID */ + + + +#ifdef CONFIG_FEATURE_COMPRESS_USAGE + +#include "usage_compressed.h" +#include "unarchive.h" + +static const char *unpack_usage_messages(void) +{ + int input[2], output[2], pid; + char *buf; + + if(pipe(input) < 0 || pipe(output) < 0) + exit(1); + + pid = fork(); + switch (pid) { + case -1: /* error */ + exit(1); + case 0: /* child */ + close(input[1]); + close(output[0]); + uncompressStream(input[0], output[1]); + exit(0); + } + /* parent */ + + close(input[0]); + close(output[1]); + pid = fork(); + switch (pid) { + case -1: /* error */ + exit(1); + case 0: /* child */ + full_write(input[1], packed_usage, sizeof(packed_usage)); + exit(0); + } + /* parent */ + close(input[1]); + + buf = xmalloc(SIZEOF_usage_messages); + full_read(output[0], buf, SIZEOF_usage_messages); + return buf; +} + +#else +#define unpack_usage_messages() usage_messages +#endif /* ENABLE_FEATURE_COMPRESS_USAGE */ + +void bb_show_usage(void) +{ + if (ENABLE_SHOW_USAGE) { + const char *format_string; + const char *usage_string = unpack_usage_messages(); + int i; + + for (i = applet_using - applets; i > 0;) + if (!*usage_string++) --i; + + format_string = "%s\n\nUsage: %s %s\n\n"; + if (*usage_string == '\b') + format_string = "%s\n\nNo help available.\n\n"; + fprintf(stderr, format_string, bb_msg_full_version, + applet_using->name, usage_string); + } + + exit(xfunc_error_retval); +} + +static int applet_name_compare(const void *name, const void *vapplet) +{ + const struct BB_applet *applet = vapplet; + + return strcmp(name, applet->name); +} + +extern const size_t NUM_APPLETS; + +struct BB_applet *find_applet_by_name(const char *name) +{ + return bsearch(name, applets, NUM_APPLETS, sizeof(struct BB_applet), + applet_name_compare); +} + +void run_applet_by_name(const char *name, int argc, char **argv) +{ + if (ENABLE_FEATURE_SUID_CONFIG) parse_config_file(); + + if (!strncmp(name, "busybox", 7)) busybox_main(argc, argv); + /* Do a binary search to find the applet entry given the name. */ + applet_using = find_applet_by_name(name); + if (applet_using) { + applet_name = applet_using->name; + if(argc==2 && !strcmp(argv[1], "--help")) bb_show_usage(); + if(ENABLE_FEATURE_SUID) check_suid(applet_using); + exit((*(applet_using->main))(argc, argv)); + } +} diff --git a/applets/busybox.c b/applets/busybox.c new file mode 100644 index 000000000..bb9eb3af7 --- /dev/null +++ b/applets/busybox.c @@ -0,0 +1,148 @@ +/* vi: set sw=4 ts=4: */ +/* + * BusyBox' main applet dispatcher. + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + */ +#include "busybox.h" + +const char *applet_name ATTRIBUTE_EXTERNALLY_VISIBLE; + +#ifdef CONFIG_FEATURE_INSTALLER +/* + * directory table + * this should be consistent w/ the enum, busybox.h::Location, + * or else... + */ +static const char usr_bin [] ="/usr/bin"; +static const char usr_sbin[] ="/usr/sbin"; + +static const char* const install_dir[] = { + &usr_bin [8], /* "", equivalent to "/" for concat_path_file() */ + &usr_bin [4], /* "/bin" */ + &usr_sbin[4], /* "/sbin" */ + usr_bin, + usr_sbin +}; + +/* abstract link() */ +typedef int (*__link_f)(const char *, const char *); + +/* create (sym)links for each applet */ +static void install_links(const char *busybox, int use_symbolic_links) +{ + __link_f Link = link; + + char *fpc; + int i; + int rc; + + if (use_symbolic_links) + Link = symlink; + + for (i = 0; applets[i].name != NULL; i++) { + fpc = concat_path_file( + install_dir[applets[i].location], applets[i].name); + rc = Link(busybox, fpc); + if (rc!=0 && errno!=EEXIST) { + bb_perror_msg("%s", fpc); + } + free(fpc); + } +} + +#else +#define install_links(x,y) +#endif /* CONFIG_FEATURE_INSTALLER */ + +int main(int argc, char **argv) +{ + const char *s; + + applet_name=argv[0]; + if (*applet_name == '-') applet_name++; + for (s = applet_name; *s ;) + if (*(s++) == '/') applet_name = s; + + /* Set locale for everybody except 'init' */ + if (ENABLE_LOCALE_SUPPORT && getpid() != 1) + setlocale(LC_ALL, ""); + + run_applet_by_name(applet_name, argc, argv); + bb_error_msg_and_die("applet not found"); +} + +int busybox_main(int argc, char **argv) +{ + /* + * This style of argument parsing doesn't scale well + * in the event that busybox starts wanting more --options. + * If someone has a cleaner approach, by all means implement it. + */ + if (ENABLE_FEATURE_INSTALLER && argc > 1 && !strcmp(argv[1], "--install")) { + int use_symbolic_links = 0; + int rc = 0; + char *busybox; + + /* to use symlinks, or not to use symlinks... */ + if (argc > 2) { + if ((strcmp(argv[2], "-s") == 0)) { + use_symbolic_links = 1; + } + } + + /* link */ +// XXX: FIXME: this is broken. Why not just use argv[0] ? + busybox = xreadlink("/proc/self/exe"); + if (busybox) { + install_links(busybox, use_symbolic_links); + free(busybox); + } else { + rc = 1; + } + return rc; + } + + /* Deal with --help. (Also print help when called with no arguments) */ + + if (argc==1 || !strcmp(argv[1],"--help") ) { + if (argc>2) { + applet_name = argv[2]; + run_applet_by_name(applet_name, 2, argv); + } else { + const struct BB_applet *a; + int col, output_width; + + if (ENABLE_FEATURE_AUTOWIDTH) { + /* Obtain the terminal width. */ + get_terminal_width_height(0, &output_width, NULL); + /* leading tab and room to wrap */ + output_width -= sizeof("start-stop-daemon, ") + 8; + } else output_width = 80 - sizeof("start-stop-daemon, ") - 8; + + printf("%s\n" + "Copyright (C) 1998-2006  Erik Andersen, Rob Landley, and others.\n" + "Licensed under GPLv2.  See source distribution for full notice.\n\n" + "Usage: busybox [function] [arguments]...\n" + " or: [function] [arguments]...\n\n" + "\tBusyBox is a multi-call binary that combines many common Unix\n" + "\tutilities into a single executable. Most people will create a\n" + "\tlink to busybox for each function they wish to use and BusyBox\n" + "\twill act like whatever it was invoked as!\n" + "\nCurrently defined functions:\n", bb_msg_full_version); + + col=0; + for(a = applets; a->name;) { + col += printf("%s%s", (col ? ", " : "\t"), (a++)->name); + if (col > output_width && a->name) { + printf(",\n"); + col = 0; + } + } + printf("\n\n"); + exit(0); + } + } else run_applet_by_name(argv[1], argc-1, argv+1); + + bb_error_msg_and_die("applet not found"); +} diff --git a/applets/busybox.mkll b/applets/busybox.mkll new file mode 100755 index 000000000..6d61f7e82 --- /dev/null +++ b/applets/busybox.mkll @@ -0,0 +1,24 @@ +#!/bin/sh +# Make busybox links list file. + +# input $1: full path to Config.h +# input $2: full path to applets.h +# output (stdout): list of pathnames that should be linked to busybox + +# Maintainer: Larry Doolittle + +export LC_ALL=POSIX +export LC_CTYPE=POSIX + +CONFIG_H=${1:-include/autoconf.h} +APPLETS_H=${2:-include/applets.h} +$HOSTCC -E -DMAKE_LINKS -include $CONFIG_H $APPLETS_H | + awk '/^[ \t]*LINK/{ + dir=substr($2,8) + gsub("_","/",dir) + if(dir=="/ROOT") dir="" + file=$3 + gsub("\"","",file) + if (file=="busybox") next + print tolower(dir) "/" file + }' diff --git a/applets/individual.c b/applets/individual.c new file mode 100644 index 000000000..072168352 --- /dev/null +++ b/applets/individual.c @@ -0,0 +1,27 @@ +/* Minimal wrapper to build an individual busybox applet. + * + * Copyright 2005 Rob Landley +#include +//Ok to remove? #include "bb_config.h" +#include "usage.h" + +int main(int argc, char *argv[]) +{ + applet_name=argv[0]; + + return APPLET_main(argc,argv); +} + +void bb_show_usage(void) +{ + printf(APPLET_full_usage "\n"); + + exit(1); +} diff --git a/applets/install.sh b/applets/install.sh new file mode 100755 index 000000000..4ec96c254 --- /dev/null +++ b/applets/install.sh @@ -0,0 +1,94 @@ +#!/bin/sh + +export LC_ALL=POSIX +export LC_CTYPE=POSIX + +prefix=${1} +if [ -z "$prefix" ]; then + echo "usage: applets/install.sh DESTINATION [--symlinks/--hardlinks]" + exit 1; +fi +h=`sort busybox.links | uniq` +cleanup="0" +noclobber="0" +case "$2" in + --hardlinks) linkopts="-f";; + --symlinks) linkopts="-fs";; + --cleanup) cleanup="1";; + --noclobber) noclobber="1";; + "") h="";; + *) echo "Unknown install option: $2"; exit 1;; +esac + +if [ -n "$DO_INSTALL_LIBS" ] && [ "$DO_INSTALL_LIBS" != "n" ]; then + # get the target dir for the libs + # assume it starts with lib + libdir=$($CC -print-file-name=libc.so | \ + sed -n 's%^.*\(/lib[^\/]*\)/libc.so%\1%p') + if test -z "$libdir"; then + libdir=/lib + fi + + mkdir -p $prefix/$libdir || exit 1 + for i in $DO_INSTALL_LIBS; do + rm -f $prefix/$libdir/$i || exit 1 + if [ -f $i ]; then + cp -a $i $prefix/$libdir/ || exit 1 + chmod 0644 $prefix/$libdir/$i || exit 1 + fi + done +fi + +if [ "$cleanup" = "1" ] && [ -e "$prefix/bin/busybox" ]; then + inode=`ls -i "$prefix/bin/busybox" | awk '{print $1}'` + sub_shell_it=` + cd "$prefix" + for d in usr/sbin usr/bin sbin bin ; do + pd=$PWD + if [ -d "$d" ]; then + cd $d + ls -iL . | grep "^ *$inode" | awk '{print $2}' | env -i xargs rm -f + fi + cd "$pd" + done + ` +fi + +rm -f $prefix/bin/busybox || exit 1 +mkdir -p $prefix/bin || exit 1 +install -m 755 busybox $prefix/bin/busybox || exit 1 + +for i in $h ; do + appdir=`dirname $i` + mkdir -p $prefix/$appdir || exit 1 + if [ "$2" = "--hardlinks" ]; then + bb_path="$prefix/bin/busybox" + else + case "$appdir" in + /) + bb_path="bin/busybox" + ;; + /bin) + bb_path="busybox" + ;; + /sbin) + bb_path="../bin/busybox" + ;; + /usr/bin|/usr/sbin) + bb_path="../../bin/busybox" + ;; + *) + echo "Unknown installation directory: $appdir" + exit 1 + ;; + esac + fi + if [ "$noclobber" = "0" ] || [ ! -e "$prefix$i" ]; then + echo " $prefix$i -> $bb_path" + ln $linkopts $bb_path $prefix$i || exit 1 + else + echo " $prefix$i already exists" + fi +done + +exit 0 diff --git a/applets/usage.c b/applets/usage.c new file mode 100644 index 000000000..d888fa93d --- /dev/null +++ b/applets/usage.c @@ -0,0 +1,17 @@ +/* vi: set sw=4 ts=4: */ +#include + +#include "../include/autoconf.h" +#include "../include/busybox.h" + +static const char usage_messages[] = +#define MAKE_USAGE +#include "usage.h" +#include "applets.h" +; + +int main(void) +{ + write(1, usage_messages, sizeof(usage_messages)); + return 0; +} diff --git a/applets/usage_compressed b/applets/usage_compressed new file mode 100755 index 000000000..ab164aa12 --- /dev/null +++ b/applets/usage_compressed @@ -0,0 +1,19 @@ +#!/bin/sh + +target="$1" +loc="$2" + +test "$target" || exit 1 +test "$loc" || loc=. +test -x "$loc/usage" || exit 1 +test "$SED" || SED=sed + +sz=`"$loc/usage" | wc -c` || exit 1 + +exec >"$target" + +echo 'static const char packed_usage[] = ' +"$loc/usage" | bzip2 -1 | od -v -t x1 \ +| $SED -e 's/^[^ ]*//' -e 's/ \(..\)/\\x\1/g' -e 's/^\(.*\)$/"\1"/' +echo ';' +echo '#define SIZEOF_usage_messages' `expr 0 + $sz` diff --git a/arch/i386/Makefile b/arch/i386/Makefile new file mode 100644 index 000000000..595868ec5 --- /dev/null +++ b/arch/i386/Makefile @@ -0,0 +1,5 @@ +# ========================================================================== +# Build system +# ========================================================================== + +CFLAGS += -march=i386 -mpreferred-stack-boundary=2 diff --git a/archival/Config.in b/archival/Config.in new file mode 100644 index 000000000..de493efa2 --- /dev/null +++ b/archival/Config.in @@ -0,0 +1,300 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Archival Utilities" + +config AR + bool "ar" + default n + help + ar is an archival utility program used to create, modify, and + extract contents from archives. An archive is a single file holding + a collection of other files in a structure that makes it possible to + retrieve the original individual files (called archive members). + The original files' contents, mode (permissions), timestamp, owner, + and group are preserved in the archive, and can be restored on + extraction. + + The stored filename is limited to 15 characters. (for more information + see long filename support). + ar has 60 bytes of overheads for every stored file. + + This implementation of ar can extract archives, it cannot create or + modify them. + On an x86 system, the ar applet adds about 1K. + + Unless you have a specific application which requires ar, you should + probably say N here. + +config FEATURE_AR_LONG_FILENAMES + bool "Enable support for long filenames (not need for debs)" + default n + depends on AR + help + By default the ar format can only store the first 15 characters of the + filename, this option removes that limitation. + It supports the GNU ar long filename method which moves multiple long + filenames into a the data section of a new ar entry. + +config BUNZIP2 + bool "bunzip2" + default n + help + bunzip2 is a compression utility using the Burrows-Wheeler block + sorting text compression algorithm, and Huffman coding. Compression + is generally considerably better than that achieved by more + conventional LZ77/LZ78-based compressors, and approaches the + performance of the PPM family of statistical compressors. + + The BusyBox bunzip2 applet is limited to de-compression only. + On an x86 system, this applet adds about 11K. + + Unless you have a specific application which requires bunzip2, you + should probably say N here. + +config CPIO + bool "cpio" + default n + help + cpio is an archival utility program used to create, modify, and extract + contents from archives. + cpio has 110 bytes of overheads for every stored file. + + This implementation of cpio can extract cpio archives created in the + "newc" or "crc" format, it cannot create or modify them. + + Unless you have a specific application which requires cpio, you should + probably say N here. + +config DPKG + bool "dpkg" + default n + help + dpkg is a medium-level tool to install, build, remove and manage Debian packages. + + This implementation of dpkg has a number of limitations, you should use the + official dpkg if possible. + +config DPKG_DEB + bool "dpkg_deb" + default n + help + dpkg-deb packs, unpacks and provides information about Debian archives. + + This implementation of dpkg-deb cannot pack archives. + + Unless you have a specific application which requires dpkg-deb, you should + probably say N here. + +config FEATURE_DPKG_DEB_EXTRACT_ONLY + bool "extract only (-x)" + default n + depends on DPKG_DEB + help + This reduces dpkg-deb to the equivalent of "ar -p data.tar.gz | tar -zx". + However it saves space as none of the extra dpkg-deb, ar or tar options are + needed, they are linked to internally. + +config GUNZIP + bool "gunzip" + default n + help + gunzip is used to decompress archives created by gzip. + You can use the `-t' option to test the integrity of + an archive, without decompressing it. + +config FEATURE_GUNZIP_UNCOMPRESS + bool "Uncompress support" + default n + depends on GUNZIP + help + Enable if you want gunzip to have the ability to decompress + archives created by the program compress (not much + used anymore). + +config GZIP + bool "gzip" + default n + help + gzip is used to compress files. + It's probably the most widely used UNIX compression program. + +config RPM2CPIO + bool "rpm2cpio" + default n + help + Converts an RPM file into a CPIO archive. + +config RPM + bool "rpm" + default n + help + Mini RPM applet - queries and extracts + +config TAR + bool "tar" + default n + help + tar is an archiving program. It's commonly used with gzip to + create compressed archives. It's probably the most widely used + UNIX archive program. + +config FEATURE_TAR_CREATE + bool "Enable archive creation" + default y + depends on TAR + help + If you enable this option you'll be able to create + tar archives using the `-c' option. + +config FEATURE_TAR_BZIP2 + bool "Enable -j option to handle .tar.bz2 files" + default n + depends on TAR + help + If you enable this option you'll be able to extract + archives compressed with bzip2. + +config FEATURE_TAR_LZMA + bool "Enable -a option to handle .tar.lzma files" + default n + depends on TAR + help + If you enable this option you'll be able to extract + archives compressed with lzma. + +config FEATURE_TAR_FROM + bool "Enable -X (exclude from) and -T (include from) options)" + default n + depends on TAR + help + If you enable this option you'll be able to specify + a list of files to include or exclude from an archive. + +config FEATURE_TAR_GZIP + bool "Enable -z option" + default y + depends on TAR + help + If you enable this option tar will be able to call gzip, + when creating or extracting tar gziped archives. + +config FEATURE_TAR_COMPRESS + bool "Enable -Z option" + default n + depends on TAR + help + If you enable this option tar will be able to call uncompress, + when extracting .tar.Z archives. + +config FEATURE_TAR_OLDGNU_COMPATIBILITY + bool "Enable support for old tar header format" + default N + depends on TAR + help + This option is required to unpack archives created in + the old GNU format; help to kill this old format by + repacking your ancient archives with the new format. + +config FEATURE_TAR_GNU_EXTENSIONS + bool "Enable support for some GNU tar extensions" + default y + depends on TAR + help + With this option busybox supports GNU long filenames and + linknames. + +config FEATURE_TAR_LONG_OPTIONS + bool "Enable long options" + default n + depends on TAR && GETOPT_LONG + help + Enable use of long options, increases size by about 400 Bytes + +config UNCOMPRESS + bool "uncompress" + default n + help + uncompress is used to decompress archives created by compress. + Not much used anymore, replaced by gzip/gunzip. + +config UNLZMA + bool "unlzma" + default n + help + unlzma is a compression utility using the Lempel-Ziv-Markov chain + compression algorithm, and range coding. Compression + is generally considerably better than that achieved by the bzip2 + compressors. + + The BusyBox unlzma applet is limited to de-compression only. + On an x86 system, this applet adds about 4K. + + Unless you have a specific application which requires unlzma, you + should probably say N here. + +config FEATURE_LZMA_FAST + bool "Optimze unlzma for speed" + default n + depends on UNLZMA + help + This option reduces decompression time by about 33% at the cost of + a 2K bigger binary. + +config UNZIP + bool "unzip" + default n + help + unzip will list or extract files from a ZIP archive, + commonly found on DOS/WIN systems. The default behavior + (with no options) is to extract the archive into the + current directory. Use the `-d' option to extract to a + directory of your choice. + +comment "Common options for cpio and tar" + depends on CPIO || TAR + +config FEATURE_UNARCHIVE_TAPE + bool "Enable tape drive support" + default n + depends on CPIO || TAR + help + I don't think this is needed anymore. + +comment "Common options for dpkg and dpkg_deb" + depends on DPKG || DPKG_DEB + +config FEATURE_DEB_TAR_GZ + bool "gzip debian packages (normal)" + default y if DPKG || DPKG_DEB + depends on DPKG || DPKG_DEB + help + This is the default compression method inside the debian ar file. + + If you want compatibility with standard .deb's you should say yes here. + +config FEATURE_DEB_TAR_BZ2 + bool "bzip2 debian packages" + default n + depends on DPKG || DPKG_DEB + help + This allows dpkg and dpkg-deb to extract deb's that are compressed internally + with bzip2 instead of gzip. + + You only want this if you are creating your own custom debian packages that + use an internal control.tar.bz2 or data.tar.bz2. + +config FEATURE_DEB_TAR_LZMA + bool "lzma debian packages" + default n + depends on DPKG || DPKG_DEB + help + This allows dpkg and dpkg-deb to extract deb's that are compressed + internally with lzma instead of gzip. + + You only want this if you are creating your own custom debian + packages that use an internal control.tar.lzma or data.tar.lzma. + +endmenu diff --git a/archival/Kbuild b/archival/Kbuild new file mode 100644 index 000000000..f85e0c2a7 --- /dev/null +++ b/archival/Kbuild @@ -0,0 +1,22 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +libs-y += libunarchive/ + +lib-y:= +lib-$(CONFIG_AR) += ar.o +lib-$(CONFIG_BUNZIP2) += bunzip2.o +lib-$(CONFIG_UNLZMA) += unlzma.o +lib-$(CONFIG_CPIO) += cpio.o +lib-$(CONFIG_DPKG) += dpkg.o +lib-$(CONFIG_DPKG_DEB) += dpkg_deb.o +lib-$(CONFIG_GUNZIP) += gunzip.o +lib-$(CONFIG_GZIP) += gzip.o +lib-$(CONFIG_RPM2CPIO) += rpm2cpio.o +lib-$(CONFIG_RPM) += rpm.o +lib-$(CONFIG_TAR) += tar.o +lib-$(CONFIG_UNCOMPRESS) += uncompress.o +lib-$(CONFIG_UNZIP) += unzip.o diff --git a/archival/ar.c b/archival/ar.c new file mode 100644 index 000000000..521e03799 --- /dev/null +++ b/archival/ar.c @@ -0,0 +1,93 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini ar implementation for busybox + * + * Copyright (C) 2000 by Glenn McGrath + * Written by Glenn McGrath 1 June 2000 + * + * Based in part on BusyBox tar, Debian dpkg-deb and GNU ar. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * There is no single standard to adhere to so ar may not portable + * between different systems + * http://www.unix-systems.org/single_unix_specification_v2/xcu/ar.html + */ + +#include "busybox.h" +#include "unarchive.h" + +static void header_verbose_list_ar(const file_header_t *file_header) +{ + const char *mode = bb_mode_string(file_header->mode); + char *mtime; + + mtime = ctime(&file_header->mtime); + mtime[16] = ' '; + memmove(&mtime[17], &mtime[20], 4); + mtime[21] = '\0'; + printf("%s %d/%d%7d %s %s\n", &mode[1], file_header->uid, file_header->gid, + (int) file_header->size, &mtime[4], file_header->name); +} + +#define AR_CTX_PRINT 0x01 +#define AR_CTX_LIST 0x02 +#define AR_CTX_EXTRACT 0x04 +#define AR_OPT_PRESERVE_DATE 0x08 +#define AR_OPT_VERBOSE 0x10 +#define AR_OPT_CREATE 0x20 +#define AR_OPT_INSERT 0x40 + +int ar_main(int argc, char **argv) +{ + archive_handle_t *archive_handle; + unsigned opt; + static const char msg_unsupported_err[] = + "Archive %s not supported. Install binutils 'ar'."; + char magic[8]; + + archive_handle = init_handle(); + + /* Prepend '-' to the first argument if required */ + opt_complementary = "--:p:t:x:-1:?:p--tx:t--px:x--pt"; + opt = getopt32(argc, argv, "ptxovcr"); + + if (opt & AR_CTX_PRINT) { + archive_handle->action_data = data_extract_to_stdout; + } + if (opt & AR_CTX_LIST) { + archive_handle->action_header = header_list; + } + if (opt & AR_CTX_EXTRACT) { + archive_handle->action_data = data_extract_all; + } + if (opt & AR_OPT_PRESERVE_DATE) { + archive_handle->flags |= ARCHIVE_PRESERVE_DATE; + } + if (opt & AR_OPT_VERBOSE) { + archive_handle->action_header = header_verbose_list_ar; + } + if (opt & AR_OPT_CREATE) { + bb_error_msg_and_die(msg_unsupported_err, "creation"); + } + if (opt & AR_OPT_INSERT) { + bb_error_msg_and_die(msg_unsupported_err, "insertion"); + } + + archive_handle->src_fd = xopen(argv[optind++], O_RDONLY); + + while (optind < argc) { + archive_handle->filter = filter_accept_list; + llist_add_to(&(archive_handle->accept), argv[optind++]); + } + + xread(archive_handle->src_fd, magic, 7); + if (strncmp(magic, "!", 7) != 0) { + bb_error_msg_and_die("invalid ar magic"); + } + archive_handle->offset += 7; + + while (get_header_ar(archive_handle) == EXIT_SUCCESS); + + return EXIT_SUCCESS; +} diff --git a/archival/bunzip2.c b/archival/bunzip2.c new file mode 100644 index 000000000..757001fe4 --- /dev/null +++ b/archival/bunzip2.c @@ -0,0 +1,66 @@ +/* vi: set sw=4 ts=4: */ +/* + * Modified for busybox by Glenn McGrath + * Added support output to stdout by Thomas Lundquist + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include "unarchive.h" + +#define BUNZIP2_OPT_STDOUT 1 +#define BUNZIP2_OPT_FORCE 2 + +int bunzip2_main(int argc, char **argv) +{ + USE_DESKTOP(long long) int status; + char *filename; + unsigned opt; + int src_fd, dst_fd; + + opt = getopt32(argc, argv, "cf"); + + /* Set input filename and number */ + filename = argv[optind]; + if ((filename) && (filename[0] != '-') && (filename[1] != '\0')) { + /* Open input file */ + src_fd = xopen(filename, O_RDONLY); + } else { + src_fd = STDIN_FILENO; + filename = 0; + } + + /* if called as bzcat force the stdout flag */ + if ((opt & BUNZIP2_OPT_STDOUT) || applet_name[2] == 'c') + filename = 0; + + /* Check that the input is sane. */ + if (isatty(src_fd) && (opt & BUNZIP2_OPT_FORCE) == 0) { + bb_error_msg_and_die("compressed data not read from terminal, " + "use -f to force it"); + } + + if (filename) { + struct stat stat_buf; + /* extension = filename+strlen(filename)-4 is buggy: + * strlen may be less than 4 */ + char *extension = strrchr(filename, '.'); + if (!extension || strcmp(extension, ".bz2") != 0) { + bb_error_msg_and_die("invalid extension"); + } + xstat(filename, &stat_buf); + *extension = '\0'; + dst_fd = xopen3(filename, O_WRONLY | O_CREAT | O_TRUNC, + stat_buf.st_mode); + } else dst_fd = STDOUT_FILENO; + status = uncompressStream(src_fd, dst_fd); + if (filename) { + if (status >= 0) filename[strlen(filename)] = '.'; + if (unlink(filename) < 0) { + bb_error_msg_and_die("cannot remove %s", filename); + } + } + + return status; +} diff --git a/archival/cpio.c b/archival/cpio.c new file mode 100644 index 000000000..73651ba10 --- /dev/null +++ b/archival/cpio.c @@ -0,0 +1,86 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini cpio implementation for busybox + * + * Copyright (C) 2001 by Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Limitations: + * Doesn't check CRC's + * Only supports new ASCII and CRC formats + * + */ +#include +#include +#include +#include +#include "unarchive.h" +#include "busybox.h" + +#define CPIO_OPT_EXTRACT 0x01 +#define CPIO_OPT_TEST 0x02 +#define CPIO_OPT_UNCONDITIONAL 0x04 +#define CPIO_OPT_VERBOSE 0x08 +#define CPIO_OPT_FILE 0x10 +#define CPIO_OPT_CREATE_LEADING_DIR 0x20 +#define CPIO_OPT_PRESERVE_MTIME 0x40 + +int cpio_main(int argc, char **argv) +{ + archive_handle_t *archive_handle; + char *cpio_filename = NULL; + unsigned opt; + + /* Initialise */ + archive_handle = init_handle(); + archive_handle->src_fd = STDIN_FILENO; + archive_handle->seek = seek_by_read; + archive_handle->flags = ARCHIVE_EXTRACT_NEWER | ARCHIVE_PRESERVE_DATE; + + opt = getopt32(argc, argv, "ituvF:dm", &cpio_filename); + + /* One of either extract or test options must be given */ + if ((opt & (CPIO_OPT_TEST | CPIO_OPT_EXTRACT)) == 0) { + bb_show_usage(); + } + + if (opt & CPIO_OPT_TEST) { + /* if both extract and test options are given, ignore extract option */ + if (opt & CPIO_OPT_EXTRACT) { + opt &= ~CPIO_OPT_EXTRACT; + } + archive_handle->action_header = header_list; + } + if (opt & CPIO_OPT_EXTRACT) { + archive_handle->action_data = data_extract_all; + } + if (opt & CPIO_OPT_UNCONDITIONAL) { + archive_handle->flags |= ARCHIVE_EXTRACT_UNCONDITIONAL; + archive_handle->flags &= ~ARCHIVE_EXTRACT_NEWER; + } + if (opt & CPIO_OPT_VERBOSE) { + if (archive_handle->action_header == header_list) { + archive_handle->action_header = header_verbose_list; + } else { + archive_handle->action_header = header_list; + } + } + if (cpio_filename) { /* CPIO_OPT_FILE */ + archive_handle->src_fd = xopen(cpio_filename, O_RDONLY); + archive_handle->seek = seek_by_jump; + } + if (opt & CPIO_OPT_CREATE_LEADING_DIR) { + archive_handle->flags |= ARCHIVE_CREATE_LEADING_DIRS; + } + + while (optind < argc) { + archive_handle->filter = filter_accept_list; + llist_add_to(&(archive_handle->accept), argv[optind]); + optind++; + } + + while (get_header_cpio(archive_handle) == EXIT_SUCCESS); + + return EXIT_SUCCESS; +} diff --git a/archival/dpkg.c b/archival/dpkg.c new file mode 100644 index 000000000..9024d41d2 --- /dev/null +++ b/archival/dpkg.c @@ -0,0 +1,1819 @@ +/* vi: set sw=4 ts=4: */ +/* + * mini dpkg implementation for busybox. + * this is not meant as a replacement for dpkg + * + * written by glenn mcgrath with the help of others + * copyright (c) 2001 by glenn mcgrath + * + * started life as a busybox implementation of udpkg + * + * licensed under gplv2 or later, see file license in this tarball for details. + */ + +/* + * known difference between busybox dpkg and the official dpkg that i don't + * consider important, its worth keeping a note of differences anyway, just to + * make it easier to maintain. + * - the first value for the confflile: field isnt placed on a new line. + * - when installing a package the status: field is placed at the end of the + * section, rather than just after the package: field. + * + * bugs that need to be fixed + * - (unknown, please let me know when you find any) + * + */ + +#include "busybox.h" +#include "unarchive.h" + +/* note: if you vary hash_prime sizes be aware, + * 1) tweaking these will have a big effect on how much memory this program uses. + * 2) for computational efficiency these hash tables should be at least 20% + * larger than the maximum number of elements stored in it. + * 3) all _hash_prime's must be a prime number or chaos is assured, if your looking + * for a prime, try http://www.utm.edu/research/primes/lists/small/10000.txt + * 4) if you go bigger than 15 bits you may get into trouble (untested) as its + * sometimes cast to an unsigned int, if you go to 16 bit you will overlap + * int's and chaos is assured, 16381 is the max prime for 14 bit field + */ + +/* NAME_HASH_PRIME, Stores package names and versions, + * I estimate it should be at least 50% bigger than PACKAGE_HASH_PRIME, + * as there a lot of duplicate version numbers */ +#define NAME_HASH_PRIME 16381 + +/* PACKAGE_HASH_PRIME, Maximum number of unique packages, + * It must not be smaller than STATUS_HASH_PRIME, + * Currently only packages from status_hashtable are stored in here, but in + * future this may be used to store packages not only from a status file, + * but an available_hashtable, and even multiple packages files. + * Package can be stored more than once if they have different versions. + * e.g. The same package may have different versions in the status file + * and available file */ +#define PACKAGE_HASH_PRIME 10007 +typedef struct edge_s { + unsigned int operator:3; + unsigned int type:4; + unsigned int name:14; + unsigned int version:14; +} edge_t; + +typedef struct common_node_s { + unsigned int name:14; + unsigned int version:14; + unsigned int num_of_edges:14; + edge_t **edge; +} common_node_t; + +/* Currently it doesnt store packages that have state-status of not-installed + * So it only really has to be the size of the maximum number of packages + * likely to be installed at any one time, so there is a bit of leeway here */ +#define STATUS_HASH_PRIME 8191 +typedef struct status_node_s { + unsigned int package:14; /* has to fit PACKAGE_HASH_PRIME */ + unsigned int status:14; /* has to fit STATUS_HASH_PRIME */ +} status_node_t; + +/* Were statically declared here, but such a big bss is nommu-unfriendly */ +static char **name_hashtable; /* [NAME_HASH_PRIME + 1] */ +static common_node_t **package_hashtable; /* [PACKAGE_HASH_PRIME + 1] */ +static status_node_t **status_hashtable; /* [STATUS_HASH_PRIME + 1] */ + +/* Even numbers are for 'extras', like ored dependencies or null */ +enum edge_type_e { + EDGE_NULL = 0, + EDGE_PRE_DEPENDS = 1, + EDGE_OR_PRE_DEPENDS = 2, + EDGE_DEPENDS = 3, + EDGE_OR_DEPENDS = 4, + EDGE_REPLACES = 5, + EDGE_PROVIDES = 7, + EDGE_CONFLICTS = 9, + EDGE_SUGGESTS = 11, + EDGE_RECOMMENDS = 13, + EDGE_ENHANCES = 15 +}; +enum operator_e { + VER_NULL = 0, + VER_EQUAL = 1, + VER_LESS = 2, + VER_LESS_EQUAL = 3, + VER_MORE = 4, + VER_MORE_EQUAL = 5, + VER_ANY = 6 +}; + +enum dpkg_opt_e { + dpkg_opt_purge = 1, + dpkg_opt_remove = 2, + dpkg_opt_unpack = 4, + dpkg_opt_configure = 8, + dpkg_opt_install = 16, + dpkg_opt_package_name = 32, + dpkg_opt_filename = 64, + dpkg_opt_list_installed = 128, + dpkg_opt_force_ignore_depends = 256 +}; + +typedef struct deb_file_s { + char *control_file; + char *filename; + unsigned int package:14; +} deb_file_t; + + +static void make_hash(const char *key, unsigned int *start, unsigned int *decrement, const int hash_prime) +{ + unsigned long int hash_num = key[0]; + int len = strlen(key); + int i; + + /* Maybe i should have uses a "proper" hashing algorithm here instead + * of making one up myself, seems to be working ok though. */ + for (i = 1; i < len; i++) { + /* shifts the ascii based value and adds it to previous value + * shift amount is mod 24 because long int is 32 bit and data + * to be shifted is 8, don't want to shift data to where it has + * no effect*/ + hash_num += ((key[i] + key[i-1]) << ((key[i] * i) % 24)); + } + *start = (unsigned int) hash_num % hash_prime; + *decrement = (unsigned int) 1 + (hash_num % (hash_prime - 1)); +} + +/* this adds the key to the hash table */ +static int search_name_hashtable(const char *key) +{ + unsigned int probe_address = 0; + unsigned int probe_decrement = 0; +// char *temp; + + make_hash(key, &probe_address, &probe_decrement, NAME_HASH_PRIME); + while (name_hashtable[probe_address] != NULL) { + if (strcmp(name_hashtable[probe_address], key) == 0) { + return probe_address; + } else { + probe_address -= probe_decrement; + if ((int)probe_address < 0) { + probe_address += NAME_HASH_PRIME; + } + } + } + name_hashtable[probe_address] = xstrdup(key); + return probe_address; +} + +/* this DOESNT add the key to the hashtable + * TODO make it consistent with search_name_hashtable + */ +static unsigned int search_status_hashtable(const char *key) +{ + unsigned int probe_address = 0; + unsigned int probe_decrement = 0; + + make_hash(key, &probe_address, &probe_decrement, STATUS_HASH_PRIME); + while (status_hashtable[probe_address] != NULL) { + if (strcmp(key, name_hashtable[package_hashtable[status_hashtable[probe_address]->package]->name]) == 0) { + break; + } else { + probe_address -= probe_decrement; + if ((int)probe_address < 0) { + probe_address += STATUS_HASH_PRIME; + } + } + } + return probe_address; +} + +/* Need to rethink version comparison, maybe the official dpkg has something i can use ? */ +static int version_compare_part(const char *version1, const char *version2) +{ + int upstream_len1 = 0; + int upstream_len2 = 0; + char *name1_char; + char *name2_char; + int len1 = 0; + int len2 = 0; + int tmp_int; + int ver_num1; + int ver_num2; + int ret; + + if (version1 == NULL) { + version1 = xstrdup(""); + } + if (version2 == NULL) { + version2 = xstrdup(""); + } + upstream_len1 = strlen(version1); + upstream_len2 = strlen(version2); + + while ((len1 < upstream_len1) || (len2 < upstream_len2)) { + /* Compare non-digit section */ + tmp_int = strcspn(&version1[len1], "0123456789"); + name1_char = xstrndup(&version1[len1], tmp_int); + len1 += tmp_int; + tmp_int = strcspn(&version2[len2], "0123456789"); + name2_char = xstrndup(&version2[len2], tmp_int); + len2 += tmp_int; + tmp_int = strcmp(name1_char, name2_char); + free(name1_char); + free(name2_char); + if (tmp_int != 0) { + ret = tmp_int; + goto cleanup_version_compare_part; + } + + /* Compare digits */ + tmp_int = strspn(&version1[len1], "0123456789"); + name1_char = xstrndup(&version1[len1], tmp_int); + len1 += tmp_int; + tmp_int = strspn(&version2[len2], "0123456789"); + name2_char = xstrndup(&version2[len2], tmp_int); + len2 += tmp_int; + ver_num1 = atoi(name1_char); + ver_num2 = atoi(name2_char); + free(name1_char); + free(name2_char); + if (ver_num1 < ver_num2) { + ret = -1; + goto cleanup_version_compare_part; + } + else if (ver_num1 > ver_num2) { + ret = 1; + goto cleanup_version_compare_part; + } + } + ret = 0; +cleanup_version_compare_part: + return ret; +} + +/* if ver1 < ver2 return -1, + * if ver1 = ver2 return 0, + * if ver1 > ver2 return 1, + */ +static int version_compare(const unsigned int ver1, const unsigned int ver2) +{ + char *ch_ver1 = name_hashtable[ver1]; + char *ch_ver2 = name_hashtable[ver2]; + + char epoch1, epoch2; + char *deb_ver1, *deb_ver2; + char *ver1_ptr, *ver2_ptr; + char *upstream_ver1; + char *upstream_ver2; + int result; + + /* Compare epoch */ + if (ch_ver1[1] == ':') { + epoch1 = ch_ver1[0]; + ver1_ptr = strchr(ch_ver1, ':') + 1; + } else { + epoch1 = '0'; + ver1_ptr = ch_ver1; + } + if (ch_ver2[1] == ':') { + epoch2 = ch_ver2[0]; + ver2_ptr = strchr(ch_ver2, ':') + 1; + } else { + epoch2 = '0'; + ver2_ptr = ch_ver2; + } + if (epoch1 < epoch2) { + return -1; + } + else if (epoch1 > epoch2) { + return 1; + } + + /* Compare upstream version */ + upstream_ver1 = xstrdup(ver1_ptr); + upstream_ver2 = xstrdup(ver2_ptr); + + /* Chop off debian version, and store for later use */ + deb_ver1 = strrchr(upstream_ver1, '-'); + deb_ver2 = strrchr(upstream_ver2, '-'); + if (deb_ver1) { + deb_ver1[0] = '\0'; + deb_ver1++; + } + if (deb_ver2) { + deb_ver2[0] = '\0'; + deb_ver2++; + } + result = version_compare_part(upstream_ver1, upstream_ver2); + if (!result) + /* Compare debian versions */ + result = version_compare_part(deb_ver1, deb_ver2); + + free(upstream_ver1); + free(upstream_ver2); + return result; +} + +static int test_version(const unsigned int version1, const unsigned int version2, const unsigned int operator) +{ + const int version_result = version_compare(version1, version2); + switch (operator) { + case VER_ANY: + return TRUE; + case VER_EQUAL: + if (version_result == 0) { + return TRUE; + } + break; + case VER_LESS: + if (version_result < 0) { + return TRUE; + } + break; + case VER_LESS_EQUAL: + if (version_result <= 0) { + return TRUE; + } + break; + case VER_MORE: + if (version_result > 0) { + return TRUE; + } + break; + case VER_MORE_EQUAL: + if (version_result >= 0) { + return TRUE; + } + break; + } + return FALSE; +} + + +static int search_package_hashtable(const unsigned int name, const unsigned int version, const unsigned int operator) +{ + unsigned int probe_address = 0; + unsigned int probe_decrement = 0; + + make_hash(name_hashtable[name], &probe_address, &probe_decrement, PACKAGE_HASH_PRIME); + while (package_hashtable[probe_address] != NULL) { + if (package_hashtable[probe_address]->name == name) { + if (operator == VER_ANY) { + return probe_address; + } + if (test_version(package_hashtable[probe_address]->version, version, operator)) { + return probe_address; + } + } + probe_address -= probe_decrement; + if ((int)probe_address < 0) { + probe_address += PACKAGE_HASH_PRIME; + } + } + return probe_address; +} + +/* + * This function searches through the entire package_hashtable looking + * for a package which provides "needle". It returns the index into + * the package_hashtable for the providing package. + * + * needle is the index into name_hashtable of the package we are + * looking for. + * + * start_at is the index in the package_hashtable to start looking + * at. If start_at is -1 then start at the beginning. This is to allow + * for repeated searches since more than one package might provide + * needle. + * + * FIXME: I don't think this is very efficient, but I thought I'd keep + * it simple for now until it proves to be a problem. + */ +static int search_for_provides(int needle, int start_at) { + int i, j; + common_node_t *p; + for (i = start_at + 1; i < PACKAGE_HASH_PRIME; i++) { + p = package_hashtable[i]; + if (p == NULL) continue; + for (j = 0; j < p->num_of_edges; j++) + if (p->edge[j]->type == EDGE_PROVIDES && p->edge[j]->name == needle) + return i; + } + return -1; +} + +/* + * Add an edge to a node + */ +static void add_edge_to_node(common_node_t *node, edge_t *edge) +{ + node->num_of_edges++; + node->edge = xrealloc(node->edge, sizeof(edge_t) * (node->num_of_edges + 1)); + node->edge[node->num_of_edges - 1] = edge; +} + +/* + * Create one new node and one new edge for every dependency. + * + * Dependencies which contain multiple alternatives are represented as + * an EDGE_OR_PRE_DEPENDS or EDGE_OR_DEPENDS node, followed by a + * number of EDGE_PRE_DEPENDS or EDGE_DEPENDS nodes. The name field of + * the OR edge contains the full dependency string while the version + * field contains the number of EDGE nodes which follow as part of + * this alternative. + */ +static void add_split_dependencies(common_node_t *parent_node, const char *whole_line, unsigned int edge_type) +{ + char *line = xstrdup(whole_line); + char *line2; + char *line_ptr1 = NULL; + char *line_ptr2 = NULL; + char *field; + char *field2; + char *version; + edge_t *edge; + edge_t *or_edge; + int offset_ch; + + field = strtok_r(line, ",", &line_ptr1); + do { + /* skip leading spaces */ + field += strspn(field, " "); + line2 = xstrdup(field); + field2 = strtok_r(line2, "|", &line_ptr2); + if ((edge_type == EDGE_DEPENDS || edge_type == EDGE_PRE_DEPENDS) && + (strcmp(field, field2) != 0)) { + or_edge = (edge_t *)xmalloc(sizeof(edge_t)); + or_edge->type = edge_type + 1; + } else { + or_edge = NULL; + } + + if (or_edge) { + or_edge->name = search_name_hashtable(field); + or_edge->version = 0; // tracks the number of altenatives + + add_edge_to_node(parent_node, or_edge); + } + + do { + edge = (edge_t *) xmalloc(sizeof(edge_t)); + edge->type = edge_type; + + /* Skip any extra leading spaces */ + field2 += strspn(field2, " "); + + /* Get dependency version info */ + version = strchr(field2, '('); + if (version == NULL) { + edge->operator = VER_ANY; + /* Get the versions hash number, adding it if the number isnt already in there */ + edge->version = search_name_hashtable("ANY"); + } else { + /* Skip leading ' ' or '(' */ + version += strspn(field2, " "); + version += strspn(version, "("); + /* Calculate length of any operator characters */ + offset_ch = strspn(version, "<=>"); + /* Determine operator */ + if (offset_ch > 0) { + if (strncmp(version, "=", offset_ch) == 0) { + edge->operator = VER_EQUAL; + } + else if (strncmp(version, "<<", offset_ch) == 0) { + edge->operator = VER_LESS; + } + else if (strncmp(version, "<=", offset_ch) == 0) { + edge->operator = VER_LESS_EQUAL; + } + else if (strncmp(version, ">>", offset_ch) == 0) { + edge->operator = VER_MORE; + } + else if (strncmp(version, ">=", offset_ch) == 0) { + edge->operator = VER_MORE_EQUAL; + } else { + bb_error_msg_and_die("illegal operator"); + } + } + /* skip to start of version numbers */ + version += offset_ch; + version += strspn(version, " "); + + /* Truncate version at trailing ' ' or ')' */ + version[strcspn(version, " )")] = '\0'; + /* Get the versions hash number, adding it if the number isnt already in there */ + edge->version = search_name_hashtable(version); + } + + /* Get the dependency name */ + field2[strcspn(field2, " (")] = '\0'; + edge->name = search_name_hashtable(field2); + + if (or_edge) + or_edge->version++; + + add_edge_to_node(parent_node, edge); + } while ((field2 = strtok_r(NULL, "|", &line_ptr2)) != NULL); + free(line2); + } while ((field = strtok_r(NULL, ",", &line_ptr1)) != NULL); + free(line); + + return; +} + +static void free_package(common_node_t *node) +{ + unsigned i; + if (node) { + for (i = 0; i < node->num_of_edges; i++) { + free(node->edge[i]); + } + free(node->edge); + free(node); + } +} + +/* + * Gets the next package field from package_buffer, seperated into the field name + * and field value, it returns the int offset to the first character of the next field + */ +static int read_package_field(const char *package_buffer, char **field_name, char **field_value) +{ + int offset_name_start = 0; + int offset_name_end = 0; + int offset_value_start = 0; + int offset_value_end = 0; + int offset = 0; + int next_offset; + int name_length; + int value_length; + int exit_flag = FALSE; + + if (package_buffer == NULL) { + *field_name = NULL; + *field_value = NULL; + return -1; + } + while (1) { + next_offset = offset + 1; + switch (package_buffer[offset]) { + case '\0': + exit_flag = TRUE; + break; + case ':': + if (offset_name_end == 0) { + offset_name_end = offset; + offset_value_start = next_offset; + } + /* TODO: Name might still have trailing spaces if ':' isnt + * immediately after name */ + break; + case '\n': + /* TODO: The char next_offset may be out of bounds */ + if (package_buffer[next_offset] != ' ') { + exit_flag = TRUE; + break; + } + case '\t': + case ' ': + /* increment the value start point if its a just filler */ + if (offset_name_start == offset) { + offset_name_start++; + } + if (offset_value_start == offset) { + offset_value_start++; + } + break; + } + if (exit_flag) { + /* Check that the names are valid */ + offset_value_end = offset; + name_length = offset_name_end - offset_name_start; + value_length = offset_value_end - offset_value_start; + if (name_length == 0) { + break; + } + if ((name_length > 0) && (value_length > 0)) { + break; + } + + /* If not valid, start fresh with next field */ + exit_flag = FALSE; + offset_name_start = offset + 1; + offset_name_end = 0; + offset_value_start = offset + 1; + offset_value_end = offset + 1; + offset++; + } + offset++; + } + if (name_length == 0) { + *field_name = NULL; + } else { + *field_name = xstrndup(&package_buffer[offset_name_start], name_length); + } + if (value_length > 0) { + *field_value = xstrndup(&package_buffer[offset_value_start], value_length); + } else { + *field_value = NULL; + } + return next_offset; +} + +static unsigned int fill_package_struct(char *control_buffer) +{ + static const char *const field_names[] = { "Package", "Version", + "Pre-Depends", "Depends","Replaces", "Provides", + "Conflicts", "Suggests", "Recommends", "Enhances", 0 + }; + + common_node_t *new_node = (common_node_t *) xzalloc(sizeof(common_node_t)); + char *field_name; + char *field_value; + int field_start = 0; + int num = -1; + int buffer_length = strlen(control_buffer); + + new_node->version = search_name_hashtable("unknown"); + while (field_start < buffer_length) { + unsigned field_num; + + field_start += read_package_field(&control_buffer[field_start], + &field_name, &field_value); + + if (field_name == NULL) { + goto fill_package_struct_cleanup; /* Oh no, the dreaded goto statement ! */ + } + + field_num = index_in_str_array(field_names, field_name); + switch (field_num) { + case 0: /* Package */ + new_node->name = search_name_hashtable(field_value); + break; + case 1: /* Version */ + new_node->version = search_name_hashtable(field_value); + break; + case 2: /* Pre-Depends */ + add_split_dependencies(new_node, field_value, EDGE_PRE_DEPENDS); + break; + case 3: /* Depends */ + add_split_dependencies(new_node, field_value, EDGE_DEPENDS); + break; + case 4: /* Replaces */ + add_split_dependencies(new_node, field_value, EDGE_REPLACES); + break; + case 5: /* Provides */ + add_split_dependencies(new_node, field_value, EDGE_PROVIDES); + break; + case 6: /* Conflicts */ + add_split_dependencies(new_node, field_value, EDGE_CONFLICTS); + break; + case 7: /* Suggests */ + add_split_dependencies(new_node, field_value, EDGE_SUGGESTS); + break; + case 8: /* Recommends */ + add_split_dependencies(new_node, field_value, EDGE_RECOMMENDS); + break; + case 9: /* Enhances */ + add_split_dependencies(new_node, field_value, EDGE_ENHANCES); + break; + } +fill_package_struct_cleanup: + free(field_name); + free(field_value); + } + + if (new_node->version == search_name_hashtable("unknown")) { + free_package(new_node); + return -1; + } + num = search_package_hashtable(new_node->name, new_node->version, VER_EQUAL); + if (package_hashtable[num] == NULL) { + package_hashtable[num] = new_node; + } else { + free_package(new_node); + } + return num; +} + +/* if num = 1, it returns the want status, 2 returns flag, 3 returns status */ +static unsigned int get_status(const unsigned int status_node, const int num) +{ + char *status_string = name_hashtable[status_hashtable[status_node]->status]; + char *state_sub_string; + unsigned int state_sub_num; + int len; + int i; + + /* set tmp_string to point to the start of the word number */ + for (i = 1; i < num; i++) { + /* skip past a word */ + status_string += strcspn(status_string, " "); + /* skip past the separating spaces */ + status_string += strspn(status_string, " "); + } + len = strcspn(status_string, " \n"); + state_sub_string = xstrndup(status_string, len); + state_sub_num = search_name_hashtable(state_sub_string); + free(state_sub_string); + return state_sub_num; +} + +static void set_status(const unsigned int status_node_num, const char *new_value, const int position) +{ + const unsigned int new_value_len = strlen(new_value); + const unsigned int new_value_num = search_name_hashtable(new_value); + unsigned int want = get_status(status_node_num, 1); + unsigned int flag = get_status(status_node_num, 2); + unsigned int status = get_status(status_node_num, 3); + int want_len = strlen(name_hashtable[want]); + int flag_len = strlen(name_hashtable[flag]); + int status_len = strlen(name_hashtable[status]); + char *new_status; + + switch (position) { + case 1: + want = new_value_num; + want_len = new_value_len; + break; + case 2: + flag = new_value_num; + flag_len = new_value_len; + break; + case 3: + status = new_value_num; + status_len = new_value_len; + break; + default: + bb_error_msg_and_die("DEBUG ONLY: this shouldnt happen"); + } + + new_status = xasprintf("%s %s %s", name_hashtable[want], name_hashtable[flag], name_hashtable[status]); + status_hashtable[status_node_num]->status = search_name_hashtable(new_status); + free(new_status); + return; +} + +static const char *describe_status(int status_num) { + int status_want, status_state ; + if (status_hashtable[status_num] == NULL || status_hashtable[status_num]->status == 0) + return "is not installed or flagged to be installed\n"; + + status_want = get_status(status_num, 1); + status_state = get_status(status_num, 3); + + if (status_state == search_name_hashtable("installed")) { + if (status_want == search_name_hashtable("install")) + return "is installed"; + if (status_want == search_name_hashtable("deinstall")) + return "is marked to be removed"; + if (status_want == search_name_hashtable("purge")) + return "is marked to be purged"; + } + if (status_want == search_name_hashtable("unknown")) + return "is in an indeterminate state"; + if (status_want == search_name_hashtable("install")) + return "is marked to be installed"; + + return "is not installed or flagged to be installed"; +} + + +static void index_status_file(const char *filename) +{ + FILE *status_file; + char *control_buffer; + char *status_line; + status_node_t *status_node = NULL; + unsigned int status_num; + + status_file = xfopen(filename, "r"); + while ((control_buffer = xmalloc_fgets_str(status_file, "\n\n")) != NULL) { + const unsigned int package_num = fill_package_struct(control_buffer); + if (package_num != -1) { + status_node = xmalloc(sizeof(status_node_t)); + /* fill_package_struct doesnt handle the status field */ + status_line = strstr(control_buffer, "Status:"); + if (status_line != NULL) { + status_line += 7; + status_line += strspn(status_line, " \n\t"); + status_line = xstrndup(status_line, strcspn(status_line, "\n")); + status_node->status = search_name_hashtable(status_line); + free(status_line); + } + status_node->package = package_num; + status_num = search_status_hashtable(name_hashtable[package_hashtable[status_node->package]->name]); + status_hashtable[status_num] = status_node; + } + free(control_buffer); + } + fclose(status_file); + return; +} + +static void write_buffer_no_status(FILE *new_status_file, const char *control_buffer) +{ + char *name; + char *value; + int start = 0; + while (1) { + start += read_package_field(&control_buffer[start], &name, &value); + if (name == NULL) { + break; + } + if (strcmp(name, "Status") != 0) { + fprintf(new_status_file, "%s: %s\n", name, value); + } + } + return; +} + +/* This could do with a cleanup */ +static void write_status_file(deb_file_t **deb_file) +{ + FILE *old_status_file = xfopen("/var/lib/dpkg/status", "r"); + FILE *new_status_file = xfopen("/var/lib/dpkg/status.udeb", "w"); + char *package_name; + char *status_from_file; + char *control_buffer = NULL; + char *tmp_string; + int status_num; + int field_start = 0; + int write_flag; + int i = 0; + + /* Update previously known packages */ + while ((control_buffer = xmalloc_fgets_str(old_status_file, "\n\n")) != NULL) { + if ((tmp_string = strstr(control_buffer, "Package:")) == NULL) { + continue; + } + + tmp_string += 8; + tmp_string += strspn(tmp_string, " \n\t"); + package_name = xstrndup(tmp_string, strcspn(tmp_string, "\n")); + write_flag = FALSE; + tmp_string = strstr(control_buffer, "Status:"); + if (tmp_string != NULL) { + /* Seperate the status value from the control buffer */ + tmp_string += 7; + tmp_string += strspn(tmp_string, " \n\t"); + status_from_file = xstrndup(tmp_string, strcspn(tmp_string, "\n")); + } else { + status_from_file = NULL; + } + + /* Find this package in the status hashtable */ + status_num = search_status_hashtable(package_name); + if (status_hashtable[status_num] != NULL) { + const char *status_from_hashtable = name_hashtable[status_hashtable[status_num]->status]; + if (strcmp(status_from_file, status_from_hashtable) != 0) { + /* New status isnt exactly the same as old status */ + const int state_status = get_status(status_num, 3); + if ((strcmp("installed", name_hashtable[state_status]) == 0) || + (strcmp("unpacked", name_hashtable[state_status]) == 0)) { + /* We need to add the control file from the package */ + i = 0; + while (deb_file[i] != NULL) { + if (strcmp(package_name, name_hashtable[package_hashtable[deb_file[i]->package]->name]) == 0) { + /* Write a status file entry with a modified status */ + /* remove trailing \n's */ + write_buffer_no_status(new_status_file, deb_file[i]->control_file); + set_status(status_num, "ok", 2); + fprintf(new_status_file, "Status: %s\n\n", + name_hashtable[status_hashtable[status_num]->status]); + write_flag = TRUE; + break; + } + i++; + } + /* This is temperary, debugging only */ + if (deb_file[i] == NULL) { + bb_error_msg_and_die("ALERT: cannot find a control file, " + "your status file may be broken, status may be " + "incorrect for %s", package_name); + } + } + else if (strcmp("not-installed", name_hashtable[state_status]) == 0) { + /* Only write the Package, Status, Priority and Section lines */ + fprintf(new_status_file, "Package: %s\n", package_name); + fprintf(new_status_file, "Status: %s\n", status_from_hashtable); + + while (1) { + char *field_name; + char *field_value; + field_start += read_package_field(&control_buffer[field_start], &field_name, &field_value); + if (field_name == NULL) { + break; + } + if ((strcmp(field_name, "Priority") == 0) || + (strcmp(field_name, "Section") == 0)) { + fprintf(new_status_file, "%s: %s\n", field_name, field_value); + } + } + write_flag = TRUE; + fputs("\n", new_status_file); + } + else if (strcmp("config-files", name_hashtable[state_status]) == 0) { + /* only change the status line */ + while (1) { + char *field_name; + char *field_value; + field_start += read_package_field(&control_buffer[field_start], &field_name, &field_value); + if (field_name == NULL) { + break; + } + /* Setup start point for next field */ + if (strcmp(field_name, "Status") == 0) { + fprintf(new_status_file, "Status: %s\n", status_from_hashtable); + } else { + fprintf(new_status_file, "%s: %s\n", field_name, field_value); + } + } + write_flag = TRUE; + fputs("\n", new_status_file); + } + } + } + /* If the package from the status file wasnt handle above, do it now*/ + if (! write_flag) { + fprintf(new_status_file, "%s\n\n", control_buffer); + } + + free(status_from_file); + free(package_name); + free(control_buffer); + } + + /* Write any new packages */ + for (i = 0; deb_file[i] != NULL; i++) { + status_num = search_status_hashtable(name_hashtable[package_hashtable[deb_file[i]->package]->name]); + if (strcmp("reinstreq", name_hashtable[get_status(status_num, 2)]) == 0) { + write_buffer_no_status(new_status_file, deb_file[i]->control_file); + set_status(status_num, "ok", 2); + fprintf(new_status_file, "Status: %s\n\n", name_hashtable[status_hashtable[status_num]->status]); + } + } + fclose(old_status_file); + fclose(new_status_file); + + + /* Create a separate backfile to dpkg */ + if (rename("/var/lib/dpkg/status", "/var/lib/dpkg/status.udeb.bak") == -1) { + struct stat stat_buf; + xstat("/var/lib/dpkg/status", &stat_buf); + /* Its ok if renaming the status file fails because status + * file doesnt exist, maybe we are starting from scratch */ + bb_error_msg("no status file found, creating new one"); + } + + if (rename("/var/lib/dpkg/status.udeb", "/var/lib/dpkg/status") == -1) { + bb_error_msg_and_die("DANGER: cannot create status file, " + "you need to manually repair your status file"); + } +} + +/* This function returns TRUE if the given package can satisfy a + * dependency of type depend_type. + * + * A pre-depends is satisfied only if a package is already installed, + * which a regular depends can be satisfied by a package which we want + * to install. + */ +static int package_satisfies_dependency(int package, int depend_type) +{ + int status_num = search_status_hashtable(name_hashtable[package_hashtable[package]->name]); + + /* status could be unknown if package is a pure virtual + * provides which cannot satisfy any dependency by itself. + */ + if (status_hashtable[status_num] == NULL) + return 0; + + switch (depend_type) { + case EDGE_PRE_DEPENDS: return get_status(status_num, 3) == search_name_hashtable("installed"); + case EDGE_DEPENDS: return get_status(status_num, 1) == search_name_hashtable("install"); + } + return 0; +} + +static int check_deps(deb_file_t **deb_file, int deb_start, int dep_max_count) +{ + int *conflicts = NULL; + int conflicts_num = 0; + int i = deb_start; + int j; + + /* Check for conflicts + * TODO: TEST if conflicts with other packages to be installed + * + * Add install packages and the packages they provide + * to the list of files to check conflicts for + */ + + /* Create array of package numbers to check against + * installed package for conflicts*/ + while (deb_file[i] != NULL) { + const unsigned int package_num = deb_file[i]->package; + conflicts = xrealloc(conflicts, sizeof(int) * (conflicts_num + 1)); + conflicts[conflicts_num] = package_num; + conflicts_num++; + /* add provides to conflicts list */ + for (j = 0; j < package_hashtable[package_num]->num_of_edges; j++) { + if (package_hashtable[package_num]->edge[j]->type == EDGE_PROVIDES) { + const int conflicts_package_num = search_package_hashtable( + package_hashtable[package_num]->edge[j]->name, + package_hashtable[package_num]->edge[j]->version, + package_hashtable[package_num]->edge[j]->operator); + if (package_hashtable[conflicts_package_num] == NULL) { + /* create a new package */ + common_node_t *new_node = (common_node_t *) xzalloc(sizeof(common_node_t)); + new_node->name = package_hashtable[package_num]->edge[j]->name; + new_node->version = package_hashtable[package_num]->edge[j]->version; + package_hashtable[conflicts_package_num] = new_node; + } + conflicts = xrealloc(conflicts, sizeof(int) * (conflicts_num + 1)); + conflicts[conflicts_num] = conflicts_package_num; + conflicts_num++; + } + } + i++; + } + + /* Check conflicts */ + i = 0; + while (deb_file[i] != NULL) { + const common_node_t *package_node = package_hashtable[deb_file[i]->package]; + int status_num = 0; + status_num = search_status_hashtable(name_hashtable[package_node->name]); + + if (get_status(status_num, 3) == search_name_hashtable("installed")) { + i++; + continue; + } + + for (j = 0; j < package_node->num_of_edges; j++) { + const edge_t *package_edge = package_node->edge[j]; + + if (package_edge->type == EDGE_CONFLICTS) { + const unsigned int package_num = + search_package_hashtable(package_edge->name, + package_edge->version, + package_edge->operator); + int result = 0; + if (package_hashtable[package_num] != NULL) { + status_num = search_status_hashtable(name_hashtable[package_hashtable[package_num]->name]); + + if (get_status(status_num, 1) == search_name_hashtable("install")) { + result = test_version(package_hashtable[deb_file[i]->package]->version, + package_edge->version, package_edge->operator); + } + } + + if (result) { + bb_error_msg_and_die("package %s conflicts with %s", + name_hashtable[package_node->name], + name_hashtable[package_edge->name]); + } + } + } + i++; + } + + + /* Check dependendcies */ + for (i = 0; i < PACKAGE_HASH_PRIME; i++) { + int status_num = 0; + int number_of_alternatives = 0; + const edge_t * root_of_alternatives = NULL; + const common_node_t *package_node = package_hashtable[i]; + + /* If the package node does not exist then this + * package is a virtual one. In which case there are + * no dependencies to check. + */ + if (package_node == NULL) continue; + + status_num = search_status_hashtable(name_hashtable[package_node->name]); + + /* If there is no status then this package is a + * virtual one provided by something else. In which + * case there are no dependencies to check. + */ + if (status_hashtable[status_num] == NULL) continue; + + /* If we don't want this package installed then we may + * as well ignore it's dependencies. + */ + if (get_status(status_num, 1) != search_name_hashtable("install")) { + continue; + } + + /* This code is tested only for EDGE_DEPENDS, since I + * have no suitable pre-depends available. There is no + * reason that it shouldn't work though :-) + */ + for (j = 0; j < package_node->num_of_edges; j++) { + const edge_t *package_edge = package_node->edge[j]; + unsigned int package_num; + + if (package_edge->type == EDGE_OR_PRE_DEPENDS || + package_edge->type == EDGE_OR_DEPENDS) { /* start an EDGE_OR_ list */ + number_of_alternatives = package_edge->version; + root_of_alternatives = package_edge; + continue; + } else if (number_of_alternatives == 0) { /* not in the middle of an EDGE_OR_ list */ + number_of_alternatives = 1; + root_of_alternatives = NULL; + } + + package_num = search_package_hashtable(package_edge->name, package_edge->version, package_edge->operator); + + if (package_edge->type == EDGE_PRE_DEPENDS || + package_edge->type == EDGE_DEPENDS) { + int result=1; + status_num = 0; + + /* If we are inside an alternative then check + * this edge is the right type. + * + * EDGE_DEPENDS == OR_DEPENDS -1 + * EDGE_PRE_DEPENDS == OR_PRE_DEPENDS -1 + */ + if (root_of_alternatives && package_edge->type != root_of_alternatives->type - 1) + bb_error_msg_and_die("fatal error, package dependencies corrupt: %d != %d - 1", + package_edge->type, root_of_alternatives->type); + + if (package_hashtable[package_num] != NULL) + result = !package_satisfies_dependency(package_num, package_edge->type); + + if (result) { /* check for other package which provide what we are looking for */ + int provider = -1; + + while ((provider = search_for_provides(package_edge->name, provider)) > -1) { + if (package_hashtable[provider] == NULL) { + puts("Have a provider but no package information for it"); + continue; + } + result = !package_satisfies_dependency(provider, package_edge->type); + + if (result == 0) + break; + } + } + + /* It must be already installed, or to be installed */ + number_of_alternatives--; + if (result && number_of_alternatives == 0) { + if (root_of_alternatives) + bb_error_msg_and_die( + "package %s %sdepends on %s, " + "which cannot be satisfied", + name_hashtable[package_node->name], + package_edge->type == EDGE_PRE_DEPENDS ? "pre-" : "", + name_hashtable[root_of_alternatives->name]); + else + bb_error_msg_and_die( + "package %s %sdepends on %s, which %s\n", + name_hashtable[package_node->name], + package_edge->type == EDGE_PRE_DEPENDS ? "pre-" : "", + name_hashtable[package_edge->name], + describe_status(status_num)); + } else if (result == 0 && number_of_alternatives) { + /* we've found a package which + * satisfies the dependency, + * so skip over the rest of + * the alternatives. + */ + j += number_of_alternatives; + number_of_alternatives = 0; + } + } + } + } + free(conflicts); + return TRUE; +} + +static char **create_list(const char *filename) +{ + FILE *list_stream; + char **file_list = NULL; + char *line = NULL; + int count = 0; + + /* don't use [xw]fopen here, handle error ourself */ + list_stream = fopen(filename, "r"); + if (list_stream == NULL) { + return NULL; + } + + while ((line = xmalloc_getline(list_stream)) != NULL) { + file_list = xrealloc(file_list, sizeof(char *) * (count + 2)); + file_list[count] = line; + count++; + } + fclose(list_stream); + + if (count == 0) { + return NULL; + } else { + file_list[count] = NULL; + return file_list; + } +} + +/* maybe i should try and hook this into remove_file.c somehow */ +static int remove_file_array(char **remove_names, char **exclude_names) +{ + struct stat path_stat; + int match_flag; + int remove_flag = FALSE; + int i,j; + + if (remove_names == NULL) { + return FALSE; + } + for (i = 0; remove_names[i] != NULL; i++) { + match_flag = FALSE; + if (exclude_names != NULL) { + for (j = 0; exclude_names[j] != 0; j++) { + if (strcmp(remove_names[i], exclude_names[j]) == 0) { + match_flag = TRUE; + break; + } + } + } + if (!match_flag) { + if (lstat(remove_names[i], &path_stat) < 0) { + continue; + } + if (S_ISDIR(path_stat.st_mode)) { + if (rmdir(remove_names[i]) != -1) { + remove_flag = TRUE; + } + } else { + if (unlink(remove_names[i]) != -1) { + remove_flag = TRUE; + } + } + } + } + return remove_flag; +} + +static int run_package_script(const char *package_name, const char *script_type) +{ + struct stat path_stat; + char *script_path; + int result; + + script_path = xasprintf("/var/lib/dpkg/info/%s.%s", package_name, script_type); + + /* If the file doesnt exist is isnt a fatal */ + result = lstat(script_path, &path_stat) < 0 ? EXIT_SUCCESS : system(script_path); + free(script_path); + return result; +} + +static const char *all_control_files[] = {"preinst", "postinst", "prerm", "postrm", + "list", "md5sums", "shlibs", "conffiles", "config", "templates", NULL }; + +static char **all_control_list(const char *package_name) +{ + unsigned i = 0; + char **remove_files; + + /* Create a list of all /var/lib/dpkg/info/ files */ + remove_files = xzalloc(sizeof(all_control_files)); + while (all_control_files[i]) { + remove_files[i] = xasprintf("/var/lib/dpkg/info/%s.%s", package_name, all_control_files[i]); + i++; + } + + return remove_files; +} + +static void free_array(char **array) +{ + + if (array) { + unsigned i = 0; + while (array[i]) { + free(array[i]); + i++; + } + free(array); + } +} + +/* This function lists information on the installed packages. It loops through + * the status_hashtable to retrieve the info. This results in smaller code than + * scanning the status file. The resulting list, however, is unsorted. + */ +static void list_packages(void) +{ + int i; + + puts(" Name Version"); + puts("+++-==============-=============="); + + /* go through status hash, dereference package hash and finally strings */ + for (i=0; istatus]; + name_str = name_hashtable[package_hashtable[status_hashtable[i]->package]->name]; + vers_str = name_hashtable[package_hashtable[status_hashtable[i]->package]->version]; + + /* get abbreviation for status field 1 */ + s1 = stat_str[0] == 'i' ? 'i' : 'r'; + + /* get abbreviation for status field 2 */ + for (j=0, spccnt=0; stat_str[j] && spccnt<2; j++) { + if (stat_str[j] == ' ') spccnt++; + } + s2 = stat_str[j]; + + /* print out the line formatted like Debian dpkg */ + printf("%c%c %-14s %s\n", s1, s2, name_str, vers_str); + } + } +} + +static void remove_package(const unsigned int package_num, int noisy) +{ + const char *package_name = name_hashtable[package_hashtable[package_num]->name]; + const char *package_version = name_hashtable[package_hashtable[package_num]->version]; + const unsigned int status_num = search_status_hashtable(package_name); + const int package_name_length = strlen(package_name); + char **remove_files; + char **exclude_files; + char list_name[package_name_length + 25]; + char conffile_name[package_name_length + 30]; + int return_value; + + if (noisy) + printf("Removing %s (%s)...\n", package_name, package_version); + + /* run prerm script */ + return_value = run_package_script(package_name, "prerm"); + if (return_value == -1) { + bb_error_msg_and_die("script failed, prerm failure"); + } + + /* Create a list of files to remove, and a separate list of those to keep */ + sprintf(list_name, "/var/lib/dpkg/info/%s.list", package_name); + remove_files = create_list(list_name); + + sprintf(conffile_name, "/var/lib/dpkg/info/%s.conffiles", package_name); + exclude_files = create_list(conffile_name); + + /* Some directories can't be removed straight away, so do multiple passes */ + while (remove_file_array(remove_files, exclude_files)) /*repeat */; + free_array(exclude_files); + free_array(remove_files); + + /* Create a list of files in /var/lib/dpkg/info/.* to keep */ + exclude_files = xzalloc(sizeof(char*) * 3); + exclude_files[0] = xstrdup(conffile_name); + exclude_files[1] = xasprintf("/var/lib/dpkg/info/%s.postrm", package_name); + + /* Create a list of all /var/lib/dpkg/info/ files */ + remove_files = all_control_list(package_name); + + remove_file_array(remove_files, exclude_files); + free_array(remove_files); + free_array(exclude_files); + + /* rename .conffile to .list */ + rename(conffile_name, list_name); + + /* Change package status */ + set_status(status_num, "config-files", 3); +} + +static void purge_package(const unsigned int package_num) +{ + const char *package_name = name_hashtable[package_hashtable[package_num]->name]; + const char *package_version = name_hashtable[package_hashtable[package_num]->version]; + const unsigned int status_num = search_status_hashtable(package_name); + char **remove_files; + char **exclude_files; + char list_name[strlen(package_name) + 25]; + + printf("Purging %s (%s)...\n", package_name, package_version); + + /* run prerm script */ + if (run_package_script(package_name, "prerm") != 0) { + bb_error_msg_and_die("script failed, prerm failure"); + } + + /* Create a list of files to remove */ + sprintf(list_name, "/var/lib/dpkg/info/%s.list", package_name); + remove_files = create_list(list_name); + + exclude_files = xzalloc(sizeof(char*)); + + /* Some directories cant be removed straight away, so do multiple passes */ + while (remove_file_array(remove_files, exclude_files)) /* repeat */; + free_array(remove_files); + + /* Create a list of all /var/lib/dpkg/info/ files */ + remove_files = all_control_list(package_name); + remove_file_array(remove_files, exclude_files); + free_array(remove_files); + free(exclude_files); + + /* run postrm script */ + if (run_package_script(package_name, "postrm") == -1) { + bb_error_msg_and_die("postrm fialure.. set status to what?"); + } + + /* Change package status */ + set_status(status_num, "not-installed", 3); +} + +static archive_handle_t *init_archive_deb_ar(const char *filename) +{ + archive_handle_t *ar_handle; + + /* Setup an ar archive handle that refers to the gzip sub archive */ + ar_handle = init_handle(); + ar_handle->filter = filter_accept_list_reassign; + ar_handle->src_fd = xopen(filename, O_RDONLY); + + return ar_handle; +} + +static void init_archive_deb_control(archive_handle_t *ar_handle) +{ + archive_handle_t *tar_handle; + + /* Setup the tar archive handle */ + tar_handle = init_handle(); + tar_handle->src_fd = ar_handle->src_fd; + + /* We don't care about data.tar.* or debian-binary, just control.tar.* */ +#ifdef CONFIG_FEATURE_DEB_TAR_GZ + llist_add_to(&(ar_handle->accept), "control.tar.gz"); +#endif +#ifdef CONFIG_FEATURE_DEB_TAR_BZ2 + llist_add_to(&(ar_handle->accept), "control.tar.bz2"); +#endif + + /* Assign the tar handle as a subarchive of the ar handle */ + ar_handle->sub_archive = tar_handle; + + return; +} + +static void init_archive_deb_data(archive_handle_t *ar_handle) +{ + archive_handle_t *tar_handle; + + /* Setup the tar archive handle */ + tar_handle = init_handle(); + tar_handle->src_fd = ar_handle->src_fd; + + /* We don't care about control.tar.* or debian-binary, just data.tar.* */ +#ifdef CONFIG_FEATURE_DEB_TAR_GZ + llist_add_to(&(ar_handle->accept), "data.tar.gz"); +#endif +#ifdef CONFIG_FEATURE_DEB_TAR_BZ2 + llist_add_to(&(ar_handle->accept), "data.tar.bz2"); +#endif + + /* Assign the tar handle as a subarchive of the ar handle */ + ar_handle->sub_archive = tar_handle; + + return; +} + +static char *deb_extract_control_file_to_buffer(archive_handle_t *ar_handle, llist_t *myaccept) +{ + ar_handle->sub_archive->action_data = data_extract_to_buffer; + ar_handle->sub_archive->accept = myaccept; + ar_handle->sub_archive->filter = filter_accept_list; + + unpack_ar_archive(ar_handle); + close(ar_handle->src_fd); + + return ar_handle->sub_archive->buffer; +} + +static void data_extract_all_prefix(archive_handle_t *archive_handle) +{ + char *name_ptr = archive_handle->file_header->name; + + name_ptr += strspn(name_ptr, "./"); + if (name_ptr[0] != '\0') { + archive_handle->file_header->name = xasprintf("%s%s", archive_handle->buffer, name_ptr); + data_extract_all(archive_handle); + } + return; +} + +static void unpack_package(deb_file_t *deb_file) +{ + const char *package_name = name_hashtable[package_hashtable[deb_file->package]->name]; + const unsigned int status_num = search_status_hashtable(package_name); + const unsigned int status_package_num = status_hashtable[status_num]->package; + char *info_prefix; + char *list_filename; + archive_handle_t *archive_handle; + FILE *out_stream; + llist_t *accept_list = NULL; + int i = 0; + + /* If existing version, remove it first */ + if (strcmp(name_hashtable[get_status(status_num, 3)], "installed") == 0) { + /* Package is already installed, remove old version first */ + printf("Preparing to replace %s %s (using %s)...\n", package_name, + name_hashtable[package_hashtable[status_package_num]->version], + deb_file->filename); + remove_package(status_package_num, 0); + } else { + printf("Unpacking %s (from %s)...\n", package_name, deb_file->filename); + } + + /* Extract control.tar.gz to /var/lib/dpkg/info/.filename */ + info_prefix = xasprintf("/var/lib/dpkg/info/%s.", package_name); + archive_handle = init_archive_deb_ar(deb_file->filename); + init_archive_deb_control(archive_handle); + + while (all_control_files[i]) { + char *c = xasprintf("./%s", all_control_files[i]); + llist_add_to(&accept_list, c); + i++; + } + archive_handle->sub_archive->accept = accept_list; + archive_handle->sub_archive->filter = filter_accept_list; + archive_handle->sub_archive->action_data = data_extract_all_prefix; + archive_handle->sub_archive->buffer = info_prefix; + archive_handle->sub_archive->flags |= ARCHIVE_EXTRACT_UNCONDITIONAL; + unpack_ar_archive(archive_handle); + + /* Run the preinst prior to extracting */ + if (run_package_script(package_name, "preinst") != 0) { + /* when preinst returns exit code != 0 then quit installation process */ + bb_error_msg_and_die("subprocess pre-installation script returned error"); + } + + /* Extract data.tar.gz to the root directory */ + archive_handle = init_archive_deb_ar(deb_file->filename); + init_archive_deb_data(archive_handle); + archive_handle->sub_archive->action_data = data_extract_all_prefix; + archive_handle->sub_archive->buffer = "/"; + archive_handle->sub_archive->flags |= ARCHIVE_EXTRACT_UNCONDITIONAL; + unpack_ar_archive(archive_handle); + + /* Create the list file */ + list_filename = xasprintf("/var/lib/dpkg/info/%s.list", package_name); + out_stream = xfopen(list_filename, "w"); + while (archive_handle->sub_archive->passed) { + /* the leading . has been stripped by data_extract_all_prefix already */ + fputs(archive_handle->sub_archive->passed->data, out_stream); + fputc('\n', out_stream); + archive_handle->sub_archive->passed = archive_handle->sub_archive->passed->link; + } + fclose(out_stream); + + /* change status */ + set_status(status_num, "install", 1); + set_status(status_num, "unpacked", 3); + + free(info_prefix); + free(list_filename); +} + +static void configure_package(deb_file_t *deb_file) +{ + const char *package_name = name_hashtable[package_hashtable[deb_file->package]->name]; + const char *package_version = name_hashtable[package_hashtable[deb_file->package]->version]; + const int status_num = search_status_hashtable(package_name); + + printf("Setting up %s (%s)...\n", package_name, package_version); + + /* Run the postinst script */ + if (run_package_script(package_name, "postinst") != 0) { + /* TODO: handle failure gracefully */ + bb_error_msg_and_die("postrm failure.. set status to what?"); + } + /* Change status to reflect success */ + set_status(status_num, "install", 1); + set_status(status_num, "installed", 3); +} + +int dpkg_main(int argc, char **argv) +{ + deb_file_t **deb_file = NULL; + status_node_t *status_node; + int opt; + int package_num; + int dpkg_opt = 0; + int deb_count = 0; + int state_status; + int status_num; + int i; + + name_hashtable = xzalloc(sizeof(name_hashtable[0]) * (NAME_HASH_PRIME + 1)); + package_hashtable = xzalloc(sizeof(package_hashtable[0]) * (PACKAGE_HASH_PRIME + 1)); + status_hashtable = xzalloc(sizeof(status_hashtable[0]) * (STATUS_HASH_PRIME + 1)); + + while ((opt = getopt(argc, argv, "CF:ilPru")) != -1) { + switch (opt) { + case 'C': // equivalent to --configure in official dpkg + dpkg_opt |= dpkg_opt_configure; + dpkg_opt |= dpkg_opt_package_name; + break; + case 'F': // equivalent to --force in official dpkg + if (strcmp(optarg, "depends") == 0) { + dpkg_opt |= dpkg_opt_force_ignore_depends; + } + break; + case 'i': + dpkg_opt |= dpkg_opt_install; + dpkg_opt |= dpkg_opt_filename; + break; + case 'l': + dpkg_opt |= dpkg_opt_list_installed; + break; + case 'P': + dpkg_opt |= dpkg_opt_purge; + dpkg_opt |= dpkg_opt_package_name; + break; + case 'r': + dpkg_opt |= dpkg_opt_remove; + dpkg_opt |= dpkg_opt_package_name; + break; + case 'u': /* Equivalent to --unpack in official dpkg */ + dpkg_opt |= dpkg_opt_unpack; + dpkg_opt |= dpkg_opt_filename; + break; + default: + bb_show_usage(); + } + } + /* check for non-option argument if expected */ + if ((dpkg_opt == 0) || ((argc == optind) && !(dpkg_opt && dpkg_opt_list_installed))) { + bb_show_usage(); + } + +/* puts("(Reading database ... xxxxx files and directories installed.)"); */ + index_status_file("/var/lib/dpkg/status"); + + /* if the list action was given print the installed packages and exit */ + if (dpkg_opt & dpkg_opt_list_installed) { + list_packages(); + return EXIT_SUCCESS; + } + + /* Read arguments and store relevant info in structs */ + while (optind < argc) { + /* deb_count = nb_elem - 1 and we need nb_elem + 1 to allocate terminal node [NULL pointer] */ + deb_file = xrealloc(deb_file, sizeof(deb_file_t *) * (deb_count + 2)); + deb_file[deb_count] = (deb_file_t *) xzalloc(sizeof(deb_file_t)); + if (dpkg_opt & dpkg_opt_filename) { + archive_handle_t *archive_handle; + llist_t *control_list = NULL; + + /* Extract the control file */ + llist_add_to(&control_list, "./control"); + archive_handle = init_archive_deb_ar(argv[optind]); + init_archive_deb_control(archive_handle); + deb_file[deb_count]->control_file = deb_extract_control_file_to_buffer(archive_handle, control_list); + if (deb_file[deb_count]->control_file == NULL) { + bb_error_msg_and_die("cannot extract control file"); + } + deb_file[deb_count]->filename = xstrdup(argv[optind]); + package_num = fill_package_struct(deb_file[deb_count]->control_file); + + if (package_num == -1) { + bb_error_msg("invalid control file in %s", argv[optind]); + optind++; + continue; + } + deb_file[deb_count]->package = (unsigned int) package_num; + + /* Add the package to the status hashtable */ + if ((dpkg_opt & dpkg_opt_unpack) || (dpkg_opt & dpkg_opt_install)) { + /* Try and find a currently installed version of this package */ + status_num = search_status_hashtable(name_hashtable[package_hashtable[deb_file[deb_count]->package]->name]); + /* If no previous entry was found initialise a new entry */ + if ((status_hashtable[status_num] == NULL) || + (status_hashtable[status_num]->status == 0)) { + status_node = (status_node_t *) xmalloc(sizeof(status_node_t)); + status_node->package = deb_file[deb_count]->package; + /* reinstreq isnt changed to "ok" until the package control info + * is written to the status file*/ + status_node->status = search_name_hashtable("install reinstreq not-installed"); + status_hashtable[status_num] = status_node; + } else { + set_status(status_num, "install", 1); + set_status(status_num, "reinstreq", 2); + } + } + } + else if (dpkg_opt & dpkg_opt_package_name) { + deb_file[deb_count]->package = search_package_hashtable( + search_name_hashtable(argv[optind]), + search_name_hashtable("ANY"), VER_ANY); + if (package_hashtable[deb_file[deb_count]->package] == NULL) { + bb_error_msg_and_die("package %s is uninstalled or unknown", argv[optind]); + } + package_num = deb_file[deb_count]->package; + status_num = search_status_hashtable(name_hashtable[package_hashtable[package_num]->name]); + state_status = get_status(status_num, 3); + + /* check package status is "installed" */ + if (dpkg_opt & dpkg_opt_remove) { + if ((strcmp(name_hashtable[state_status], "not-installed") == 0) || + (strcmp(name_hashtable[state_status], "config-files") == 0)) { + bb_error_msg_and_die("%s is already removed", name_hashtable[package_hashtable[package_num]->name]); + } + set_status(status_num, "deinstall", 1); + } + else if (dpkg_opt & dpkg_opt_purge) { + /* if package status is "conf-files" then its ok */ + if (strcmp(name_hashtable[state_status], "not-installed") == 0) { + bb_error_msg_and_die("%s is already purged", name_hashtable[package_hashtable[package_num]->name]); + } + set_status(status_num, "purge", 1); + } + } + deb_count++; + optind++; + } + deb_file[deb_count] = NULL; + + /* Check that the deb file arguments are installable */ + if ((dpkg_opt & dpkg_opt_force_ignore_depends) != dpkg_opt_force_ignore_depends) { + if (!check_deps(deb_file, 0, deb_count)) { + bb_error_msg_and_die("dependency check failed"); + } + } + + /* TODO: install or remove packages in the correct dependency order */ + for (i = 0; i < deb_count; i++) { + /* Remove or purge packages */ + if (dpkg_opt & dpkg_opt_remove) { + remove_package(deb_file[i]->package, 1); + } + else if (dpkg_opt & dpkg_opt_purge) { + purge_package(deb_file[i]->package); + } + else if (dpkg_opt & dpkg_opt_unpack) { + unpack_package(deb_file[i]); + } + else if (dpkg_opt & dpkg_opt_install) { + unpack_package(deb_file[i]); + /* package is configured in second pass below */ + } + else if (dpkg_opt & dpkg_opt_configure) { + configure_package(deb_file[i]); + } + } + /* configure installed packages */ + if (dpkg_opt & dpkg_opt_install) { + for (i = 0; i < deb_count; i++) + configure_package(deb_file[i]); + } + + write_status_file(deb_file); + + if (ENABLE_FEATURE_CLEAN_UP) { + for (i = 0; i < deb_count; i++) { + free(deb_file[i]->control_file); + free(deb_file[i]->filename); + free(deb_file[i]); + } + + free(deb_file); + + for (i = 0; i < NAME_HASH_PRIME; i++) { + free(name_hashtable[i]); + } + + for (i = 0; i < PACKAGE_HASH_PRIME; i++) { + if (package_hashtable[i] != NULL) { + free_package(package_hashtable[i]); + } + } + + for (i = 0; i < STATUS_HASH_PRIME; i++) { + free(status_hashtable[i]); + } + + free(status_hashtable); + free(package_hashtable); + free(name_hashtable); + } + + return EXIT_SUCCESS; +} diff --git a/archival/dpkg_deb.c b/archival/dpkg_deb.c new file mode 100644 index 000000000..d11d9df17 --- /dev/null +++ b/archival/dpkg_deb.c @@ -0,0 +1,96 @@ +/* vi: set sw=4 ts=4: */ +/* + * dpkg-deb packs, unpacks and provides information about Debian archives. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ +#include "busybox.h" +#include "unarchive.h" + +#define DPKG_DEB_OPT_CONTENTS 1 +#define DPKG_DEB_OPT_CONTROL 2 +#define DPKG_DEB_OPT_FIELD 4 +#define DPKG_DEB_OPT_EXTRACT 8 +#define DPKG_DEB_OPT_EXTRACT_VERBOSE 16 + +int dpkg_deb_main(int argc, char **argv) +{ + archive_handle_t *ar_archive; + archive_handle_t *tar_archive; + llist_t *control_tar_llist = NULL; + unsigned opt; + char *extract_dir = NULL; + short argcount = 1; + + /* Setup the tar archive handle */ + tar_archive = init_handle(); + + /* Setup an ar archive handle that refers to the gzip sub archive */ + ar_archive = init_handle(); + ar_archive->sub_archive = tar_archive; + ar_archive->filter = filter_accept_list_reassign; + +#ifdef CONFIG_FEATURE_DEB_TAR_GZ + llist_add_to(&(ar_archive->accept), "data.tar.gz"); + llist_add_to(&control_tar_llist, "control.tar.gz"); +#endif + +#ifdef CONFIG_FEATURE_DEB_TAR_BZ2 + llist_add_to(&(ar_archive->accept), "data.tar.bz2"); + llist_add_to(&control_tar_llist, "control.tar.bz2"); +#endif + + opt_complementary = "?c--efXx:e--cfXx:f--ceXx:X--cefx:x--cefX"; + opt = getopt32(argc, argv, "cefXx"); + + if (opt & DPKG_DEB_OPT_CONTENTS) { + tar_archive->action_header = header_verbose_list; + } + if (opt & DPKG_DEB_OPT_CONTROL) { + ar_archive->accept = control_tar_llist; + tar_archive->action_data = data_extract_all; + if (optind + 1 == argc) { + extract_dir = "./DEBIAN"; + } else { + argcount++; + } + } + if (opt & DPKG_DEB_OPT_FIELD) { + /* Print the entire control file + * it should accept a second argument which specifies a + * specific field to print */ + ar_archive->accept = control_tar_llist; + llist_add_to(&(tar_archive->accept), "./control"); + tar_archive->filter = filter_accept_list; + tar_archive->action_data = data_extract_to_stdout; + } + if (opt & DPKG_DEB_OPT_EXTRACT) { + tar_archive->action_header = header_list; + } + if (opt & (DPKG_DEB_OPT_EXTRACT_VERBOSE | DPKG_DEB_OPT_EXTRACT)) { + tar_archive->action_data = data_extract_all; + argcount = 2; + } + + if ((optind + argcount) != argc) { + bb_show_usage(); + } + + tar_archive->src_fd = ar_archive->src_fd = xopen(argv[optind++], O_RDONLY); + + /* Workout where to extract the files */ + /* 2nd argument is a dir name */ + if (argv[optind]) { + extract_dir = argv[optind]; + } + if (extract_dir) { + mkdir(extract_dir, 0777); /* bb_make_directory(extract_dir, 0777, 0) */ + xchdir(extract_dir); + } + unpack_ar_archive(ar_archive); + + /* Cleanup */ + close(ar_archive->src_fd); + + return EXIT_SUCCESS; +} diff --git a/archival/gunzip.c b/archival/gunzip.c new file mode 100644 index 000000000..e24401c71 --- /dev/null +++ b/archival/gunzip.c @@ -0,0 +1,162 @@ +/* vi: set sw=4 ts=4: */ +/* + * Gzip implementation for busybox + * + * Based on GNU gzip v1.2.4 Copyright (C) 1992-1993 Jean-loup Gailly. + * + * Originally adjusted for busybox by Sven Rudolph + * based on gzip sources + * + * Adjusted further by Erik Andersen to support files as + * well as stdin/stdout, and to generally behave itself wrt command line + * handling. + * + * General cleanup to better adhere to the style guide and make use of standard + * busybox functions by Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * gzip (GNU zip) -- compress files with zip algorithm and 'compress' interface + * Copyright (C) 1992-1993 Jean-loup Gailly + * The unzip code was written and put in the public domain by Mark Adler. + * Portions of the lzw code are derived from the public domain 'compress' + * written by Spencer Thomas, Joe Orost, James Woods, Jim McKie, Steve Davies, + * Ken Turkowski, Dave Mack and Peter Jannesen. + * + * See the license_msg below and the file COPYING for the software license. + * See the file algorithm.doc for the compression algorithms and file formats. + */ + +#include "busybox.h" +#include "unarchive.h" + +#define GUNZIP_OPT_STDOUT 1 +#define GUNZIP_OPT_FORCE 2 +#define GUNZIP_OPT_TEST 4 +#define GUNZIP_OPT_DECOMPRESS 8 +#define GUNZIP_OPT_VERBOSE 0x10 + +int gunzip_main(int argc, char **argv) +{ + USE_DESKTOP(long long) int status; + int exitcode = 0; + unsigned opt; + + opt = getopt32(argc, argv, "cftdv"); + /* if called as zcat */ + if (strcmp(applet_name, "zcat") == 0) { + opt |= GUNZIP_OPT_STDOUT; + } + + do { + struct stat stat_buf; + char *old_path = argv[optind]; + char *delete_path = NULL; + char *new_path = NULL; + int src_fd; + int dst_fd; + + optind++; + + if (old_path == NULL || strcmp(old_path, "-") == 0) { + src_fd = STDIN_FILENO; + opt |= GUNZIP_OPT_STDOUT; + USE_DESKTOP(opt &= ~GUNZIP_OPT_VERBOSE;) + optind = argc; /* we don't handle "gunzip - a.gz b.gz" */ + } else { + src_fd = xopen(old_path, O_RDONLY); + /* Get the time stamp on the input file. */ + fstat(src_fd, &stat_buf); + } + + /* Check that the input is sane. */ + if (isatty(src_fd) && !(opt & GUNZIP_OPT_FORCE)) { + bb_error_msg_and_die + ("compressed data not read from terminal, use -f to force it"); + } + + /* Set output filename and number */ + if (opt & GUNZIP_OPT_TEST) { + dst_fd = xopen(bb_dev_null, O_WRONLY); /* why does test use filenum 2 ? */ + } else if (opt & GUNZIP_OPT_STDOUT) { + dst_fd = STDOUT_FILENO; + } else { + char *extension; + + new_path = xstrdup(old_path); + + extension = strrchr(new_path, '.'); +#ifdef CONFIG_FEATURE_GUNZIP_UNCOMPRESS + if (extension && (strcmp(extension, ".Z") == 0)) { + *extension = '\0'; + } else +#endif + if (extension && (strcmp(extension, ".gz") == 0)) { + *extension = '\0'; + } else if (extension && (strcmp(extension, ".tgz") == 0)) { + extension[2] = 'a'; + extension[3] = 'r'; + } else { + // FIXME: should we die or just skip to next? + bb_error_msg_and_die("invalid extension"); + } + + /* Open output file (with correct permissions) */ + dst_fd = xopen3(new_path, O_WRONLY | O_CREAT | O_TRUNC, + stat_buf.st_mode); + + /* If unzip succeeds remove the old file */ + delete_path = old_path; + } + + status = -1; + /* do the decompression, and cleanup */ + if (xread_char(src_fd) == 0x1f) { + unsigned char magic2; + + magic2 = xread_char(src_fd); + if (ENABLE_FEATURE_GUNZIP_UNCOMPRESS && magic2 == 0x9d) { + status = uncompress(src_fd, dst_fd); + } else if (magic2 == 0x8b) { + check_header_gzip(src_fd); // FIXME: xfunc? _or_die? + status = inflate_gunzip(src_fd, dst_fd); + } else { + bb_error_msg("invalid magic"); + exitcode = 1; + } + if (status < 0) { + bb_error_msg("error inflating"); + exitcode = 1; + } + else if (ENABLE_DESKTOP && (opt & GUNZIP_OPT_VERBOSE)) { + fprintf(stderr, "%s: %u%% - replaced with %s\n", + old_path, (unsigned)(stat_buf.st_size*100 / (status+1)), new_path); + } + } else { + bb_error_msg("invalid magic"); + exitcode = 1; + } + if (status < 0 && new_path) { + /* Unzip failed, remove new path instead of old path */ + delete_path = new_path; + } + + if (dst_fd != STDOUT_FILENO) { + close(dst_fd); + } + if (src_fd != STDIN_FILENO) { + close(src_fd); + } + + /* delete_path will be NULL if in test mode or from stdin */ + if (delete_path && (unlink(delete_path) == -1)) { + bb_error_msg("cannot remove %s", delete_path); + exitcode = 1; + } + + free(new_path); + + } while (optind < argc); + + return exitcode; +} diff --git a/archival/gzip.c b/archival/gzip.c new file mode 100644 index 000000000..c9b67538e --- /dev/null +++ b/archival/gzip.c @@ -0,0 +1,2473 @@ +/* vi: set sw=4 ts=4: */ +/* + * Gzip implementation for busybox + * + * Based on GNU gzip Copyright (C) 1992-1993 Jean-loup Gailly. + * + * Originally adjusted for busybox by Charles P. Wright + * "this is a stripped down version of gzip I put into busybox, it does + * only standard in to standard out with -9 compression. It also requires + * the zcat module for some important functions." + * + * Adjusted further by Erik Andersen to support + * files as well as stdin/stdout, and to generally behave itself wrt + * command line handling. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* TODO: full support for -v for DESKTOP +/usr/bin/gzip -v a bogus aa +a: 85.1% -- replaced with a.gz +gzip: bogus: No such file or directory +aa: 85.1% -- replaced with aa.gz +*/ + +#define SMALL_MEM + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "busybox.h" + +typedef unsigned char uch; +typedef unsigned short ush; +typedef unsigned long ulg; + +/* Return codes from gzip */ +#define OK 0 +#define ERROR 1 +#define WARNING 2 + +/* Compression methods (see algorithm.doc) */ +/* Only STORED and DEFLATED are supported by this BusyBox module */ +#define STORED 0 +/* methods 4 to 7 reserved */ +#define DEFLATED 8 + +/* To save memory for 16 bit systems, some arrays are overlaid between + * the various modules: + * deflate: prev+head window d_buf l_buf outbuf + * unlzw: tab_prefix tab_suffix stack inbuf outbuf + * For compression, input is done in window[]. For decompression, output + * is done in window except for unlzw. + */ + +#ifndef INBUFSIZ +# ifdef SMALL_MEM +# define INBUFSIZ 0x2000 /* input buffer size */ +# else +# define INBUFSIZ 0x8000 /* input buffer size */ +# endif +#endif +#define INBUF_EXTRA 64 /* required by unlzw() */ + +#ifndef OUTBUFSIZ +# ifdef SMALL_MEM +# define OUTBUFSIZ 8192 /* output buffer size */ +# else +# define OUTBUFSIZ 16384 /* output buffer size */ +# endif +#endif +#define OUTBUF_EXTRA 2048 /* required by unlzw() */ + +#ifndef DIST_BUFSIZE +# ifdef SMALL_MEM +# define DIST_BUFSIZE 0x2000 /* buffer for distances, see trees.c */ +# else +# define DIST_BUFSIZE 0x8000 /* buffer for distances, see trees.c */ +# endif +#endif + +# define DECLARE(type, array, size) static type * array +# define ALLOC(type, array, size) { \ + array = (type*)xzalloc((size_t)(((size)+1L)/2) * 2*sizeof(type)); \ + } +# define FREE(array) {free(array), array=NULL;} + +#define tab_suffix window +#define tab_prefix prev /* hash link (see deflate.c) */ +#define head (prev+WSIZE) /* hash head (see deflate.c) */ + +static long bytes_in; /* number of input bytes */ + +#define isize bytes_in +/* for compatibility with old zip sources (to be cleaned) */ + +typedef int file_t; /* Do not use stdio */ + +#define NO_FILE (-1) /* in memory compression */ + + +#define PACK_MAGIC "\037\036" /* Magic header for packed files */ +#define GZIP_MAGIC "\037\213" /* Magic header for gzip files, 1F 8B */ +#define OLD_GZIP_MAGIC "\037\236" /* Magic header for gzip 0.5 = freeze 1.x */ +#define LZH_MAGIC "\037\240" /* Magic header for SCO LZH Compress files */ +#define PKZIP_MAGIC "\120\113\003\004" /* Magic header for pkzip files */ + +/* gzip flag byte */ +#define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */ +#define CONTINUATION 0x02 /* bit 1 set: continuation of multi-part gzip file */ +#define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */ +#define ORIG_NAME 0x08 /* bit 3 set: original file name present */ +#define COMMENT 0x10 /* bit 4 set: file comment present */ +#define RESERVED 0xC0 /* bit 6,7: reserved */ + +/* internal file attribute */ +#define UNKNOWN 0xffff +#define BINARY 0 +#define ASCII 1 + +#ifndef WSIZE +# define WSIZE 0x8000 /* window size--must be a power of two, and */ +#endif /* at least 32K for zip's deflate method */ + +#define MIN_MATCH 3 +#define MAX_MATCH 258 +/* The minimum and maximum match lengths */ + +#define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1) +/* Minimum amount of lookahead, except at the end of the input file. + * See deflate.c for comments about the MIN_MATCH+1. + */ + +#define MAX_DIST (WSIZE-MIN_LOOKAHEAD) +/* In order to simplify the code, particularly on 16 bit machines, match + * distances are limited to MAX_DIST instead of WSIZE. + */ + +/* put_byte is used for the compressed output */ +#define put_byte(c) {outbuf[outcnt++]=(uch)(c); if (outcnt==OUTBUFSIZ)\ + flush_outbuf();} + +#define seekable() 0 /* force sequential output */ +#define translate_eol 0 /* no option -a yet */ + +/* Diagnostic functions */ +#ifdef DEBUG +# define Assert(cond,msg) {if(!(cond)) bb_error_msg(msg);} +# define Trace(x) fprintf x +# define Tracev(x) {if (verbose) fprintf x ;} +# define Tracevv(x) {if (verbose>1) fprintf x ;} +# define Tracec(c,x) {if (verbose && (c)) fprintf x ;} +# define Tracecv(c,x) {if (verbose>1 && (c)) fprintf x ;} +#else +# define Assert(cond,msg) +# define Trace(x) +# define Tracev(x) +# define Tracevv(x) +# define Tracec(c,x) +# define Tracecv(c,x) +#endif + +#define WARN(msg) {if (!quiet) fprintf msg ; \ + if (exit_code == OK) exit_code = WARNING;} + +#ifndef MAX_PATH_LEN +# define MAX_PATH_LEN 1024 /* max pathname length */ +#endif + + + /* from zip.c: */ +static int zip(int in, int out); +static int file_read(char *buf, unsigned size); + + /* from deflate.c */ +static void lm_init(ush * flags); +static ulg deflate(void); + + /* from trees.c */ +static void ct_init(ush * attr, int *methodp); +static int ct_tally(int dist, int lc); +static ulg flush_block(char *buf, ulg stored_len, int eof); + + /* from bits.c */ +static void bi_init(file_t zipfile); +static void send_bits(int value, int length); +static unsigned bi_reverse(unsigned value, int length); +static void bi_windup(void); +static void copy_block(char *buf, unsigned len, int header); +static int (*read_buf) (char *buf, unsigned size); + + /* from util.c: */ +static void flush_outbuf(void); + +/* lzw.h -- define the lzw functions. + * Copyright (C) 1992-1993 Jean-loup Gailly. + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License, see the file COPYING. + */ + +#ifndef BITS +# define BITS 16 +#endif +#define INIT_BITS 9 /* Initial number of bits per code */ + +#define BIT_MASK 0x1f /* Mask for 'number of compression bits' */ +/* Mask 0x20 is reserved to mean a fourth header byte, and 0x40 is free. + * It's a pity that old uncompress does not check bit 0x20. That makes + * extension of the format actually undesirable because old compress + * would just crash on the new format instead of giving a meaningful + * error message. It does check the number of bits, but it's more + * helpful to say "unsupported format, get a new version" than + * "can only handle 16 bits". + */ + +/* tailor.h -- target dependent definitions + * Copyright (C) 1992-1993 Jean-loup Gailly. + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License, see the file COPYING. + */ + +/* The target dependent definitions should be defined here only. + * The target dependent functions should be defined in tailor.c. + */ + + + /* Common defaults */ + +#ifndef OS_CODE +# define OS_CODE 0x03 /* assume Unix */ +#endif + +#ifndef PATH_SEP +# define PATH_SEP '/' +#endif + +#ifndef OPTIONS_VAR +# define OPTIONS_VAR "GZIP" +#endif + +#ifndef Z_SUFFIX +# define Z_SUFFIX ".gz" +#endif + +#ifdef MAX_EXT_CHARS +# define MAX_SUFFIX MAX_EXT_CHARS +#else +# define MAX_SUFFIX 30 +#endif + + /* global buffers */ + +DECLARE(uch, inbuf, INBUFSIZ + INBUF_EXTRA); +DECLARE(uch, outbuf, OUTBUFSIZ + OUTBUF_EXTRA); +DECLARE(ush, d_buf, DIST_BUFSIZE); +DECLARE(uch, window, 2L * WSIZE); +DECLARE(ush, tab_prefix, 1L << BITS); + +static int foreground; /* set if program run in foreground */ +static int method = DEFLATED; /* compression method */ +static int exit_code = OK; /* program exit code */ +static long time_stamp; /* original time stamp (modification time) */ +static char z_suffix[MAX_SUFFIX + 1]; /* default suffix (can be set with --suffix) */ + +static int ifd; /* input file descriptor */ +static int ofd; /* output file descriptor */ +#ifdef DEBUG +static unsigned insize; /* valid bytes in inbuf */ +#endif +static unsigned outcnt; /* bytes in output buffer */ + +static uint32_t *crc_32_tab; + +/* Output a 16 bit value, lsb first */ +static void put_short(ush w) +{ + if (outcnt < OUTBUFSIZ - 2) { + outbuf[outcnt++] = (uch) ((w) & 0xff); + outbuf[outcnt++] = (uch) ((ush) (w) >> 8); + } else { + put_byte((uch) ((w) & 0xff)); + put_byte((uch) ((ush) (w) >> 8)); + } +} + +/* ======================================================================== + * Signal and error handler. + */ +static void abort_gzip(int ATTRIBUTE_UNUSED ignored) +{ + exit(ERROR); +} + +/* =========================================================================== + * Clear input and output buffers + */ +static void clear_bufs(void) +{ + outcnt = 0; +#ifdef DEBUG + insize = 0; +#endif + bytes_in = 0L; +} + +/* =========================================================================== + * Does the same as write(), but also handles partial pipe writes and checks + * for error return. + */ +static void write_buf(int fd, void *buf, unsigned cnt) +{ + unsigned n; + + while ((n = write(fd, buf, cnt)) != cnt) { + if (n == (unsigned) (-1)) bb_error_msg_and_die(bb_msg_write_error); + cnt -= n; + buf = (void *) ((char *) buf + n); + } +} + +/* =========================================================================== + * Run a set of bytes through the crc shift register. If s is a NULL + * pointer, then initialize the crc shift register contents instead. + * Return the current crc in either case. + */ +static uint32_t updcrc(uch * s, unsigned n) +{ + static uint32_t crc = ~0; /* shift register contents */ + uint32_t c; /* temporary variable */ + + if (s == NULL) { + c = ~0; + } else { + c = crc; + if (n) + do { + c = crc_32_tab[((int) c ^ (*s++)) & 0xff] ^ (c >> 8); + } while (--n); + } + crc = c; + return ~c; +} + +/* bits.c -- output variable-length bit strings + * Copyright (C) 1992-1993 Jean-loup Gailly + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License, see the file COPYING. + */ + + +/* + * PURPOSE + * + * Output variable-length bit strings. Compression can be done + * to a file or to memory. (The latter is not supported in this version.) + * + * DISCUSSION + * + * The PKZIP "deflate" file format interprets compressed file data + * as a sequence of bits. Multi-bit strings in the file may cross + * byte boundaries without restriction. + * + * The first bit of each byte is the low-order bit. + * + * The routines in this file allow a variable-length bit value to + * be output right-to-left (useful for literal values). For + * left-to-right output (useful for code strings from the tree routines), + * the bits must have been reversed first with bi_reverse(). + * + * For in-memory compression, the compressed bit stream goes directly + * into the requested output buffer. The input data is read in blocks + * by the mem_read() function. The buffer is limited to 64K on 16 bit + * machines. + * + * INTERFACE + * + * void bi_init (FILE *zipfile) + * Initialize the bit string routines. + * + * void send_bits (int value, int length) + * Write out a bit string, taking the source bits right to + * left. + * + * int bi_reverse (int value, int length) + * Reverse the bits of a bit string, taking the source bits left to + * right and emitting them right to left. + * + * void bi_windup (void) + * Write out any remaining bits in an incomplete byte. + * + * void copy_block(char *buf, unsigned len, int header) + * Copy a stored block to the zip file, storing first the length and + * its one's complement if requested. + * + */ + +/* =========================================================================== + * Local data used by the "bit string" routines. + */ + +static file_t zfile; /* output gzip file */ + +static unsigned short bi_buf; + +/* Output buffer. bits are inserted starting at the bottom (least significant + * bits). + */ + +#define Buf_size (8 * 2*sizeof(char)) +/* Number of bits used within bi_buf. (bi_buf might be implemented on + * more than 16 bits on some systems.) + */ + +static int bi_valid; + +/* Current input function. Set to mem_read for in-memory compression */ + +#ifdef DEBUG +ulg bits_sent; /* bit length of the compressed data */ +#endif + +/* =========================================================================== + * Initialize the bit string routines. + */ +static void bi_init(file_t zipfile) +{ + zfile = zipfile; + bi_buf = 0; + bi_valid = 0; +#ifdef DEBUG + bits_sent = 0L; +#endif + + /* Set the defaults for file compression. They are set by memcompress + * for in-memory compression. + */ + if (zfile != NO_FILE) { + read_buf = file_read; + } +} + +/* =========================================================================== + * Send a value on a given number of bits. + * IN assertion: length <= 16 and value fits in length bits. + */ +static void send_bits(int value, int length) +{ +#ifdef DEBUG + Tracev((stderr, " l %2d v %4x ", length, value)); + Assert(length > 0 && length <= 15, "invalid length"); + bits_sent += (ulg) length; +#endif + /* If not enough room in bi_buf, use (valid) bits from bi_buf and + * (16 - bi_valid) bits from value, leaving (width - (16-bi_valid)) + * unused bits in value. + */ + if (bi_valid > (int) Buf_size - length) { + bi_buf |= (value << bi_valid); + put_short(bi_buf); + bi_buf = (ush) value >> (Buf_size - bi_valid); + bi_valid += length - Buf_size; + } else { + bi_buf |= value << bi_valid; + bi_valid += length; + } +} + +/* =========================================================================== + * Reverse the first len bits of a code, using straightforward code (a faster + * method would use a table) + * IN assertion: 1 <= len <= 15 + */ +static unsigned bi_reverse(unsigned code, int len) +{ + unsigned res = 0; + + do { + res |= code & 1; + code >>= 1, res <<= 1; + } while (--len > 0); + return res >> 1; +} + +/* =========================================================================== + * Write out any remaining bits in an incomplete byte. + */ +static void bi_windup(void) +{ + if (bi_valid > 8) { + put_short(bi_buf); + } else if (bi_valid > 0) { + put_byte(bi_buf); + } + bi_buf = 0; + bi_valid = 0; +#ifdef DEBUG + bits_sent = (bits_sent + 7) & ~7; +#endif +} + +/* =========================================================================== + * Copy a stored block to the zip file, storing first the length and its + * one's complement if requested. + */ +static void copy_block(char *buf, unsigned len, int header) +{ + bi_windup(); /* align on byte boundary */ + + if (header) { + put_short((ush) len); + put_short((ush) ~ len); +#ifdef DEBUG + bits_sent += 2 * 16; +#endif + } +#ifdef DEBUG + bits_sent += (ulg) len << 3; +#endif + while (len--) { + put_byte(*buf++); + } +} + +/* deflate.c -- compress data using the deflation algorithm + * Copyright (C) 1992-1993 Jean-loup Gailly + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License, see the file COPYING. + */ + +/* + * PURPOSE + * + * Identify new text as repetitions of old text within a fixed- + * length sliding window trailing behind the new text. + * + * DISCUSSION + * + * The "deflation" process depends on being able to identify portions + * of the input text which are identical to earlier input (within a + * sliding window trailing behind the input currently being processed). + * + * The most straightforward technique turns out to be the fastest for + * most input files: try all possible matches and select the longest. + * The key feature of this algorithm is that insertions into the string + * dictionary are very simple and thus fast, and deletions are avoided + * completely. Insertions are performed at each input character, whereas + * string matches are performed only when the previous match ends. So it + * is preferable to spend more time in matches to allow very fast string + * insertions and avoid deletions. The matching algorithm for small + * strings is inspired from that of Rabin & Karp. A brute force approach + * is used to find longer strings when a small match has been found. + * A similar algorithm is used in comic (by Jan-Mark Wams) and freeze + * (by Leonid Broukhis). + * A previous version of this file used a more sophisticated algorithm + * (by Fiala and Greene) which is guaranteed to run in linear amortized + * time, but has a larger average cost, uses more memory and is patented. + * However the F&G algorithm may be faster for some highly redundant + * files if the parameter max_chain_length (described below) is too large. + * + * ACKNOWLEDGMENTS + * + * The idea of lazy evaluation of matches is due to Jan-Mark Wams, and + * I found it in 'freeze' written by Leonid Broukhis. + * Thanks to many info-zippers for bug reports and testing. + * + * REFERENCES + * + * APPNOTE.TXT documentation file in PKZIP 1.93a distribution. + * + * A description of the Rabin and Karp algorithm is given in the book + * "Algorithms" by R. Sedgewick, Addison-Wesley, p252. + * + * Fiala,E.R., and Greene,D.H. + * Data Compression with Finite Windows, Comm.ACM, 32,4 (1989) 490-595 + * + * INTERFACE + * + * void lm_init (int pack_level, ush *flags) + * Initialize the "longest match" routines for a new file + * + * ulg deflate (void) + * Processes a new input file and return its compressed length. Sets + * the compressed length, crc, deflate flags and internal file + * attributes. + */ + + +/* =========================================================================== + * Configuration parameters + */ + +/* Compile with MEDIUM_MEM to reduce the memory requirements or + * with SMALL_MEM to use as little memory as possible. Use BIG_MEM if the + * entire input file can be held in memory (not possible on 16 bit systems). + * Warning: defining these symbols affects HASH_BITS (see below) and thus + * affects the compression ratio. The compressed output + * is still correct, and might even be smaller in some cases. + */ + +#ifdef SMALL_MEM +# define HASH_BITS 13 /* Number of bits used to hash strings */ +#endif +#ifdef MEDIUM_MEM +# define HASH_BITS 14 +#endif +#ifndef HASH_BITS +# define HASH_BITS 15 + /* For portability to 16 bit machines, do not use values above 15. */ +#endif + +/* To save space (see unlzw.c), we overlay prev+head with tab_prefix and + * window with tab_suffix. Check that we can do this: + */ +#if (WSIZE<<1) > (1< BITS-1 +# error cannot overlay head with tab_prefix1 +#endif +#define HASH_SIZE (unsigned)(1<= HASH_BITS + */ + +static unsigned int prev_length; + +/* Length of the best match at previous step. Matches not greater than this + * are discarded. This is used in the lazy match evaluation. + */ + +static unsigned strstart; /* start of string to insert */ +static unsigned match_start; /* start of matching string */ +static int eofile; /* flag set at end of input file */ +static unsigned lookahead; /* number of valid bytes ahead in window */ + +enum { + max_chain_length = 4096, + +/* To speed up deflation, hash chains are never searched beyond this length. + * A higher limit improves compression ratio but degrades the speed. + */ + + max_lazy_match = 258, + +/* Attempt to find a better match only when the current match is strictly + * smaller than this value. This mechanism is used only for compression + * levels >= 4. + */ + max_insert_length = max_lazy_match, +/* Insert new strings in the hash table only if the match length + * is not greater than this length. This saves time but degrades compression. + * max_insert_length is used only for compression levels <= 3. + */ + + good_match = 32, + +/* Use a faster search when the previous match is longer than this */ + + +/* Values for max_lazy_match, good_match and max_chain_length, depending on + * the desired pack level (0..9). The values given below have been tuned to + * exclude worst case performance for pathological files. Better values may be + * found for specific files. + */ + + nice_match = 258 /* Stop searching when current match exceeds this */ + +/* Note: the deflate() code requires max_lazy >= MIN_MATCH and max_chain >= 4 + * For deflate_fast() (levels <= 3) good is ignored and lazy has a different + * meaning. + */ +}; + +#define EQUAL 0 +/* result of memcmp for equal strings */ + +/* =========================================================================== + * Prototypes for local functions. + */ +static void fill_window(void); + +static int longest_match(IPos cur_match); + +#ifdef DEBUG +static void check_match(IPos start, IPos match, int length); +#endif + +/* =========================================================================== + * Update a hash value with the given input byte + * IN assertion: all calls to to UPDATE_HASH are made with consecutive + * input characters, so that a running hash key can be computed from the + * previous key instead of complete recalculation each time. + */ +#define UPDATE_HASH(h,c) (h = (((h)<= 1 + */ + +/* For MSDOS, OS/2 and 386 Unix, an optimized version is in match.asm or + * match.s. The code is functionally equivalent, so you can use the C version + * if desired. + */ +static int longest_match(IPos cur_match) +{ + unsigned chain_length = max_chain_length; /* max hash chain length */ + uch *scan = window + strstart; /* current string */ + uch *match; /* matched string */ + int len; /* length of current match */ + int best_len = prev_length; /* best match length so far */ + IPos limit = + strstart > (IPos) MAX_DIST ? strstart - (IPos) MAX_DIST : NIL; + /* Stop when cur_match becomes <= limit. To simplify the code, + * we prevent matches with the string of window index 0. + */ + +/* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. + * It is easy to get rid of this optimization if necessary. + */ +#if HASH_BITS < 8 || MAX_MATCH != 258 +# error Code too clever +#endif + uch *strend = window + strstart + MAX_MATCH; + uch scan_end1 = scan[best_len - 1]; + uch scan_end = scan[best_len]; + + /* Do not waste too much time if we already have a good match: */ + if (prev_length >= good_match) { + chain_length >>= 2; + } + Assert(strstart <= window_size - MIN_LOOKAHEAD, "insufficient lookahead"); + + do { + Assert(cur_match < strstart, "no future"); + match = window + cur_match; + + /* Skip to next match if the match length cannot increase + * or if the match length is less than 2: + */ + if (match[best_len] != scan_end || + match[best_len - 1] != scan_end1 || + *match != *scan || *++match != scan[1]) + continue; + + /* The check at best_len-1 can be removed because it will be made + * again later. (This heuristic is not always a win.) + * It is not necessary to compare scan[2] and match[2] since they + * are always equal when the other bytes match, given that + * the hash keys are equal and that HASH_BITS >= 8. + */ + scan += 2, match++; + + /* We check for insufficient lookahead only every 8th comparison; + * the 256th check will be made at strstart+258. + */ + do { + } while (*++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && + *++scan == *++match && *++scan == *++match && scan < strend); + + len = MAX_MATCH - (int) (strend - scan); + scan = strend - MAX_MATCH; + + if (len > best_len) { + match_start = cur_match; + best_len = len; + if (len >= nice_match) + break; + scan_end1 = scan[best_len - 1]; + scan_end = scan[best_len]; + } + } while ((cur_match = prev[cur_match & WMASK]) > limit + && --chain_length != 0); + + return best_len; +} + +#ifdef DEBUG +/* =========================================================================== + * Check that the match at match_start is indeed a match. + */ +static void check_match(IPos start, IPos match, int length) +{ + /* check that the match is indeed a match */ + if (memcmp((char *) window + match, + (char *) window + start, length) != EQUAL) { + bb_error_msg(" start %d, match %d, length %d", start, match, length); + bb_error_msg("invalid match"); + } + if (verbose > 1) { + bb_error_msg("\\[%d,%d]", start - match, length); + do { + putc(window[start++], stderr); + } while (--length != 0); + } +} +#else +# define check_match(start, match, length) +#endif + +/* =========================================================================== + * Fill the window when the lookahead becomes insufficient. + * Updates strstart and lookahead, and sets eofile if end of input file. + * IN assertion: lookahead < MIN_LOOKAHEAD && strstart + lookahead > 0 + * OUT assertions: at least one byte has been read, or eofile is set; + * file reads are performed for at least two bytes (required for the + * translate_eol option). + */ +static void fill_window(void) +{ + unsigned n, m; + unsigned more = + (unsigned) (window_size - (ulg) lookahead - (ulg) strstart); + /* Amount of free space at the end of the window. */ + + /* If the window is almost full and there is insufficient lookahead, + * move the upper half to the lower one to make room in the upper half. + */ + if (more == (unsigned) EOF) { + /* Very unlikely, but possible on 16 bit machine if strstart == 0 + * and lookahead == 1 (input done one byte at time) + */ + more--; + } else if (strstart >= WSIZE + MAX_DIST) { + /* By the IN assertion, the window is not empty so we can't confuse + * more == 0 with more == 64K on a 16 bit machine. + */ + Assert(window_size == (ulg) 2 * WSIZE, "no sliding with BIG_MEM"); + + memcpy((char *) window, (char *) window + WSIZE, (unsigned) WSIZE); + match_start -= WSIZE; + strstart -= WSIZE; /* we now have strstart >= MAX_DIST: */ + + block_start -= (long) WSIZE; + + for (n = 0; n < HASH_SIZE; n++) { + m = head[n]; + head[n] = (Pos) (m >= WSIZE ? m - WSIZE : NIL); + } + for (n = 0; n < WSIZE; n++) { + m = prev[n]; + prev[n] = (Pos) (m >= WSIZE ? m - WSIZE : NIL); + /* If n is not on any hash chain, prev[n] is garbage but + * its value will never be used. + */ + } + more += WSIZE; + } + /* At this point, more >= 2 */ + if (!eofile) { + n = read_buf((char *) window + strstart + lookahead, more); + if (n == 0 || n == (unsigned) EOF) { + eofile = 1; + } else { + lookahead += n; + } + } +} + +/* =========================================================================== + * Flush the current block, with given end-of-file flag. + * IN assertion: strstart is set to the end of the current match. + */ +#define FLUSH_BLOCK(eof) \ + flush_block(block_start >= 0L ? (char*)&window[(unsigned)block_start] : \ + (char*)NULL, (long)strstart - block_start, (eof)) + +/* =========================================================================== + * Same as above, but achieves better compression. We use a lazy + * evaluation for matches: a match is finally adopted only if there is + * no better match at the next window position. + */ +static ulg deflate(void) +{ + IPos hash_head; /* head of hash chain */ + IPos prev_match; /* previous match */ + int flush; /* set if current block must be flushed */ + int match_available = 0; /* set if previous match exists */ + unsigned match_length = MIN_MATCH - 1; /* length of best match */ + + /* Process the input block. */ + while (lookahead != 0) { + /* Insert the string window[strstart .. strstart+2] in the + * dictionary, and set hash_head to the head of the hash chain: + */ + INSERT_STRING(strstart, hash_head); + + /* Find the longest match, discarding those <= prev_length. + */ + prev_length = match_length, prev_match = match_start; + match_length = MIN_MATCH - 1; + + if (hash_head != NIL && prev_length < max_lazy_match && + strstart - hash_head <= MAX_DIST) { + /* To simplify the code, we prevent matches with the string + * of window index 0 (in particular we have to avoid a match + * of the string with itself at the start of the input file). + */ + match_length = longest_match(hash_head); + /* longest_match() sets match_start */ + if (match_length > lookahead) + match_length = lookahead; + + /* Ignore a length 3 match if it is too distant: */ + if (match_length == MIN_MATCH && strstart - match_start > TOO_FAR) { + /* If prev_match is also MIN_MATCH, match_start is garbage + * but we will ignore the current match anyway. + */ + match_length--; + } + } + /* If there was a match at the previous step and the current + * match is not better, output the previous match: + */ + if (prev_length >= MIN_MATCH && match_length <= prev_length) { + + check_match(strstart - 1, prev_match, prev_length); + + flush = + ct_tally(strstart - 1 - prev_match, prev_length - MIN_MATCH); + + /* Insert in hash table all strings up to the end of the match. + * strstart-1 and strstart are already inserted. + */ + lookahead -= prev_length - 1; + prev_length -= 2; + do { + strstart++; + INSERT_STRING(strstart, hash_head); + /* strstart never exceeds WSIZE-MAX_MATCH, so there are + * always MIN_MATCH bytes ahead. If lookahead < MIN_MATCH + * these bytes are garbage, but it does not matter since the + * next lookahead bytes will always be emitted as literals. + */ + } while (--prev_length != 0); + match_available = 0; + match_length = MIN_MATCH - 1; + strstart++; + if (flush) + FLUSH_BLOCK(0), block_start = strstart; + + } else if (match_available) { + /* If there was no match at the previous position, output a + * single literal. If there was a match but the current match + * is longer, truncate the previous match to a single literal. + */ + Tracevv((stderr, "%c", window[strstart - 1])); + if (ct_tally(0, window[strstart - 1])) { + FLUSH_BLOCK(0), block_start = strstart; + } + strstart++; + lookahead--; + } else { + /* There is no previous match to compare with, wait for + * the next step to decide. + */ + match_available = 1; + strstart++; + lookahead--; + } + Assert(strstart <= isize && lookahead <= isize, "a bit too far"); + + /* Make sure that we always have enough lookahead, except + * at the end of the input file. We need MAX_MATCH bytes + * for the next match, plus MIN_MATCH bytes to insert the + * string following the next match. + */ + while (lookahead < MIN_LOOKAHEAD && !eofile) + fill_window(); + } + if (match_available) + ct_tally(0, window[strstart - 1]); + + return FLUSH_BLOCK(1); /* eof */ +} + +/* gzip (GNU zip) -- compress files with zip algorithm and 'compress' interface + * Copyright (C) 1992-1993 Jean-loup Gailly + * The unzip code was written and put in the public domain by Mark Adler. + * Portions of the lzw code are derived from the public domain 'compress' + * written by Spencer Thomas, Joe Orost, James Woods, Jim McKie, Steve Davies, + * Ken Turkowski, Dave Mack and Peter Jannesen. + * + * See the license_msg below and the file COPYING for the software license. + * See the file algorithm.doc for the compression algorithms and file formats. + */ + +/* Compress files with zip algorithm and 'compress' interface. + * See usage() and help() functions below for all options. + * Outputs: + * file.gz: compressed file with same mode, owner, and utimes + * or stdout with -c option or if stdin used as input. + * If the output file name had to be truncated, the original name is kept + * in the compressed file. + */ + + /* configuration */ + +typedef struct dirent dir_type; + +/* ======================================================================== */ +int gzip_main(int argc, char **argv) +{ + enum { + OPT_tostdout = 0x1, + OPT_force = 0x2, + }; + + unsigned opt; + int result; + int inFileNum; + int outFileNum; + struct stat statBuf; + char *delFileName; + + opt = getopt32(argc, argv, "cf123456789qv" USE_GUNZIP("d")); + //if (opt & 0x1) // -c + //if (opt & 0x2) // -f + /* Ignore 1-9 (compression level) options */ + //if (opt & 0x4) // -1 + //if (opt & 0x8) // -2 + //if (opt & 0x10) // -3 + //if (opt & 0x20) // -4 + //if (opt & 0x40) // -5 + //if (opt & 0x80) // -6 + //if (opt & 0x100) // -7 + //if (opt & 0x200) // -8 + //if (opt & 0x400) // -9 + //if (opt & 0x800) // -q + //if (opt & 0x1000) // -v + if (ENABLE_GUNZIP && (opt & 0x2000)) { // -d + /* FIXME: getopt32 should not depend on optind */ + optind = 1; + return gunzip_main(argc, argv); + } + + foreground = signal(SIGINT, SIG_IGN) != SIG_IGN; + if (foreground) { + (void) signal(SIGINT, abort_gzip); + } +#ifdef SIGTERM + if (signal(SIGTERM, SIG_IGN) != SIG_IGN) { + (void) signal(SIGTERM, abort_gzip); + } +#endif +#ifdef SIGHUP + if (signal(SIGHUP, SIG_IGN) != SIG_IGN) { + (void) signal(SIGHUP, abort_gzip); + } +#endif + + strncpy(z_suffix, Z_SUFFIX, sizeof(z_suffix) - 1); + + /* Allocate all global buffers (for DYN_ALLOC option) */ + ALLOC(uch, inbuf, INBUFSIZ + INBUF_EXTRA); + ALLOC(uch, outbuf, OUTBUFSIZ + OUTBUF_EXTRA); + ALLOC(ush, d_buf, DIST_BUFSIZE); + ALLOC(uch, window, 2L * WSIZE); + ALLOC(ush, tab_prefix, 1L << BITS); + + /* Initialise the CRC32 table */ + crc_32_tab = crc32_filltable(0); + + clear_bufs(); + + if (optind == argc) { + time_stamp = 0; + zip(STDIN_FILENO, STDOUT_FILENO); + } else { + int i; + + for (i = optind; i < argc; i++) { + char *path = NULL; + + clear_bufs(); + if (strcmp(argv[i], "-") == 0) { + time_stamp = 0; + inFileNum = STDIN_FILENO; + outFileNum = STDOUT_FILENO; + } else { + inFileNum = xopen(argv[i], O_RDONLY); + if (fstat(inFileNum, &statBuf) < 0) + bb_perror_msg_and_die("%s", argv[i]); + time_stamp = statBuf.st_ctime; + + if (!(opt & OPT_tostdout)) { + path = xasprintf("%s.gz", argv[i]); + + /* Open output file */ +#if (__GLIBC__ >= 2) && (__GLIBC_MINOR__ >= 1) && defined O_NOFOLLOW + outFileNum = + open(path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW); +#else + outFileNum = open(path, O_RDWR | O_CREAT | O_EXCL); +#endif + if (outFileNum < 0) { + bb_perror_msg("%s", path); + free(path); + continue; + } + + /* Set permissions on the file */ + fchmod(outFileNum, statBuf.st_mode); + } else + outFileNum = STDOUT_FILENO; + } + + if (path == NULL && isatty(outFileNum) && !(opt & OPT_force)) { + bb_error_msg + ("compressed data not written to a terminal. Use -f to force compression."); + free(path); + continue; + } + + result = zip(inFileNum, outFileNum); + + if (path != NULL) { + close(inFileNum); + close(outFileNum); + + /* Delete the original file */ + if (result == OK) + delFileName = argv[i]; + else + delFileName = path; + + if (unlink(delFileName) < 0) + bb_perror_msg("%s", delFileName); + } + + free(path); + } + } + + return exit_code; +} + +/* trees.c -- output deflated data using Huffman coding + * Copyright (C) 1992-1993 Jean-loup Gailly + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License, see the file COPYING. + */ + +/* + * PURPOSE + * + * Encode various sets of source values using variable-length + * binary code trees. + * + * DISCUSSION + * + * The PKZIP "deflation" process uses several Huffman trees. The more + * common source values are represented by shorter bit sequences. + * + * Each code tree is stored in the ZIP file in a compressed form + * which is itself a Huffman encoding of the lengths of + * all the code strings (in ascending order by source values). + * The actual code strings are reconstructed from the lengths in + * the UNZIP process, as described in the "application note" + * (APPNOTE.TXT) distributed as part of PKWARE's PKZIP program. + * + * REFERENCES + * + * Lynch, Thomas J. + * Data Compression: Techniques and Applications, pp. 53-55. + * Lifetime Learning Publications, 1985. ISBN 0-534-03418-7. + * + * Storer, James A. + * Data Compression: Methods and Theory, pp. 49-50. + * Computer Science Press, 1988. ISBN 0-7167-8156-5. + * + * Sedgewick, R. + * Algorithms, p290. + * Addison-Wesley, 1983. ISBN 0-201-06672-6. + * + * INTERFACE + * + * void ct_init (ush *attr, int *methodp) + * Allocate the match buffer, initialize the various tables and save + * the location of the internal file attribute (ascii/binary) and + * method (DEFLATE/STORE) + * + * void ct_tally (int dist, int lc); + * Save the match info and tally the frequency counts. + * + * long flush_block (char *buf, ulg stored_len, int eof) + * Determine the best encoding for the current block: dynamic trees, + * static trees or store, and output the encoded block to the zip + * file. Returns the total compressed length for the file so far. + * + */ + +/* =========================================================================== + * Constants + */ + +#define MAX_BITS 15 +/* All codes must not exceed MAX_BITS bits */ + +#define MAX_BL_BITS 7 +/* Bit length codes must not exceed MAX_BL_BITS bits */ + +#define LENGTH_CODES 29 +/* number of length codes, not counting the special END_BLOCK code */ + +#define LITERALS 256 +/* number of literal bytes 0..255 */ + +#define END_BLOCK 256 +/* end of block literal code */ + +#define L_CODES (LITERALS+1+LENGTH_CODES) +/* number of Literal or Length codes, including the END_BLOCK code */ + +#define D_CODES 30 +/* number of distance codes */ + +#define BL_CODES 19 +/* number of codes used to transfer the bit lengths */ + +typedef uch extra_bits_t; + +/* extra bits for each length code */ +static const extra_bits_t extra_lbits[LENGTH_CODES] + = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, + 4, 4, 5, 5, 5, 5, 0 +}; + +/* extra bits for each distance code */ +static const extra_bits_t extra_dbits[D_CODES] + = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, + 10, 10, 11, 11, 12, 12, 13, 13 +}; + +/* extra bits for each bit length code */ +static const extra_bits_t extra_blbits[BL_CODES] += { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7 }; + +#define STORED_BLOCK 0 +#define STATIC_TREES 1 +#define DYN_TREES 2 +/* The three kinds of block type */ + +#ifndef LIT_BUFSIZE +# ifdef SMALL_MEM +# define LIT_BUFSIZE 0x2000 +# else +# ifdef MEDIUM_MEM +# define LIT_BUFSIZE 0x4000 +# else +# define LIT_BUFSIZE 0x8000 +# endif +# endif +#endif +#ifndef DIST_BUFSIZE +# define DIST_BUFSIZE LIT_BUFSIZE +#endif +/* Sizes of match buffers for literals/lengths and distances. There are + * 4 reasons for limiting LIT_BUFSIZE to 64K: + * - frequencies can be kept in 16 bit counters + * - if compression is not successful for the first block, all input data is + * still in the window so we can still emit a stored block even when input + * comes from standard input. (This can also be done for all blocks if + * LIT_BUFSIZE is not greater than 32K.) + * - if compression is not successful for a file smaller than 64K, we can + * even emit a stored file instead of a stored block (saving 5 bytes). + * - creating new Huffman trees less frequently may not provide fast + * adaptation to changes in the input data statistics. (Take for + * example a binary file with poorly compressible code followed by + * a highly compressible string table.) Smaller buffer sizes give + * fast adaptation but have of course the overhead of transmitting trees + * more frequently. + * - I can't count above 4 + * The current code is general and allows DIST_BUFSIZE < LIT_BUFSIZE (to save + * memory at the expense of compression). Some optimizations would be possible + * if we rely on DIST_BUFSIZE == LIT_BUFSIZE. + */ +#if LIT_BUFSIZE > INBUFSIZ +#error cannot overlay l_buf and inbuf +#endif +#define REP_3_6 16 +/* repeat previous bit length 3-6 times (2 bits of repeat count) */ +#define REPZ_3_10 17 +/* repeat a zero length 3-10 times (3 bits of repeat count) */ +#define REPZ_11_138 18 +/* repeat a zero length 11-138 times (7 bits of repeat count) */ + +/* =========================================================================== + * Local data + */ + +/* Data structure describing a single value and its code string. */ +typedef struct ct_data { + union { + ush freq; /* frequency count */ + ush code; /* bit string */ + } fc; + union { + ush dad; /* father node in Huffman tree */ + ush len; /* length of bit string */ + } dl; +} ct_data; + +#define Freq fc.freq +#define Code fc.code +#define Dad dl.dad +#define Len dl.len + +#define HEAP_SIZE (2*L_CODES+1) +/* maximum heap size */ + +static ct_data dyn_ltree[HEAP_SIZE]; /* literal and length tree */ +static ct_data dyn_dtree[2 * D_CODES + 1]; /* distance tree */ + +static ct_data static_ltree[L_CODES + 2]; + +/* The static literal tree. Since the bit lengths are imposed, there is no + * need for the L_CODES extra codes used during heap construction. However + * The codes 286 and 287 are needed to build a canonical tree (see ct_init + * below). + */ + +static ct_data static_dtree[D_CODES]; + +/* The static distance tree. (Actually a trivial tree since all codes use + * 5 bits.) + */ + +static ct_data bl_tree[2 * BL_CODES + 1]; + +/* Huffman tree for the bit lengths */ + +typedef struct tree_desc { + ct_data *dyn_tree; /* the dynamic tree */ + ct_data *static_tree; /* corresponding static tree or NULL */ + const extra_bits_t *extra_bits; /* extra bits for each code or NULL */ + int extra_base; /* base index for extra_bits */ + int elems; /* max number of elements in the tree */ + int max_length; /* max bit length for the codes */ + int max_code; /* largest code with non zero frequency */ +} tree_desc; + +static tree_desc l_desc = + { dyn_ltree, static_ltree, extra_lbits, LITERALS + 1, L_CODES, + MAX_BITS, 0 +}; + +static tree_desc d_desc = + { dyn_dtree, static_dtree, extra_dbits, 0, D_CODES, MAX_BITS, 0 }; + +static tree_desc bl_desc = + { bl_tree, (ct_data *) 0, extra_blbits, 0, BL_CODES, MAX_BL_BITS, + 0 +}; + + +static ush bl_count[MAX_BITS + 1]; + +/* number of codes at each bit length for an optimal tree */ + +static const uch bl_order[BL_CODES] += { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + +/* The lengths of the bit length codes are sent in order of decreasing + * probability, to avoid transmitting the lengths for unused bit length codes. + */ + +static int heap[2 * L_CODES + 1]; /* heap used to build the Huffman trees */ +static int heap_len; /* number of elements in the heap */ +static int heap_max; /* element of largest frequency */ + +/* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used. + * The same heap array is used to build all trees. + */ + +static uch depth[2 * L_CODES + 1]; + +/* Depth of each subtree used as tie breaker for trees of equal frequency */ + +static uch length_code[MAX_MATCH - MIN_MATCH + 1]; + +/* length code for each normalized match length (0 == MIN_MATCH) */ + +static uch dist_code[512]; + +/* distance codes. The first 256 values correspond to the distances + * 3 .. 258, the last 256 values correspond to the top 8 bits of + * the 15 bit distances. + */ + +static int base_length[LENGTH_CODES]; + +/* First normalized length for each code (0 = MIN_MATCH) */ + +static int base_dist[D_CODES]; + +/* First normalized distance for each code (0 = distance of 1) */ + +#define l_buf inbuf +/* DECLARE(uch, l_buf, LIT_BUFSIZE); buffer for literals or lengths */ + +/* DECLARE(ush, d_buf, DIST_BUFSIZE); buffer for distances */ + +static uch flag_buf[(LIT_BUFSIZE / 8)]; + +/* flag_buf is a bit array distinguishing literals from lengths in + * l_buf, thus indicating the presence or absence of a distance. + */ + +static unsigned last_lit; /* running index in l_buf */ +static unsigned last_dist; /* running index in d_buf */ +static unsigned last_flags; /* running index in flag_buf */ +static uch flags; /* current flags not yet saved in flag_buf */ +static uch flag_bit; /* current bit used in flags */ + +/* bits are filled in flags starting at bit 0 (least significant). + * Note: these flags are overkill in the current code since we don't + * take advantage of DIST_BUFSIZE == LIT_BUFSIZE. + */ + +static ulg opt_len; /* bit length of current block with optimal trees */ +static ulg static_len; /* bit length of current block with static trees */ + +static ulg compressed_len; /* total bit length of compressed file */ + + +static ush *file_type; /* pointer to UNKNOWN, BINARY or ASCII */ +static int *file_method; /* pointer to DEFLATE or STORE */ + +/* =========================================================================== + * Local (static) routines in this file. + */ + +static void init_block(void); +static void pqdownheap(ct_data * tree, int k); +static void gen_bitlen(tree_desc * desc); +static void gen_codes(ct_data * tree, int max_code); +static void build_tree(tree_desc * desc); +static void scan_tree(ct_data * tree, int max_code); +static void send_tree(ct_data * tree, int max_code); +static int build_bl_tree(void); +static void send_all_trees(int lcodes, int dcodes, int blcodes); +static void compress_block(ct_data * ltree, ct_data * dtree); +static void set_file_type(void); + + +#ifndef DEBUG +# define send_code(c, tree) send_bits(tree[c].Code, tree[c].Len) + /* Send a code of the given tree. c and tree must not have side effects */ + +#else /* DEBUG */ +# define send_code(c, tree) \ + { if (verbose>1) bb_error_msg("\ncd %3d ",(c)); \ + send_bits(tree[c].Code, tree[c].Len); } +#endif + +#define d_code(dist) \ + ((dist) < 256 ? dist_code[dist] : dist_code[256+((dist)>>7)]) +/* Mapping from a distance to a distance code. dist is the distance - 1 and + * must not have side effects. dist_code[256] and dist_code[257] are never + * used. + */ + +/* the arguments must not have side effects */ + +/* =========================================================================== + * Allocate the match buffer, initialize the various tables and save the + * location of the internal file attribute (ascii/binary) and method + * (DEFLATE/STORE). + */ +static void ct_init(ush * attr, int *methodp) +{ + int n; /* iterates over tree elements */ + int bits; /* bit counter */ + int length; /* length value */ + int code; /* code value */ + int dist; /* distance index */ + + file_type = attr; + file_method = methodp; + compressed_len = 0L; + + if (static_dtree[0].Len != 0) + return; /* ct_init already called */ + + /* Initialize the mapping length (0..255) -> length code (0..28) */ + length = 0; + for (code = 0; code < LENGTH_CODES - 1; code++) { + base_length[code] = length; + for (n = 0; n < (1 << extra_lbits[code]); n++) { + length_code[length++] = (uch) code; + } + } + Assert(length == 256, "ct_init: length != 256"); + /* Note that the length 255 (match length 258) can be represented + * in two different ways: code 284 + 5 bits or code 285, so we + * overwrite length_code[255] to use the best encoding: + */ + length_code[length - 1] = (uch) code; + + /* Initialize the mapping dist (0..32K) -> dist code (0..29) */ + dist = 0; + for (code = 0; code < 16; code++) { + base_dist[code] = dist; + for (n = 0; n < (1 << extra_dbits[code]); n++) { + dist_code[dist++] = (uch) code; + } + } + Assert(dist == 256, "ct_init: dist != 256"); + dist >>= 7; /* from now on, all distances are divided by 128 */ + for (; code < D_CODES; code++) { + base_dist[code] = dist << 7; + for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) { + dist_code[256 + dist++] = (uch) code; + } + } + Assert(dist == 256, "ct_init: 256+dist != 512"); + + /* Construct the codes of the static literal tree */ + for (bits = 0; bits <= MAX_BITS; bits++) + bl_count[bits] = 0; + n = 0; + while (n <= 143) + static_ltree[n++].Len = 8, bl_count[8]++; + while (n <= 255) + static_ltree[n++].Len = 9, bl_count[9]++; + while (n <= 279) + static_ltree[n++].Len = 7, bl_count[7]++; + while (n <= 287) + static_ltree[n++].Len = 8, bl_count[8]++; + /* Codes 286 and 287 do not exist, but we must include them in the + * tree construction to get a canonical Huffman tree (longest code + * all ones) + */ + gen_codes((ct_data *) static_ltree, L_CODES + 1); + + /* The static distance tree is trivial: */ + for (n = 0; n < D_CODES; n++) { + static_dtree[n].Len = 5; + static_dtree[n].Code = bi_reverse(n, 5); + } + + /* Initialize the first block of the first file: */ + init_block(); +} + +/* =========================================================================== + * Initialize a new block. + */ +static void init_block(void) +{ + int n; /* iterates over tree elements */ + + /* Initialize the trees. */ + for (n = 0; n < L_CODES; n++) + dyn_ltree[n].Freq = 0; + for (n = 0; n < D_CODES; n++) + dyn_dtree[n].Freq = 0; + for (n = 0; n < BL_CODES; n++) + bl_tree[n].Freq = 0; + + dyn_ltree[END_BLOCK].Freq = 1; + opt_len = static_len = 0L; + last_lit = last_dist = last_flags = 0; + flags = 0; + flag_bit = 1; +} + +#define SMALLEST 1 +/* Index within the heap array of least frequent node in the Huffman tree */ + + +/* =========================================================================== + * Remove the smallest element from the heap and recreate the heap with + * one less element. Updates heap and heap_len. + */ +#define pqremove(tree, top) \ +{\ + top = heap[SMALLEST]; \ + heap[SMALLEST] = heap[heap_len--]; \ + pqdownheap(tree, SMALLEST); \ +} + +/* =========================================================================== + * Compares to subtrees, using the tree depth as tie breaker when + * the subtrees have equal frequency. This minimizes the worst case length. + */ +#define smaller(tree, n, m) \ + (tree[n].Freq < tree[m].Freq || \ + (tree[n].Freq == tree[m].Freq && depth[n] <= depth[m])) + +/* =========================================================================== + * Restore the heap property by moving down the tree starting at node k, + * exchanging a node with the smallest of its two sons if necessary, stopping + * when the heap property is re-established (each father smaller than its + * two sons). + */ +static void pqdownheap(ct_data * tree, int k) +{ + int v = heap[k]; + int j = k << 1; /* left son of k */ + + while (j <= heap_len) { + /* Set j to the smallest of the two sons: */ + if (j < heap_len && smaller(tree, heap[j + 1], heap[j])) + j++; + + /* Exit if v is smaller than both sons */ + if (smaller(tree, v, heap[j])) + break; + + /* Exchange v with the smallest son */ + heap[k] = heap[j]; + k = j; + + /* And continue down the tree, setting j to the left son of k */ + j <<= 1; + } + heap[k] = v; +} + +/* =========================================================================== + * Compute the optimal bit lengths for a tree and update the total bit length + * for the current block. + * IN assertion: the fields freq and dad are set, heap[heap_max] and + * above are the tree nodes sorted by increasing frequency. + * OUT assertions: the field len is set to the optimal bit length, the + * array bl_count contains the frequencies for each bit length. + * The length opt_len is updated; static_len is also updated if stree is + * not null. + */ +static void gen_bitlen(tree_desc * desc) +{ + ct_data *tree = desc->dyn_tree; + const extra_bits_t *extra = desc->extra_bits; + int base = desc->extra_base; + int max_code = desc->max_code; + int max_length = desc->max_length; + ct_data *stree = desc->static_tree; + int h; /* heap index */ + int n, m; /* iterate over the tree elements */ + int bits; /* bit length */ + int xbits; /* extra bits */ + ush f; /* frequency */ + int overflow = 0; /* number of elements with bit length too large */ + + for (bits = 0; bits <= MAX_BITS; bits++) + bl_count[bits] = 0; + + /* In a first pass, compute the optimal bit lengths (which may + * overflow in the case of the bit length tree). + */ + tree[heap[heap_max]].Len = 0; /* root of the heap */ + + for (h = heap_max + 1; h < HEAP_SIZE; h++) { + n = heap[h]; + bits = tree[tree[n].Dad].Len + 1; + if (bits > max_length) + bits = max_length, overflow++; + tree[n].Len = (ush) bits; + /* We overwrite tree[n].Dad which is no longer needed */ + + if (n > max_code) + continue; /* not a leaf node */ + + bl_count[bits]++; + xbits = 0; + if (n >= base) + xbits = extra[n - base]; + f = tree[n].Freq; + opt_len += (ulg) f *(bits + xbits); + + if (stree) + static_len += (ulg) f *(stree[n].Len + xbits); + } + if (overflow == 0) + return; + + Trace((stderr, "\nbit length overflow\n")); + /* This happens for example on obj2 and pic of the Calgary corpus */ + + /* Find the first bit length which could increase: */ + do { + bits = max_length - 1; + while (bl_count[bits] == 0) + bits--; + bl_count[bits]--; /* move one leaf down the tree */ + bl_count[bits + 1] += 2; /* move one overflow item as its brother */ + bl_count[max_length]--; + /* The brother of the overflow item also moves one step up, + * but this does not affect bl_count[max_length] + */ + overflow -= 2; + } while (overflow > 0); + + /* Now recompute all bit lengths, scanning in increasing frequency. + * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all + * lengths instead of fixing only the wrong ones. This idea is taken + * from 'ar' written by Haruhiko Okumura.) + */ + for (bits = max_length; bits != 0; bits--) { + n = bl_count[bits]; + while (n != 0) { + m = heap[--h]; + if (m > max_code) + continue; + if (tree[m].Len != (unsigned) bits) { + Trace((stderr, "code %d bits %d->%d\n", m, tree[m].Len, + bits)); + opt_len += + ((long) bits - (long) tree[m].Len) * (long) tree[m].Freq; + tree[m].Len = (ush) bits; + } + n--; + } + } +} + +/* =========================================================================== + * Generate the codes for a given tree and bit counts (which need not be + * optimal). + * IN assertion: the array bl_count contains the bit length statistics for + * the given tree and the field len is set for all tree elements. + * OUT assertion: the field code is set for all tree elements of non + * zero code length. + */ +static void gen_codes(ct_data * tree, int max_code) +{ + ush next_code[MAX_BITS + 1]; /* next code value for each bit length */ + ush code = 0; /* running code value */ + int bits; /* bit index */ + int n; /* code index */ + + /* The distribution counts are first used to generate the code values + * without bit reversal. + */ + for (bits = 1; bits <= MAX_BITS; bits++) { + next_code[bits] = code = (code + bl_count[bits - 1]) << 1; + } + /* Check that the bit counts in bl_count are consistent. The last code + * must be all ones. + */ + Assert(code + bl_count[MAX_BITS] - 1 == (1 << MAX_BITS) - 1, + "inconsistent bit counts"); + Tracev((stderr, "\ngen_codes: max_code %d ", max_code)); + + for (n = 0; n <= max_code; n++) { + int len = tree[n].Len; + + if (len == 0) + continue; + /* Now reverse the bits */ + tree[n].Code = bi_reverse(next_code[len]++, len); + + Tracec(tree != static_ltree, + (stderr, "\nn %3d %c l %2d c %4x (%x) ", n, + (isgraph(n) ? n : ' '), len, tree[n].Code, + next_code[len] - 1)); + } +} + +/* =========================================================================== + * Construct one Huffman tree and assigns the code bit strings and lengths. + * Update the total bit length for the current block. + * IN assertion: the field freq is set for all tree elements. + * OUT assertions: the fields len and code are set to the optimal bit length + * and corresponding code. The length opt_len is updated; static_len is + * also updated if stree is not null. The field max_code is set. + */ +static void build_tree(tree_desc * desc) +{ + ct_data *tree = desc->dyn_tree; + ct_data *stree = desc->static_tree; + int elems = desc->elems; + int n, m; /* iterate over heap elements */ + int max_code = -1; /* largest code with non zero frequency */ + int node = elems; /* next internal node of the tree */ + + /* Construct the initial heap, with least frequent element in + * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + * heap[0] is not used. + */ + heap_len = 0, heap_max = HEAP_SIZE; + + for (n = 0; n < elems; n++) { + if (tree[n].Freq != 0) { + heap[++heap_len] = max_code = n; + depth[n] = 0; + } else { + tree[n].Len = 0; + } + } + + /* The pkzip format requires that at least one distance code exists, + * and that at least one bit should be sent even if there is only one + * possible code. So to avoid special checks later on we force at least + * two codes of non zero frequency. + */ + while (heap_len < 2) { + int new = heap[++heap_len] = (max_code < 2 ? ++max_code : 0); + + tree[new].Freq = 1; + depth[new] = 0; + opt_len--; + if (stree) + static_len -= stree[new].Len; + /* new is 0 or 1 so it does not have extra bits */ + } + desc->max_code = max_code; + + /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, + * establish sub-heaps of increasing lengths: + */ + for (n = heap_len / 2; n >= 1; n--) + pqdownheap(tree, n); + + /* Construct the Huffman tree by repeatedly combining the least two + * frequent nodes. + */ + do { + pqremove(tree, n); /* n = node of least frequency */ + m = heap[SMALLEST]; /* m = node of next least frequency */ + + heap[--heap_max] = n; /* keep the nodes sorted by frequency */ + heap[--heap_max] = m; + + /* Create a new node father of n and m */ + tree[node].Freq = tree[n].Freq + tree[m].Freq; + depth[node] = (uch) (MAX(depth[n], depth[m]) + 1); + tree[n].Dad = tree[m].Dad = (ush) node; +#ifdef DUMP_BL_TREE + if (tree == bl_tree) { + bb_error_msg("\nnode %d(%d), sons %d(%d) %d(%d)", + node, tree[node].Freq, n, tree[n].Freq, m, tree[m].Freq); + } +#endif + /* and insert the new node in the heap */ + heap[SMALLEST] = node++; + pqdownheap(tree, SMALLEST); + + } while (heap_len >= 2); + + heap[--heap_max] = heap[SMALLEST]; + + /* At this point, the fields freq and dad are set. We can now + * generate the bit lengths. + */ + gen_bitlen((tree_desc *) desc); + + /* The field len is now set, we can generate the bit codes */ + gen_codes((ct_data *) tree, max_code); +} + +/* =========================================================================== + * Scan a literal or distance tree to determine the frequencies of the codes + * in the bit length tree. Updates opt_len to take into account the repeat + * counts. (The contribution of the bit length codes will be added later + * during the construction of bl_tree.) + */ +static void scan_tree(ct_data * tree, int max_code) +{ + int n; /* iterates over all tree elements */ + int prevlen = -1; /* last emitted length */ + int curlen; /* length of current code */ + int nextlen = tree[0].Len; /* length of next code */ + int count = 0; /* repeat count of the current code */ + int max_count = 7; /* max repeat count */ + int min_count = 4; /* min repeat count */ + + if (nextlen == 0) + max_count = 138, min_count = 3; + tree[max_code + 1].Len = (ush) 0xffff; /* guard */ + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; + nextlen = tree[n + 1].Len; + if (++count < max_count && curlen == nextlen) { + continue; + } else if (count < min_count) { + bl_tree[curlen].Freq += count; + } else if (curlen != 0) { + if (curlen != prevlen) + bl_tree[curlen].Freq++; + bl_tree[REP_3_6].Freq++; + } else if (count <= 10) { + bl_tree[REPZ_3_10].Freq++; + } else { + bl_tree[REPZ_11_138].Freq++; + } + count = 0; + prevlen = curlen; + if (nextlen == 0) { + max_count = 138, min_count = 3; + } else if (curlen == nextlen) { + max_count = 6, min_count = 3; + } else { + max_count = 7, min_count = 4; + } + } +} + +/* =========================================================================== + * Send a literal or distance tree in compressed form, using the codes in + * bl_tree. + */ +static void send_tree(ct_data * tree, int max_code) +{ + int n; /* iterates over all tree elements */ + int prevlen = -1; /* last emitted length */ + int curlen; /* length of current code */ + int nextlen = tree[0].Len; /* length of next code */ + int count = 0; /* repeat count of the current code */ + int max_count = 7; /* max repeat count */ + int min_count = 4; /* min repeat count */ + +/* tree[max_code+1].Len = -1; *//* guard already set */ + if (nextlen == 0) + max_count = 138, min_count = 3; + + for (n = 0; n <= max_code; n++) { + curlen = nextlen; + nextlen = tree[n + 1].Len; + if (++count < max_count && curlen == nextlen) { + continue; + } else if (count < min_count) { + do { + send_code(curlen, bl_tree); + } while (--count != 0); + + } else if (curlen != 0) { + if (curlen != prevlen) { + send_code(curlen, bl_tree); + count--; + } + Assert(count >= 3 && count <= 6, " 3_6?"); + send_code(REP_3_6, bl_tree); + send_bits(count - 3, 2); + + } else if (count <= 10) { + send_code(REPZ_3_10, bl_tree); + send_bits(count - 3, 3); + + } else { + send_code(REPZ_11_138, bl_tree); + send_bits(count - 11, 7); + } + count = 0; + prevlen = curlen; + if (nextlen == 0) { + max_count = 138, min_count = 3; + } else if (curlen == nextlen) { + max_count = 6, min_count = 3; + } else { + max_count = 7, min_count = 4; + } + } +} + +/* =========================================================================== + * Construct the Huffman tree for the bit lengths and return the index in + * bl_order of the last bit length code to send. + */ +static int build_bl_tree(void) +{ + int max_blindex; /* index of last bit length code of non zero freq */ + + /* Determine the bit length frequencies for literal and distance trees */ + scan_tree((ct_data *) dyn_ltree, l_desc.max_code); + scan_tree((ct_data *) dyn_dtree, d_desc.max_code); + + /* Build the bit length tree: */ + build_tree((tree_desc *) (&bl_desc)); + /* opt_len now includes the length of the tree representations, except + * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. + */ + + /* Determine the number of bit length codes to send. The pkzip format + * requires that at least 4 bit length codes be sent. (appnote.txt says + * 3 but the actual value used is 4.) + */ + for (max_blindex = BL_CODES - 1; max_blindex >= 3; max_blindex--) { + if (bl_tree[bl_order[max_blindex]].Len != 0) + break; + } + /* Update opt_len to include the bit length tree and counts */ + opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4; + Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", opt_len, static_len)); + + return max_blindex; +} + +/* =========================================================================== + * Send the header for a block using dynamic Huffman trees: the counts, the + * lengths of the bit length codes, the literal tree and the distance tree. + * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. + */ +static void send_all_trees(int lcodes, int dcodes, int blcodes) +{ + int rank; /* index in bl_order */ + + Assert(lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); + Assert(lcodes <= L_CODES && dcodes <= D_CODES + && blcodes <= BL_CODES, "too many codes"); + Tracev((stderr, "\nbl counts: ")); + send_bits(lcodes - 257, 5); /* not +255 as stated in appnote.txt */ + send_bits(dcodes - 1, 5); + send_bits(blcodes - 4, 4); /* not -3 as stated in appnote.txt */ + for (rank = 0; rank < blcodes; rank++) { + Tracev((stderr, "\nbl code %2d ", bl_order[rank])); + send_bits(bl_tree[bl_order[rank]].Len, 3); + } + Tracev((stderr, "\nbl tree: sent %ld", bits_sent)); + + send_tree((ct_data *) dyn_ltree, lcodes - 1); /* send the literal tree */ + Tracev((stderr, "\nlit tree: sent %ld", bits_sent)); + + send_tree((ct_data *) dyn_dtree, dcodes - 1); /* send the distance tree */ + Tracev((stderr, "\ndist tree: sent %ld", bits_sent)); +} + +/* =========================================================================== + * Determine the best encoding for the current block: dynamic trees, static + * trees or store, and output the encoded block to the zip file. This function + * returns the total compressed length for the file so far. + */ +static ulg flush_block(char *buf, ulg stored_len, int eof) +{ + ulg opt_lenb, static_lenb; /* opt_len and static_len in bytes */ + int max_blindex; /* index of last bit length code of non zero freq */ + + flag_buf[last_flags] = flags; /* Save the flags for the last 8 items */ + + /* Check if the file is ascii or binary */ + if (*file_type == (ush) UNKNOWN) + set_file_type(); + + /* Construct the literal and distance trees */ + build_tree((tree_desc *) (&l_desc)); + Tracev((stderr, "\nlit data: dyn %ld, stat %ld", opt_len, static_len)); + + build_tree((tree_desc *) (&d_desc)); + Tracev((stderr, "\ndist data: dyn %ld, stat %ld", opt_len, static_len)); + /* At this point, opt_len and static_len are the total bit lengths of + * the compressed block data, excluding the tree representations. + */ + + /* Build the bit length tree for the above two trees, and get the index + * in bl_order of the last bit length code to send. + */ + max_blindex = build_bl_tree(); + + /* Determine the best encoding. Compute first the block length in bytes */ + opt_lenb = (opt_len + 3 + 7) >> 3; + static_lenb = (static_len + 3 + 7) >> 3; + + Trace((stderr, + "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u dist %u ", + opt_lenb, opt_len, static_lenb, static_len, stored_len, + last_lit, last_dist)); + + if (static_lenb <= opt_lenb) + opt_lenb = static_lenb; + + /* If compression failed and this is the first and last block, + * and if the zip file can be seeked (to rewrite the local header), + * the whole file is transformed into a stored file: + */ + if (stored_len <= opt_lenb && eof && compressed_len == 0L && seekable()) { + /* Since LIT_BUFSIZE <= 2*WSIZE, the input data must be there: */ + if (buf == (char *) 0) + bb_error_msg("block vanished"); + + copy_block(buf, (unsigned) stored_len, 0); /* without header */ + compressed_len = stored_len << 3; + *file_method = STORED; + + } else if (stored_len + 4 <= opt_lenb && buf != (char *) 0) { + /* 4: two words for the lengths */ + /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. + * Otherwise we can't have processed more than WSIZE input bytes since + * the last block flush, because compression would have been + * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to + * transform a block into a stored block. + */ + send_bits((STORED_BLOCK << 1) + eof, 3); /* send block type */ + compressed_len = (compressed_len + 3 + 7) & ~7L; + compressed_len += (stored_len + 4) << 3; + + copy_block(buf, (unsigned) stored_len, 1); /* with header */ + + } else if (static_lenb == opt_lenb) { + send_bits((STATIC_TREES << 1) + eof, 3); + compress_block((ct_data *) static_ltree, (ct_data *) static_dtree); + compressed_len += 3 + static_len; + } else { + send_bits((DYN_TREES << 1) + eof, 3); + send_all_trees(l_desc.max_code + 1, d_desc.max_code + 1, + max_blindex + 1); + compress_block((ct_data *) dyn_ltree, (ct_data *) dyn_dtree); + compressed_len += 3 + opt_len; + } + Assert(compressed_len == bits_sent, "bad compressed size"); + init_block(); + + if (eof) { + bi_windup(); + compressed_len += 7; /* align on byte boundary */ + } + Tracev((stderr, "\ncomprlen %lu(%lu) ", compressed_len >> 3, + compressed_len - 7 * eof)); + + return compressed_len >> 3; +} + +/* =========================================================================== + * Save the match info and tally the frequency counts. Return true if + * the current block must be flushed. + */ +static int ct_tally(int dist, int lc) +{ + l_buf[last_lit++] = (uch) lc; + if (dist == 0) { + /* lc is the unmatched char */ + dyn_ltree[lc].Freq++; + } else { + /* Here, lc is the match length - MIN_MATCH */ + dist--; /* dist = match distance - 1 */ + Assert((ush) dist < (ush) MAX_DIST && + (ush) lc <= (ush) (MAX_MATCH - MIN_MATCH) && + (ush) d_code(dist) < (ush) D_CODES, "ct_tally: bad match"); + + dyn_ltree[length_code[lc] + LITERALS + 1].Freq++; + dyn_dtree[d_code(dist)].Freq++; + + d_buf[last_dist++] = (ush) dist; + flags |= flag_bit; + } + flag_bit <<= 1; + + /* Output the flags if they fill a byte: */ + if ((last_lit & 7) == 0) { + flag_buf[last_flags++] = flags; + flags = 0, flag_bit = 1; + } + /* Try to guess if it is profitable to stop the current block here */ + if ((last_lit & 0xfff) == 0) { + /* Compute an upper bound for the compressed length */ + ulg out_length = (ulg) last_lit * 8L; + ulg in_length = (ulg) strstart - block_start; + int dcode; + + for (dcode = 0; dcode < D_CODES; dcode++) { + out_length += + (ulg) dyn_dtree[dcode].Freq * (5L + extra_dbits[dcode]); + } + out_length >>= 3; + Trace((stderr, + "\nlast_lit %u, last_dist %u, in %ld, out ~%ld(%ld%%) ", + last_lit, last_dist, in_length, out_length, + 100L - out_length * 100L / in_length)); + if (last_dist < last_lit / 2 && out_length < in_length / 2) + return 1; + } + return (last_lit == LIT_BUFSIZE - 1 || last_dist == DIST_BUFSIZE); + /* We avoid equality with LIT_BUFSIZE because of wraparound at 64K + * on 16 bit machines and because stored blocks are restricted to + * 64K-1 bytes. + */ +} + +/* =========================================================================== + * Send the block data compressed using the given Huffman trees + */ +static void compress_block(ct_data * ltree, ct_data * dtree) +{ + unsigned dist; /* distance of matched string */ + int lc; /* match length or unmatched char (if dist == 0) */ + unsigned lx = 0; /* running index in l_buf */ + unsigned dx = 0; /* running index in d_buf */ + unsigned fx = 0; /* running index in flag_buf */ + uch flag = 0; /* current flags */ + unsigned code; /* the code to send */ + int extra; /* number of extra bits to send */ + + if (last_lit != 0) + do { + if ((lx & 7) == 0) + flag = flag_buf[fx++]; + lc = l_buf[lx++]; + if ((flag & 1) == 0) { + send_code(lc, ltree); /* send a literal byte */ + Tracecv(isgraph(lc), (stderr, " '%c' ", lc)); + } else { + /* Here, lc is the match length - MIN_MATCH */ + code = length_code[lc]; + send_code(code + LITERALS + 1, ltree); /* send the length code */ + extra = extra_lbits[code]; + if (extra != 0) { + lc -= base_length[code]; + send_bits(lc, extra); /* send the extra length bits */ + } + dist = d_buf[dx++]; + /* Here, dist is the match distance - 1 */ + code = d_code(dist); + Assert(code < D_CODES, "bad d_code"); + + send_code(code, dtree); /* send the distance code */ + extra = extra_dbits[code]; + if (extra != 0) { + dist -= base_dist[code]; + send_bits(dist, extra); /* send the extra distance bits */ + } + } /* literal or match pair ? */ + flag >>= 1; + } while (lx < last_lit); + + send_code(END_BLOCK, ltree); +} + +/* =========================================================================== + * Set the file type to ASCII or BINARY, using a crude approximation: + * binary if more than 20% of the bytes are <= 6 or >= 128, ascii otherwise. + * IN assertion: the fields freq of dyn_ltree are set and the total of all + * frequencies does not exceed 64K (to fit in an int on 16 bit machines). + */ +static void set_file_type(void) +{ + int n = 0; + unsigned ascii_freq = 0; + unsigned bin_freq = 0; + + while (n < 7) + bin_freq += dyn_ltree[n++].Freq; + while (n < 128) + ascii_freq += dyn_ltree[n++].Freq; + while (n < LITERALS) + bin_freq += dyn_ltree[n++].Freq; + *file_type = bin_freq > (ascii_freq >> 2) ? BINARY : ASCII; + if (*file_type == BINARY && translate_eol) { + bb_error_msg("-l used on binary file"); + } +} + +/* zip.c -- compress files to the gzip or pkzip format + * Copyright (C) 1992-1993 Jean-loup Gailly + * This is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License, see the file COPYING. + */ + + +static uint32_t crc; /* crc on uncompressed file data */ +static long header_bytes; /* number of bytes in gzip header */ + +static void put_long(ulg n) +{ + put_short((n) & 0xffff); + put_short(((ulg) (n)) >> 16); +} + +/* put_header_byte is used for the compressed output + * - for the initial 4 bytes that can't overflow the buffer. + */ +#define put_header_byte(c) {outbuf[outcnt++]=(uch)(c);} + +/* =========================================================================== + * Deflate in to out. + * IN assertions: the input and output buffers are cleared. + * The variables time_stamp and save_orig_name are initialized. + */ +static int zip(int in, int out) +{ + uch my_flags = 0; /* general purpose bit flags */ + ush attr = 0; /* ascii/binary flag */ + ush deflate_flags = 0; /* pkzip -es, -en or -ex equivalent */ + + ifd = in; + ofd = out; + outcnt = 0; + + /* Write the header to the gzip file. See algorithm.doc for the format */ + + method = DEFLATED; + put_header_byte(GZIP_MAGIC[0]); /* magic header */ + put_header_byte(GZIP_MAGIC[1]); + put_header_byte(DEFLATED); /* compression method */ + + put_header_byte(my_flags); /* general flags */ + put_long(time_stamp); + + /* Write deflated file to zip file */ + crc = updcrc(0, 0); + + bi_init(out); + ct_init(&attr, &method); + lm_init(&deflate_flags); + + put_byte((uch) deflate_flags); /* extra flags */ + put_byte(OS_CODE); /* OS identifier */ + + header_bytes = (long) outcnt; + + (void) deflate(); + + /* Write the crc and uncompressed size */ + put_long(crc); + put_long(isize); + header_bytes += 2 * sizeof(long); + + flush_outbuf(); + return OK; +} + + +/* =========================================================================== + * Read a new buffer from the current input file, perform end-of-line + * translation, and update the crc and input file size. + * IN assertion: size >= 2 (for end-of-line translation) + */ +static int file_read(char *buf, unsigned size) +{ + unsigned len; + + Assert(insize == 0, "inbuf not empty"); + + len = read(ifd, buf, size); + if (len == (unsigned) (-1) || len == 0) + return (int) len; + + crc = updcrc((uch *) buf, len); + isize += (ulg) len; + return (int) len; +} + +/* =========================================================================== + * Write the output buffer outbuf[0..outcnt-1] and update bytes_out. + * (used for the compressed data only) + */ +static void flush_outbuf(void) +{ + if (outcnt == 0) + return; + + write_buf(ofd, (char *) outbuf, outcnt); + outcnt = 0; +} diff --git a/archival/libunarchive/Kbuild b/archival/libunarchive/Kbuild new file mode 100644 index 000000000..4e1454184 --- /dev/null +++ b/archival/libunarchive/Kbuild @@ -0,0 +1,59 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2004 by Erik Andersen +# +# Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + +lib-y:= \ +\ + data_skip.o \ + data_extract_all.o \ + data_extract_to_stdout.o \ + data_extract_to_buffer.o \ +\ + filter_accept_all.o \ + filter_accept_list.o \ + filter_accept_reject_list.o \ +\ + header_skip.o \ + header_list.o \ + header_verbose_list.o \ +\ + archive_xread_all_eof.o \ +\ + seek_by_read.o \ + seek_by_jump.o \ +\ + data_align.o \ + find_list_entry.o \ + open_transformer.o \ + init_handle.o + +GUNZIP_FILES:= check_header_gzip.o decompress_unzip.o +DPKG_FILES:= \ + get_header_ar.o \ + unpack_ar_archive.o \ + get_header_tar.o \ + filter_accept_list_reassign.o + +lib-$(CONFIG_AR) += get_header_ar.o unpack_ar_archive.o +lib-$(CONFIG_BUNZIP2) += decompress_bunzip2.o +lib-$(CONFIG_UNLZMA) += decompress_unlzma.o +lib-$(CONFIG_CPIO) += get_header_cpio.o +lib-$(CONFIG_DPKG) += $(DPKG_FILES) +lib-$(CONFIG_DPKG_DEB) += $(DPKG_FILES) +lib-$(CONFIG_FEATURE_DEB_TAR_GZ) += $(GUNZIP_FILES) get_header_tar_gz.o +lib-$(CONFIG_FEATURE_DEB_TAR_BZ2) += decompress_bunzip2.o get_header_tar_bz2.o +lib-$(CONFIG_FEATURE_DEB_TAR_LZMA) += decompress_unlzma.o get_header_tar_lzma.o +lib-$(CONFIG_GUNZIP) += $(GUNZIP_FILES) +lib-$(CONFIG_FEATURE_GUNZIP_UNCOMPRESS) += decompress_uncompress.o +lib-$(CONFIG_RPM2CPIO) += $(GUNZIP_FILES) get_header_cpio.o +lib-$(CONFIG_RPM) += $(GUNZIP_FILES) get_header_cpio.o +lib-$(CONFIG_TAR) += get_header_tar.o +lib-$(CONFIG_FEATURE_TAR_BZIP2) += decompress_bunzip2.o get_header_tar_bz2.o +lib-$(CONFIG_FEATURE_TAR_LZMA) += decompress_unlzma.o get_header_tar_lzma.o +lib-$(CONFIG_FEATURE_TAR_GZIP) += $(GUNZIP_FILES) get_header_tar_gz.o +lib-$(CONFIG_FEATURE_TAR_COMPRESS) += decompress_uncompress.o +lib-$(CONFIG_UNCOMPRESS) += decompress_uncompress.o +lib-$(CONFIG_UNZIP) += $(GUNZIP_FILES) +lib-$(CONFIG_FEATURE_COMPRESS_USAGE) += decompress_bunzip2.o diff --git a/archival/libunarchive/archive_xread_all_eof.c b/archival/libunarchive/archive_xread_all_eof.c new file mode 100644 index 000000000..007f68c6d --- /dev/null +++ b/archival/libunarchive/archive_xread_all_eof.c @@ -0,0 +1,20 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "unarchive.h" +#include "libbb.h" + +ssize_t archive_xread_all_eof(archive_handle_t *archive_handle, + unsigned char *buf, size_t count) +{ + ssize_t size; + + size = full_read(archive_handle->src_fd, buf, count); + if (size != 0 && size != count) { + bb_error_msg_and_die("short read: %u of %u", + (unsigned)size, (unsigned)count); + } + return size; +} diff --git a/archival/libunarchive/check_header_gzip.c b/archival/libunarchive/check_header_gzip.c new file mode 100644 index 000000000..3e42035b9 --- /dev/null +++ b/archival/libunarchive/check_header_gzip.c @@ -0,0 +1,62 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ +#include +#include +#include "libbb.h" +#include "unarchive.h" /* for external decl of check_header_gzip */ + +void check_header_gzip(int src_fd) +{ + union { + unsigned char raw[8]; + struct { + unsigned char method; + unsigned char flags; + unsigned int mtime; + unsigned char xtra_flags; + unsigned char os_flags; + } formatted; + } header; + + xread(src_fd, header.raw, 8); + + /* Check the compression method */ + if (header.formatted.method != 8) { + bb_error_msg_and_die("unknown compression method %d", + header.formatted.method); + } + + if (header.formatted.flags & 0x04) { + /* bit 2 set: extra field present */ + unsigned char extra_short; + + extra_short = xread_char(src_fd) + (xread_char(src_fd) << 8); + while (extra_short > 0) { + /* Ignore extra field */ + xread_char(src_fd); + extra_short--; + } + } + + /* Discard original name if any */ + if (header.formatted.flags & 0x08) { + /* bit 3 set: original file name present */ + while(xread_char(src_fd) != 0); + } + + /* Discard file comment if any */ + if (header.formatted.flags & 0x10) { + /* bit 4 set: file comment present */ + while(xread_char(src_fd) != 0); + } + + /* Read the header checksum */ + if (header.formatted.flags & 0x02) { + xread_char(src_fd); + xread_char(src_fd); + } + + return; +} diff --git a/archival/libunarchive/data_align.c b/archival/libunarchive/data_align.c new file mode 100644 index 000000000..81594d87c --- /dev/null +++ b/archival/libunarchive/data_align.c @@ -0,0 +1,22 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include + +#include +#include + +#include "libbb.h" +#include "unarchive.h" + +void data_align(archive_handle_t *archive_handle, const unsigned short boundary) +{ + const unsigned short skip_amount = (boundary - (archive_handle->offset % boundary)) % boundary; + + archive_handle->seek(archive_handle, skip_amount); + archive_handle->offset += skip_amount; + + return; +} diff --git a/archival/libunarchive/data_extract_all.c b/archival/libunarchive/data_extract_all.c new file mode 100644 index 000000000..67f8f3534 --- /dev/null +++ b/archival/libunarchive/data_extract_all.c @@ -0,0 +1,119 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include "unarchive.h" + +void data_extract_all(archive_handle_t *archive_handle) +{ + file_header_t *file_header = archive_handle->file_header; + int dst_fd; + int res; + + if (archive_handle->flags & ARCHIVE_CREATE_LEADING_DIRS) { + char *name = xstrdup(file_header->name); + bb_make_directory(dirname(name), -1, FILEUTILS_RECUR); + free(name); + } + + /* Check if the file already exists */ + if (archive_handle->flags & ARCHIVE_EXTRACT_UNCONDITIONAL) { + /* Remove the existing entry if it exists */ + if (((file_header->mode & S_IFMT) != S_IFDIR) + && (unlink(file_header->name) == -1) + && (errno != ENOENT) + ) { + bb_perror_msg_and_die("cannot remove old file"); + } + } + else if (archive_handle->flags & ARCHIVE_EXTRACT_NEWER) { + /* Remove the existing entry if its older than the extracted entry */ + struct stat statbuf; + if (lstat(file_header->name, &statbuf) == -1) { + if (errno != ENOENT) { + bb_perror_msg_and_die("cannot stat old file"); + } + } + else if (statbuf.st_mtime <= file_header->mtime) { + if (!(archive_handle->flags & ARCHIVE_EXTRACT_QUIET)) { + bb_error_msg("%s not created: newer or " + "same age file exists", file_header->name); + } + data_skip(archive_handle); + return; + } + else if ((unlink(file_header->name) == -1) && (errno != EISDIR)) { + bb_perror_msg_and_die("cannot remove old file %s", + file_header->name); + } + } + + /* Handle hard links separately + * We identified hard links as regular files of size 0 with a symlink */ + if (S_ISREG(file_header->mode) && (file_header->link_name) + && (file_header->size == 0) + ) { + /* hard link */ + res = link(file_header->link_name, file_header->name); + if ((res == -1) && !(archive_handle->flags & ARCHIVE_EXTRACT_QUIET)) { + bb_perror_msg("cannot create hard link"); + } + } else { + /* Create the filesystem entry */ + switch (file_header->mode & S_IFMT) { + case S_IFREG: { + /* Regular file */ + dst_fd = xopen3(file_header->name, O_WRONLY | O_CREAT | O_EXCL, + file_header->mode); + bb_copyfd_size(archive_handle->src_fd, dst_fd, file_header->size); + close(dst_fd); + break; + } + case S_IFDIR: + res = mkdir(file_header->name, file_header->mode); + if ((errno != EISDIR) && (res == -1) + && !(archive_handle->flags & ARCHIVE_EXTRACT_QUIET) + ) { + bb_perror_msg("extract_archive: %s", file_header->name); + } + break; + case S_IFLNK: + /* Symlink */ + res = symlink(file_header->link_name, file_header->name); + if ((res == -1) + && !(archive_handle->flags & ARCHIVE_EXTRACT_QUIET) + ) { + bb_perror_msg("cannot create symlink " + "from %s to '%s'", + file_header->name, + file_header->link_name); + } + break; + case S_IFSOCK: + case S_IFBLK: + case S_IFCHR: + case S_IFIFO: + res = mknod(file_header->name, file_header->mode, file_header->device); + if ((res == -1) + && !(archive_handle->flags & ARCHIVE_EXTRACT_QUIET) + ) { + bb_perror_msg("cannot create node %s", file_header->name); + } + break; + default: + bb_error_msg_and_die("unrecognized file type"); + } + } + + if (!(archive_handle->flags & ARCHIVE_NOPRESERVE_OWN)) { + lchown(file_header->name, file_header->uid, file_header->gid); + } + + if (archive_handle->flags & ARCHIVE_PRESERVE_DATE) { + struct utimbuf t; + t.actime = t.modtime = file_header->mtime; + utime(file_header->name, &t); + } +} diff --git a/archival/libunarchive/data_extract_to_buffer.c b/archival/libunarchive/data_extract_to_buffer.c new file mode 100644 index 000000000..95cb8f576 --- /dev/null +++ b/archival/libunarchive/data_extract_to_buffer.c @@ -0,0 +1,18 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright 2002 Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include "unarchive.h" + +void data_extract_to_buffer(archive_handle_t *archive_handle) +{ + const unsigned int size = archive_handle->file_header->size; + + archive_handle->buffer = xzalloc(size + 1); + + xread(archive_handle->src_fd, archive_handle->buffer, size); +} diff --git a/archival/libunarchive/data_extract_to_stdout.c b/archival/libunarchive/data_extract_to_stdout.c new file mode 100644 index 000000000..788246ce7 --- /dev/null +++ b/archival/libunarchive/data_extract_to_stdout.c @@ -0,0 +1,12 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "unarchive.h" +#include + +void data_extract_to_stdout(archive_handle_t *archive_handle) +{ + bb_copyfd_size(archive_handle->src_fd, STDOUT_FILENO, archive_handle->file_header->size); +} diff --git a/archival/libunarchive/data_skip.c b/archival/libunarchive/data_skip.c new file mode 100644 index 000000000..dc3505a56 --- /dev/null +++ b/archival/libunarchive/data_skip.c @@ -0,0 +1,16 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include "unarchive.h" +#include "libbb.h" + +void data_skip(archive_handle_t *archive_handle) +{ + archive_handle->seek(archive_handle, archive_handle->file_header->size); +} diff --git a/archival/libunarchive/decompress_bunzip2.c b/archival/libunarchive/decompress_bunzip2.c new file mode 100644 index 000000000..d0a4ecb5e --- /dev/null +++ b/archival/libunarchive/decompress_bunzip2.c @@ -0,0 +1,731 @@ +/* vi: set sw=4 ts=4: */ +/* Small bzip2 deflate implementation, by Rob Landley (rob@landley.net). + + Based on bzip2 decompression code by Julian R Seward (jseward@acm.org), + which also acknowledges contributions by Mike Burrows, David Wheeler, + Peter Fenwick, Alistair Moffat, Radford Neal, Ian H. Witten, + Robert Sedgewick, and Jon L. Bentley. + + Licensed under GPLv2 or later, see file LICENSE in this tarball for details. +*/ + +/* + Size and speed optimizations by Manuel Novoa III (mjn3@codepoet.org). + + More efficient reading of Huffman codes, a streamlined read_bunzip() + function, and various other tweaks. In (limited) tests, approximately + 20% faster than bzcat on x86 and about 10% faster on arm. + + Note that about 2/3 of the time is spent in read_unzip() reversing + the Burrows-Wheeler transformation. Much of that time is delay + resulting from cache misses. + + I would ask that anyone benefiting from this work, especially those + using it in commercial products, consider making a donation to my local + non-profit hospice organization (www.hospiceacadiana.com) in the name of + the woman I loved, Toni W. Hagan, who passed away Feb. 12, 2003. + + Manuel + */ + +#include "libbb.h" +#include "unarchive.h" + +/* Constants for Huffman coding */ +#define MAX_GROUPS 6 +#define GROUP_SIZE 50 /* 64 would have been more efficient */ +#define MAX_HUFCODE_BITS 20 /* Longest Huffman code allowed */ +#define MAX_SYMBOLS 258 /* 256 literals + RUNA + RUNB */ +#define SYMBOL_RUNA 0 +#define SYMBOL_RUNB 1 + +/* Status return values */ +#define RETVAL_OK 0 +#define RETVAL_LAST_BLOCK (-1) +#define RETVAL_NOT_BZIP_DATA (-2) +#define RETVAL_UNEXPECTED_INPUT_EOF (-3) +#define RETVAL_UNEXPECTED_OUTPUT_EOF (-4) +#define RETVAL_DATA_ERROR (-5) +#define RETVAL_OUT_OF_MEMORY (-6) +#define RETVAL_OBSOLETE_INPUT (-7) + +/* Other housekeeping constants */ +#define IOBUF_SIZE 4096 + +/* This is what we know about each Huffman coding group */ +struct group_data { + /* We have an extra slot at the end of limit[] for a sentinal value. */ + int limit[MAX_HUFCODE_BITS+1],base[MAX_HUFCODE_BITS],permute[MAX_SYMBOLS]; + int minLen, maxLen; +}; + +/* Structure holding all the housekeeping data, including IO buffers and + memory that persists between calls to bunzip */ + +typedef struct { + /* State for interrupting output loop */ + + int writeCopies,writePos,writeRunCountdown,writeCount,writeCurrent; + + /* I/O tracking data (file handles, buffers, positions, etc.) */ + + int in_fd,out_fd,inbufCount,inbufPos /*,outbufPos*/; + unsigned char *inbuf /*,*outbuf*/; + unsigned int inbufBitCount, inbufBits; + + /* The CRC values stored in the block header and calculated from the data */ + + uint32_t headerCRC, totalCRC, writeCRC; + uint32_t *crc32Table; + /* Intermediate buffer and its size (in bytes) */ + + unsigned int *dbuf, dbufSize; + + /* These things are a bit too big to go on the stack */ + + unsigned char selectors[32768]; /* nSelectors=15 bits */ + struct group_data groups[MAX_GROUPS]; /* Huffman coding tables */ + + /* For I/O error handling */ + + jmp_buf jmpbuf; +} bunzip_data; + +/* Return the next nnn bits of input. All reads from the compressed input + are done through this function. All reads are big endian */ + +static unsigned int get_bits(bunzip_data *bd, char bits_wanted) +{ + unsigned int bits=0; + + /* If we need to get more data from the byte buffer, do so. (Loop getting + one byte at a time to enforce endianness and avoid unaligned access.) */ + + while (bd->inbufBitCountinbufPos==bd->inbufCount) { + if((bd->inbufCount = read(bd->in_fd, bd->inbuf, IOBUF_SIZE)) <= 0) + longjmp(bd->jmpbuf,RETVAL_UNEXPECTED_INPUT_EOF); + bd->inbufPos=0; + } + + /* Avoid 32-bit overflow (dump bit buffer to top of output) */ + + if(bd->inbufBitCount>=24) { + bits=bd->inbufBits&((1<inbufBitCount)-1); + bits_wanted-=bd->inbufBitCount; + bits<<=bits_wanted; + bd->inbufBitCount=0; + } + + /* Grab next 8 bits of input from buffer. */ + + bd->inbufBits=(bd->inbufBits<<8)|bd->inbuf[bd->inbufPos++]; + bd->inbufBitCount+=8; + } + + /* Calculate result */ + + bd->inbufBitCount-=bits_wanted; + bits|=(bd->inbufBits>>bd->inbufBitCount)&((1<dbuf; + dbufSize=bd->dbufSize; + selectors=bd->selectors; + + /* Reset longjmp I/O error handling */ + + i=setjmp(bd->jmpbuf); + if(i) return i; + + /* Read in header signature and CRC, then validate signature. + (last block signature means CRC is for whole file, return now) */ + + i = get_bits(bd,24); + j = get_bits(bd,24); + bd->headerCRC=get_bits(bd,32); + if ((i == 0x177245) && (j == 0x385090)) return RETVAL_LAST_BLOCK; + if ((i != 0x314159) || (j != 0x265359)) return RETVAL_NOT_BZIP_DATA; + + /* We can add support for blockRandomised if anybody complains. There was + some code for this in busybox 1.0.0-pre3, but nobody ever noticed that + it didn't actually work. */ + + if(get_bits(bd,1)) return RETVAL_OBSOLETE_INPUT; + if((origPtr=get_bits(bd,24)) > dbufSize) return RETVAL_DATA_ERROR; + + /* mapping table: if some byte values are never used (encoding things + like ascii text), the compression code removes the gaps to have fewer + symbols to deal with, and writes a sparse bitfield indicating which + values were present. We make a translation table to convert the symbols + back to the corresponding bytes. */ + + t=get_bits(bd, 16); + symTotal=0; + for (i=0;i<16;i++) { + if(t&(1<<(15-i))) { + k=get_bits(bd,16); + for(j=0;j<16;j++) + if(k&(1<<(15-j))) symToByte[symTotal++]=(16*i)+j; + } + } + + /* How many different Huffman coding groups does this block use? */ + + groupCount=get_bits(bd,3); + if (groupCount<2 || groupCount>MAX_GROUPS) return RETVAL_DATA_ERROR; + + /* nSelectors: Every GROUP_SIZE many symbols we select a new Huffman coding + group. Read in the group selector list, which is stored as MTF encoded + bit runs. (MTF=Move To Front, as each value is used it's moved to the + start of the list.) */ + + if(!(nSelectors=get_bits(bd, 15))) return RETVAL_DATA_ERROR; + for(i=0; i=groupCount) return RETVAL_DATA_ERROR; + + /* Decode MTF to get the next selector */ + + uc = mtfSymbol[j]; + for(;j;j--) mtfSymbol[j] = mtfSymbol[j-1]; + mtfSymbol[0]=selectors[i]=uc; + } + + /* Read the Huffman coding tables for each group, which code for symTotal + literal symbols, plus two run symbols (RUNA, RUNB) */ + + symCount=symTotal+2; + for (j=0; j (MAX_HUFCODE_BITS-1)) + return RETVAL_DATA_ERROR; + + /* If first bit is 0, stop. Else second bit indicates whether + to increment or decrement the value. Optimization: grab 2 + bits and unget the second if the first was 0. */ + + k = get_bits(bd,2); + if (k < 2) { + bd->inbufBitCount++; + break; + } + + /* Add one if second bit 1, else subtract 1. Avoids if/else */ + + t+=(((k+1)&2)-1); + } + + /* Correct for the initial -1, to get the final symbol length */ + + length[i]=t+1; + } + + /* Find largest and smallest lengths in this group */ + + minLen=maxLen=length[0]; + for(i = 1; i < symCount; i++) { + if(length[i] > maxLen) maxLen = length[i]; + else if(length[i] < minLen) minLen = length[i]; + } + + /* Calculate permute[], base[], and limit[] tables from length[]. + * + * permute[] is the lookup table for converting Huffman coded symbols + * into decoded symbols. base[] is the amount to subtract from the + * value of a Huffman symbol of a given length when using permute[]. + * + * limit[] indicates the largest numerical value a symbol with a given + * number of bits can have. This is how the Huffman codes can vary in + * length: each code with a value>limit[length] needs another bit. + */ + + hufGroup=bd->groups+j; + hufGroup->minLen = minLen; + hufGroup->maxLen = maxLen; + + /* Note that minLen can't be smaller than 1, so we adjust the base + and limit array pointers so we're not always wasting the first + entry. We do this again when using them (during symbol decoding).*/ + + base=hufGroup->base-1; + limit=hufGroup->limit-1; + + /* Calculate permute[]. Concurently, initialize temp[] and limit[]. */ + + pp=0; + for(i=minLen;i<=maxLen;i++) { + temp[i]=limit[i]=0; + for(t=0;tpermute[pp++] = t; + } + + /* Count symbols coded for at each bit length */ + + for (i=0;ilimit[length] comparison. */ + + limit[i]= (pp << (maxLen - i)) - 1; + pp<<=1; + base[i+1]=pp-(t+=temp[i]); + } + limit[maxLen+1] = INT_MAX; /* Sentinal value for reading next sym. */ + limit[maxLen]=pp+temp[maxLen]-1; + base[minLen]=0; + } + + /* We've finished reading and digesting the block header. Now read this + block's Huffman coded symbols from the file and undo the Huffman coding + and run length encoding, saving the result into dbuf[dbufCount++]=uc */ + + /* Initialize symbol occurrence counters and symbol Move To Front table */ + + for(i=0;i<256;i++) { + byteCount[i] = 0; + mtfSymbol[i]=(unsigned char)i; + } + + /* Loop through compressed symbols. */ + + runPos=dbufCount=selector=0; + for(;;) { + + /* fetch next Huffman coding group from list. */ + + symCount=GROUP_SIZE-1; + if(selector>=nSelectors) return RETVAL_DATA_ERROR; + hufGroup=bd->groups+selectors[selector++]; + base=hufGroup->base-1; + limit=hufGroup->limit-1; +continue_this_group: + + /* Read next Huffman-coded symbol. */ + + /* Note: It is far cheaper to read maxLen bits and back up than it is + to read minLen bits and then an additional bit at a time, testing + as we go. Because there is a trailing last block (with file CRC), + there is no danger of the overread causing an unexpected EOF for a + valid compressed file. As a further optimization, we do the read + inline (falling back to a call to get_bits if the buffer runs + dry). The following (up to got_huff_bits:) is equivalent to + j=get_bits(bd,hufGroup->maxLen); + */ + + while (bd->inbufBitCountmaxLen) { + if(bd->inbufPos==bd->inbufCount) { + j = get_bits(bd,hufGroup->maxLen); + goto got_huff_bits; + } + bd->inbufBits=(bd->inbufBits<<8)|bd->inbuf[bd->inbufPos++]; + bd->inbufBitCount+=8; + }; + bd->inbufBitCount-=hufGroup->maxLen; + j = (bd->inbufBits>>bd->inbufBitCount)&((1<maxLen)-1); + +got_huff_bits: + + /* Figure how how many bits are in next symbol and unget extras */ + + i=hufGroup->minLen; + while(j>limit[i]) ++i; + bd->inbufBitCount += (hufGroup->maxLen - i); + + /* Huffman decode value to get nextSym (with bounds checking) */ + + if ((i > hufGroup->maxLen) + || (((unsigned)(j=(j>>(hufGroup->maxLen-i))-base[i])) + >= MAX_SYMBOLS)) + return RETVAL_DATA_ERROR; + nextSym = hufGroup->permute[j]; + + /* We have now decoded the symbol, which indicates either a new literal + byte, or a repeated run of the most recent literal byte. First, + check if nextSym indicates a repeated run, and if so loop collecting + how many times to repeat the last literal. */ + + if (((unsigned)nextSym) <= SYMBOL_RUNB) { /* RUNA or RUNB */ + + /* If this is the start of a new run, zero out counter */ + + if(!runPos) { + runPos = 1; + t = 0; + } + + /* Neat trick that saves 1 symbol: instead of or-ing 0 or 1 at + each bit position, add 1 or 2 instead. For example, + 1011 is 1<<0 + 1<<1 + 2<<2. 1010 is 2<<0 + 2<<1 + 1<<2. + You can make any bit pattern that way using 1 less symbol than + the basic or 0/1 method (except all bits 0, which would use no + symbols, but a run of length 0 doesn't mean anything in this + context). Thus space is saved. */ + + t += (runPos << nextSym); /* +runPos if RUNA; +2*runPos if RUNB */ + if(runPos < dbufSize) runPos <<= 1; + goto end_of_huffman_loop; + } + + /* When we hit the first non-run symbol after a run, we now know + how many times to repeat the last literal, so append that many + copies to our buffer of decoded symbols (dbuf) now. (The last + literal used is the one at the head of the mtfSymbol array.) */ + + if(runPos) { + runPos=0; + if(dbufCount+t>=dbufSize) return RETVAL_DATA_ERROR; + + uc = symToByte[mtfSymbol[0]]; + byteCount[uc] += t; + while(t--) dbuf[dbufCount++]=uc; + } + + /* Is this the terminating symbol? */ + + if(nextSym>symTotal) break; + + /* At this point, nextSym indicates a new literal character. Subtract + one to get the position in the MTF array at which this literal is + currently to be found. (Note that the result can't be -1 or 0, + because 0 and 1 are RUNA and RUNB. But another instance of the + first symbol in the mtf array, position 0, would have been handled + as part of a run above. Therefore 1 unused mtf position minus + 2 non-literal nextSym values equals -1.) */ + + if(dbufCount>=dbufSize) return RETVAL_DATA_ERROR; + i = nextSym - 1; + uc = mtfSymbol[i]; + + /* Adjust the MTF array. Since we typically expect to move only a + * small number of symbols, and are bound by 256 in any case, using + * memmove here would typically be bigger and slower due to function + * call overhead and other assorted setup costs. */ + + do { + mtfSymbol[i] = mtfSymbol[i-1]; + } while (--i); + mtfSymbol[0] = uc; + uc=symToByte[uc]; + + /* We have our literal byte. Save it into dbuf. */ + + byteCount[uc]++; + dbuf[dbufCount++] = (unsigned int)uc; + + /* Skip group initialization if we're not done with this group. Done + * this way to avoid compiler warning. */ + +end_of_huffman_loop: + if(symCount--) goto continue_this_group; + } + + /* At this point, we've read all the Huffman-coded symbols (and repeated + runs) for this block from the input stream, and decoded them into the + intermediate buffer. There are dbufCount many decoded bytes in dbuf[]. + Now undo the Burrows-Wheeler transform on dbuf. + See http://dogma.net/markn/articles/bwt/bwt.htm + */ + + /* Turn byteCount into cumulative occurrence counts of 0 to n-1. */ + + j=0; + for(i=0;i<256;i++) { + k=j+byteCount[i]; + byteCount[i] = j; + j=k; + } + + /* Figure out what order dbuf would be in if we sorted it. */ + + for (i=0;i=dbufCount) return RETVAL_DATA_ERROR; + bd->writePos=dbuf[origPtr]; + bd->writeCurrent=(unsigned char)(bd->writePos&0xff); + bd->writePos>>=8; + bd->writeRunCountdown=5; + } + bd->writeCount=dbufCount; + + return RETVAL_OK; +} + +/* Undo burrows-wheeler transform on intermediate buffer to produce output. + If start_bunzip was initialized with out_fd=-1, then up to len bytes of + data are written to outbuf. Return value is number of bytes written or + error (all errors are negative numbers). If out_fd!=-1, outbuf and len + are ignored, data is written to out_fd and return is RETVAL_OK or error. +*/ + +static int read_bunzip(bunzip_data *bd, char *outbuf, int len) +{ + const unsigned int *dbuf; + int pos,current,previous,gotcount; + + /* If last read was short due to end of file, return last block now */ + if(bd->writeCount<0) return bd->writeCount; + + gotcount = 0; + dbuf=bd->dbuf; + pos=bd->writePos; + current=bd->writeCurrent; + + /* We will always have pending decoded data to write into the output + buffer unless this is the very first call (in which case we haven't + Huffman-decoded a block into the intermediate buffer yet). */ + + if (bd->writeCopies) { + + /* Inside the loop, writeCopies means extra copies (beyond 1) */ + + --bd->writeCopies; + + /* Loop outputting bytes */ + + for(;;) { + + /* If the output buffer is full, snapshot state and return */ + + if(gotcount >= len) { + bd->writePos=pos; + bd->writeCurrent=current; + bd->writeCopies++; + return len; + } + + /* Write next byte into output buffer, updating CRC */ + + outbuf[gotcount++] = current; + bd->writeCRC=(((bd->writeCRC)<<8) + ^bd->crc32Table[((bd->writeCRC)>>24)^current]); + + /* Loop now if we're outputting multiple copies of this byte */ + + if (bd->writeCopies) { + --bd->writeCopies; + continue; + } +decode_next_byte: + if (!bd->writeCount--) break; + /* Follow sequence vector to undo Burrows-Wheeler transform */ + previous=current; + pos=dbuf[pos]; + current=pos&0xff; + pos>>=8; + + /* After 3 consecutive copies of the same byte, the 4th is a repeat + count. We count down from 4 instead + * of counting up because testing for non-zero is faster */ + + if(--bd->writeRunCountdown) { + if(current!=previous) bd->writeRunCountdown=4; + } else { + + /* We have a repeated run, this byte indicates the count */ + + bd->writeCopies=current; + current=previous; + bd->writeRunCountdown=5; + + /* Sometimes there are just 3 bytes (run length 0) */ + + if(!bd->writeCopies) goto decode_next_byte; + + /* Subtract the 1 copy we'd output anyway to get extras */ + + --bd->writeCopies; + } + } + + /* Decompression of this block completed successfully */ + + bd->writeCRC=~bd->writeCRC; + bd->totalCRC=((bd->totalCRC<<1) | (bd->totalCRC>>31)) ^ bd->writeCRC; + + /* If this block had a CRC error, force file level CRC error. */ + + if(bd->writeCRC!=bd->headerCRC) { + bd->totalCRC=bd->headerCRC+1; + return RETVAL_LAST_BLOCK; + } + } + + /* Refill the intermediate buffer by Huffman-decoding next block of input */ + /* (previous is just a convenient unused temp variable here) */ + + previous=get_next_block(bd); + if(previous) { + bd->writeCount=previous; + return (previous!=RETVAL_LAST_BLOCK) ? previous : gotcount; + } + bd->writeCRC=~0; + pos=bd->writePos; + current=bd->writeCurrent; + goto decode_next_byte; +} + +/* Allocate the structure, read file header. If in_fd==-1, inbuf must contain + a complete bunzip file (len bytes long). If in_fd!=-1, inbuf and len are + ignored, and data is read from file handle into temporary buffer. */ + +static int start_bunzip(bunzip_data **bdp, int in_fd, unsigned char *inbuf, + int len) +{ + bunzip_data *bd; + unsigned int i; + const unsigned int BZh0=(((unsigned int)'B')<<24)+(((unsigned int)'Z')<<16) + +(((unsigned int)'h')<<8)+(unsigned int)'0'; + + /* Figure out how much data to allocate */ + + i=sizeof(bunzip_data); + if(in_fd!=-1) i+=IOBUF_SIZE; + + /* Allocate bunzip_data. Most fields initialize to zero. */ + + bd=*bdp=xzalloc(i); + + /* Setup input buffer */ + + if(-1==(bd->in_fd=in_fd)) { + bd->inbuf=inbuf; + bd->inbufCount=len; + } else bd->inbuf=(unsigned char *)(bd+1); + + /* Init the CRC32 table (big endian) */ + + bd->crc32Table = crc32_filltable(1); + + /* Setup for I/O error handling via longjmp */ + + i=setjmp(bd->jmpbuf); + if(i) return i; + + /* Ensure that file starts with "BZh['1'-'9']." */ + + i = get_bits(bd,32); + if (((unsigned int)(i-BZh0-1)) >= 9) return RETVAL_NOT_BZIP_DATA; + + /* Fourth byte (ascii '1'-'9'), indicates block size in units of 100k of + uncompressed data. Allocate intermediate buffer for block. */ + + bd->dbufSize=100000*(i-BZh0); + + bd->dbuf=xmalloc(bd->dbufSize * sizeof(int)); + return RETVAL_OK; +} + +/* Example usage: decompress src_fd to dst_fd. (Stops at end of bzip data, + not end of file.) */ + +USE_DESKTOP(long long) int +uncompressStream(int src_fd, int dst_fd) +{ + USE_DESKTOP(long long total_written = 0;) + char *outbuf; + bunzip_data *bd; + int i; + + outbuf=xmalloc(IOBUF_SIZE); + i=start_bunzip(&bd,src_fd,0,0); + if(!i) { + for(;;) { + if((i=read_bunzip(bd,outbuf,IOBUF_SIZE)) <= 0) break; + if(i!=write(dst_fd,outbuf,i)) { + i=RETVAL_UNEXPECTED_OUTPUT_EOF; + break; + } + USE_DESKTOP(total_written += i;) + } + } + + /* Check CRC and release memory */ + + if(i==RETVAL_LAST_BLOCK) { + if (bd->headerCRC!=bd->totalCRC) { + bb_error_msg("data integrity error when decompressing"); + } else { + i=RETVAL_OK; + } + } else if (i==RETVAL_UNEXPECTED_OUTPUT_EOF) { + bb_error_msg("compressed file ends unexpectedly"); + } else { + bb_error_msg("decompression failed"); + } + free(bd->dbuf); + free(bd); + free(outbuf); + + return i ? i : USE_DESKTOP(total_written) + 0; +} + +#ifdef TESTING + +static char * const bunzip_errors[]={NULL,"Bad file checksum","Not bzip data", + "Unexpected input EOF","Unexpected output EOF","Data error", + "Out of memory","Obsolete (pre 0.9.5) bzip format not supported."}; + +/* Dumb little test thing, decompress stdin to stdout */ +int main(int argc, char *argv[]) +{ + int i=uncompressStream(0,1); + char c; + + if(i<0) fprintf(stderr,"%s\n", bunzip_errors[-i]); + else if(read(0,&c,1)) fprintf(stderr,"Trailing garbage ignored\n"); + return -i; +} +#endif diff --git a/archival/libunarchive/decompress_uncompress.c b/archival/libunarchive/decompress_uncompress.c new file mode 100644 index 000000000..ff98254ff --- /dev/null +++ b/archival/libunarchive/decompress_uncompress.c @@ -0,0 +1,292 @@ +/* vi: set sw=4 ts=4: */ +#include "libbb.h" + +/* uncompress for busybox -- (c) 2002 Robert Griebl + * + * based on the original compress42.c source + * (see disclaimer below) + */ + + +/* (N)compress42.c - File compression ala IEEE Computer, Mar 1992. + * + * Authors: + * Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) + * Jim McKie (decvax!mcvax!jim) + * Steve Davies (decvax!vax135!petsd!peora!srd) + * Ken Turkowski (decvax!decwrl!turtlevax!ken) + * James A. Woods (decvax!ihnp4!ames!jaw) + * Joe Orost (decvax!vax135!petsd!joe) + * Dave Mack (csu@alembic.acs.com) + * Peter Jannesen, Network Communication Systems + * (peter@ncs.nl) + * + * marc@suse.de : a small security fix for a buffer overflow + * + * [... History snipped ...] + * + */ +#include +#include +#include + +/* Default input buffer size */ +#define IBUFSIZ 2048 + +/* Default output buffer size */ +#define OBUFSIZ 2048 + +/* Defines for third byte of header */ +#define MAGIC_1 (char_type)'\037' /* First byte of compressed file */ +#define MAGIC_2 (char_type)'\235' /* Second byte of compressed file */ +#define BIT_MASK 0x1f /* Mask for 'number of compresssion bits' */ + /* Masks 0x20 and 0x40 are free. */ + /* I think 0x20 should mean that there is */ + /* a fourth header byte (for expansion). */ +#define BLOCK_MODE 0x80 /* Block compresssion if table is full and */ + /* compression rate is dropping flush tables */ + /* the next two codes should not be changed lightly, as they must not */ + /* lie within the contiguous general code space. */ +#define FIRST 257 /* first free entry */ +#define CLEAR 256 /* table clear output code */ + +#define INIT_BITS 9 /* initial number of bits/code */ + + +/* machine variants which require cc -Dmachine: pdp11, z8000, DOS */ +#define FAST + +#define HBITS 17 /* 50% occupancy */ +#define HSIZE (1< BITS) { + bb_error_msg("compressed with %d bits, can only handle %d bits", maxbits, + BITS); + return -1; + } + + maxcode = MAXCODE(n_bits = INIT_BITS) - 1; + bitmask = (1 << n_bits) - 1; + oldcode = -1; + finchar = 0; + outpos = 0; + posbits = 0 << 3; + + free_ent = ((block_mode) ? FIRST : 256); + + /* As above, initialize the first 256 entries in the table. */ + clear_tab_prefixof(); + + for (code = 255; code >= 0; --code) { + tab_suffixof(code) = (unsigned char) code; + } + + do { + resetbuf:; + { + int i; + int e; + int o; + + e = insize - (o = (posbits >> 3)); + + for (i = 0; i < e; ++i) + inbuf[i] = inbuf[i + o]; + + insize = e; + posbits = 0; + } + + if (insize < (int) (IBUFSIZ + 64) - IBUFSIZ) { + rsize = safe_read(fd_in, inbuf + insize, IBUFSIZ); + insize += rsize; + } + + inbits = ((rsize > 0) ? (insize - insize % n_bits) << 3 : + (insize << 3) - (n_bits - 1)); + + while (inbits > posbits) { + if (free_ent > maxcode) { + posbits = + ((posbits - 1) + + ((n_bits << 3) - + (posbits - 1 + (n_bits << 3)) % (n_bits << 3))); + ++n_bits; + if (n_bits == maxbits) { + maxcode = maxmaxcode; + } else { + maxcode = MAXCODE(n_bits) - 1; + } + bitmask = (1 << n_bits) - 1; + goto resetbuf; + } + { + unsigned char *p = &inbuf[posbits >> 3]; + + code = ((((long) (p[0])) | ((long) (p[1]) << 8) | + ((long) (p[2]) << 16)) >> (posbits & 0x7)) & bitmask; + } + posbits += n_bits; + + + if (oldcode == -1) { + oldcode = code; + finchar = (int) oldcode; + outbuf[outpos++] = (unsigned char) finchar; + continue; + } + + if (code == CLEAR && block_mode) { + clear_tab_prefixof(); + free_ent = FIRST - 1; + posbits = + ((posbits - 1) + + ((n_bits << 3) - + (posbits - 1 + (n_bits << 3)) % (n_bits << 3))); + maxcode = MAXCODE(n_bits = INIT_BITS) - 1; + bitmask = (1 << n_bits) - 1; + goto resetbuf; + } + + incode = code; + stackp = de_stack; + + /* Special case for KwKwK string. */ + if (code >= free_ent) { + if (code > free_ent) { + unsigned char *p; + + posbits -= n_bits; + p = &inbuf[posbits >> 3]; + + bb_error_msg + ("insize:%d posbits:%d inbuf:%02X %02X %02X %02X %02X (%d)", + insize, posbits, p[-1], p[0], p[1], p[2], p[3], + (posbits & 07)); + bb_error_msg("uncompress: corrupt input"); + return -1; + } + + *--stackp = (unsigned char) finchar; + code = oldcode; + } + + /* Generate output characters in reverse order */ + while ((long int) code >= (long int) 256) { + *--stackp = tab_suffixof(code); + code = tab_prefixof(code); + } + + *--stackp = (unsigned char) (finchar = tab_suffixof(code)); + + /* And put them out in forward order */ + { + int i; + + if (outpos + (i = (de_stack - stackp)) >= OBUFSIZ) { + do { + if (i > OBUFSIZ - outpos) { + i = OBUFSIZ - outpos; + } + + if (i > 0) { + memcpy(outbuf + outpos, stackp, i); + outpos += i; + } + + if (outpos >= OBUFSIZ) { + write(fd_out, outbuf, outpos); + USE_DESKTOP(total_written += outpos;) + outpos = 0; + } + stackp += i; + } while ((i = (de_stack - stackp)) > 0); + } else { + memcpy(outbuf + outpos, stackp, i); + outpos += i; + } + } + + /* Generate the new entry. */ + if ((code = free_ent) < maxmaxcode) { + tab_prefixof(code) = (unsigned short) oldcode; + tab_suffixof(code) = (unsigned char) finchar; + free_ent = code + 1; + } + + /* Remember previous code. */ + oldcode = incode; + } + + } while (rsize > 0); + + if (outpos > 0) { + write(fd_out, outbuf, outpos); + USE_DESKTOP(total_written += outpos;) + } + + RELEASE_CONFIG_BUFFER(inbuf); + RELEASE_CONFIG_BUFFER(outbuf); + return USE_DESKTOP(total_written) + 0; +} diff --git a/archival/libunarchive/decompress_unlzma.c b/archival/libunarchive/decompress_unlzma.c new file mode 100644 index 000000000..a6902807b --- /dev/null +++ b/archival/libunarchive/decompress_unlzma.c @@ -0,0 +1,478 @@ +/* vi: set sw=4 ts=4: */ +/* + * Small lzma deflate implementation. + * Copyright (C) 2006 Aurelien Jacobs + * + * Based on LzmaDecode.c from the LZMA SDK 4.22 (http://www.7-zip.org/) + * Copyright (C) 1999-2005 Igor Pavlov + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include "unarchive.h" + +#ifdef CONFIG_FEATURE_LZMA_FAST +# define speed_inline ATTRIBUTE_ALWAYS_INLINE +#else +# define speed_inline +#endif + + +typedef struct { + int fd; + uint8_t *ptr; + uint8_t *buffer; + uint8_t *buffer_end; + int buffer_size; + uint32_t code; + uint32_t range; + uint32_t bound; +} rc_t; + + +#define RC_TOP_BITS 24 +#define RC_MOVE_BITS 5 +#define RC_MODEL_TOTAL_BITS 11 + + +/* Called twice: once at startup and once in rc_normalize() */ +static void rc_read(rc_t * rc) +{ + rc->buffer_size = read(rc->fd, rc->buffer, rc->buffer_size); + if (rc->buffer_size <= 0) + bb_error_msg_and_die("unexpected EOF"); + rc->ptr = rc->buffer; + rc->buffer_end = rc->buffer + rc->buffer_size; +} + +/* Called once */ +static void rc_init(rc_t * rc, int fd, int buffer_size) +{ + int i; + + rc->fd = fd; + rc->buffer = xmalloc(buffer_size); + rc->buffer_size = buffer_size; + rc->buffer_end = rc->buffer + rc->buffer_size; + rc->ptr = rc->buffer_end; + + rc->code = 0; + rc->range = 0xFFFFFFFF; + for (i = 0; i < 5; i++) { + if (rc->ptr >= rc->buffer_end) + rc_read(rc); + rc->code = (rc->code << 8) | *rc->ptr++; + } +} + +/* Called once. TODO: bb_maybe_free() */ +static ATTRIBUTE_ALWAYS_INLINE void rc_free(rc_t * rc) +{ + if (ENABLE_FEATURE_CLEAN_UP) + free(rc->buffer); +} + +/* Called twice, but one callsite is in speed_inline'd rc_is_bit_0_helper() */ +static void rc_do_normalize(rc_t * rc) +{ + if (rc->ptr >= rc->buffer_end) + rc_read(rc); + rc->range <<= 8; + rc->code = (rc->code << 8) | *rc->ptr++; +} +static ATTRIBUTE_ALWAYS_INLINE void rc_normalize(rc_t * rc) +{ + if (rc->range < (1 << RC_TOP_BITS)) { + rc_do_normalize(rc); + } +} + +/* Called 9 times */ +/* Why rc_is_bit_0_helper exists? + * Because we want to always expose (rc->code < rc->bound) to optimizer + */ +static speed_inline uint32_t rc_is_bit_0_helper(rc_t * rc, uint16_t * p) +{ + rc_normalize(rc); + rc->bound = *p * (rc->range >> RC_MODEL_TOTAL_BITS); + return rc->bound; +} +static ATTRIBUTE_ALWAYS_INLINE int rc_is_bit_0(rc_t * rc, uint16_t * p) +{ + uint32_t t = rc_is_bit_0_helper(rc, p); + return rc->code < t; +} + +/* Called ~10 times, but very small, thus inlined */ +static speed_inline void rc_update_bit_0(rc_t * rc, uint16_t * p) +{ + rc->range = rc->bound; + *p += ((1 << RC_MODEL_TOTAL_BITS) - *p) >> RC_MOVE_BITS; +} +static speed_inline void rc_update_bit_1(rc_t * rc, uint16_t * p) +{ + rc->range -= rc->bound; + rc->code -= rc->bound; + *p -= *p >> RC_MOVE_BITS; +} + +/* Called 4 times in unlzma loop */ +static int rc_get_bit(rc_t * rc, uint16_t * p, int *symbol) +{ + if (rc_is_bit_0(rc, p)) { + rc_update_bit_0(rc, p); + *symbol *= 2; + return 0; + } else { + rc_update_bit_1(rc, p); + *symbol = *symbol * 2 + 1; + return 1; + } +} + +/* Called once */ +static ATTRIBUTE_ALWAYS_INLINE int rc_direct_bit(rc_t * rc) +{ + rc_normalize(rc); + rc->range >>= 1; + if (rc->code >= rc->range) { + rc->code -= rc->range; + return 1; + } + return 0; +} + +/* Called twice */ +static speed_inline void +rc_bit_tree_decode(rc_t * rc, uint16_t * p, int num_levels, int *symbol) +{ + int i = num_levels; + + *symbol = 1; + while (i--) + rc_get_bit(rc, p + *symbol, symbol); + *symbol -= 1 << num_levels; +} + + +typedef struct { + uint8_t pos; + uint32_t dict_size; + uint64_t dst_size; +} __attribute__ ((packed)) lzma_header_t; + + +#define LZMA_BASE_SIZE 1846 +#define LZMA_LIT_SIZE 768 + +#define LZMA_NUM_POS_BITS_MAX 4 + +#define LZMA_LEN_NUM_LOW_BITS 3 +#define LZMA_LEN_NUM_MID_BITS 3 +#define LZMA_LEN_NUM_HIGH_BITS 8 + +#define LZMA_LEN_CHOICE 0 +#define LZMA_LEN_CHOICE_2 (LZMA_LEN_CHOICE + 1) +#define LZMA_LEN_LOW (LZMA_LEN_CHOICE_2 + 1) +#define LZMA_LEN_MID (LZMA_LEN_LOW \ + + (1 << (LZMA_NUM_POS_BITS_MAX + LZMA_LEN_NUM_LOW_BITS))) +#define LZMA_LEN_HIGH (LZMA_LEN_MID \ + +(1 << (LZMA_NUM_POS_BITS_MAX + LZMA_LEN_NUM_MID_BITS))) +#define LZMA_NUM_LEN_PROBS (LZMA_LEN_HIGH + (1 << LZMA_LEN_NUM_HIGH_BITS)) + +#define LZMA_NUM_STATES 12 +#define LZMA_NUM_LIT_STATES 7 + +#define LZMA_START_POS_MODEL_INDEX 4 +#define LZMA_END_POS_MODEL_INDEX 14 +#define LZMA_NUM_FULL_DISTANCES (1 << (LZMA_END_POS_MODEL_INDEX >> 1)) + +#define LZMA_NUM_POS_SLOT_BITS 6 +#define LZMA_NUM_LEN_TO_POS_STATES 4 + +#define LZMA_NUM_ALIGN_BITS 4 + +#define LZMA_MATCH_MIN_LEN 2 + +#define LZMA_IS_MATCH 0 +#define LZMA_IS_REP (LZMA_IS_MATCH + (LZMA_NUM_STATES <= (9 * 5 * 5)) + bb_error_msg_and_die("bad header"); + mi = header.pos / 9; + lc = header.pos % 9; + pb = mi / 5; + lp = mi % 5; + pos_state_mask = (1 << pb) - 1; + literal_pos_mask = (1 << lp) - 1; + + header.dict_size = SWAP_LE32(header.dict_size); + header.dst_size = SWAP_LE64(header.dst_size); + + if (header.dict_size == 0) + header.dict_size = 1; + + buffer = xmalloc(MIN(header.dst_size, header.dict_size)); + + num_probs = LZMA_BASE_SIZE + (LZMA_LIT_SIZE << (lc + lp)); + p = xmalloc(num_probs * sizeof(*p)); + num_probs = LZMA_LITERAL + (LZMA_LIT_SIZE << (lc + lp)); + for (i = 0; i < num_probs; i++) + p[i] = (1 << RC_MODEL_TOTAL_BITS) >> 1; + + rc_init(&rc, src_fd, 0x10000); + + while (global_pos + buffer_pos < header.dst_size) { + int pos_state = (buffer_pos + global_pos) & pos_state_mask; + + prob = + p + LZMA_IS_MATCH + (state << LZMA_NUM_POS_BITS_MAX) + pos_state; + if (rc_is_bit_0(&rc, prob)) { + mi = 1; + rc_update_bit_0(&rc, prob); + prob = (p + LZMA_LITERAL + (LZMA_LIT_SIZE + * ((((buffer_pos + global_pos) & literal_pos_mask) << lc) + + (previous_byte >> (8 - lc))))); + + if (state >= LZMA_NUM_LIT_STATES) { + int match_byte; + + pos = buffer_pos - rep0; + while (pos >= header.dict_size) + pos += header.dict_size; + match_byte = buffer[pos]; + do { + int bit; + + match_byte <<= 1; + bit = match_byte & 0x100; + prob_lit = prob + 0x100 + bit + mi; + if (rc_get_bit(&rc, prob_lit, &mi)) { + if (!bit) + break; + } else { + if (bit) + break; + } + } while (mi < 0x100); + } + while (mi < 0x100) { + prob_lit = prob + mi; + rc_get_bit(&rc, prob_lit, &mi); + } + previous_byte = (uint8_t) mi; + + buffer[buffer_pos++] = previous_byte; + if (buffer_pos == header.dict_size) { + buffer_pos = 0; + global_pos += header.dict_size; + // FIXME: error check + write(dst_fd, buffer, header.dict_size); + USE_DESKTOP(total_written += header.dict_size;) + } + if (state < 4) + state = 0; + else if (state < 10) + state -= 3; + else + state -= 6; + } else { + int offset; + uint16_t *prob_len; + + rc_update_bit_1(&rc, prob); + prob = p + LZMA_IS_REP + state; + if (rc_is_bit_0(&rc, prob)) { + rc_update_bit_0(&rc, prob); + rep3 = rep2; + rep2 = rep1; + rep1 = rep0; + state = state < LZMA_NUM_LIT_STATES ? 0 : 3; + prob = p + LZMA_LEN_CODER; + } else { + rc_update_bit_1(&rc, prob); + prob = p + LZMA_IS_REP_G0 + state; + if (rc_is_bit_0(&rc, prob)) { + rc_update_bit_0(&rc, prob); + prob = (p + LZMA_IS_REP_0_LONG + + (state << LZMA_NUM_POS_BITS_MAX) + pos_state); + if (rc_is_bit_0(&rc, prob)) { + rc_update_bit_0(&rc, prob); + + state = state < LZMA_NUM_LIT_STATES ? 9 : 11; + pos = buffer_pos - rep0; + while (pos >= header.dict_size) + pos += header.dict_size; + previous_byte = buffer[pos]; + buffer[buffer_pos++] = previous_byte; + if (buffer_pos == header.dict_size) { + buffer_pos = 0; + global_pos += header.dict_size; + // FIXME: error check + write(dst_fd, buffer, header.dict_size); + USE_DESKTOP(total_written += header.dict_size;) + } + continue; + } else { + rc_update_bit_1(&rc, prob); + } + } else { + uint32_t distance; + + rc_update_bit_1(&rc, prob); + prob = p + LZMA_IS_REP_G1 + state; + if (rc_is_bit_0(&rc, prob)) { + rc_update_bit_0(&rc, prob); + distance = rep1; + } else { + rc_update_bit_1(&rc, prob); + prob = p + LZMA_IS_REP_G2 + state; + if (rc_is_bit_0(&rc, prob)) { + rc_update_bit_0(&rc, prob); + distance = rep2; + } else { + rc_update_bit_1(&rc, prob); + distance = rep3; + rep3 = rep2; + } + rep2 = rep1; + } + rep1 = rep0; + rep0 = distance; + } + state = state < LZMA_NUM_LIT_STATES ? 8 : 11; + prob = p + LZMA_REP_LEN_CODER; + } + + prob_len = prob + LZMA_LEN_CHOICE; + if (rc_is_bit_0(&rc, prob_len)) { + rc_update_bit_0(&rc, prob_len); + prob_len = (prob + LZMA_LEN_LOW + + (pos_state << LZMA_LEN_NUM_LOW_BITS)); + offset = 0; + num_bits = LZMA_LEN_NUM_LOW_BITS; + } else { + rc_update_bit_1(&rc, prob_len); + prob_len = prob + LZMA_LEN_CHOICE_2; + if (rc_is_bit_0(&rc, prob_len)) { + rc_update_bit_0(&rc, prob_len); + prob_len = (prob + LZMA_LEN_MID + + (pos_state << LZMA_LEN_NUM_MID_BITS)); + offset = 1 << LZMA_LEN_NUM_LOW_BITS; + num_bits = LZMA_LEN_NUM_MID_BITS; + } else { + rc_update_bit_1(&rc, prob_len); + prob_len = prob + LZMA_LEN_HIGH; + offset = ((1 << LZMA_LEN_NUM_LOW_BITS) + + (1 << LZMA_LEN_NUM_MID_BITS)); + num_bits = LZMA_LEN_NUM_HIGH_BITS; + } + } + rc_bit_tree_decode(&rc, prob_len, num_bits, &len); + len += offset; + + if (state < 4) { + int pos_slot; + + state += LZMA_NUM_LIT_STATES; + prob = + p + LZMA_POS_SLOT + + ((len < + LZMA_NUM_LEN_TO_POS_STATES ? len : + LZMA_NUM_LEN_TO_POS_STATES - 1) + << LZMA_NUM_POS_SLOT_BITS); + rc_bit_tree_decode(&rc, prob, LZMA_NUM_POS_SLOT_BITS, + &pos_slot); + if (pos_slot >= LZMA_START_POS_MODEL_INDEX) { + num_bits = (pos_slot >> 1) - 1; + rep0 = 2 | (pos_slot & 1); + if (pos_slot < LZMA_END_POS_MODEL_INDEX) { + rep0 <<= num_bits; + prob = p + LZMA_SPEC_POS + rep0 - pos_slot - 1; + } else { + num_bits -= LZMA_NUM_ALIGN_BITS; + while (num_bits--) + rep0 = (rep0 << 1) | rc_direct_bit(&rc); + prob = p + LZMA_ALIGN; + rep0 <<= LZMA_NUM_ALIGN_BITS; + num_bits = LZMA_NUM_ALIGN_BITS; + } + i = 1; + mi = 1; + while (num_bits--) { + if (rc_get_bit(&rc, prob + mi, &mi)) + rep0 |= i; + i <<= 1; + } + } else + rep0 = pos_slot; + if (++rep0 == 0) + break; + } + + len += LZMA_MATCH_MIN_LEN; + + do { + pos = buffer_pos - rep0; + while (pos >= header.dict_size) + pos += header.dict_size; + previous_byte = buffer[pos]; + buffer[buffer_pos++] = previous_byte; + if (buffer_pos == header.dict_size) { + buffer_pos = 0; + global_pos += header.dict_size; + // FIXME: error check + write(dst_fd, buffer, header.dict_size); + USE_DESKTOP(total_written += header.dict_size;) + } + len--; + } while (len != 0 && buffer_pos < header.dst_size); + } + } + + // FIXME: error check + write(dst_fd, buffer, buffer_pos); + USE_DESKTOP(total_written += buffer_pos;) + rc_free(&rc); + return USE_DESKTOP(total_written) + 0; +} diff --git a/archival/libunarchive/decompress_unzip.c b/archival/libunarchive/decompress_unzip.c new file mode 100644 index 000000000..621d84c2d --- /dev/null +++ b/archival/libunarchive/decompress_unzip.c @@ -0,0 +1,924 @@ +/* vi: set sw=4 ts=4: */ +/* + * gunzip implementation for busybox + * + * Based on GNU gzip v1.2.4 Copyright (C) 1992-1993 Jean-loup Gailly. + * + * Originally adjusted for busybox by Sven Rudolph + * based on gzip sources + * + * Adjusted further by Erik Andersen to support + * files as well as stdin/stdout, and to generally behave itself wrt + * command line handling. + * + * General cleanup to better adhere to the style guide and make use of standard + * busybox functions by Glenn McGrath + * + * read_gz interface + associated hacking by Laurence Anderson + * + * Fixed huft_build() so decoding end-of-block code does not grab more bits + * than necessary (this is required by unzip applet), added inflate_cleanup() + * to free leaked bytebuffer memory (used in unzip.c), and some minor style + * guide cleanups by Ed Clark + * + * gzip (GNU zip) -- compress files with zip algorithm and 'compress' interface + * Copyright (C) 1992-1993 Jean-loup Gailly + * The unzip code was written and put in the public domain by Mark Adler. + * Portions of the lzw code are derived from the public domain 'compress' + * written by Spencer Thomas, Joe Orost, James Woods, Jim McKie, Steve Davies, + * Ken Turkowski, Dave Mack and Peter Jannesen. + * + * See the file algorithm.doc for the compression algorithms and file formats. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include "unarchive.h" + +typedef struct huft_s { + unsigned char e; /* number of extra bits or operation */ + unsigned char b; /* number of bits in this code or subcode */ + union { + unsigned short n; /* literal, length base, or distance base */ + struct huft_s *t; /* pointer to next level of table */ + } v; +} huft_t; + +static int gunzip_src_fd; +unsigned int gunzip_bytes_out; /* number of output bytes */ +static unsigned int gunzip_outbuf_count; /* bytes in output buffer */ + +/* gunzip_window size--must be a power of two, and + * at least 32K for zip's deflate method */ +enum { gunzip_wsize = 0x8000 }; +static unsigned char *gunzip_window; + +static uint32_t *gunzip_crc_table; +uint32_t gunzip_crc; + +/* If BMAX needs to be larger than 16, then h and x[] should be ulg. */ +#define BMAX 16 /* maximum bit length of any code (16 for explode) */ +#define N_MAX 288 /* maximum number of codes in any set */ + +/* bitbuffer */ +static unsigned int gunzip_bb; /* bit buffer */ +static unsigned char gunzip_bk; /* bits in bit buffer */ + +/* These control the size of the bytebuffer */ +static unsigned int bytebuffer_max = 0x8000; +static unsigned char *bytebuffer = NULL; +static unsigned int bytebuffer_offset = 0; +static unsigned int bytebuffer_size = 0; + +static const unsigned short mask_bits[] = { + 0x0000, 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, + 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff +}; + +/* Copy lengths for literal codes 257..285 */ +static const unsigned short cplens[] = { + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, + 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 +}; + +/* note: see note #13 above about the 258 in this list. */ +/* Extra bits for literal codes 257..285 */ +static const unsigned char cplext[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, + 5, 5, 5, 0, 99, 99 +}; /* 99==invalid */ + +/* Copy offsets for distance codes 0..29 */ +static const unsigned short cpdist[] = { + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, + 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577 +}; + +/* Extra bits for distance codes */ +static const unsigned char cpdext[] = { + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, + 11, 11, 12, 12, 13, 13 +}; + +/* Tables for deflate from PKZIP's appnote.txt. */ +/* Order of the bit length code lengths */ +static const unsigned char border[] = { + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 +}; + +static unsigned int fill_bitbuffer(unsigned int bitbuffer, unsigned int *current, const unsigned int required) +{ + while (*current < required) { + if (bytebuffer_offset >= bytebuffer_size) { + /* Leave the first 4 bytes empty so we can always unwind the bitbuffer + * to the front of the bytebuffer, leave 4 bytes free at end of tail + * so we can easily top up buffer in check_trailer_gzip() */ + if (1 > (bytebuffer_size = safe_read(gunzip_src_fd, &bytebuffer[4], bytebuffer_max - 8))) + bb_error_msg_and_die("unexpected end of file"); + bytebuffer_size += 4; + bytebuffer_offset = 4; + } + bitbuffer |= ((unsigned int) bytebuffer[bytebuffer_offset]) << *current; + bytebuffer_offset++; + *current += 8; + } + return bitbuffer; +} + +/* + * Free the malloc'ed tables built by huft_build(), which makes a linked + * list of the tables it made, with the links in a dummy first entry of + * each table. + * t: table to free + */ +static int huft_free(huft_t * t) +{ + huft_t *p; + huft_t *q; + + /* Go through linked list, freeing from the malloced (t[-1]) address. */ + p = t; + while (p != (huft_t *) NULL) { + q = (--p)->v.t; + free((char *) p); + p = q; + } + return 0; +} + +/* Given a list of code lengths and a maximum table size, make a set of + * tables to decode that set of codes. Return zero on success, one if + * the given code set is incomplete (the tables are still built in this + * case), two if the input is invalid (all zero length codes or an + * oversubscribed set of lengths), and three if not enough memory. + * + * b: code lengths in bits (all assumed <= BMAX) + * n: number of codes (assumed <= N_MAX) + * s: number of simple-valued codes (0..s-1) + * d: list of base values for non-simple codes + * e: list of extra bits for non-simple codes + * t: result: starting table + * m: maximum lookup bits, returns actual + */ +static +int huft_build(unsigned int *b, const unsigned int n, + const unsigned int s, const unsigned short *d, + const unsigned char *e, huft_t ** t, unsigned int *m) +{ + unsigned a; /* counter for codes of length k */ + unsigned c[BMAX + 1]; /* bit length count table */ + unsigned eob_len; /* length of end-of-block code (value 256) */ + unsigned f; /* i repeats in table every f entries */ + int g; /* maximum code length */ + int htl; /* table level */ + unsigned i; /* counter, current code */ + unsigned j; /* counter */ + int k; /* number of bits in current code */ + unsigned *p; /* pointer into c[], b[], or v[] */ + huft_t *q; /* points to current table */ + huft_t r; /* table entry for structure assignment */ + huft_t *u[BMAX]; /* table stack */ + unsigned v[N_MAX]; /* values in order of bit length */ + int ws[BMAX+1]; /* bits decoded stack */ + int w; /* bits decoded */ + unsigned x[BMAX + 1]; /* bit offsets, then code stack */ + unsigned *xp; /* pointer into x */ + int y; /* number of dummy codes added */ + unsigned z; /* number of entries in current table */ + + /* Length of EOB code, if any */ + eob_len = n > 256 ? b[256] : BMAX; + + /* Generate counts for each bit length */ + memset((void *)c, 0, sizeof(c)); + p = b; + i = n; + do { + c[*p]++; /* assume all entries <= BMAX */ + p++; /* Can't combine with above line (Solaris bug) */ + } while (--i); + if (c[0] == n) { /* null input--all zero length codes */ + *t = (huft_t *) NULL; + *m = 0; + return 2; + } + + /* Find minimum and maximum length, bound *m by those */ + for (j = 1; (c[j] == 0) && (j <= BMAX); j++); + k = j; /* minimum code length */ + for (i = BMAX; (c[i] == 0) && i; i--); + g = i; /* maximum code length */ + *m = (*m < j) ? j : ((*m > i) ? i : *m); + + /* Adjust last length count to fill out codes, if needed */ + for (y = 1 << j; j < i; j++, y <<= 1) { + if ((y -= c[j]) < 0) { + return 2; /* bad input: more codes than bits */ + } + } + if ((y -= c[i]) < 0) { + return 2; + } + c[i] += y; + + /* Generate starting offsets into the value table for each length */ + x[1] = j = 0; + p = c + 1; + xp = x + 2; + while (--i) { /* note that i == g from above */ + *xp++ = (j += *p++); + } + + /* Make a table of values in order of bit lengths */ + p = b; + i = 0; + do { + if ((j = *p++) != 0) { + v[x[j]++] = i; + } + } while (++i < n); + + /* Generate the Huffman codes and for each, make the table entries */ + x[0] = i = 0; /* first Huffman code is zero */ + p = v; /* grab values in bit order */ + htl = -1; /* no tables yet--level -1 */ + w = ws[0] = 0; /* bits decoded */ + u[0] = (huft_t *) NULL; /* just to keep compilers happy */ + q = (huft_t *) NULL; /* ditto */ + z = 0; /* ditto */ + + /* go through the bit lengths (k already is bits in shortest code) */ + for (; k <= g; k++) { + a = c[k]; + while (a--) { + /* here i is the Huffman code of length k bits for value *p */ + /* make tables up to required level */ + while (k > ws[htl + 1]) { + w = ws[++htl]; + + /* compute minimum size table less than or equal to *m bits */ + z = (z = g - w) > *m ? *m : z; /* upper limit on table size */ + if ((f = 1 << (j = k - w)) > a + 1) { /* try a k-w bit table */ + /* too few codes for k-w bit table */ + f -= a + 1; /* deduct codes from patterns left */ + xp = c + k; + while (++j < z) { /* try smaller tables up to z bits */ + if ((f <<= 1) <= *++xp) { + break; /* enough codes to use up j bits */ + } + f -= *xp; /* else deduct codes from patterns */ + } + } + j = (w + j > eob_len && w < eob_len) ? eob_len - w : j; /* make EOB code end at table */ + z = 1 << j; /* table entries for j-bit table */ + ws[htl+1] = w + j; /* set bits decoded in stack */ + + /* allocate and link in new table */ + q = (huft_t *) xzalloc((z + 1) * sizeof(huft_t)); + *t = q + 1; /* link to list for huft_free() */ + t = &(q->v.t); + u[htl] = ++q; /* table starts after link */ + + /* connect to last table, if there is one */ + if (htl) { + x[htl] = i; /* save pattern for backing up */ + r.b = (unsigned char) (w - ws[htl - 1]); /* bits to dump before this table */ + r.e = (unsigned char) (16 + j); /* bits in this table */ + r.v.t = q; /* pointer to this table */ + j = (i & ((1 << w) - 1)) >> ws[htl - 1]; + u[htl - 1][j] = r; /* connect to last table */ + } + } + + /* set up table entry in r */ + r.b = (unsigned char) (k - w); + if (p >= v + n) { + r.e = 99; /* out of values--invalid code */ + } else if (*p < s) { + r.e = (unsigned char) (*p < 256 ? 16 : 15); /* 256 is EOB code */ + r.v.n = (unsigned short) (*p++); /* simple code is just the value */ + } else { + r.e = (unsigned char) e[*p - s]; /* non-simple--look up in lists */ + r.v.n = d[*p++ - s]; + } + + /* fill code-like entries with r */ + f = 1 << (k - w); + for (j = i >> w; j < z; j += f) { + q[j] = r; + } + + /* backwards increment the k-bit code i */ + for (j = 1 << (k - 1); i & j; j >>= 1) { + i ^= j; + } + i ^= j; + + /* backup over finished tables */ + while ((i & ((1 << w) - 1)) != x[htl]) { + w = ws[--htl]; + } + } + } + + /* return actual size of base table */ + *m = ws[1]; + + /* Return true (1) if we were given an incomplete table */ + return y != 0 && g != 1; +} + +/* + * inflate (decompress) the codes in a deflated (compressed) block. + * Return an error code or zero if it all goes ok. + * + * tl, td: literal/length and distance decoder tables + * bl, bd: number of bits decoded by tl[] and td[] + */ +static int inflate_codes(huft_t * my_tl, huft_t * my_td, const unsigned int my_bl, const unsigned int my_bd, int setup) +{ + static unsigned int e; /* table entry flag/number of extra bits */ + static unsigned int n, d; /* length and index for copy */ + static unsigned int w; /* current gunzip_window position */ + static huft_t *t; /* pointer to table entry */ + static unsigned int ml, md; /* masks for bl and bd bits */ + static unsigned int b; /* bit buffer */ + static unsigned int k; /* number of bits in bit buffer */ + static huft_t *tl, *td; + static unsigned int bl, bd; + static int resumeCopy = 0; + + if (setup) { // 1st time we are called, copy in variables + tl = my_tl; + td = my_td; + bl = my_bl; + bd = my_bd; + /* make local copies of globals */ + b = gunzip_bb; /* initialize bit buffer */ + k = gunzip_bk; + w = gunzip_outbuf_count; /* initialize gunzip_window position */ + + /* inflate the coded data */ + ml = mask_bits[bl]; /* precompute masks for speed */ + md = mask_bits[bd]; + return 0; // Don't actually do anything the first time + } + + if (resumeCopy) goto do_copy; + + while (1) { /* do until end of block */ + b = fill_bitbuffer(b, &k, bl); + if ((e = (t = tl + ((unsigned) b & ml))->e) > 16) + do { + if (e == 99) { + bb_error_msg_and_die("inflate_codes error 1"); + } + b >>= t->b; + k -= t->b; + e -= 16; + b = fill_bitbuffer(b, &k, e); + } while ((e = + (t = t->v.t + ((unsigned) b & mask_bits[e]))->e) > 16); + b >>= t->b; + k -= t->b; + if (e == 16) { /* then it's a literal */ + gunzip_window[w++] = (unsigned char) t->v.n; + if (w == gunzip_wsize) { + gunzip_outbuf_count = (w); + //flush_gunzip_window(); + w = 0; + return 1; // We have a block to read + } + } else { /* it's an EOB or a length */ + + /* exit if end of block */ + if (e == 15) { + break; + } + + /* get length of block to copy */ + b = fill_bitbuffer(b, &k, e); + n = t->v.n + ((unsigned) b & mask_bits[e]); + b >>= e; + k -= e; + + /* decode distance of block to copy */ + b = fill_bitbuffer(b, &k, bd); + if ((e = (t = td + ((unsigned) b & md))->e) > 16) + do { + if (e == 99) + bb_error_msg_and_die("inflate_codes error 2"); + b >>= t->b; + k -= t->b; + e -= 16; + b = fill_bitbuffer(b, &k, e); + } while ((e = + (t = + t->v.t + ((unsigned) b & mask_bits[e]))->e) > 16); + b >>= t->b; + k -= t->b; + b = fill_bitbuffer(b, &k, e); + d = w - t->v.n - ((unsigned) b & mask_bits[e]); + b >>= e; + k -= e; + + /* do the copy */ +do_copy: do { + n -= (e = + (e = + gunzip_wsize - ((d &= gunzip_wsize - 1) > w ? d : w)) > n ? n : e); + /* copy to new buffer to prevent possible overwrite */ + if (w - d >= e) { /* (this test assumes unsigned comparison) */ + memcpy(gunzip_window + w, gunzip_window + d, e); + w += e; + d += e; + } else { + /* do it slow to avoid memcpy() overlap */ + /* !NOMEMCPY */ + do { + gunzip_window[w++] = gunzip_window[d++]; + } while (--e); + } + if (w == gunzip_wsize) { + gunzip_outbuf_count = (w); + if (n) resumeCopy = 1; + else resumeCopy = 0; + //flush_gunzip_window(); + w = 0; + return 1; + } + } while (n); + resumeCopy = 0; + } + } + + /* restore the globals from the locals */ + gunzip_outbuf_count = w; /* restore global gunzip_window pointer */ + gunzip_bb = b; /* restore global bit buffer */ + gunzip_bk = k; + + /* normally just after call to inflate_codes, but save code by putting it here */ + /* free the decoding tables, return */ + huft_free(tl); + huft_free(td); + + /* done */ + return 0; +} + +static int inflate_stored(int my_n, int my_b_stored, int my_k_stored, int setup) +{ + static unsigned int n, b_stored, k_stored, w; + if (setup) { + n = my_n; + b_stored = my_b_stored; + k_stored = my_k_stored; + w = gunzip_outbuf_count; /* initialize gunzip_window position */ + return 0; // Don't do anything first time + } + + /* read and output the compressed data */ + while (n--) { + b_stored = fill_bitbuffer(b_stored, &k_stored, 8); + gunzip_window[w++] = (unsigned char) b_stored; + if (w == gunzip_wsize) { + gunzip_outbuf_count = (w); + //flush_gunzip_window(); + w = 0; + b_stored >>= 8; + k_stored -= 8; + return 1; // We have a block + } + b_stored >>= 8; + k_stored -= 8; + } + + /* restore the globals from the locals */ + gunzip_outbuf_count = w; /* restore global gunzip_window pointer */ + gunzip_bb = b_stored; /* restore global bit buffer */ + gunzip_bk = k_stored; + return 0; // Finished +} + +/* + * decompress an inflated block + * e: last block flag + * + * GLOBAL VARIABLES: bb, kk, + */ + // Return values: -1 = inflate_stored, -2 = inflate_codes +static int inflate_block(int *e) +{ + unsigned t; /* block type */ + unsigned int b; /* bit buffer */ + unsigned int k; /* number of bits in bit buffer */ + + /* make local bit buffer */ + + b = gunzip_bb; + k = gunzip_bk; + + /* read in last block bit */ + b = fill_bitbuffer(b, &k, 1); + *e = (int) b & 1; + b >>= 1; + k -= 1; + + /* read in block type */ + b = fill_bitbuffer(b, &k, 2); + t = (unsigned) b & 3; + b >>= 2; + k -= 2; + + /* restore the global bit buffer */ + gunzip_bb = b; + gunzip_bk = k; + + /* inflate that block type */ + switch (t) { + case 0: /* Inflate stored */ + { + unsigned int n; /* number of bytes in block */ + unsigned int b_stored; /* bit buffer */ + unsigned int k_stored; /* number of bits in bit buffer */ + + /* make local copies of globals */ + b_stored = gunzip_bb; /* initialize bit buffer */ + k_stored = gunzip_bk; + + /* go to byte boundary */ + n = k_stored & 7; + b_stored >>= n; + k_stored -= n; + + /* get the length and its complement */ + b_stored = fill_bitbuffer(b_stored, &k_stored, 16); + n = ((unsigned) b_stored & 0xffff); + b_stored >>= 16; + k_stored -= 16; + + b_stored = fill_bitbuffer(b_stored, &k_stored, 16); + if (n != (unsigned) ((~b_stored) & 0xffff)) { + return 1; /* error in compressed data */ + } + b_stored >>= 16; + k_stored -= 16; + + inflate_stored(n, b_stored, k_stored, 1); // Setup inflate_stored + return -1; + } + case 1: /* Inflate fixed + * decompress an inflated type 1 (fixed Huffman codes) block. We should + * either replace this with a custom decoder, or at least precompute the + * Huffman tables. + */ + { + int i; /* temporary variable */ + huft_t *tl; /* literal/length code table */ + huft_t *td; /* distance code table */ + unsigned int bl; /* lookup bits for tl */ + unsigned int bd; /* lookup bits for td */ + unsigned int l[288]; /* length list for huft_build */ + + /* set up literal table */ + for (i = 0; i < 144; i++) { + l[i] = 8; + } + for (; i < 256; i++) { + l[i] = 9; + } + for (; i < 280; i++) { + l[i] = 7; + } + for (; i < 288; i++) { /* make a complete, but wrong code set */ + l[i] = 8; + } + bl = 7; + if ((i = huft_build(l, 288, 257, cplens, cplext, &tl, &bl)) != 0) { + return i; + } + + /* set up distance table */ + for (i = 0; i < 30; i++) { /* make an incomplete code set */ + l[i] = 5; + } + bd = 5; + if ((i = huft_build(l, 30, 0, cpdist, cpdext, &td, &bd)) > 1) { + huft_free(tl); + return i; + } + + /* decompress until an end-of-block code */ + inflate_codes(tl, td, bl, bd, 1); // Setup inflate_codes + + /* huft_free code moved into inflate_codes */ + + return -2; + } + case 2: /* Inflate dynamic */ + { + const int dbits = 6; /* bits in base distance lookup table */ + const int lbits = 9; /* bits in base literal/length lookup table */ + + huft_t *tl; /* literal/length code table */ + huft_t *td; /* distance code table */ + unsigned int i; /* temporary variables */ + unsigned int j; + unsigned int l; /* last length */ + unsigned int m; /* mask for bit lengths table */ + unsigned int n; /* number of lengths to get */ + unsigned int bl; /* lookup bits for tl */ + unsigned int bd; /* lookup bits for td */ + unsigned int nb; /* number of bit length codes */ + unsigned int nl; /* number of literal/length codes */ + unsigned int nd; /* number of distance codes */ + + unsigned int ll[286 + 30]; /* literal/length and distance code lengths */ + unsigned int b_dynamic; /* bit buffer */ + unsigned int k_dynamic; /* number of bits in bit buffer */ + + /* make local bit buffer */ + b_dynamic = gunzip_bb; + k_dynamic = gunzip_bk; + + /* read in table lengths */ + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, 5); + nl = 257 + ((unsigned int) b_dynamic & 0x1f); /* number of literal/length codes */ + + b_dynamic >>= 5; + k_dynamic -= 5; + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, 5); + nd = 1 + ((unsigned int) b_dynamic & 0x1f); /* number of distance codes */ + + b_dynamic >>= 5; + k_dynamic -= 5; + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, 4); + nb = 4 + ((unsigned int) b_dynamic & 0xf); /* number of bit length codes */ + + b_dynamic >>= 4; + k_dynamic -= 4; + if (nl > 286 || nd > 30) { + return 1; /* bad lengths */ + } + + /* read in bit-length-code lengths */ + for (j = 0; j < nb; j++) { + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, 3); + ll[border[j]] = (unsigned int) b_dynamic & 7; + b_dynamic >>= 3; + k_dynamic -= 3; + } + for (; j < 19; j++) { + ll[border[j]] = 0; + } + + /* build decoding table for trees--single level, 7 bit lookup */ + bl = 7; + i = huft_build(ll, 19, 19, NULL, NULL, &tl, &bl); + if (i != 0) { + if (i == 1) { + huft_free(tl); + } + return i; /* incomplete code set */ + } + + /* read in literal and distance code lengths */ + n = nl + nd; + m = mask_bits[bl]; + i = l = 0; + while ((unsigned int) i < n) { + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, (unsigned int)bl); + j = (td = tl + ((unsigned int) b_dynamic & m))->b; + b_dynamic >>= j; + k_dynamic -= j; + j = td->v.n; + if (j < 16) { /* length of code in bits (0..15) */ + ll[i++] = l = j; /* save last length in l */ + } else if (j == 16) { /* repeat last length 3 to 6 times */ + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, 2); + j = 3 + ((unsigned int) b_dynamic & 3); + b_dynamic >>= 2; + k_dynamic -= 2; + if ((unsigned int) i + j > n) { + return 1; + } + while (j--) { + ll[i++] = l; + } + } else if (j == 17) { /* 3 to 10 zero length codes */ + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, 3); + j = 3 + ((unsigned int) b_dynamic & 7); + b_dynamic >>= 3; + k_dynamic -= 3; + if ((unsigned int) i + j > n) { + return 1; + } + while (j--) { + ll[i++] = 0; + } + l = 0; + } else { /* j == 18: 11 to 138 zero length codes */ + b_dynamic = fill_bitbuffer(b_dynamic, &k_dynamic, 7); + j = 11 + ((unsigned int) b_dynamic & 0x7f); + b_dynamic >>= 7; + k_dynamic -= 7; + if ((unsigned int) i + j > n) { + return 1; + } + while (j--) { + ll[i++] = 0; + } + l = 0; + } + } + + /* free decoding table for trees */ + huft_free(tl); + + /* restore the global bit buffer */ + gunzip_bb = b_dynamic; + gunzip_bk = k_dynamic; + + /* build the decoding tables for literal/length and distance codes */ + bl = lbits; + + if ((i = huft_build(ll, nl, 257, cplens, cplext, &tl, &bl)) != 0) { + if (i == 1) { + bb_error_msg_and_die("incomplete literal tree"); + huft_free(tl); + } + return i; /* incomplete code set */ + } + + bd = dbits; + if ((i = huft_build(ll + nl, nd, 0, cpdist, cpdext, &td, &bd)) != 0) { + if (i == 1) { + bb_error_msg_and_die("incomplete distance tree"); + huft_free(td); + } + huft_free(tl); + return i; /* incomplete code set */ + } + + /* decompress until an end-of-block code */ + inflate_codes(tl, td, bl, bd, 1); // Setup inflate_codes + + /* huft_free code moved into inflate_codes */ + + return -2; + } + default: + /* bad block type */ + bb_error_msg_and_die("bad block type %d", t); + } +} + +static void calculate_gunzip_crc(void) +{ + int n; + for (n = 0; n < gunzip_outbuf_count; n++) { + gunzip_crc = gunzip_crc_table[((int) gunzip_crc ^ (gunzip_window[n])) & 0xff] ^ (gunzip_crc >> 8); + } + gunzip_bytes_out += gunzip_outbuf_count; +} + +static int inflate_get_next_window(void) +{ + static int method = -1; // Method == -1 for stored, -2 for codes + static int e = 0; + static int needAnotherBlock = 1; + + gunzip_outbuf_count = 0; + + while(1) { + int ret; + + if (needAnotherBlock) { + if(e) { + calculate_gunzip_crc(); + e = 0; + needAnotherBlock = 1; + return 0; + } // Last block + method = inflate_block(&e); + needAnotherBlock = 0; + } + + switch (method) { + case -1: ret = inflate_stored(0,0,0,0); + break; + case -2: ret = inflate_codes(0,0,0,0,0); + break; + default: bb_error_msg_and_die("inflate error %d", method); + } + + if (ret == 1) { + calculate_gunzip_crc(); + return 1; // More data left + } else needAnotherBlock = 1; // End of that block + } + /* Doesnt get here */ +} + +/* Initialise bytebuffer, be careful not to overfill the buffer */ +void inflate_init(unsigned int bufsize) +{ + /* Set the bytebuffer size, default is same as gunzip_wsize */ + bytebuffer_max = bufsize + 8; + bytebuffer_offset = 4; + bytebuffer_size = 0; +} + +void inflate_cleanup(void) +{ + free(bytebuffer); +} + +USE_DESKTOP(long long) int +inflate_unzip(int in, int out) +{ + USE_DESKTOP(long long total = 0;) + ssize_t nwrote; + typedef void (*sig_type) (int); + + /* Allocate all global buffers (for DYN_ALLOC option) */ + gunzip_window = xmalloc(gunzip_wsize); + gunzip_outbuf_count = 0; + gunzip_bytes_out = 0; + gunzip_src_fd = in; + + /* initialize gunzip_window, bit buffer */ + gunzip_bk = 0; + gunzip_bb = 0; + + /* Create the crc table */ + gunzip_crc_table = crc32_filltable(0); + gunzip_crc = ~0; + + /* Allocate space for buffer */ + bytebuffer = xmalloc(bytebuffer_max); + + while(1) { + int ret = inflate_get_next_window(); + nwrote = full_write(out, gunzip_window, gunzip_outbuf_count); + if (nwrote == -1) { + bb_perror_msg("write"); + return -1; + } + USE_DESKTOP(total += nwrote;) + if (ret == 0) break; + } + + /* Cleanup */ + free(gunzip_window); + free(gunzip_crc_table); + + /* Store unused bytes in a global buffer so calling applets can access it */ + if (gunzip_bk >= 8) { + /* Undo too much lookahead. The next read will be byte aligned + * so we can discard unused bits in the last meaningful byte. */ + bytebuffer_offset--; + bytebuffer[bytebuffer_offset] = gunzip_bb & 0xff; + gunzip_bb >>= 8; + gunzip_bk -= 8; + } + return USE_DESKTOP(total) + 0; +} + +USE_DESKTOP(long long) int +inflate_gunzip(int in, int out) +{ + uint32_t stored_crc = 0; + unsigned int count; + USE_DESKTOP(long long total = )inflate_unzip(in, out); + + USE_DESKTOP(if (total < 0) return total;) + + /* top up the input buffer with the rest of the trailer */ + count = bytebuffer_size - bytebuffer_offset; + if (count < 8) { + xread(in, &bytebuffer[bytebuffer_size], 8 - count); + bytebuffer_size += 8 - count; + } + for (count = 0; count != 4; count++) { + stored_crc |= (bytebuffer[bytebuffer_offset] << (count * 8)); + bytebuffer_offset++; + } + + /* Validate decompression - crc */ + if (stored_crc != (~gunzip_crc)) { + bb_error_msg("crc error"); + return -1; + } + + /* Validate decompression - size */ + if (gunzip_bytes_out != + (bytebuffer[bytebuffer_offset] | (bytebuffer[bytebuffer_offset+1] << 8) | + (bytebuffer[bytebuffer_offset+2] << 16) | (bytebuffer[bytebuffer_offset+3] << 24))) { + bb_error_msg("incorrect length"); + return -1; + } + + return USE_DESKTOP(total) + 0; +} diff --git a/archival/libunarchive/filter_accept_all.c b/archival/libunarchive/filter_accept_all.c new file mode 100644 index 000000000..5c991cac4 --- /dev/null +++ b/archival/libunarchive/filter_accept_all.c @@ -0,0 +1,17 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 2002 by Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "unarchive.h" + +/* Accept any non-null name, its not really a filter at all */ +char filter_accept_all(archive_handle_t *archive_handle) +{ + if (archive_handle->file_header->name) + return EXIT_SUCCESS; + return EXIT_FAILURE; +} diff --git a/archival/libunarchive/filter_accept_list.c b/archival/libunarchive/filter_accept_list.c new file mode 100644 index 000000000..cfc1b0c58 --- /dev/null +++ b/archival/libunarchive/filter_accept_list.c @@ -0,0 +1,19 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 2002 by Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "unarchive.h" + +/* + * Accept names that are in the accept list, ignoring reject list. + */ +char filter_accept_list(archive_handle_t *archive_handle) +{ + if (find_list_entry(archive_handle->accept, archive_handle->file_header->name)) + return EXIT_SUCCESS; + return EXIT_FAILURE; +} diff --git a/archival/libunarchive/filter_accept_list_reassign.c b/archival/libunarchive/filter_accept_list_reassign.c new file mode 100644 index 000000000..04b678007 --- /dev/null +++ b/archival/libunarchive/filter_accept_list_reassign.c @@ -0,0 +1,48 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 2002 by Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include + +#include "libbb.h" +#include "unarchive.h" + +/* + * Reassign the subarchive metadata parser based on the filename extension + * e.g. if its a .tar.gz modify archive_handle->sub_archive to process a .tar.gz + * or if its a .tar.bz2 make archive_handle->sub_archive handle that + */ +char filter_accept_list_reassign(archive_handle_t *archive_handle) +{ + /* Check the file entry is in the accept list */ + if (find_list_entry(archive_handle->accept, archive_handle->file_header->name)) { + const char *name_ptr; + + /* Extract the last 2 extensions */ + name_ptr = strrchr(archive_handle->file_header->name, '.'); + + /* Modify the subarchive handler based on the extension */ +#ifdef CONFIG_FEATURE_DEB_TAR_GZ + if (strcmp(name_ptr, ".gz") == 0) { + archive_handle->action_data_subarchive = get_header_tar_gz; + return EXIT_SUCCESS; + } +#endif +#ifdef CONFIG_FEATURE_DEB_TAR_BZ2 + if (strcmp(name_ptr, ".bz2") == 0) { + archive_handle->action_data_subarchive = get_header_tar_bz2; + return EXIT_SUCCESS; + } +#endif + if (ENABLE_FEATURE_DEB_TAR_LZMA && !strcmp(name_ptr, ".lzma")) { + archive_handle->action_data_subarchive = get_header_tar_lzma; + return EXIT_SUCCESS; + } + } + return EXIT_FAILURE; +} diff --git a/archival/libunarchive/filter_accept_reject_list.c b/archival/libunarchive/filter_accept_reject_list.c new file mode 100644 index 000000000..79da350b6 --- /dev/null +++ b/archival/libunarchive/filter_accept_reject_list.c @@ -0,0 +1,33 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 2002 by Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "unarchive.h" + +/* + * Accept names that are in the accept list and not in the reject list + */ +char filter_accept_reject_list(archive_handle_t *archive_handle) +{ + const char *key = archive_handle->file_header->name; + const llist_t *reject_entry = find_list_entry2(archive_handle->reject, key); + const llist_t *accept_entry; + + /* If the key is in a reject list fail */ + if (reject_entry) { + return EXIT_FAILURE; + } + accept_entry = find_list_entry2(archive_handle->accept, key); + + /* Fail if an accept list was specified and the key wasnt in there */ + if ((accept_entry == NULL) && archive_handle->accept) { + return EXIT_FAILURE; + } + + /* Accepted */ + return EXIT_SUCCESS; +} diff --git a/archival/libunarchive/find_list_entry.c b/archival/libunarchive/find_list_entry.c new file mode 100644 index 000000000..d1afc72ce --- /dev/null +++ b/archival/libunarchive/find_list_entry.c @@ -0,0 +1,54 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 2002 by Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include "unarchive.h" + +/* Find a string in a shell pattern list */ +const llist_t *find_list_entry(const llist_t *list, const char *filename) +{ + while (list) { + if (fnmatch(list->data, filename, 0) == 0) { + return list; + } + list = list->link; + } + return NULL; +} + +/* Same, but compares only path components present in pattern + * (extra trailing path components in filename are assumed to match) + */ +const llist_t *find_list_entry2(const llist_t *list, const char *filename) +{ + char buf[PATH_MAX]; + int pattern_slash_cnt; + const char *c; + char *d; + + while (list) { + c = list->data; + pattern_slash_cnt = 0; + while (*c) + if (*c++ == '/') pattern_slash_cnt++; + c = filename; + d = buf; + /* paranoia is better that buffer overflows */ + while (*c && d != buf + sizeof(buf)-1) { + if (*c == '/' && --pattern_slash_cnt < 0) + break; + *d++ = *c++; + } + *d = '\0'; + if (fnmatch(list->data, buf, 0) == 0) { + return list; + } + list = list->link; + } + return NULL; +} diff --git a/archival/libunarchive/get_header_ar.c b/archival/libunarchive/get_header_ar.c new file mode 100644 index 000000000..7f8c81ca0 --- /dev/null +++ b/archival/libunarchive/get_header_ar.c @@ -0,0 +1,112 @@ +/* vi: set sw=4 ts=4: */ +/* Copyright 2001 Glenn McGrath. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include "unarchive.h" + +char get_header_ar(archive_handle_t *archive_handle) +{ + file_header_t *typed = archive_handle->file_header; + union { + char raw[60]; + struct { + char name[16]; + char date[12]; + char uid[6]; + char gid[6]; + char mode[8]; + char size[10]; + char magic[2]; + } formatted; + } ar; +#ifdef CONFIG_FEATURE_AR_LONG_FILENAMES + static char *ar_long_names; + static unsigned int ar_long_name_size; +#endif + + /* dont use xread as we want to handle the error ourself */ + if (read(archive_handle->src_fd, ar.raw, 60) != 60) { + /* End Of File */ + return EXIT_FAILURE; + } + + /* ar header starts on an even byte (2 byte aligned) + * '\n' is used for padding + */ + if (ar.raw[0] == '\n') { + /* fix up the header, we started reading 1 byte too early */ + memmove(ar.raw, &ar.raw[1], 59); + ar.raw[59] = xread_char(archive_handle->src_fd); + archive_handle->offset++; + } + archive_handle->offset += 60; + + /* align the headers based on the header magic */ + if ((ar.formatted.magic[0] != '`') || (ar.formatted.magic[1] != '\n')) { + bb_error_msg_and_die("invalid ar header"); + } + + typed->mode = xstrtoul(ar.formatted.mode, 8); + typed->mtime = xatou(ar.formatted.date); + typed->uid = xatou(ar.formatted.uid); + typed->gid = xatou(ar.formatted.gid); + typed->size = xatoul(ar.formatted.size); + + /* long filenames have '/' as the first character */ + if (ar.formatted.name[0] == '/') { +#ifdef CONFIG_FEATURE_AR_LONG_FILENAMES + if (ar.formatted.name[1] == '/') { + /* If the second char is a '/' then this entries data section + * stores long filename for multiple entries, they are stored + * in static variable long_names for use in future entries */ + ar_long_name_size = typed->size; + ar_long_names = xmalloc(ar_long_name_size); + xread(archive_handle->src_fd, ar_long_names, ar_long_name_size); + archive_handle->offset += ar_long_name_size; + /* This ar entries data section only contained filenames for other records + * they are stored in the static ar_long_names for future reference */ + return get_header_ar(archive_handle); /* Return next header */ + } else if (ar.formatted.name[1] == ' ') { + /* This is the index of symbols in the file for compilers */ + data_skip(archive_handle); + archive_handle->offset += typed->size; + return get_header_ar(archive_handle); /* Return next header */ + } else { + /* The number after the '/' indicates the offset in the ar data section + (saved in variable long_name) that conatains the real filename */ + const unsigned int long_offset = atoi(&ar.formatted.name[1]); + if (long_offset >= ar_long_name_size) { + bb_error_msg_and_die("can't resolve long filename"); + } + typed->name = xstrdup(ar_long_names + long_offset); + } +#else + bb_error_msg_and_die("long filenames not supported"); +#endif + } else { + /* short filenames */ + typed->name = xstrndup(ar.formatted.name, 16); + } + + typed->name[strcspn(typed->name, " /")] = '\0'; + + if (archive_handle->filter(archive_handle) == EXIT_SUCCESS) { + archive_handle->action_header(typed); + if (archive_handle->sub_archive) { + while (archive_handle->action_data_subarchive(archive_handle->sub_archive) == EXIT_SUCCESS); + } else { + archive_handle->action_data(archive_handle); + } + } else { + data_skip(archive_handle); + } + + archive_handle->offset += typed->size; + /* Set the file pointer to the correct spot, we may have been reading a compressed file */ + lseek(archive_handle->src_fd, archive_handle->offset, SEEK_SET); + + return EXIT_SUCCESS; +} diff --git a/archival/libunarchive/get_header_cpio.c b/archival/libunarchive/get_header_cpio.c new file mode 100644 index 000000000..e0272003c --- /dev/null +++ b/archival/libunarchive/get_header_cpio.c @@ -0,0 +1,160 @@ +/* vi: set sw=4 ts=4: */ +/* Copyright 2002 Laurence Anderson + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include "unarchive.h" + +typedef struct hardlinks_s { + char *name; + int inode; + struct hardlinks_s *next; +} hardlinks_t; + +char get_header_cpio(archive_handle_t *archive_handle) +{ + static hardlinks_t *saved_hardlinks = NULL; + static unsigned short pending_hardlinks = 0; + static int inode; + file_header_t *file_header = archive_handle->file_header; + char cpio_header[110]; + int namesize; + char dummy[16]; + int major, minor, nlink; + + if (pending_hardlinks) { /* Deal with any pending hardlinks */ + hardlinks_t *tmp, *oldtmp; + + tmp = saved_hardlinks; + oldtmp = NULL; + + file_header->link_name = file_header->name; + file_header->size = 0; + + while (tmp) { + if (tmp->inode != inode) { + tmp = tmp->next; + continue; + } + + file_header->name = tmp->name; + + if (archive_handle->filter(archive_handle) == EXIT_SUCCESS) { + archive_handle->action_data(archive_handle); + archive_handle->action_header(archive_handle->file_header); + } + + pending_hardlinks--; + + oldtmp = tmp; + tmp = tmp->next; + free(oldtmp->name); + free(oldtmp); + if (oldtmp == saved_hardlinks) + saved_hardlinks = tmp; + } + + file_header->name = file_header->link_name; + + if (pending_hardlinks > 1) { + bb_error_msg("error resolving hardlink: archive made by GNU cpio 2.0-2.2?"); + } + + /* No more pending hardlinks, read next file entry */ + pending_hardlinks = 0; + } + + /* There can be padding before archive header */ + data_align(archive_handle, 4); + + if (archive_xread_all_eof(archive_handle, (unsigned char*)cpio_header, 110) == 0) { + return EXIT_FAILURE; + } + archive_handle->offset += 110; + + if (strncmp(&cpio_header[0], "07070", 5) != 0 + || (cpio_header[5] != '1' && cpio_header[5] != '2') + ) { + bb_error_msg_and_die("unsupported cpio format, use newc or crc"); + } + + { + unsigned long tmpsize; + sscanf(cpio_header, "%6c%8x%8x%8x%8x%8x%8lx%8lx%16c%8x%8x%8x%8c", + dummy, &inode, (unsigned int*)&file_header->mode, + (unsigned int*)&file_header->uid, (unsigned int*)&file_header->gid, + &nlink, &file_header->mtime, &tmpsize, + dummy, &major, &minor, &namesize, dummy); + file_header->size = tmpsize; + } + + free(file_header->name); + file_header->name = xzalloc(namesize + 1); + /* Read in filename */ + xread(archive_handle->src_fd, file_header->name, namesize); + archive_handle->offset += namesize; + + /* Update offset amount and skip padding before file contents */ + data_align(archive_handle, 4); + + if (strcmp(file_header->name, "TRAILER!!!") == 0) { + /* Always round up */ + printf("%d blocks\n", (int) (archive_handle->offset % 512 ? + archive_handle->offset / 512 + 1 : + archive_handle->offset / 512 + )); + if (saved_hardlinks) { /* Bummer - we still have unresolved hardlinks */ + hardlinks_t *tmp = saved_hardlinks; + hardlinks_t *oldtmp = NULL; + while (tmp) { + bb_error_msg("%s not created: cannot resolve hardlink", tmp->name); + oldtmp = tmp; + tmp = tmp->next; + free(oldtmp->name); + free(oldtmp); + } + saved_hardlinks = NULL; + pending_hardlinks = 0; + } + return EXIT_FAILURE; + } + + if (S_ISLNK(file_header->mode)) { + file_header->link_name = xzalloc(file_header->size + 1); + xread(archive_handle->src_fd, file_header->link_name, file_header->size); + archive_handle->offset += file_header->size; + file_header->size = 0; /* Stop possible seeks in future */ + } else { + file_header->link_name = NULL; + } + if (nlink > 1 && !S_ISDIR(file_header->mode)) { + if (file_header->size == 0) { /* Put file on a linked list for later */ + hardlinks_t *new = xmalloc(sizeof(hardlinks_t)); + new->next = saved_hardlinks; + new->inode = inode; + /* name current allocated, freed later */ + new->name = file_header->name; + file_header->name = NULL; + saved_hardlinks = new; + return EXIT_SUCCESS; /* Skip this one */ + } + /* Found the file with data in */ + pending_hardlinks = nlink; + } + file_header->device = makedev(major, minor); + + if (archive_handle->filter(archive_handle) == EXIT_SUCCESS) { + archive_handle->action_data(archive_handle); + archive_handle->action_header(archive_handle->file_header); + } else { + data_skip(archive_handle); + } + + archive_handle->offset += file_header->size; + + free(file_header->link_name); + + return EXIT_SUCCESS; +} diff --git a/archival/libunarchive/get_header_tar.c b/archival/libunarchive/get_header_tar.c new file mode 100644 index 000000000..66c3314a1 --- /dev/null +++ b/archival/libunarchive/get_header_tar.c @@ -0,0 +1,275 @@ +/* vi: set sw=4 ts=4: */ +/* Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * FIXME: + * In privileged mode if uname and gname map to a uid and gid then use the + * mapped value instead of the uid/gid values in tar header + * + * References: + * GNU tar and star man pages, + * Opengroup's ustar interchange format, + * http://www.opengroup.org/onlinepubs/007904975/utilities/pax.html + */ + +#include "libbb.h" +#include "unarchive.h" + +#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS +static char *longname; +static char *linkname; +#else +enum { + longname = 0, + linkname = 0, +}; +#endif + +/* NB: _DESTROYS_ str[len] character! */ +static unsigned long long getOctal(char *str, int len) +{ + unsigned long long v; + /* Actually, tar header allows leading spaces also. + * Oh well, we will be liberal and skip this... + * The only downside probably is that we allow "-123" too :) + if (*str < '0' || *str > '7') + bb_error_msg_and_die("corrupted octal value in tar header"); + */ + str[len] = '\0'; + v = strtoull(str, &str, 8); + if (*str) + bb_error_msg_and_die("corrupted octal value in tar header"); + return v; +} +#define GET_OCTAL(a) getOctal((a), sizeof(a)) + +void BUG_tar_header_size(void); +char get_header_tar(archive_handle_t *archive_handle) +{ + static int end; + + file_header_t *file_header = archive_handle->file_header; + struct { + /* ustar header, Posix 1003.1 */ + char name[100]; /* 0-99 */ + char mode[8]; /* 100-107 */ + char uid[8]; /* 108-115 */ + char gid[8]; /* 116-123 */ + char size[12]; /* 124-135 */ + char mtime[12]; /* 136-147 */ + char chksum[8]; /* 148-155 */ + char typeflag; /* 156-156 */ + char linkname[100]; /* 157-256 */ + char magic[6]; /* 257-262 */ + char version[2]; /* 263-264 */ + char uname[32]; /* 265-296 */ + char gname[32]; /* 297-328 */ + char devmajor[8]; /* 329-336 */ + char devminor[8]; /* 337-344 */ + char prefix[155]; /* 345-499 */ + char padding[12]; /* 500-512 */ + } tar; + char *cp; + int sum, i; + int parse_names; + + if (sizeof(tar) != 512) + BUG_tar_header_size(); + again: + + /* Align header */ + data_align(archive_handle, 512); + + again_after_align: + + xread(archive_handle->src_fd, &tar, 512); + archive_handle->offset += 512; + + /* If there is no filename its an empty header */ + if (tar.name[0] == 0) { + if (end) { + /* This is the second consecutive empty header! End of archive! + * Read until the end to empty the pipe from gz or bz2 + */ + while (full_read(archive_handle->src_fd, &tar, 512) == 512) + /* repeat */; + return EXIT_FAILURE; + } + end = 1; + return EXIT_SUCCESS; + } + end = 0; + + /* Check header has valid magic, "ustar" is for the proper tar + * 0's are for the old tar format + */ + if (strncmp(tar.magic, "ustar", 5) != 0) { +#if ENABLE_FEATURE_TAR_OLDGNU_COMPATIBILITY + if (memcmp(tar.magic, "\0\0\0\0", 5) != 0) +#endif + bb_error_msg_and_die("invalid tar magic"); + } + /* Do checksum on headers */ + sum = ' ' * sizeof(tar.chksum); + for (i = 0; i < 148 ; i++) { + sum += ((char*)&tar)[i]; + } + for (i = 156; i < 512 ; i++) { + sum += ((char*)&tar)[i]; + } + /* This field does not need special treatment (getOctal) */ + if (sum != xstrtoul(tar.chksum, 8)) { + bb_error_msg_and_die("invalid tar header checksum"); + } + + /* 0 is reserved for high perf file, treat as normal file */ + if (!tar.typeflag) tar.typeflag = '0'; + parse_names = (tar.typeflag >= '0' && tar.typeflag <= '7'); + + /* getOctal trashes subsequent field, therefore we call it + * on fields in reverse order */ + if (tar.devmajor[0]) { + unsigned minor = GET_OCTAL(tar.devminor); + unsigned major = GET_OCTAL(tar.devmajor); + file_header->device = makedev(major, minor); + } + file_header->link_name = NULL; + if (!linkname && parse_names && tar.linkname[0]) { + /* we trash magic[0] here, it's ok */ + tar.linkname[sizeof(tar.linkname)] = '\0'; + file_header->link_name = xstrdup(tar.linkname); + /* FIXME: what if we have non-link object with link_name? */ + /* Will link_name be free()ed? */ + } + file_header->mtime = GET_OCTAL(tar.mtime); + file_header->size = GET_OCTAL(tar.size); + file_header->gid = GET_OCTAL(tar.gid); + file_header->uid = GET_OCTAL(tar.uid); + /* Set bits 0-11 of the files mode */ + file_header->mode = 07777 & GET_OCTAL(tar.mode); + + file_header->name = NULL; + if (!longname && parse_names) { + /* we trash mode[0] here, it's ok */ + tar.name[sizeof(tar.name)] = '\0'; + if (tar.prefix[0]) { + /* and padding[0] */ + tar.prefix[sizeof(tar.prefix)] = '\0'; + file_header->name = concat_path_file(tar.prefix, tar.name); + } else + file_header->name = xstrdup(tar.name); + } + + /* Set bits 12-15 of the files mode */ + /* (typeflag was not trashed because chksum does not use getOctal) */ + switch (tar.typeflag) { + /* busybox identifies hard links as being regular files with 0 size and a link name */ + case '1': + file_header->mode |= S_IFREG; + break; + case '7': + /* case 0: */ + case '0': +#if ENABLE_FEATURE_TAR_OLDGNU_COMPATIBILITY + if (last_char_is(file_header->name, '/')) { + file_header->mode |= S_IFDIR; + } else +#endif + file_header->mode |= S_IFREG; + break; + case '2': + file_header->mode |= S_IFLNK; + break; + case '3': + file_header->mode |= S_IFCHR; + break; + case '4': + file_header->mode |= S_IFBLK; + break; + case '5': + file_header->mode |= S_IFDIR; + break; + case '6': + file_header->mode |= S_IFIFO; + break; +#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS + case 'L': + /* free: paranoia: tar with several consecutive longnames */ + free(longname); + /* For paranoia reasons we allocate extra NUL char */ + longname = xzalloc(file_header->size + 1); + /* We read ASCIZ string, including NUL */ + xread(archive_handle->src_fd, longname, file_header->size); + archive_handle->offset += file_header->size; + /* return get_header_tar(archive_handle); */ + /* gcc 4.1.1 didn't optimize it into jump */ + /* so we will do it ourself, this also saves stack */ + goto again; + case 'K': + free(linkname); + linkname = xzalloc(file_header->size + 1); + xread(archive_handle->src_fd, linkname, file_header->size); + archive_handle->offset += file_header->size; + /* return get_header_tar(archive_handle); */ + goto again; + case 'D': /* GNU dump dir */ + case 'M': /* Continuation of multi volume archive */ + case 'N': /* Old GNU for names > 100 characters */ + case 'S': /* Sparse file */ + case 'V': /* Volume header */ +#endif + case 'g': /* pax global header */ + case 'x': { /* pax extended header */ + off_t sz; + bb_error_msg("warning: skipping header '%c'", tar.typeflag); + sz = (file_header->size + 511) & ~(off_t)511; + archive_handle->offset += sz; + sz >>= 9; /* sz /= 512 but w/o contortions for signed div */ + while (sz--) + xread(archive_handle->src_fd, &tar, 512); + /* return get_header_tar(archive_handle); */ + goto again_after_align; + } + default: + bb_error_msg_and_die("unknown typeflag: 0x%x", tar.typeflag); + } + +#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS + if (longname) { + file_header->name = longname; + longname = NULL; + } + if (linkname) { + file_header->link_name = linkname; + linkname = NULL; + } +#endif + if (!strncmp(file_header->name, "/../"+1, 3) + || strstr(file_header->name, "/../") + ) { + bb_error_msg_and_die("name with '..' encountered: '%s'", + file_header->name); + } + + /* Strip trailing '/' in directories */ + /* Must be done after mode is set as '/' is used to check if its a directory */ + cp = last_char_is(file_header->name, '/'); + + if (archive_handle->filter(archive_handle) == EXIT_SUCCESS) { + archive_handle->action_header(archive_handle->file_header); + /* Note that we kill the '/' only after action_header() */ + /* (like GNU tar 1.15.1: verbose mode outputs "dir/dir/") */ + if (cp) *cp = '\0'; + archive_handle->flags |= ARCHIVE_EXTRACT_QUIET; + archive_handle->action_data(archive_handle); + llist_add_to(&(archive_handle->passed), file_header->name); + } else { + data_skip(archive_handle); + free(file_header->name); + } + archive_handle->offset += file_header->size; + + free(file_header->link_name); + /* Do not free(file_header->name)! */ + + return EXIT_SUCCESS; +} diff --git a/archival/libunarchive/get_header_tar_bz2.c b/archival/libunarchive/get_header_tar_bz2.c new file mode 100644 index 000000000..e7a80fc41 --- /dev/null +++ b/archival/libunarchive/get_header_tar_bz2.c @@ -0,0 +1,27 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "libbb.h" +#include "unarchive.h" + +char get_header_tar_bz2(archive_handle_t *archive_handle) +{ + /* Can't lseek over pipes */ + archive_handle->seek = seek_by_read; + + archive_handle->src_fd = open_transformer(archive_handle->src_fd, uncompressStream); + archive_handle->offset = 0; + while (get_header_tar(archive_handle) == EXIT_SUCCESS) /**/; + + /* Can only do one file at a time */ + return EXIT_FAILURE; +} diff --git a/archival/libunarchive/get_header_tar_gz.c b/archival/libunarchive/get_header_tar_gz.c new file mode 100644 index 000000000..41c02e16a --- /dev/null +++ b/archival/libunarchive/get_header_tar_gz.c @@ -0,0 +1,31 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include + +#include "libbb.h" +#include "unarchive.h" + +char get_header_tar_gz(archive_handle_t *archive_handle) +{ + unsigned char magic[2]; + + /* Can't lseek over pipes */ + archive_handle->seek = seek_by_read; + + xread(archive_handle->src_fd, &magic, 2); + if ((magic[0] != 0x1f) || (magic[1] != 0x8b)) { + bb_error_msg_and_die("invalid gzip magic"); + } + + check_header_gzip(archive_handle->src_fd); + + archive_handle->src_fd = open_transformer(archive_handle->src_fd, inflate_gunzip); + archive_handle->offset = 0; + while (get_header_tar(archive_handle) == EXIT_SUCCESS) /**/; + + /* Can only do one file at a time */ + return EXIT_FAILURE; +} diff --git a/archival/libunarchive/get_header_tar_lzma.c b/archival/libunarchive/get_header_tar_lzma.c new file mode 100644 index 000000000..06b8daa0f --- /dev/null +++ b/archival/libunarchive/get_header_tar_lzma.c @@ -0,0 +1,22 @@ +/* vi: set sw=4 ts=4: */ +/* + * Small lzma deflate implementation. + * Copyright (C) 2006 Aurelien Jacobs + * + * Licensed under GPL v2, see file LICENSE in this tarball for details. + */ + +#include "unarchive.h" + +char get_header_tar_lzma(archive_handle_t * archive_handle) +{ + /* Can't lseek over pipes */ + archive_handle->seek = seek_by_read; + + archive_handle->src_fd = open_transformer(archive_handle->src_fd, unlzma); + archive_handle->offset = 0; + while (get_header_tar(archive_handle) == EXIT_SUCCESS) /**/; + + /* Can only do one file at a time */ + return EXIT_FAILURE; +} diff --git a/archival/libunarchive/header_list.c b/archival/libunarchive/header_list.c new file mode 100644 index 000000000..fb461a68e --- /dev/null +++ b/archival/libunarchive/header_list.c @@ -0,0 +1,11 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ +#include +#include "unarchive.h" + +void header_list(const file_header_t *file_header) +{ + puts(file_header->name); +} diff --git a/archival/libunarchive/header_skip.c b/archival/libunarchive/header_skip.c new file mode 100644 index 000000000..53242e011 --- /dev/null +++ b/archival/libunarchive/header_skip.c @@ -0,0 +1,10 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ +#include +#include "unarchive.h" + +void header_skip(const file_header_t *file_header ATTRIBUTE_UNUSED) +{ +} diff --git a/archival/libunarchive/header_verbose_list.c b/archival/libunarchive/header_verbose_list.c new file mode 100644 index 000000000..7b97e524c --- /dev/null +++ b/archival/libunarchive/header_verbose_list.c @@ -0,0 +1,31 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include "unarchive.h" + +void header_verbose_list(const file_header_t *file_header) +{ + struct tm *mtime = localtime(&(file_header->mtime)); + + printf("%s %d/%d %9"OFF_FMT"u %4u-%02u-%02u %02u:%02u:%02u %s", + bb_mode_string(file_header->mode), + file_header->uid, + file_header->gid, + file_header->size, + 1900 + mtime->tm_year, + 1 + mtime->tm_mon, + mtime->tm_mday, + mtime->tm_hour, + mtime->tm_min, + mtime->tm_sec, + file_header->name); + + if (file_header->link_name) { + printf(" -> %s", file_header->link_name); + } + /* putchar isnt used anywhere else i dont think */ + puts(""); +} diff --git a/archival/libunarchive/init_handle.c b/archival/libunarchive/init_handle.c new file mode 100644 index 000000000..309d329ea --- /dev/null +++ b/archival/libunarchive/init_handle.c @@ -0,0 +1,22 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include "unarchive.h" + +archive_handle_t *init_handle(void) +{ + archive_handle_t *archive_handle; + + /* Initialize default values */ + archive_handle = xzalloc(sizeof(archive_handle_t)); + archive_handle->file_header = xzalloc(sizeof(file_header_t)); + archive_handle->action_header = header_skip; + archive_handle->action_data = data_skip; + archive_handle->filter = filter_accept_all; + archive_handle->seek = seek_by_jump; + + return archive_handle; +} diff --git a/archival/libunarchive/open_transformer.c b/archival/libunarchive/open_transformer.c new file mode 100644 index 000000000..456d3e986 --- /dev/null +++ b/archival/libunarchive/open_transformer.c @@ -0,0 +1,44 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include + +#include "libbb.h" + +#include "unarchive.h" + +/* transformer(), more than meets the eye */ +int open_transformer(int src_fd, + USE_DESKTOP(long long) int (*transformer)(int src_fd, int dst_fd)) +{ + int fd_pipe[2]; + int pid; + + if (pipe(fd_pipe) != 0) { + bb_perror_msg_and_die("can't create pipe"); + } + + pid = fork(); + if (pid == -1) { + bb_perror_msg_and_die("fork failed"); + } + + if (pid == 0) { + /* child process */ + close(fd_pipe[0]); /* We don't wan't to read from the parent */ + // FIXME: error check? + transformer(src_fd, fd_pipe[1]); + close(fd_pipe[1]); /* Send EOF */ + close(src_fd); + exit(0); + /* notreached */ + } + + /* parent process */ + close(fd_pipe[1]); /* Don't want to write to the child */ + + return fd_pipe[0]; +} diff --git a/archival/libunarchive/seek_by_jump.c b/archival/libunarchive/seek_by_jump.c new file mode 100644 index 000000000..d605f61dd --- /dev/null +++ b/archival/libunarchive/seek_by_jump.c @@ -0,0 +1,24 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include + +#include "libbb.h" +#include "unarchive.h" + +void seek_by_jump(const archive_handle_t *archive_handle, const unsigned int amount) +{ + if (lseek(archive_handle->src_fd, (off_t) amount, SEEK_CUR) == (off_t) -1) { +#ifdef CONFIG_FEATURE_UNARCHIVE_TAPE + if (errno == ESPIPE) { + seek_by_read(archive_handle, amount); + } else +#endif + bb_perror_msg_and_die("seek failure"); + } +} diff --git a/archival/libunarchive/seek_by_read.c b/archival/libunarchive/seek_by_read.c new file mode 100644 index 000000000..d56f94b21 --- /dev/null +++ b/archival/libunarchive/seek_by_read.c @@ -0,0 +1,19 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include + +#include "unarchive.h" +#include "libbb.h" + +/* If we are reading through a pipe(), or from stdin then we can't lseek, + * we must read and discard the data to skip over it. + */ +void seek_by_read(const archive_handle_t *archive_handle, const unsigned int jump_size) +{ + if (jump_size) { + bb_copyfd_size(archive_handle->src_fd, -1, jump_size); + } +} diff --git a/archival/libunarchive/unpack_ar_archive.c b/archival/libunarchive/unpack_ar_archive.c new file mode 100644 index 000000000..6a84ae811 --- /dev/null +++ b/archival/libunarchive/unpack_ar_archive.c @@ -0,0 +1,22 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ +#include +#include +#include +#include "unarchive.h" +#include "libbb.h" + +void unpack_ar_archive(archive_handle_t *ar_archive) +{ + char magic[7]; + + xread(ar_archive->src_fd, magic, 7); + if (strncmp(magic, "!", 7) != 0) { + bb_error_msg_and_die("invalid ar magic"); + } + ar_archive->offset += 7; + + while (get_header_ar(ar_archive) == EXIT_SUCCESS); +} diff --git a/archival/rpm.c b/archival/rpm.c new file mode 100644 index 000000000..2ef7232f4 --- /dev/null +++ b/archival/rpm.c @@ -0,0 +1,319 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini rpm applet for busybox + * + * Copyright (C) 2001,2002 by Laurence Anderson + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include "unarchive.h" + +#define RPM_HEADER_MAGIC "\216\255\350" +#define RPM_CHAR_TYPE 1 +#define RPM_INT8_TYPE 2 +#define RPM_INT16_TYPE 3 +#define RPM_INT32_TYPE 4 +/* #define RPM_INT64_TYPE 5 ---- These aren't supported (yet) */ +#define RPM_STRING_TYPE 6 +#define RPM_BIN_TYPE 7 +#define RPM_STRING_ARRAY_TYPE 8 +#define RPM_I18NSTRING_TYPE 9 + +#define RPMTAG_NAME 1000 +#define RPMTAG_VERSION 1001 +#define RPMTAG_RELEASE 1002 +#define RPMTAG_SUMMARY 1004 +#define RPMTAG_DESCRIPTION 1005 +#define RPMTAG_BUILDTIME 1006 +#define RPMTAG_BUILDHOST 1007 +#define RPMTAG_SIZE 1009 +#define RPMTAG_VENDOR 1011 +#define RPMTAG_LICENSE 1014 +#define RPMTAG_PACKAGER 1015 +#define RPMTAG_GROUP 1016 +#define RPMTAG_URL 1020 +#define RPMTAG_PREIN 1023 +#define RPMTAG_POSTIN 1024 +#define RPMTAG_FILEFLAGS 1037 +#define RPMTAG_FILEUSERNAME 1039 +#define RPMTAG_FILEGROUPNAME 1040 +#define RPMTAG_SOURCERPM 1044 +#define RPMTAG_PREINPROG 1085 +#define RPMTAG_POSTINPROG 1086 +#define RPMTAG_PREFIXS 1098 +#define RPMTAG_DIRINDEXES 1116 +#define RPMTAG_BASENAMES 1117 +#define RPMTAG_DIRNAMES 1118 +#define RPMFILE_CONFIG (1 << 0) +#define RPMFILE_DOC (1 << 1) + +enum rpm_functions_e { + rpm_query = 1, + rpm_install = 2, + rpm_query_info = 4, + rpm_query_package = 8, + rpm_query_list = 16, + rpm_query_list_doc = 32, + rpm_query_list_config = 64 +}; + +typedef struct { + uint32_t tag; /* 4 byte tag */ + uint32_t type; /* 4 byte type */ + uint32_t offset; /* 4 byte offset */ + uint32_t count; /* 4 byte count */ +} rpm_index; + +static void *map; +static rpm_index **mytags; +static int tagcount; + +static void extract_cpio_gz(int fd); +static rpm_index **rpm_gettags(int fd, int *num_tags); +static int bsearch_rpmtag(const void *key, const void *item); +static char *rpm_getstring(int tag, int itemindex); +static int rpm_getint(int tag, int itemindex); +static int rpm_getcount(int tag); +static void fileaction_dobackup(char *filename, int fileref); +static void fileaction_setowngrp(char *filename, int fileref); +static void loop_through_files(int filetag, void (*fileaction)(char *filename, int fileref)); + +int rpm_main(int argc, char **argv) +{ + int opt = 0, func = 0, rpm_fd, offset; + + while ((opt = getopt(argc, argv, "iqpldc")) != -1) { + switch (opt) { + case 'i': // First arg: Install mode, with q: Information + if (!func) func |= rpm_install; + else func |= rpm_query_info; + break; + case 'q': // First arg: Query mode + if (!func) func |= rpm_query; + else bb_show_usage(); + break; + case 'p': // Query a package + func |= rpm_query_package; + break; + case 'l': // List files in a package + func |= rpm_query_list; + break; + case 'd': // List doc files in a package (implies list) + func |= rpm_query_list; + func |= rpm_query_list_doc; + break; + case 'c': // List config files in a package (implies list) + func |= rpm_query_list; + func |= rpm_query_list_config; + break; + default: + bb_show_usage(); + } + } + + if (optind == argc) bb_show_usage(); + while (optind < argc) { + rpm_fd = xopen(argv[optind], O_RDONLY); + mytags = rpm_gettags(rpm_fd, (int *) &tagcount); + offset = lseek(rpm_fd, 0, SEEK_CUR); + if (!mytags) { printf("Error reading rpm header\n"); exit(-1); } + map = mmap(0, offset > getpagesize() ? (offset + offset % getpagesize()) : getpagesize(), PROT_READ, MAP_PRIVATE, rpm_fd, 0); // Mimimum is one page + if (func & rpm_install) { + loop_through_files(RPMTAG_BASENAMES, fileaction_dobackup); /* Backup any config files */ + extract_cpio_gz(rpm_fd); // Extact the archive + loop_through_files(RPMTAG_BASENAMES, fileaction_setowngrp); /* Set the correct file uid/gid's */ + } else if (func & rpm_query && func & rpm_query_package) { + if (!((func & rpm_query_info) || (func & rpm_query_list))) { // If just a straight query, just give package name + printf("%s-%s-%s\n", rpm_getstring(RPMTAG_NAME, 0), rpm_getstring(RPMTAG_VERSION, 0), rpm_getstring(RPMTAG_RELEASE, 0)); + } + if (func & rpm_query_info) { + /* Do the nice printout */ + time_t bdate_time; + struct tm *bdate; + char bdatestring[50]; + printf("Name : %-29sRelocations: %s\n", rpm_getstring(RPMTAG_NAME, 0), rpm_getstring(RPMTAG_PREFIXS, 0) ? rpm_getstring(RPMTAG_PREFIXS, 0) : "(not relocateable)"); + printf("Version : %-34sVendor: %s\n", rpm_getstring(RPMTAG_VERSION, 0), rpm_getstring(RPMTAG_VENDOR, 0) ? rpm_getstring(RPMTAG_VENDOR, 0) : "(none)"); + bdate_time = rpm_getint(RPMTAG_BUILDTIME, 0); + bdate = localtime((time_t *) &bdate_time); + strftime(bdatestring, 50, "%a %d %b %Y %T %Z", bdate); + printf("Release : %-30sBuild Date: %s\n", rpm_getstring(RPMTAG_RELEASE, 0), bdatestring); + printf("Install date: %-30sBuild Host: %s\n", "(not installed)", rpm_getstring(RPMTAG_BUILDHOST, 0)); + printf("Group : %-30sSource RPM: %s\n", rpm_getstring(RPMTAG_GROUP, 0), rpm_getstring(RPMTAG_SOURCERPM, 0)); + printf("Size : %-33dLicense: %s\n", rpm_getint(RPMTAG_SIZE, 0), rpm_getstring(RPMTAG_LICENSE, 0)); + printf("URL : %s\n", rpm_getstring(RPMTAG_URL, 0)); + printf("Summary : %s\n", rpm_getstring(RPMTAG_SUMMARY, 0)); + printf("Description :\n%s\n", rpm_getstring(RPMTAG_DESCRIPTION, 0)); + } + if (func & rpm_query_list) { + int count, it, flags; + count = rpm_getcount(RPMTAG_BASENAMES); + for (it = 0; it < count; it++) { + flags = rpm_getint(RPMTAG_FILEFLAGS, it); + switch ((func & rpm_query_list_doc) + (func & rpm_query_list_config)) + { + case rpm_query_list_doc: if (!(flags & RPMFILE_DOC)) continue; break; + case rpm_query_list_config: if (!(flags & RPMFILE_CONFIG)) continue; break; + case rpm_query_list_doc + rpm_query_list_config: if (!((flags & RPMFILE_CONFIG) || (flags & RPMFILE_DOC))) continue; break; + } + printf("%s%s\n", rpm_getstring(RPMTAG_DIRNAMES, rpm_getint(RPMTAG_DIRINDEXES, it)), rpm_getstring(RPMTAG_BASENAMES, it)); + } + } + } + optind++; + free (mytags); + } + return 0; +} + +static void extract_cpio_gz(int fd) { + archive_handle_t *archive_handle; + unsigned char magic[2]; + + /* Initialise */ + archive_handle = init_handle(); + archive_handle->seek = seek_by_read; + //archive_handle->action_header = header_list; + archive_handle->action_data = data_extract_all; + archive_handle->flags |= ARCHIVE_PRESERVE_DATE; + archive_handle->flags |= ARCHIVE_CREATE_LEADING_DIRS; + archive_handle->src_fd = fd; + archive_handle->offset = 0; + + xread(archive_handle->src_fd, &magic, 2); + if ((magic[0] != 0x1f) || (magic[1] != 0x8b)) { + bb_error_msg_and_die("invalid gzip magic"); + } + check_header_gzip(archive_handle->src_fd); + xchdir("/"); // Install RPM's to root + + archive_handle->src_fd = open_transformer(archive_handle->src_fd, inflate_gunzip); + archive_handle->offset = 0; + while (get_header_cpio(archive_handle) == EXIT_SUCCESS); +} + + +static rpm_index **rpm_gettags(int fd, int *num_tags) +{ + rpm_index **tags = xzalloc(200 * sizeof(struct rpmtag *)); /* We should never need mode than 200, and realloc later */ + int pass, tagindex = 0; + lseek(fd, 96, SEEK_CUR); /* Seek past the unused lead */ + + for (pass = 0; pass < 2; pass++) { /* 1st pass is the signature headers, 2nd is the main stuff */ + struct { + char magic[3]; /* 3 byte magic: 0x8e 0xad 0xe8 */ + uint8_t version; /* 1 byte version number */ + uint32_t reserved; /* 4 bytes reserved */ + uint32_t entries; /* Number of entries in header (4 bytes) */ + uint32_t size; /* Size of store (4 bytes) */ + } header; + rpm_index *tmpindex; + int storepos; + + read(fd, &header, sizeof(header)); + if (strncmp((char *) &header.magic, RPM_HEADER_MAGIC, 3) != 0) return NULL; /* Invalid magic */ + if (header.version != 1) return NULL; /* This program only supports v1 headers */ + header.size = ntohl(header.size); + header.entries = ntohl(header.entries); + storepos = lseek(fd,0,SEEK_CUR) + header.entries * 16; + + while (header.entries--) { + tmpindex = tags[tagindex++] = xmalloc(sizeof(rpm_index)); + read(fd, tmpindex, sizeof(rpm_index)); + tmpindex->tag = ntohl(tmpindex->tag); tmpindex->type = ntohl(tmpindex->type); tmpindex->count = ntohl(tmpindex->count); + tmpindex->offset = storepos + ntohl(tmpindex->offset); + if (pass==0) tmpindex->tag -= 743; + } + lseek(fd, header.size, SEEK_CUR); /* Seek past store */ + if (pass==0) lseek(fd, (8 - (lseek(fd,0,SEEK_CUR) % 8)) % 8, SEEK_CUR); /* Skip padding to 8 byte boundary after reading signature headers */ + } + tags = realloc(tags, tagindex * sizeof(struct rpmtag *)); /* realloc tags to save space */ + *num_tags = tagindex; + return tags; /* All done, leave the file at the start of the gzipped cpio archive */ +} + +static int bsearch_rpmtag(const void *key, const void *item) +{ + int *tag = (int *)key; + rpm_index **tmp = (rpm_index **) item; + return (*tag - tmp[0]->tag); +} + +static int rpm_getcount(int tag) +{ + rpm_index **found; + found = bsearch(&tag, mytags, tagcount, sizeof(struct rpmtag *), bsearch_rpmtag); + if (!found) return 0; + else return found[0]->count; +} + +static char *rpm_getstring(int tag, int itemindex) +{ + rpm_index **found; + found = bsearch(&tag, mytags, tagcount, sizeof(struct rpmtag *), bsearch_rpmtag); + if (!found || itemindex >= found[0]->count) return NULL; + if (found[0]->type == RPM_STRING_TYPE || found[0]->type == RPM_I18NSTRING_TYPE || found[0]->type == RPM_STRING_ARRAY_TYPE) { + int n; + char *tmpstr = (char *) (map + found[0]->offset); + for (n=0; n < itemindex; n++) tmpstr = tmpstr + strlen(tmpstr) + 1; + return tmpstr; + } else return NULL; +} + +static int rpm_getint(int tag, int itemindex) +{ + rpm_index **found; + int n, *tmpint; + /* gcc throws warnings here when sizeof(void*)!=sizeof(int) ... + * it's ok to ignore it because tag won't be used as a pointer */ + found = bsearch(&tag, mytags, tagcount, sizeof(struct rpmtag *), bsearch_rpmtag); + if (!found || itemindex >= found[0]->count) return -1; + tmpint = (int *) (map + found[0]->offset); + if (found[0]->type == RPM_INT32_TYPE) { + for (n=0; ntype == RPM_INT16_TYPE) { + for (n=0; ntype == RPM_INT8_TYPE) { + for (n=0; n + * + * Note, that as of BusyBox-0.43, tar has been completely rewritten from the + * ground up. It still has remnants of the old code lying about, but it is + * very different now (i.e., cleaner, less global variables, etc.) + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Based in part in the tar implementation in sash + * Copyright (c) 1999 by David I. Bell + * Permission is granted to use, distribute, or modify this source, + * provided that this copyright notice remains intact. + * Permission to distribute sash derived code under the GPL has been granted. + * + * Based in part on the tar implementation from busybox-0.28 + * Copyright (C) 1995 Bruce Perens + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include "unarchive.h" +#include +#include + +#if ENABLE_FEATURE_TAR_CREATE + +/* Tar file constants */ + +#define TAR_BLOCK_SIZE 512 + +/* POSIX tar Header Block, from POSIX 1003.1-1990 */ +#define NAME_SIZE 100 +#define NAME_SIZE_STR "100" +struct TarHeader { /* byte offset */ + char name[NAME_SIZE]; /* 0-99 */ + char mode[8]; /* 100-107 */ + char uid[8]; /* 108-115 */ + char gid[8]; /* 116-123 */ + char size[12]; /* 124-135 */ + char mtime[12]; /* 136-147 */ + char chksum[8]; /* 148-155 */ + char typeflag; /* 156-156 */ + char linkname[NAME_SIZE]; /* 157-256 */ + char magic[6]; /* 257-262 */ + char version[2]; /* 263-264 */ + char uname[32]; /* 265-296 */ + char gname[32]; /* 297-328 */ + char devmajor[8]; /* 329-336 */ + char devminor[8]; /* 337-344 */ + char prefix[155]; /* 345-499 */ + char padding[12]; /* 500-512 (pad to exactly the TAR_BLOCK_SIZE) */ +}; +typedef struct TarHeader TarHeader; + +/* +** writeTarFile(), writeFileToTarball(), and writeTarHeader() are +** the only functions that deal with the HardLinkInfo structure. +** Even these functions use the xxxHardLinkInfo() functions. +*/ +typedef struct HardLinkInfo HardLinkInfo; +struct HardLinkInfo { + HardLinkInfo *next; /* Next entry in list */ + dev_t dev; /* Device number */ + ino_t ino; /* Inode number */ + short linkCount; /* (Hard) Link Count */ + char name[1]; /* Start of filename (must be last) */ +}; + +/* Some info to be carried along when creating a new tarball */ +struct TarBallInfo { + int tarFd; /* Open-for-write file descriptor + for the tarball */ + struct stat statBuf; /* Stat info for the tarball, letting + us know the inode and device that the + tarball lives, so we can avoid trying + to include the tarball into itself */ + int verboseFlag; /* Whether to print extra stuff or not */ + const llist_t *excludeList; /* List of files to not include */ + HardLinkInfo *hlInfoHead; /* Hard Link Tracking Information */ + HardLinkInfo *hlInfo; /* Hard Link Info for the current file */ +}; +typedef struct TarBallInfo TarBallInfo; + +/* A nice enum with all the possible tar file content types */ +enum TarFileType { + REGTYPE = '0', /* regular file */ + REGTYPE0 = '\0', /* regular file (ancient bug compat) */ + LNKTYPE = '1', /* hard link */ + SYMTYPE = '2', /* symbolic link */ + CHRTYPE = '3', /* character special */ + BLKTYPE = '4', /* block special */ + DIRTYPE = '5', /* directory */ + FIFOTYPE = '6', /* FIFO special */ + CONTTYPE = '7', /* reserved */ + GNULONGLINK = 'K', /* GNU long (>100 chars) link name */ + GNULONGNAME = 'L', /* GNU long (>100 chars) file name */ +}; +typedef enum TarFileType TarFileType; + +/* Might be faster (and bigger) if the dev/ino were stored in numeric order;) */ +static void addHardLinkInfo(HardLinkInfo ** hlInfoHeadPtr, + struct stat *statbuf, + const char *fileName) +{ + /* Note: hlInfoHeadPtr can never be NULL! */ + HardLinkInfo *hlInfo; + + hlInfo = xmalloc(sizeof(HardLinkInfo) + strlen(fileName)); + hlInfo->next = *hlInfoHeadPtr; + *hlInfoHeadPtr = hlInfo; + hlInfo->dev = statbuf->st_dev; + hlInfo->ino = statbuf->st_ino; + hlInfo->linkCount = statbuf->st_nlink; + strcpy(hlInfo->name, fileName); +} + +static void freeHardLinkInfo(HardLinkInfo ** hlInfoHeadPtr) +{ + HardLinkInfo *hlInfo; + HardLinkInfo *hlInfoNext; + + if (hlInfoHeadPtr) { + hlInfo = *hlInfoHeadPtr; + while (hlInfo) { + hlInfoNext = hlInfo->next; + free(hlInfo); + hlInfo = hlInfoNext; + } + *hlInfoHeadPtr = NULL; + } + return; +} + +/* Might be faster (and bigger) if the dev/ino were stored in numeric order;) */ +static HardLinkInfo *findHardLinkInfo(HardLinkInfo * hlInfo, struct stat *statbuf) +{ + while (hlInfo) { + if ((statbuf->st_ino == hlInfo->ino) && (statbuf->st_dev == hlInfo->dev)) + break; + hlInfo = hlInfo->next; + } + return hlInfo; +} + +/* Put an octal string into the specified buffer. + * The number is zero padded and possibly null terminated. + * Stores low-order bits only if whole value does not fit. */ +static void putOctal(char *cp, int len, off_t value) +{ + char tempBuffer[sizeof(off_t)*3+1]; + char *tempString = tempBuffer; + int width; + + width = sprintf(tempBuffer, "%0*"OFF_FMT"o", len, value); + tempString += (width - len); + + /* If string has leading zeroes, we can drop one */ + /* and field will have trailing '\0' */ + /* (increases chances of compat with other tars) */ + if (tempString[0] == '0') + tempString++; + + /* Copy the string to the field */ + memcpy(cp, tempString, len); +} +#define PUT_OCTAL(a, b) putOctal((a), sizeof(a), (b)) + +static void chksum_and_xwrite(int fd, struct TarHeader* hp) +{ + const unsigned char *cp; + int chksum, size; + + strcpy(hp->magic, "ustar "); + + /* Calculate and store the checksum (i.e., the sum of all of the bytes of + * the header). The checksum field must be filled with blanks for the + * calculation. The checksum field is formatted differently from the + * other fields: it has 6 digits, a null, then a space -- rather than + * digits, followed by a null like the other fields... */ + memset(hp->chksum, ' ', sizeof(hp->chksum)); + cp = (const unsigned char *) hp; + chksum = 0; + size = sizeof(*hp); + do { chksum += *cp++; } while (--size); + putOctal(hp->chksum, sizeof(hp->chksum)-1, chksum); + + /* Now write the header out to disk */ + xwrite(fd, hp, sizeof(*hp)); +} + +#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS +static void writeLongname(int fd, int type, const char *name, int dir) +{ + static const struct { + char mode[8]; /* 100-107 */ + char uid[8]; /* 108-115 */ + char gid[8]; /* 116-123 */ + char size[12]; /* 124-135 */ + char mtime[12]; /* 136-147 */ + } prefilled = { + "0000000", + "0000000", + "0000000", + "00000000000", + "00000000000", + }; + struct TarHeader header; + int size; + + dir = !!dir; /* normalize: 0/1 */ + size = strlen(name) + 1 + dir; /* GNU tar uses strlen+1 */ + /* + dir: account for possible '/' */ + + memset(&header, 0, sizeof(header)); + strcpy(header.name, "././@LongLink"); + memcpy(header.mode, prefilled.mode, sizeof(prefilled)); + PUT_OCTAL(header.size, size); + header.typeflag = type; + chksum_and_xwrite(fd, &header); + + /* Write filename[/] and pad the block. */ + /* dir=0: writes 'name', pads */ + /* dir=1: writes 'name', writes '/', pads */ + dir *= 2; + xwrite(fd, name, size - dir); + xwrite(fd, "/", dir); + size = (-size) & (TAR_BLOCK_SIZE-1); + memset(&header, 0, size); + xwrite(fd, &header, size); +} +#endif + +/* Write out a tar header for the specified file/directory/whatever */ +void BUG_tar_header_size(void); +static int writeTarHeader(struct TarBallInfo *tbInfo, + const char *header_name, const char *fileName, struct stat *statbuf) +{ + struct TarHeader header; + + if (sizeof(header) != 512) + BUG_tar_header_size(); + + memset(&header, 0, sizeof(struct TarHeader)); + + strncpy(header.name, header_name, sizeof(header.name)); + + /* POSIX says to mask mode with 07777. */ + PUT_OCTAL(header.mode, statbuf->st_mode & 07777); + PUT_OCTAL(header.uid, statbuf->st_uid); + PUT_OCTAL(header.gid, statbuf->st_gid); + memset(header.size, '0', sizeof(header.size)-1); /* Regular file size is handled later */ + PUT_OCTAL(header.mtime, statbuf->st_mtime); + + /* Enter the user and group names */ + safe_strncpy(header.uname, get_cached_username(statbuf->st_uid), sizeof(header.uname)); + safe_strncpy(header.gname, get_cached_groupname(statbuf->st_gid), sizeof(header.gname)); + + if (tbInfo->hlInfo) { + /* This is a hard link */ + header.typeflag = LNKTYPE; + strncpy(header.linkname, tbInfo->hlInfo->name, + sizeof(header.linkname)); +#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS + /* Write out long linkname if needed */ + if (header.linkname[sizeof(header.linkname)-1]) + writeLongname(tbInfo->tarFd, GNULONGLINK, + tbInfo->hlInfo->name, 0); +#endif + } else if (S_ISLNK(statbuf->st_mode)) { + char *lpath = xreadlink(fileName); + if (!lpath) /* Already printed err msg inside xreadlink() */ + return FALSE; + header.typeflag = SYMTYPE; + strncpy(header.linkname, lpath, sizeof(header.linkname)); +#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS + /* Write out long linkname if needed */ + if (header.linkname[sizeof(header.linkname)-1]) + writeLongname(tbInfo->tarFd, GNULONGLINK, lpath, 0); +#else + /* If it is larger than 100 bytes, bail out */ + if (header.linkname[sizeof(header.linkname)-1]) { + free(lpath); + bb_error_msg("names longer than "NAME_SIZE_STR" chars not supported"); + return FALSE; + } +#endif + free(lpath); + } else if (S_ISDIR(statbuf->st_mode)) { + header.typeflag = DIRTYPE; + /* Append '/' only if there is a space for it */ + if (!header.name[sizeof(header.name)-1]) + header.name[strlen(header.name)] = '/'; + } else if (S_ISCHR(statbuf->st_mode)) { + header.typeflag = CHRTYPE; + PUT_OCTAL(header.devmajor, major(statbuf->st_rdev)); + PUT_OCTAL(header.devminor, minor(statbuf->st_rdev)); + } else if (S_ISBLK(statbuf->st_mode)) { + header.typeflag = BLKTYPE; + PUT_OCTAL(header.devmajor, major(statbuf->st_rdev)); + PUT_OCTAL(header.devminor, minor(statbuf->st_rdev)); + } else if (S_ISFIFO(statbuf->st_mode)) { + header.typeflag = FIFOTYPE; + } else if (S_ISREG(statbuf->st_mode)) { + if (sizeof(statbuf->st_size) > 4 + && statbuf->st_size > (off_t)0777777777777LL + ) { + bb_error_msg_and_die("cannot store file '%s' " + "of size %"OFF_FMT"d, aborting", + fileName, statbuf->st_size); + } + header.typeflag = REGTYPE; + PUT_OCTAL(header.size, statbuf->st_size); + } else { + bb_error_msg("%s: unknown file type", fileName); + return FALSE; + } + +#if ENABLE_FEATURE_TAR_GNU_EXTENSIONS + /* Write out long name if needed */ + /* (we, like GNU tar, output long linkname *before* long name) */ + if (header.name[sizeof(header.name)-1]) + writeLongname(tbInfo->tarFd, GNULONGNAME, + header_name, S_ISDIR(statbuf->st_mode)); +#endif + + /* Now write the header out to disk */ + chksum_and_xwrite(tbInfo->tarFd, &header); + + /* Now do the verbose thing (or not) */ + if (tbInfo->verboseFlag) { + FILE *vbFd = stdout; + + if (tbInfo->tarFd == STDOUT_FILENO) /* If the archive goes to stdout, verbose to stderr */ + vbFd = stderr; + /* GNU "tar cvvf" prints "extended" listing a-la "ls -l" */ + /* We don't have such excesses here: for us "v" == "vv" */ + /* '/' is probably a GNUism */ + fprintf(vbFd, "%s%s\n", header_name, + S_ISDIR(statbuf->st_mode) ? "/" : ""); + } + + return TRUE; +} + +# if ENABLE_FEATURE_TAR_FROM +static int exclude_file(const llist_t *excluded_files, const char *file) +{ + while (excluded_files) { + if (excluded_files->data[0] == '/') { + if (fnmatch(excluded_files->data, file, + FNM_PATHNAME | FNM_LEADING_DIR) == 0) + return 1; + } else { + const char *p; + + for (p = file; p[0] != '\0'; p++) { + if ((p == file || p[-1] == '/') && p[0] != '/' && + fnmatch(excluded_files->data, p, + FNM_PATHNAME | FNM_LEADING_DIR) == 0) + return 1; + } + } + excluded_files = excluded_files->link; + } + + return 0; +} +# else +#define exclude_file(excluded_files, file) 0 +# endif + +static int writeFileToTarball(const char *fileName, struct stat *statbuf, + void *userData, int depth) +{ + struct TarBallInfo *tbInfo = (struct TarBallInfo *) userData; + const char *header_name; + int inputFileFd = -1; + + /* + * Check to see if we are dealing with a hard link. + * If so - + * Treat the first occurance of a given dev/inode as a file while + * treating any additional occurances as hard links. This is done + * by adding the file information to the HardLinkInfo linked list. + */ + tbInfo->hlInfo = NULL; + if (statbuf->st_nlink > 1) { + tbInfo->hlInfo = findHardLinkInfo(tbInfo->hlInfoHead, statbuf); + if (tbInfo->hlInfo == NULL) + addHardLinkInfo(&tbInfo->hlInfoHead, statbuf, fileName); + } + + /* It is against the rules to archive a socket */ + if (S_ISSOCK(statbuf->st_mode)) { + bb_error_msg("%s: socket ignored", fileName); + return TRUE; + } + + /* It is a bad idea to store the archive we are in the process of creating, + * so check the device and inode to be sure that this particular file isn't + * the new tarball */ + if (tbInfo->statBuf.st_dev == statbuf->st_dev && + tbInfo->statBuf.st_ino == statbuf->st_ino) { + bb_error_msg("%s: file is the archive; skipping", fileName); + return TRUE; + } + + header_name = fileName; + while (header_name[0] == '/') { + static int alreadyWarned = FALSE; + + if (alreadyWarned == FALSE) { + bb_error_msg("removing leading '/' from member names"); + alreadyWarned = TRUE; + } + header_name++; + } + +#if !ENABLE_FEATURE_TAR_GNU_EXTENSIONS + if (strlen(fileName) >= NAME_SIZE) { + bb_error_msg("names longer than "NAME_SIZE_STR" chars not supported"); + return TRUE; + } +#endif + + if (header_name[0] == '\0') + return TRUE; + + if (exclude_file(tbInfo->excludeList, header_name)) + return SKIP; + + /* Is this a regular file? */ + if (tbInfo->hlInfo == NULL && S_ISREG(statbuf->st_mode)) { + /* open the file we want to archive, and make sure all is well */ + inputFileFd = open(fileName, O_RDONLY); + if (inputFileFd < 0) { + bb_perror_msg("%s: cannot open", fileName); + return FALSE; + } + } + + /* Add an entry to the tarball */ + if (writeTarHeader(tbInfo, header_name, fileName, statbuf) == FALSE) { + return FALSE; + } + + /* If it was a regular file, write out the body */ + if (inputFileFd >= 0) { + off_t readSize = 0; + + /* write the file to the archive */ + readSize = bb_copyfd_size(inputFileFd, tbInfo->tarFd, statbuf->st_size); + /* readSize < 0 means that error was already reported */ + if (readSize != statbuf->st_size && readSize >= 0) { + /* Deadly. We record size into header first, */ + /* and then write out file. If file shrinks in between, */ + /* tar will be corrupted. So bail out. */ + /* NB: GNU tar 1.16 warns and pads with zeroes */ + /* or even seeks back and updates header */ + bb_error_msg_and_die("short read from %s, aborting", fileName); + } + /* Check that file did not grow in between? */ + /* if (safe_read(inputFileFd,1) == 1) warn but continue? */ + close(inputFileFd); + + /* Pad the file up to the tar block size */ + /* (a few tricks here in the name of code size) */ + readSize = (-(int)readSize) & (TAR_BLOCK_SIZE-1); + memset(bb_common_bufsiz1, 0, readSize); + xwrite(tbInfo->tarFd, bb_common_bufsiz1, readSize); + } + + return TRUE; +} + +static int writeTarFile(const int tar_fd, const int verboseFlag, + const unsigned long dereferenceFlag, const llist_t *include, + const llist_t *exclude, const int gzip) +{ + pid_t gzipPid = 0; + int errorFlag = FALSE; + struct TarBallInfo tbInfo; + + tbInfo.hlInfoHead = NULL; + + fchmod(tar_fd, 0644); + tbInfo.tarFd = tar_fd; + tbInfo.verboseFlag = verboseFlag; + + /* Store the stat info for the tarball's file, so + * can avoid including the tarball into itself.... */ + if (fstat(tbInfo.tarFd, &tbInfo.statBuf) < 0) + bb_perror_msg_and_die("cannot stat tar file"); + + if ((ENABLE_FEATURE_TAR_GZIP || ENABLE_FEATURE_TAR_BZIP2) && gzip) { + int gzipDataPipe[2] = { -1, -1 }; + int gzipStatusPipe[2] = { -1, -1 }; + volatile int vfork_exec_errno = 0; + char *zip_exec = (gzip == 1) ? "gzip" : "bzip2"; + + + if (pipe(gzipDataPipe) < 0 || pipe(gzipStatusPipe) < 0) + bb_perror_msg_and_die("pipe"); + + signal(SIGPIPE, SIG_IGN); /* we only want EPIPE on errors */ + +# if __GNUC__ + /* Avoid vfork clobbering */ + (void) &include; + (void) &errorFlag; + (void) &zip_exec; +# endif + + gzipPid = vfork(); + + if (gzipPid == 0) { + dup2(gzipDataPipe[0], 0); + close(gzipDataPipe[1]); + + dup2(tbInfo.tarFd, 1); + + close(gzipStatusPipe[0]); + fcntl(gzipStatusPipe[1], F_SETFD, FD_CLOEXEC); /* close on exec shows success */ + + execlp(zip_exec, zip_exec, "-f", NULL); + vfork_exec_errno = errno; + + close(gzipStatusPipe[1]); + exit(-1); + } else if (gzipPid > 0) { + close(gzipDataPipe[0]); + close(gzipStatusPipe[1]); + + while (1) { + char buf; + + int n = full_read(gzipStatusPipe[0], &buf, 1); + + if (n == 0 && vfork_exec_errno != 0) { + errno = vfork_exec_errno; + bb_perror_msg_and_die("cannot exec %s", zip_exec); + } else if ((n < 0) && (errno == EAGAIN || errno == EINTR)) + continue; /* try it again */ + break; + } + close(gzipStatusPipe[0]); + + tbInfo.tarFd = gzipDataPipe[1]; + } else bb_perror_msg_and_die("vfork gzip"); + } + + tbInfo.excludeList = exclude; + + /* Read the directory/files and iterate over them one at a time */ + while (include) { + if (!recursive_action(include->data, TRUE, dereferenceFlag, + FALSE, writeFileToTarball, writeFileToTarball, &tbInfo, 0)) + { + errorFlag = TRUE; + } + include = include->link; + } + /* Write two empty blocks to the end of the archive */ + memset(bb_common_bufsiz1, 0, 2*TAR_BLOCK_SIZE); + xwrite(tbInfo.tarFd, bb_common_bufsiz1, 2*TAR_BLOCK_SIZE); + + /* To be pedantically correct, we would check if the tarball + * is smaller than 20 tar blocks, and pad it if it was smaller, + * but that isn't necessary for GNU tar interoperability, and + * so is considered a waste of space */ + + /* Close so the child process (if any) will exit */ + close(tbInfo.tarFd); + + /* Hang up the tools, close up shop, head home */ + if (ENABLE_FEATURE_CLEAN_UP) + freeHardLinkInfo(&tbInfo.hlInfoHead); + + if (errorFlag) + bb_error_msg("error exit delayed from previous errors"); + + if (gzipPid && waitpid(gzipPid, NULL, 0) == -1) + bb_error_msg("waitpid failed"); + + return !errorFlag; +} +#else +int writeTarFile(const int tar_fd, const int verboseFlag, + const unsigned long dereferenceFlag, const llist_t *include, + const llist_t *exclude, const int gzip); +#endif /* tar_create */ + +#if ENABLE_FEATURE_TAR_FROM +static llist_t *append_file_list_to_list(llist_t *list) +{ + FILE *src_stream; + llist_t *cur = list; + llist_t *tmp; + char *line; + llist_t *newlist = NULL; + + while (cur) { + src_stream = xfopen(cur->data, "r"); + tmp = cur; + cur = cur->link; + free(tmp); + while ((line = xmalloc_getline(src_stream)) != NULL) { + /* kill trailing '/' unless the string is just "/" */ + char *cp = last_char_is(line, '/'); + if (cp > line) + *cp = '\0'; + llist_add_to(&newlist, line); + } + fclose(src_stream); + } + return newlist; +} +#else +#define append_file_list_to_list(x) 0 +#endif + +#if ENABLE_FEATURE_TAR_COMPRESS +static char get_header_tar_Z(archive_handle_t *archive_handle) +{ + /* Can't lseek over pipes */ + archive_handle->seek = seek_by_read; + + /* do the decompression, and cleanup */ + if (xread_char(archive_handle->src_fd) != 0x1f + || xread_char(archive_handle->src_fd) != 0x9d + ) { + bb_error_msg_and_die("invalid magic"); + } + + archive_handle->src_fd = open_transformer(archive_handle->src_fd, uncompress); + archive_handle->offset = 0; + while (get_header_tar(archive_handle) == EXIT_SUCCESS) + /* nothing */; + + /* Can only do one file at a time */ + return EXIT_FAILURE; +} +#else +#define get_header_tar_Z 0 +#endif + +enum { + OPTBIT_KEEP_OLD = 7, + USE_FEATURE_TAR_CREATE( OPTBIT_CREATE ,) + USE_FEATURE_TAR_CREATE( OPTBIT_DEREFERENCE ,) + USE_FEATURE_TAR_BZIP2( OPTBIT_BZIP2 ,) + USE_FEATURE_TAR_LZMA( OPTBIT_LZMA ,) + USE_FEATURE_TAR_FROM( OPTBIT_INCLUDE_FROM,) + USE_FEATURE_TAR_FROM( OPTBIT_EXCLUDE_FROM,) + USE_FEATURE_TAR_GZIP( OPTBIT_GZIP ,) + USE_FEATURE_TAR_COMPRESS(OPTBIT_COMPRESS ,) + OPTBIT_NOPRESERVE_OWN, + OPTBIT_NOPRESERVE_PERM, + OPT_TEST = 1 << 0, // t + OPT_EXTRACT = 1 << 1, // x + OPT_BASEDIR = 1 << 2, // C + OPT_TARNAME = 1 << 3, // f + OPT_2STDOUT = 1 << 4, // O + OPT_P = 1 << 5, // p + OPT_VERBOSE = 1 << 6, // v + OPT_KEEP_OLD = 1 << 7, // k + OPT_CREATE = USE_FEATURE_TAR_CREATE( (1<flags = ARCHIVE_CREATE_LEADING_DIRS + | ARCHIVE_PRESERVE_DATE + | ARCHIVE_EXTRACT_UNCONDITIONAL; + + /* Prepend '-' to the first argument if required */ + opt_complementary = "--:" // first arg is options + "tt:vv:" // count -t,-v + "?:" // bail out with usage instead of error return + "X::T::" // cumulative lists + "\xff::" // cumulative lists for --exclude + USE_FEATURE_TAR_CREATE("c:") "t:x:" // at least one of these is reqd + USE_FEATURE_TAR_CREATE("c--tx:t--cx:x--ct") // mutually exclusive + SKIP_FEATURE_TAR_CREATE("t--x:x--t"); // mutually exclusive +#if ENABLE_FEATURE_TAR_LONG_OPTIONS + applet_long_options = tar_long_options; +#endif + opt = getopt32(argc, argv, + "txC:f:Opvk" + USE_FEATURE_TAR_CREATE( "ch" ) + USE_FEATURE_TAR_BZIP2( "j" ) + USE_FEATURE_TAR_LZMA( "a" ) + USE_FEATURE_TAR_FROM( "T:X:") + USE_FEATURE_TAR_GZIP( "z" ) + USE_FEATURE_TAR_COMPRESS("Z" ) + , + &base_dir, // -C dir + &tar_filename, // -f filename + USE_FEATURE_TAR_FROM(&(tar_handle->accept),) // T + USE_FEATURE_TAR_FROM(&(tar_handle->reject),) // X + USE_FEATURE_TAR_FROM(&excludes ,) // --exclude + &verboseFlag, // combined count for -t and -v + &verboseFlag // combined count for -t and -v + ); + + if (verboseFlag) tar_handle->action_header = header_verbose_list; + if (verboseFlag == 1) tar_handle->action_header = header_list; + + if (opt & OPT_EXTRACT) + tar_handle->action_data = data_extract_all; + + if (opt & OPT_2STDOUT) + tar_handle->action_data = data_extract_to_stdout; + + if (opt & OPT_KEEP_OLD) + tar_handle->flags &= ~ARCHIVE_EXTRACT_UNCONDITIONAL; + + if (opt & OPT_NOPRESERVE_OWN) + tar_handle->flags |= ARCHIVE_NOPRESERVE_OWN; + + if (opt & OPT_NOPRESERVE_PERM) + tar_handle->flags |= ARCHIVE_NOPRESERVE_PERM; + + if (opt & OPT_GZIP) + get_header_ptr = get_header_tar_gz; + + if (opt & OPT_BZIP2) + get_header_ptr = get_header_tar_bz2; + + if (opt & OPT_LZMA) + get_header_ptr = get_header_tar_lzma; + + if (opt & OPT_COMPRESS) + get_header_ptr = get_header_tar_Z; + + if (ENABLE_FEATURE_TAR_FROM) { + tar_handle->reject = append_file_list_to_list(tar_handle->reject); + /* Append excludes to reject */ + while (excludes) { + llist_t *temp = excludes->link; + excludes->link = tar_handle->reject; + tar_handle->reject = excludes; + excludes = temp; + } + tar_handle->accept = append_file_list_to_list(tar_handle->accept); + } + + /* Check if we are reading from stdin */ + if (argv[optind] && *argv[optind] == '-') { + /* Default is to read from stdin, so just skip to next arg */ + optind++; + } + + /* Setup an array of filenames to work with */ + /* TODO: This is the same as in ar, separate function ? */ + while (optind < argc) { + /* kill trailing '/' unless the string is just "/" */ + char *cp = last_char_is(argv[optind], '/'); + if (cp > argv[optind]) + *cp = '\0'; + llist_add_to(&tar_handle->accept, argv[optind]); + optind++; + } + tar_handle->accept = rev_llist(tar_handle->accept); + + if (tar_handle->accept || tar_handle->reject) + tar_handle->filter = filter_accept_reject_list; + + /* Open the tar file */ + { + FILE *tar_stream; + int flags; + + if (opt & OPT_CREATE) { + /* Make sure there is at least one file to tar up. */ + if (tar_handle->accept == NULL) + bb_error_msg_and_die("empty archive"); + + tar_stream = stdout; + /* Mimicking GNU tar 1.15.1: */ + flags = O_WRONLY|O_CREAT|O_TRUNC; + /* was doing unlink; open(O_WRONLY|O_CREAT|O_EXCL); why? */ + } else { + tar_stream = stdin; + flags = O_RDONLY; + } + + if (tar_filename[0] == '-' && !tar_filename[1]) { + tar_handle->src_fd = fileno(tar_stream); + tar_handle->seek = seek_by_read; + } else { + tar_handle->src_fd = xopen(tar_filename, flags); + } + } + + if (base_dir) + xchdir(base_dir); + + /* create an archive */ + if (opt & OPT_CREATE) { + int zipMode = 0; + if (ENABLE_FEATURE_TAR_GZIP && get_header_ptr == get_header_tar_gz) + zipMode = 1; + if (ENABLE_FEATURE_TAR_BZIP2 && get_header_ptr == get_header_tar_bz2) + zipMode = 2; + writeTarFile(tar_handle->src_fd, verboseFlag, opt & OPT_DEREFERENCE, + tar_handle->accept, + tar_handle->reject, zipMode); + /* NB: writeTarFile() closes tar_handle->src_fd */ + return EXIT_SUCCESS; + } + + while (get_header_ptr(tar_handle) == EXIT_SUCCESS) + /* nothing */; + + /* Check that every file that should have been extracted was */ + while (tar_handle->accept) { + if (!find_list_entry(tar_handle->reject, tar_handle->accept->data) + && !find_list_entry(tar_handle->passed, tar_handle->accept->data) + ) { + bb_error_msg_and_die("%s: not found in archive", + tar_handle->accept->data); + } + tar_handle->accept = tar_handle->accept->link; + } + if (ENABLE_FEATURE_CLEAN_UP /* && tar_handle->src_fd != STDIN_FILENO */) + close(tar_handle->src_fd); + + return EXIT_SUCCESS; +} diff --git a/archival/uncompress.c b/archival/uncompress.c new file mode 100644 index 000000000..3c78961d5 --- /dev/null +++ b/archival/uncompress.c @@ -0,0 +1,94 @@ +/* vi: set sw=4 ts=4: */ +/* + * Uncompress applet for busybox (c) 2002 Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include "unarchive.h" + +#define GUNZIP_TO_STDOUT 1 +#define GUNZIP_FORCE 2 + +int uncompress_main(int argc, char **argv) +{ + int status = EXIT_SUCCESS; + unsigned long flags; + + flags = getopt32(argc, argv, "cf"); + + while (optind < argc) { + char *compressed_file = argv[optind++]; + char *delete_path = NULL; + char *uncompressed_file = NULL; + int src_fd; + int dst_fd; + + if (strcmp(compressed_file, "-") == 0) { + src_fd = STDIN_FILENO; + flags |= GUNZIP_TO_STDOUT; + } else { + src_fd = xopen(compressed_file, O_RDONLY); + } + + /* Check that the input is sane. */ + if (isatty(src_fd) && ((flags & GUNZIP_FORCE) == 0)) { + bb_error_msg_and_die + ("compressed data not read from terminal. Use -f to force it."); + } + + /* Set output filename and number */ + if (flags & GUNZIP_TO_STDOUT) { + dst_fd = STDOUT_FILENO; + } else { + struct stat stat_buf; + char *extension; + + uncompressed_file = xstrdup(compressed_file); + + extension = strrchr(uncompressed_file, '.'); + if (!extension || (strcmp(extension, ".Z") != 0)) { + bb_error_msg_and_die("invalid extension"); + } + *extension = '\0'; + + /* Open output file */ + xstat(compressed_file, &stat_buf); + dst_fd = xopen3(uncompressed_file, + O_WRONLY | O_CREAT | O_TRUNC, + stat_buf.st_mode); + + /* If unzip succeeds remove the old file */ + delete_path = compressed_file; + } + + /* do the decompression, and cleanup */ + if ((xread_char(src_fd) != 0x1f) || (xread_char(src_fd) != 0x9d)) { + bb_error_msg_and_die("invalid magic"); + } + + status = uncompress(src_fd, dst_fd); + + if ((status != EXIT_SUCCESS) && (uncompressed_file)) { + /* Unzip failed, remove the uncomressed file instead of compressed file */ + delete_path = uncompressed_file; + } + + if (dst_fd != STDOUT_FILENO) { + close(dst_fd); + } + if (src_fd != STDIN_FILENO) { + close(src_fd); + } + + /* delete_path will be NULL if in test mode or from stdin */ + if (delete_path && (unlink(delete_path) == -1)) { + bb_error_msg_and_die("cannot remove %s", delete_path); + } + + free(uncompressed_file); + } + + return status; +} diff --git a/archival/unlzma.c b/archival/unlzma.c new file mode 100644 index 000000000..46fbefdc0 --- /dev/null +++ b/archival/unlzma.c @@ -0,0 +1,64 @@ +/* vi: set sw=4 ts=4: */ +/* + * Small lzma deflate implementation. + * Copyright (C) 2006 Aurelien Jacobs + * + * Based on bunzip.c from busybox + * + * Licensed under GPL v2, see file LICENSE in this tarball for details. + */ + +/* Why our g[un]zip/bunzip2 are so ugly compared to this beauty? */ + +#include "busybox.h" +#include "unarchive.h" + +#define UNLZMA_OPT_STDOUT 1 + +int unlzma_main(int argc, char **argv) +{ + USE_DESKTOP(long long) int status; + char *filename; + unsigned opt; + int src_fd, dst_fd; + + opt = getopt32(argc, argv, "c"); + + /* Set input filename and number */ + filename = argv[optind]; + if (filename && (filename[0] != '-') && (filename[1] != '\0')) { + /* Open input file */ + src_fd = xopen(filename, O_RDONLY); + } else { + src_fd = STDIN_FILENO; + filename = 0; + } + + /* if called as lzmacat force the stdout flag */ + if ((opt & UNLZMA_OPT_STDOUT) || applet_name[4] == 'c') + filename = 0; + + if (filename) { + struct stat stat_buf; + /* bug: char *extension = filename + strlen(filename) - 5; */ + char *extension = strrchr(filename, '.'); + if (!extension || strcmp(extension, ".lzma") != 0) { + bb_error_msg_and_die("invalid extension"); + } + xstat(filename, &stat_buf); + *extension = '\0'; + dst_fd = xopen3(filename, O_WRONLY | O_CREAT | O_TRUNC, + stat_buf.st_mode); + } else + dst_fd = STDOUT_FILENO; + status = unlzma(src_fd, dst_fd); + if (filename) { + if (status >= 0) /* if success delete src, else delete dst */ + filename[strlen(filename)] = '.'; + if (unlink(filename) < 0) { + bb_error_msg_and_die("cannot remove %s", filename); + } + } + + return (status < 0); +} diff --git a/archival/unzip.c b/archival/unzip.c new file mode 100644 index 000000000..8b1c281c4 --- /dev/null +++ b/archival/unzip.c @@ -0,0 +1,386 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini unzip implementation for busybox + * + * Copyright (C) 2004 by Ed Clark + * + * Loosely based on original busybox unzip applet by Laurence Anderson. + * All options and features should work in this version. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +/* For reference see + * http://www.pkware.com/company/standards/appnote/ + * http://www.info-zip.org/pub/infozip/doc/appnote-iz-latest.zip + */ + +/* TODO + * Endian issues + * Zip64 + other methods + * Improve handling of zip format, ie. + * - deferred CRC, comp. & uncomp. lengths (zip header flags bit 3) + * - unix file permissions, etc. + * - central directory + */ + +#include "busybox.h" +#include "unarchive.h" + +#define ZIP_FILEHEADER_MAGIC SWAP_LE32(0x04034b50) +#define ZIP_CDS_MAGIC SWAP_LE32(0x02014b50) +#define ZIP_CDS_END_MAGIC SWAP_LE32(0x06054b50) +#define ZIP_DD_MAGIC SWAP_LE32(0x08074b50) + +extern unsigned int gunzip_crc; +extern unsigned int gunzip_bytes_out; + +typedef union { + unsigned char raw[26]; + struct { + unsigned short version; /* 0-1 */ + unsigned short flags; /* 2-3 */ + unsigned short method; /* 4-5 */ + unsigned short modtime; /* 6-7 */ + unsigned short moddate; /* 8-9 */ + unsigned int crc32 ATTRIBUTE_PACKED; /* 10-13 */ + unsigned int cmpsize ATTRIBUTE_PACKED; /* 14-17 */ + unsigned int ucmpsize ATTRIBUTE_PACKED; /* 18-21 */ + unsigned short filename_len; /* 22-23 */ + unsigned short extra_len; /* 24-25 */ + } formatted ATTRIBUTE_PACKED; +} zip_header_t; + +static void unzip_skip(int fd, off_t skip) +{ + if (lseek(fd, skip, SEEK_CUR) == (off_t)-1) { + if ((errno != ESPIPE) || (bb_copyfd_size(fd, -1, skip) != skip)) { + bb_error_msg_and_die("seek failure"); + } + } +} + +static void unzip_create_leading_dirs(char *fn) +{ + /* Create all leading directories */ + char *name = xstrdup(fn); + if (bb_make_directory(dirname(name), 0777, FILEUTILS_RECUR)) { + bb_error_msg_and_die("exiting"); /* bb_make_directory is noisy */ + } + free(name); +} + +static int unzip_extract(zip_header_t *zip_header, int src_fd, int dst_fd) +{ + if (zip_header->formatted.method == 0) { + /* Method 0 - stored (not compressed) */ + off_t size = zip_header->formatted.ucmpsize; + if (size && (bb_copyfd_size(src_fd, dst_fd, size) != size)) { + bb_error_msg_and_die("cannot complete extraction"); + } + + } else { + /* Method 8 - inflate */ + inflate_init(zip_header->formatted.cmpsize); + inflate_unzip(src_fd, dst_fd); + inflate_cleanup(); + /* Validate decompression - crc */ + if (zip_header->formatted.crc32 != (gunzip_crc ^ 0xffffffffL)) { + bb_error_msg("invalid compressed data--%s error", "crc"); + return 1; + } + /* Validate decompression - size */ + if (zip_header->formatted.ucmpsize != gunzip_bytes_out) { + bb_error_msg("invalid compressed data--%s error", "length"); + return 1; + } + } + return 0; +} + +int unzip_main(int argc, char **argv) +{ + zip_header_t zip_header; + enum {v_silent, v_normal, v_list} verbosity = v_normal; + enum {o_prompt, o_never, o_always} overwrite = o_prompt; + unsigned int total_size = 0; + unsigned int total_entries = 0; + int src_fd = -1, dst_fd = -1; + char *src_fn = NULL, *dst_fn = NULL; + llist_t *zaccept = NULL; + llist_t *zreject = NULL; + char *base_dir = NULL; + int failed, i, opt, opt_range = 0, list_header_done = 0; + char key_buf[512]; + struct stat stat_buf; + + while((opt = getopt(argc, argv, "-d:lnopqx")) != -1) { + switch (opt_range) { + case 0: /* Options */ + switch (opt) { + case 'l': /* List */ + verbosity = v_list; + break; + + case 'n': /* Never overwrite existing files */ + overwrite = o_never; + break; + + case 'o': /* Always overwrite existing files */ + overwrite = o_always; + break; + + case 'p': /* Extract files to stdout and fall through to set verbosity */ + dst_fd = STDOUT_FILENO; + + case 'q': /* Be quiet */ + verbosity = (verbosity == v_normal) ? v_silent : verbosity; + break; + + case 1 : /* The zip file */ + src_fn = xstrndup(optarg, strlen(optarg)+4); + opt_range++; + break; + + default: + bb_show_usage(); + + } + break; + + case 1: /* Include files */ + if (opt == 1) { + llist_add_to(&zaccept, optarg); + + } else if (opt == 'd') { + base_dir = optarg; + opt_range += 2; + + } else if (opt == 'x') { + opt_range++; + + } else { + bb_show_usage(); + } + break; + + case 2 : /* Exclude files */ + if (opt == 1) { + llist_add_to(&zreject, optarg); + + } else if (opt == 'd') { /* Extract to base directory */ + base_dir = optarg; + opt_range++; + + } else { + bb_show_usage(); + } + break; + + default: + bb_show_usage(); + } + } + + if (src_fn == NULL) { + bb_show_usage(); + } + + /* Open input file */ + if (strcmp("-", src_fn) == 0) { + src_fd = STDIN_FILENO; + /* Cannot use prompt mode since zip data is arriving on STDIN */ + overwrite = (overwrite == o_prompt) ? o_never : overwrite; + + } else { + static const char *const extn[] = {"", ".zip", ".ZIP"}; + int orig_src_fn_len = strlen(src_fn); + for(i = 0; (i < 3) && (src_fd == -1); i++) { + strcpy(src_fn + orig_src_fn_len, extn[i]); + src_fd = open(src_fn, O_RDONLY); + } + if (src_fd == -1) { + src_fn[orig_src_fn_len] = 0; + bb_error_msg_and_die("cannot open %s, %s.zip, %s.ZIP", src_fn, src_fn, src_fn); + } + } + + /* Change dir if necessary */ + if (base_dir) + xchdir(base_dir); + + if (verbosity != v_silent) + printf("Archive: %s\n", src_fn); + + failed = 0; + + while (1) { + unsigned int magic; + + /* Check magic number */ + xread(src_fd, &magic, 4); + if (magic == ZIP_CDS_MAGIC) { + break; + } else if (magic != ZIP_FILEHEADER_MAGIC) { + bb_error_msg_and_die("invalid zip magic %08X", magic); + } + + /* Read the file header */ + xread(src_fd, zip_header.raw, 26); + zip_header.formatted.version = SWAP_LE32(zip_header.formatted.version); + zip_header.formatted.flags = SWAP_LE32(zip_header.formatted.flags); + zip_header.formatted.method = SWAP_LE32(zip_header.formatted.method); + zip_header.formatted.modtime = SWAP_LE32(zip_header.formatted.modtime); + zip_header.formatted.moddate = SWAP_LE32(zip_header.formatted.moddate); + zip_header.formatted.crc32 = SWAP_LE32(zip_header.formatted.crc32); + zip_header.formatted.cmpsize = SWAP_LE32(zip_header.formatted.cmpsize); + zip_header.formatted.ucmpsize = SWAP_LE32(zip_header.formatted.ucmpsize); + zip_header.formatted.filename_len = SWAP_LE32(zip_header.formatted.filename_len); + zip_header.formatted.extra_len = SWAP_LE32(zip_header.formatted.extra_len); + if ((zip_header.formatted.method != 0) && (zip_header.formatted.method != 8)) { + bb_error_msg_and_die("unsupported compression method %d", zip_header.formatted.method); + } + + /* Read filename */ + free(dst_fn); + dst_fn = xzalloc(zip_header.formatted.filename_len + 1); + xread(src_fd, dst_fn, zip_header.formatted.filename_len); + + /* Skip extra header bytes */ + unzip_skip(src_fd, zip_header.formatted.extra_len); + + if ((verbosity == v_list) && !list_header_done){ + printf(" Length Date Time Name\n" + " -------- ---- ---- ----\n"); + list_header_done = 1; + } + + /* Filter zip entries */ + if (find_list_entry(zreject, dst_fn) || + (zaccept && !find_list_entry(zaccept, dst_fn))) { /* Skip entry */ + i = 'n'; + + } else { /* Extract entry */ + total_size += zip_header.formatted.ucmpsize; + + if (verbosity == v_list) { /* List entry */ + unsigned int dostime = zip_header.formatted.modtime | (zip_header.formatted.moddate << 16); + printf("%9u %02u-%02u-%02u %02u:%02u %s\n", + zip_header.formatted.ucmpsize, + (dostime & 0x01e00000) >> 21, + (dostime & 0x001f0000) >> 16, + (((dostime & 0xfe000000) >> 25) + 1980) % 100, + (dostime & 0x0000f800) >> 11, + (dostime & 0x000007e0) >> 5, + dst_fn); + total_entries++; + i = 'n'; + + } else if (dst_fd == STDOUT_FILENO) { /* Extracting to STDOUT */ + i = -1; + + } else if (last_char_is(dst_fn, '/')) { /* Extract directory */ + if (stat(dst_fn, &stat_buf) == -1) { + if (errno != ENOENT) { + bb_perror_msg_and_die("cannot stat '%s'",dst_fn); + } + if (verbosity == v_normal) { + printf(" creating: %s\n", dst_fn); + } + unzip_create_leading_dirs(dst_fn); + if (bb_make_directory(dst_fn, 0777, 0)) { + bb_error_msg_and_die("exiting"); + } + } else { + if (!S_ISDIR(stat_buf.st_mode)) { + bb_error_msg_and_die("'%s' exists but is not directory", dst_fn); + } + } + i = 'n'; + + } else { /* Extract file */ + _check_file: + if (stat(dst_fn, &stat_buf) == -1) { /* File does not exist */ + if (errno != ENOENT) { + bb_perror_msg_and_die("cannot stat '%s'",dst_fn); + } + i = 'y'; + + } else { /* File already exists */ + if (overwrite == o_never) { + i = 'n'; + + } else if (S_ISREG(stat_buf.st_mode)) { /* File is regular file */ + if (overwrite == o_always) { + i = 'y'; + } else { + printf("replace %s? [y]es, [n]o, [A]ll, [N]one, [r]ename: ", dst_fn); + if (!fgets(key_buf, 512, stdin)) { + bb_perror_msg_and_die("cannot read input"); + } + i = key_buf[0]; + } + + } else { /* File is not regular file */ + bb_error_msg_and_die("'%s' exists but is not regular file",dst_fn); + } + } + } + } + + switch (i) { + case 'A': + overwrite = o_always; + case 'y': /* Open file and fall into unzip */ + unzip_create_leading_dirs(dst_fn); + dst_fd = xopen(dst_fn, O_WRONLY | O_CREAT | O_TRUNC); + case -1: /* Unzip */ + if (verbosity == v_normal) { + printf(" inflating: %s\n", dst_fn); + } + if (unzip_extract(&zip_header, src_fd, dst_fd)) { + failed = 1; + } + if (dst_fd != STDOUT_FILENO) { + /* closing STDOUT is potentially bad for future business */ + close(dst_fd); + } + break; + + case 'N': + overwrite = o_never; + case 'n': + /* Skip entry data */ + unzip_skip(src_fd, zip_header.formatted.cmpsize); + break; + + case 'r': + /* Prompt for new name */ + printf("new name: "); + if (!fgets(key_buf, 512, stdin)) { + bb_perror_msg_and_die("cannot read input"); + } + free(dst_fn); + dst_fn = xstrdup(key_buf); + chomp(dst_fn); + goto _check_file; + + default: + printf("error: invalid response [%c]\n",(char)i); + goto _check_file; + } + + /* Data descriptor section */ + if (zip_header.formatted.flags & 4) { + /* skip over duplicate crc, compressed size and uncompressed size */ + unzip_skip(src_fd, 12); + } + } + + if (verbosity == v_list) { + printf(" -------- -------\n" + "%9d %d files\n", total_size, total_entries); + } + + return failed; +} diff --git a/console-tools/Config.in b/console-tools/Config.in new file mode 100644 index 000000000..4a5710de6 --- /dev/null +++ b/console-tools/Config.in @@ -0,0 +1,105 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Console Utilities" + +config CHVT + bool "chvt" + default n + help + This program is used to change to another terminal. + Example: chvt 4 (change to terminal /dev/tty4) + +config CLEAR + bool "clear" + default n + help + This program clears the terminal screen. + +config DEALLOCVT + bool "deallocvt" + default n + help + This program deallocates unused virtual consoles. + +config DUMPKMAP + bool "dumpkmap" + default n + help + This program dumps the kernel's keyboard translation table to + stdout, in binary format. You can then use loadkmap to load it. + +config LOADFONT + bool "loadfont" + default n + help + This program loads a console font from standard input. + +config LOADKMAP + bool "loadkmap" + default n + help + This program loads a keyboard translation table from + standard input. + +config OPENVT + bool "openvt" + default n + help + This program is used to start a command on an unused + virtual terminal. + +config RESET + bool "reset" + default n + help + This program is used to reset the terminal screen, if it + gets messed up. + +config RESIZE + bool "resize" + default n + help + This program is used to (re)set the width and height of your current + terminal. + +config FEATURE_RESIZE_PRINT + bool "print environment variables" + default n + depends on RESIZE + help + Prints the newly set size (number of columns and rows) of + the terminal. + E.g.: + COLUMNS=80;LINES=44;export COLUMNS LINES; + +config SETCONSOLE + bool "setconsole" + default n + help + This program redirects the system console to another device, + like the current tty while logged in via telnet. + +config FEATURE_SETCONSOLE_LONG_OPTIONS + bool "Enable long options" + default n + depends on SET_CONSOLE && GETOPT_LONG + help + Support long options for the setconsole applet. + +config SETKEYCODES + bool "setkeycodes" + default n + help + This program loads entries into the kernel's scancode-to-keycode + map, allowing unusual keyboards to generate usable keycodes. + +config SETLOGCONS + bool "setlogcons" + default n + help + This program redirects the output console of kernel messages. + +endmenu diff --git a/console-tools/Kbuild b/console-tools/Kbuild new file mode 100644 index 000000000..a55bc087c --- /dev/null +++ b/console-tools/Kbuild @@ -0,0 +1,19 @@ +# 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_CHVT) += chvt.o +lib-$(CONFIG_CLEAR) += clear.o +lib-$(CONFIG_DEALLOCVT) += deallocvt.o +lib-$(CONFIG_DUMPKMAP) += dumpkmap.o +lib-$(CONFIG_SETCONSOLE) += setconsole.o +lib-$(CONFIG_LOADFONT) += loadfont.o +lib-$(CONFIG_LOADKMAP) += loadkmap.o +lib-$(CONFIG_OPENVT) += openvt.o +lib-$(CONFIG_RESET) += reset.o +lib-$(CONFIG_RESIZE) += resize.o +lib-$(CONFIG_SETKEYCODES) += setkeycodes.o +lib-$(CONFIG_SETLOGCONS) += setlogcons.o diff --git a/console-tools/chvt.c b/console-tools/chvt.c new file mode 100644 index 000000000..bbc5a9c31 --- /dev/null +++ b/console-tools/chvt.c @@ -0,0 +1,33 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini chvt implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +/* From */ +enum { + VT_ACTIVATE = 0x5606, /* make vt active */ + VT_WAITACTIVE = 0x5607 /* wait for vt active */ +}; + +int chvt_main(int argc, char **argv) +{ + int fd, num; + + if (argc != 2) { + bb_show_usage(); + } + + fd = get_console_fd(); + num = xatoul_range(argv[1], 1, 63); + if ((-1 == ioctl(fd, VT_ACTIVATE, num)) + || (-1 == ioctl(fd, VT_WAITACTIVE, num))) { + bb_perror_msg_and_die("ioctl"); + } + return EXIT_SUCCESS; +} diff --git a/console-tools/clear.c b/console-tools/clear.c new file mode 100644 index 000000000..9686d5004 --- /dev/null +++ b/console-tools/clear.c @@ -0,0 +1,21 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini clear implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + */ + +/* no options, no getopt */ + +#include +#include +#include "busybox.h" + + +int clear_main(int argc, char **argv) +{ + return printf("\033[H\033[J") != 6; +} diff --git a/console-tools/deallocvt.c b/console-tools/deallocvt.c new file mode 100644 index 000000000..cd581b1c8 --- /dev/null +++ b/console-tools/deallocvt.c @@ -0,0 +1,37 @@ +/* vi: set sw=4 ts=4: */ +/* + * Disallocate virtual terminal(s) + * + * Copyright (C) 2003 by Tito Ragusa + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* no options, no getopt */ + +#include "busybox.h" + +/* From */ +enum { VT_DISALLOCATE = 0x5608 }; /* free memory associated to vt */ + +int deallocvt_main(int argc, char *argv[]) +{ + /* num = 0 deallocate all unused consoles */ + int num = 0; + + switch (argc) { + case 2: + num = xatoul_range(argv[1], 1, 63); + /* Fallthrough */ + case 1: + break; + default: + bb_show_usage(); + } + + if (-1 == ioctl(get_console_fd(), VT_DISALLOCATE, num)) { + bb_perror_msg_and_die("VT_DISALLOCATE"); + } + return EXIT_SUCCESS; +} diff --git a/console-tools/dumpkmap.c b/console-tools/dumpkmap.c new file mode 100644 index 000000000..7c6633ae8 --- /dev/null +++ b/console-tools/dumpkmap.c @@ -0,0 +1,65 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini dumpkmap implementation for busybox + * + * Copyright (C) Arne Bernin + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + */ + +#include "busybox.h" + +/* From */ +struct kbentry { + unsigned char kb_table; + unsigned char kb_index; + unsigned short kb_value; +}; +#define KDGKBENT 0x4B46 /* gets one entry in translation table */ + +/* From */ +#define NR_KEYS 128 +#define MAX_NR_KEYMAPS 256 + +int dumpkmap_main(int argc, char **argv) +{ + struct kbentry ke; + int i, j, fd; + char flags[MAX_NR_KEYMAPS], magic[] = "bkeymap"; + + if (argc >= 2 && *argv[1] == '-') + bb_show_usage(); + + fd = xopen(CURRENT_VC, O_RDWR); + + write(1, magic, 7); + + /* Here we want to set everything to 0 except for indexes: + * [0-2] [4-6] [8-10] [12] */ + memset(flags, 0x00, MAX_NR_KEYMAPS); + memset(flags, 0x01, 13); + flags[3] = flags[7] = flags[11] = 0; + + /* dump flags */ + write(1, flags, MAX_NR_KEYMAPS); + + for (i = 0; i < MAX_NR_KEYMAPS; i++) { + if (flags[i] == 1) { + for (j = 0; j < NR_KEYS; j++) { + ke.kb_index = j; + ke.kb_table = i; + if (ioctl(fd, KDGKBENT, &ke) < 0) { + bb_perror_msg("ioctl failed with %s, %s, %p", + (char *)&ke.kb_index, + (char *)&ke.kb_table, + &ke.kb_value); + } else { + write(1, (void*)&ke.kb_value, 2); + } + } + } + } + close(fd); + return EXIT_SUCCESS; +} diff --git a/console-tools/loadfont.c b/console-tools/loadfont.c new file mode 100644 index 000000000..5a05876c2 --- /dev/null +++ b/console-tools/loadfont.c @@ -0,0 +1,193 @@ +/* vi: set sw=4 ts=4: */ +/* + * loadfont.c - Eugene Crosser & Andries Brouwer + * + * Version 0.96bb + * + * Loads the console font, and possibly the corresponding screen map(s). + * (Adapted for busybox by Matej Vela.) + */ +#include "busybox.h" +#include + +enum { + PSF_MAGIC1 = 0x36, + PSF_MAGIC2 = 0x04, + + PSF_MODE512 = 0x01, + PSF_MODEHASTAB = 0x02, + PSF_MAXMODE = 0x03, + PSF_SEPARATOR = 0xFFFF +}; + +struct psf_header { + unsigned char magic1, magic2; /* Magic number */ + unsigned char mode; /* PSF font mode */ + unsigned char charsize; /* Character size */ +}; + +#define PSF_MAGIC_OK(x) ((x).magic1 == PSF_MAGIC1 && (x).magic2 == PSF_MAGIC2) + +static void loadnewfont(int fd); + +int loadfont_main(int argc, char **argv) +{ + int fd; + + if (argc != 1) + bb_show_usage(); + + fd = xopen(CURRENT_VC, O_RDWR); + loadnewfont(fd); + + return EXIT_SUCCESS; +} + +static void do_loadfont(int fd, unsigned char *inbuf, int unit, int fontsize) +{ + char buf[16384]; + int i; + + memset(buf, 0, sizeof(buf)); + + if (unit < 1 || unit > 32) + bb_error_msg_and_die("bad character size %d", unit); + + for (i = 0; i < fontsize; i++) + memcpy(buf + (32 * i), inbuf + (unit * i), unit); + +#if defined( PIO_FONTX ) && !defined( __sparc__ ) + { + struct consolefontdesc cfd; + + cfd.charcount = fontsize; + cfd.charheight = unit; + cfd.chardata = buf; + + if (ioctl(fd, PIO_FONTX, &cfd) == 0) + return; /* success */ + bb_perror_msg("PIO_FONTX ioctl error (trying PIO_FONT)"); + } +#endif + if (ioctl(fd, PIO_FONT, buf)) + bb_perror_msg_and_die("PIO_FONT ioctl error"); +} + +static void +do_loadtable(int fd, unsigned char *inbuf, int tailsz, int fontsize) +{ + struct unimapinit advice; + struct unimapdesc ud; + struct unipair *up; + int ct = 0, maxct; + int glyph; + u_short unicode; + + maxct = tailsz; /* more than enough */ + up = (struct unipair *) xmalloc(maxct * sizeof(struct unipair)); + + for (glyph = 0; glyph < fontsize; glyph++) { + while (tailsz >= 2) { + unicode = (((u_short) inbuf[1]) << 8) + inbuf[0]; + tailsz -= 2; + inbuf += 2; + if (unicode == PSF_SEPARATOR) + break; + up[ct].unicode = unicode; + up[ct].fontpos = glyph; + ct++; + } + } + + /* Note: after PIO_UNIMAPCLR and before PIO_UNIMAP + this printf did not work on many kernels */ + + advice.advised_hashsize = 0; + advice.advised_hashstep = 0; + advice.advised_hashlevel = 0; + if (ioctl(fd, PIO_UNIMAPCLR, &advice)) { +#ifdef ENOIOCTLCMD + if (errno == ENOIOCTLCMD) { + bb_error_msg("it seems this kernel is older than 1.1.92"); + bb_error_msg_and_die("no Unicode mapping table loaded"); + } else +#endif + bb_perror_msg_and_die("PIO_UNIMAPCLR"); + } + ud.entry_ct = ct; + ud.entries = up; + if (ioctl(fd, PIO_UNIMAP, &ud)) { + bb_perror_msg_and_die("PIO_UNIMAP"); + } +} + +static void loadnewfont(int fd) +{ + int unit; + unsigned char inbuf[32768]; /* primitive */ + unsigned int inputlth, offset; + + /* + * We used to look at the length of the input file + * with stat(); now that we accept compressed files, + * just read the entire file. + */ + inputlth = fread(inbuf, 1, sizeof(inbuf), stdin); + if (ferror(stdin)) + bb_perror_msg_and_die("error reading input font"); + /* use malloc/realloc in case of giant files; + maybe these do not occur: 16kB for the font, + and 16kB for the map leaves 32 unicode values + for each font position */ + if (!feof(stdin)) + bb_perror_msg_and_die("font too large"); + + /* test for psf first */ + { + struct psf_header psfhdr; + int fontsize; + int hastable; + unsigned int head0, head; + + if (inputlth < sizeof(struct psf_header)) + goto no_psf; + + psfhdr = *(struct psf_header *) &inbuf[0]; + + if (!PSF_MAGIC_OK(psfhdr)) + goto no_psf; + + if (psfhdr.mode > PSF_MAXMODE) + bb_error_msg_and_die("unsupported psf file mode"); + fontsize = ((psfhdr.mode & PSF_MODE512) ? 512 : 256); +#if !defined( PIO_FONTX ) || defined( __sparc__ ) + if (fontsize != 256) + bb_error_msg_and_die("only fontsize 256 supported"); +#endif + hastable = (psfhdr.mode & PSF_MODEHASTAB); + unit = psfhdr.charsize; + head0 = sizeof(struct psf_header); + + head = head0 + fontsize * unit; + if (head > inputlth || (!hastable && head != inputlth)) + bb_error_msg_and_die("input file: bad length"); + do_loadfont(fd, inbuf + head0, unit, fontsize); + if (hastable) + do_loadtable(fd, inbuf + head, inputlth - head, fontsize); + return; + } + no_psf: + + /* file with three code pages? */ + if (inputlth == 9780) { + offset = 40; + unit = 16; + } else { + /* bare font */ + if (inputlth & 0377) + bb_error_msg_and_die("bad input file size"); + offset = 0; + unit = inputlth / 256; + } + do_loadfont(fd, inbuf + offset, unit, 256); +} diff --git a/console-tools/loadkmap.c b/console-tools/loadkmap.c new file mode 100644 index 000000000..ce9b6817c --- /dev/null +++ b/console-tools/loadkmap.c @@ -0,0 +1,61 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini loadkmap implementation for busybox + * + * Copyright (C) 1998 Enrique Zanardi + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + */ + +#include "busybox.h" + +#define BINARY_KEYMAP_MAGIC "bkeymap" + +/* From */ +struct kbentry { + unsigned char kb_table; + unsigned char kb_index; + unsigned short kb_value; +}; +/* sets one entry in translation table */ +#define KDSKBENT 0x4B47 + +/* From */ +#define NR_KEYS 128 +#define MAX_NR_KEYMAPS 256 + +int loadkmap_main(int argc, char **argv) +{ + struct kbentry ke; + int i, j, fd; + u_short ibuff[NR_KEYS]; + char flags[MAX_NR_KEYMAPS]; + char buff[7]; + + if (argc != 1) + bb_show_usage(); + + fd = xopen(CURRENT_VC, O_RDWR); + + xread(0, buff, 7); + if (strncmp(buff, BINARY_KEYMAP_MAGIC, 7)) + bb_error_msg_and_die("this is not a valid binary keymap"); + + xread(0, flags, MAX_NR_KEYMAPS); + + for (i = 0; i < MAX_NR_KEYMAPS; i++) { + if (flags[i] == 1) { + xread(0, ibuff, NR_KEYS * sizeof(u_short)); + for (j = 0; j < NR_KEYS; j++) { + ke.kb_index = j; + ke.kb_table = i; + ke.kb_value = ibuff[j]; + ioctl(fd, KDSKBENT, &ke); + } + } + } + + if (ENABLE_FEATURE_CLEAN_UP) close(fd); + return 0; +} diff --git a/console-tools/openvt.c b/console-tools/openvt.c new file mode 100644 index 000000000..f1cf5645b --- /dev/null +++ b/console-tools/openvt.c @@ -0,0 +1,45 @@ +/* vi: set sw=4 ts=4: */ +/* + * openvt.c - open a vt to run a command. + * + * busyboxed by Quy Tonthat + * hacked by Tito + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* getopt not needed */ + +#include "busybox.h" + +int openvt_main(int argc, char **argv) +{ + int fd; + char vtname[sizeof(VC_FORMAT) + 2]; + + + if (argc < 3) { + bb_show_usage(); + } + /* check for illegal vt number: < 1 or > 63 */ + sprintf(vtname, VC_FORMAT, (int)xatoul_range(argv[1], 1, 63)); + + if (fork() == 0) { + /* leave current vt */ + if (setsid() < 0) { + bb_perror_msg_and_die("setsid"); + } + close(0); /* so that new vt becomes stdin */ + + /* and grab new one */ + fd = xopen(vtname, O_RDWR); + + /* Reassign stdout and sterr */ + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + + execvp(argv[2], &argv[2]); + _exit(1); + } + return EXIT_SUCCESS; +} diff --git a/console-tools/reset.c b/console-tools/reset.c new file mode 100644 index 000000000..26aab667b --- /dev/null +++ b/console-tools/reset.c @@ -0,0 +1,32 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini reset implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * Written by Erik Andersen and Kent Robotti + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* no options, no getopt */ + +#include +#include +#include +#include "busybox.h" + +int reset_main(int argc, char **argv) +{ + if (isatty(1)) { + /* See 'man 4 console_codes' for details: + * "ESC c" -- Reset + * "ESC ( K" -- Select user mapping + * "ESC [ J" -- Erase display + * "ESC [ 0 m" -- Reset all display attributes + * "ESC [ ? 25 h" -- Make cursor visible. + */ + printf("\033c\033(K\033[J\033[0m\033[?25h"); + } + return EXIT_SUCCESS; +} + diff --git a/console-tools/resize.c b/console-tools/resize.c new file mode 100644 index 000000000..c405629aa --- /dev/null +++ b/console-tools/resize.c @@ -0,0 +1,38 @@ +/* vi: set sw=4 ts=4: */ +/* + * resize - set terminal width and height. + * + * Copyright 2006 Bernhard Fischer + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ +/* no options, no getopt */ +#include "busybox.h" + +int resize_main(int argc, char **argv) +{ + struct termios old, new; + struct winsize w = {0,0,0,0}; + int ret; + + tcgetattr(STDOUT_FILENO, &old); /* fiddle echo */ + new = old; + new.c_cflag |= (CLOCAL | CREAD); + new.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + tcsetattr(STDOUT_FILENO, TCSANOW, &new); + /* save_cursor_pos 7 + * scroll_whole_screen [r + * put_cursor_waaaay_off [$x;$yH + * get_cursor_pos [6n + * restore_cursor_pos 8 + */ + printf("\0337\033[r\033[999;999H\033[6n"); + scanf("\033[%hu;%huR", &w.ws_row, &w.ws_col); + ret = ioctl(STDOUT_FILENO, TIOCSWINSZ, &w); + printf("\0338"); + tcsetattr(STDOUT_FILENO, TCSANOW, &old); + if (ENABLE_FEATURE_RESIZE_PRINT) + printf("COLUMNS=%d;LINES=%d;export COLUMNS LINES;\n", + w.ws_col, w.ws_row); + return ret; +} diff --git a/console-tools/setconsole.c b/console-tools/setconsole.c new file mode 100644 index 000000000..ef81f298b --- /dev/null +++ b/console-tools/setconsole.c @@ -0,0 +1,47 @@ +/* vi: set sw=4 ts=4: */ +/* + * setconsole.c - redirect system console output + * + * Copyright (C) 2004,2005 Enrik Berkhan + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +#if ENABLE_FEATURE_SETCONSOLE_LONG_OPTIONS +static const struct option setconsole_long_options[] = { + { "reset", 0, NULL, 'r' }, + { 0, 0, 0, 0 } +}; +#endif + +#define OPT_SETCONS_RESET 1 + +int setconsole_main(int argc, char **argv) +{ + unsigned long flags; + const char *device = CURRENT_TTY; + +#if ENABLE_FEATURE_SETCONSOLE_LONG_OPTIONS + applet_long_options = setconsole_long_options; +#endif + flags = getopt32(argc, argv, "r"); + + if (argc - optind > 1) + bb_show_usage(); + + if (argc - optind == 1) { + if (flags & OPT_SETCONS_RESET) + bb_show_usage(); + device = argv[optind]; + } else { + if (flags & OPT_SETCONS_RESET) + device = CONSOLE_DEV; + } + + if (-1 == ioctl(xopen(device, O_RDONLY), TIOCCONS)) { + bb_perror_msg_and_die("TIOCCONS"); + } + return EXIT_SUCCESS; +} diff --git a/console-tools/setkeycodes.c b/console-tools/setkeycodes.c new file mode 100644 index 000000000..d82525786 --- /dev/null +++ b/console-tools/setkeycodes.c @@ -0,0 +1,53 @@ +/* vi: set sw=4 ts=4: */ +/* + * setkeycodes + * + * Copyright (C) 1994-1998 Andries E. Brouwer + * + * Adjusted for BusyBox by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include "busybox.h" + + +/* From */ +struct kbkeycode { + unsigned int scancode, keycode; +}; +enum { + KDSETKEYCODE = 0x4B4D /* write kernel keycode table entry */ +}; + +extern int +setkeycodes_main(int argc, char** argv) +{ + int fd, sc; + struct kbkeycode a; + + if (argc % 2 != 1 || argc < 2) { + bb_show_usage(); + } + + fd = get_console_fd(); + + while (argc > 2) { + a.keycode = xatoul_range(argv[2], 0, 127); + a.scancode = sc = xstrtoul_range(argv[1], 16, 0, 255); + if (a.scancode > 127) { + a.scancode -= 0xe000; + a.scancode += 128; + } + if (ioctl(fd, KDSETKEYCODE, &a)) { + bb_perror_msg_and_die("failed to set SCANCODE %x to KEYCODE %d", sc, a.keycode); + } + argc -= 2; + argv += 2; + } + return EXIT_SUCCESS; +} diff --git a/console-tools/setlogcons.c b/console-tools/setlogcons.c new file mode 100644 index 000000000..ae15b9b28 --- /dev/null +++ b/console-tools/setlogcons.c @@ -0,0 +1,31 @@ +/* vi: set sw=4 ts=4: */ +/* + * setlogcons: Send kernel messages to the current console or to console N + * + * Copyright (C) 2006 by Jan Kiszka + * + * Based on setlogcons (kbd-1.12) by Andries E. Brouwer + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +extern int setlogcons_main(int argc, char **argv) +{ + struct { + char fn; + char subarg; + } arg; + + arg.fn = 11; /* redirect kernel messages */ + arg.subarg = 0; /* to specified console (current as default) */ + + if (argc == 2) + arg.subarg = xatoul_range(argv[1], 0, 63); + + if (ioctl(xopen(VC_1, O_RDONLY), TIOCLINUX, &arg)) + bb_perror_msg_and_die("TIOCLINUX"); + + return 0; +} diff --git a/coreutils/Config.in b/coreutils/Config.in new file mode 100644 index 000000000..000f3a8af --- /dev/null +++ b/coreutils/Config.in @@ -0,0 +1,782 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Coreutils" + +config BASENAME + bool "basename" + default n + help + basename is used to strip the directory and suffix from filenames, + leaving just the filename itself. Enable this option if you wish + to enable the 'basename' utility. + +config CAL + bool "cal" + default n + help + cal is used to display a monthly calender. + +config CAT + bool "cat" + default n + help + cat is used to concatenate files and print them to the standard + output. Enable this option if you wish to enable the 'cat' utility. + +config CATV + bool "catv" + default n + help + Display nonprinting characters as escape sequences (like some + implementations' cat -v option). + +config CHGRP + bool "chgrp" + default n + help + chgrp is used to change the group ownership of files. + +config CHMOD + bool "chmod" + default n + help + chmod is used to change the access permission of files. + +config CHOWN + bool "chown" + default n + help + chown is used to change the user and/or group ownership + of files. + +config CHROOT + bool "chroot" + default n + help + chroot is used to change the root directory and run a command. + The default command is `/bin/sh'. + +config CKSUM + bool "cksum" + default n + help + cksum is used to calculate the CRC32 checksum of a file. + +config CMP + bool "cmp" + default n + help + cmp is used to compare two files and returns the result + to standard output. + +config COMM + bool "comm" + default n + help + comm is used to compare two files line by line and return + a three-column output. + +config CP + bool "cp" + default n + help + cp is used to copy files and directories. + +config CUT + bool "cut" + default n + help + cut is used to print selected parts of lines from + each file to stdout. + +config DATE + bool "date" + default n + help + date is used to set the system date or display the + current time in the given format. + +config FEATURE_DATE_ISOFMT + bool "Enable ISO date format output (-I)" + default y + depends on DATE + help + Enable option (-I) to output an ISO-8601 compliant + date/time string. + +config DD + bool "dd" + default n + help + dd copies a file (from standard input to standard output, + by default) using specific input and output blocksizes, + while optionally performing conversions on it. + +config FEATURE_DD_SIGNAL_HANDLING + bool "Enable DD signal handling for status reporting" + default y + depends on DD + help + sending a SIGUSR1 signal to a running `dd' process makes it + print to standard error the number of records read and written + so far, then to resume copying. + + $ dd if=/dev/zero of=/dev/null& pid=$! $ kill -USR1 $pid; sleep 1; kill $pid + 10899206+0 records in 10899206+0 records out + +config FEATURE_DD_IBS_OBS + bool "Enable ibs, obs and conv options" + default n + depends on DD + help + Enables support for writing a certain number of bytes in and out, + at a time, and performing conversions on the data stream. + +config DF + bool "df" + default n + help + df reports the amount of disk space used and available + on filesystems. + +config DIFF + bool "diff" + default n + help + diff compares two files or directories and outputs the + differences between them in a form that can be given to + the patch command. + +config FEATURE_DIFF_BINARY + bool "Enable checks for binary files" + default y + depends on DIFF + help + This option enables support for checking for binary files + before a comparison is carried out. + +config FEATURE_DIFF_DIR + bool "Enable directory support" + default y + depends on DIFF + help + This option enables support for directory and subdirectory + comparison. + +config FEATURE_DIFF_MINIMAL + bool "Enable -d option to find smaller sets of changes" + default n + depends on DIFF + help + Enabling this option allows the use of -d to make diff + try hard to find the smallest possible set of changes. + +config DIRNAME + bool "dirname" + default n + help + dirname is used to strip a non-directory suffix from + a file name. + +config DOS2UNIX + bool "dos2unix/unix2dos" + default n + help + dos2unix is used to convert a text file from DOS format to + UNIX format, and vice versa. + +config UNIX2DOS + bool + default y + depends on DOS2UNIX + help + unix2dos is used to convert a text file from UNIX format to + DOS format, and vice versa. + +config DU + bool "du (default blocksize of 512 bytes)" + default n + help + du is used to report the amount of disk space used + for specified files. + +config FEATURE_DU_DEFAULT_BLOCKSIZE_1K + bool "Use a default blocksize of 1024 bytes (1K)" + default y + depends on DU + help + Use a blocksize of (1K) instead of the default 512b. + +config ECHO + bool "echo (basic SuSv3 version taking no options)" + default n + help + echo is used to print a specified string to stdout. + +# this entry also appears in shell/Config.in, next to the echo builtin +config FEATURE_FANCY_ECHO + bool "Enable echo options (-n and -e)" + default y + depends on ECHO + help + This adds options (-n and -e) to echo. + +config ENV + bool "env" + default n + help + env is used to set an environment variable and run + a command; without options it displays the current + environment. + +config FEATURE_ENV_LONG_OPTIONS + bool "Enable long options" + default n + depends on ENV && GETOPT_LONG + help + Support long options for the env applet. + +config EXPR + bool "expr" + default n + help + expr is used to calculate numbers and print the result + to standard output. + +config EXPR_MATH_SUPPORT_64 + bool "Extend Posix numbers support to 64 bit" + default n + depends on EXPR + help + Enable 64-bit math support in the expr applet. This will make + the applet slightly larger, but will allow computation with very + large numbers. + +config FALSE + bool "false" + default n + help + false returns an exit code of FALSE (1). + +config FOLD + bool "fold" + default n + help + Wrap text to fit a specific width. + +config HEAD + bool "head" + default n + help + head is used to print the first specified number of lines + from files. + +config FEATURE_FANCY_HEAD + bool "Enable head options (-c, -q, and -v)" + default n + depends on HEAD + help + This enables the head options (-c, -q, and -v). + +config HOSTID + bool "hostid" + default n + help + hostid prints the numeric identifier (in hexadecimal) for + the current host. + +config ID + bool "id" + default n + help + id displays the current user and group ID names. + +config INSTALL + bool "install" + default n + help + Copy files and set attributes. + +config FEATURE_INSTALL_LONG_OPTIONS + bool "Enable long options" + default n + depends on INSTALL && GETOPT_LONG + help + Support long options for the install applet. + +config LENGTH + bool "length" + default n + help + length is used to print out the length of a specified string. + +config LN + bool "ln" + default n + help + ln is used to create hard or soft links between files. + +config LOGNAME + bool "logname" + default n + help + logname is used to print the current user's login name. + +config LS + bool "ls" + default n + help + ls is used to list the contents of directories. + +config FEATURE_LS_FILETYPES + bool "Enable filetyping options (-p and -F)" + default y + depends on LS + help + Enable the ls options (-p and -F). + +config FEATURE_LS_FOLLOWLINKS + bool "Enable symlinks dereferencing (-L)" + default y + depends on LS + help + Enable the ls option (-L). + +config FEATURE_LS_RECURSIVE + bool "Enable recursion (-R)" + default y + depends on LS + help + Enable the ls option (-R). + +config FEATURE_LS_SORTFILES + bool "Sort the file names" + default y + depends on LS + help + Allow ls to sort file names alphabetically. + +config FEATURE_LS_TIMESTAMPS + bool "Show file timestamps" + default y + depends on LS + help + Allow ls to display timestamps for files. + +config FEATURE_LS_USERNAME + bool "Show username/groupnames" + default y + depends on LS + help + Allow ls to display username/groupname for files. + +config FEATURE_LS_COLOR + bool "Allow use of color to identify file types" + default y + depends on LS && GETOPT_LONG + help + This enables the --color option to ls. + +config FEATURE_LS_COLOR_IS_DEFAULT + bool "Produce colored ls output by default" + default n + depends on FEATURE_LS_COLOR + help + Saying yes here will turn coloring on by default, + even if no "--color" option is given to the ls command. + This is not recommended, since the colors are not + configurable, and the output may not be legible on + many output screens. + +config MD5SUM + bool "md5sum" + default n + help + md5sum is used to print or check MD5 checksums. + +config MKDIR + bool "mkdir" + default n + help + mkdir is used to create directories with the specified names. + +config FEATURE_MKDIR_LONG_OPTIONS + bool "Enable long options" + default n + depends on MKDIR && GETOPT_LONG + help + Support long options for the mkdir applet. + +config MKFIFO + bool "mkfifo" + default n + help + mkfifo is used to create FIFOs (named pipes). + The `mknod' program can also create FIFOs. + +config MKNOD + bool "mknod" + default n + help + mknod is used to create FIFOs or block/character special + files with the specified names. + +config MV + bool "mv" + default n + help + mv is used to move or rename files or directories. + +config FEATURE_MV_LONG_OPTIONS + bool "Enable long options" + default n + depends on MV && GETOPT_LONG + help + Support long options for the mv applet. + +config NICE + bool "nice" + default n + help + nice runs a program with modified scheduling priority. + +config NOHUP + bool "nohup" + default n + help + run a command immune to hangups, with output to a non-tty. + +config OD + bool "od" + default n + help + od is used to dump binary files in octal and other formats. + +config PRINTENV + bool "printenv" + default n + help + printenv is used to print all or part of environment. + +config PRINTF + bool "printf" + default n + help + printf is used to format and print specified strings. + It's similar to `echo' except it has more options. + +config PWD + bool "pwd" + default n + help + pwd is used to print the current directory. + +config REALPATH + bool "realpath" + default n + help + Return the canonicalized absolute pathname. + This isn't provided by GNU shellutils, but where else does it belong. + +config RM + bool "rm" + default n + help + rm is used to remove files or directories. + +config RMDIR + bool "rmdir" + default n + help + rmdir is used to remove empty directories. + +config SEQ + bool "seq" + default n + help + print a sequence of numbers + +config SHA1SUM + bool "sha1sum" + default n + help + Compute and check SHA1 message digest + +config SLEEP + bool "sleep (single integer arg with no suffix)" + default n + help + sleep is used to pause for a specified number of seconds, + +config FEATURE_FANCY_SLEEP + bool "Enable multiple integer args and optional time suffixes" + default n + depends on SLEEP + help + Allow sleep to pause for specified minutes, hours, and days. + +config SORT + bool "sort" + default n + help + sort is used to sort lines of text in specified files. + +config FEATURE_SORT_BIG + bool "full SuSv3 compliant sort (Support -ktcsbdfiozgM)" + default y + depends on SORT + help + Without this, sort only supports -r, -u, and an integer version + of -n. Selecting this adds sort keys, floating point support, and + more. This adds a little over 3k to a nonstatic build on x86. + + The SuSv3 sort standard is available at: + http://www.opengroup.org/onlinepubs/007904975/utilities/sort.html + +config STAT + bool "stat" + default n + help + display file or filesystem status. + +config FEATURE_STAT_FORMAT + bool "Enable custom formats (-c)" + default n + depends on STAT + help + Without this, stat will not support the '-c format' option where + users can pass a custom format string for output. This adds about + 7k to a nonstatic build on amd64. + +config STTY + bool "stty" + default n + help + stty is used to change and print terminal line settings. + +config SUM + bool "sum" + default n + help + checksum and count the blocks in a file + +config SYNC + bool "sync" + default n + help + sync is used to flush filesystem buffers. + +config TAIL + bool "tail" + default n + help + tail is used to print the last specified number of lines + from files. + +config FEATURE_FANCY_TAIL + bool "Enable extra tail options (-q, -s, and -v)" + default y + depends on TAIL + help + The options (-q, -s, and -v) are provided by GNU tail, but + are not specific in the SUSv3 standard. + +config TEE + bool "tee" + default n + help + tee is used to read from standard input and write + to standard output and files. + +config FEATURE_TEE_USE_BLOCK_IO + bool "Enable block i/o (larger/faster) instead of byte i/o." + default n + depends on TEE + help + Enable this option for a faster tee, at expense of size. + +config TEST + bool "test" + default n + help + test is used to check file types and compare values, + returning an appropriate exit code. The bash shell + has test built in, ash can build it in optionally. + +config FEATURE_TEST_64 + bool "Extend test to 64 bit" + default n + depends on TEST + help + Enable 64-bit support in test. + +config TOUCH + bool "touch" + default n + help + touch is used to create or change the access and/or + modification timestamp of specified files. + +config TR + bool "tr" + default n + help + tr is used to squeeze, and/or delete characters from standard + input, writing to standard output. + +config FEATURE_TR_CLASSES + bool "Enable character classes (such as [:upper:])" + default n + depends on TR + help + Enable character classes, enabling commands such as: + tr [:upper:] [:lower:] to convert input into lowercase. + +config FEATURE_TR_EQUIV + bool "Enable equivalence classes" + default n + depends on TR + help + Enable equivalence classes, which essentially add the enclosed + character to the current set. For instance, tr [=a=] xyz would + replace all instances of 'a' with 'xyz'. This option is mainly + useful for cases when no other way of expressing a character + is possible. + +config TRUE + bool "true" + default n + help + true returns an exit code of TRUE (0). + +config TTY + bool "tty" + default n + help + tty is used to print the name of the current terminal to + standard output. + +config UNAME + bool "uname" + default n + help + uname is used to print system information. + +config UNIQ + bool "uniq" + default n + help + uniq is used to remove duplicate lines from a sorted file. + +config USLEEP + bool "usleep" + default n + help + usleep is used to pause for a specified number of microseconds. + +config UUDECODE + bool "uudecode" + default n + help + uudecode is used to decode a uuencoded file. + +config UUENCODE + bool "uuencode" + default n + help + uuencode is used to uuencode a file. + +config WATCH + bool "watch" + default n + select DATE + help + watch is used to execute a program periodically, showing + output to the screen. + +config WC + bool "wc" + default n + help + wc is used to print the number of bytes, words, and lines, + in specified files. + +config FEATURE_WC_LARGE + bool "Support very large files in wc" + default n + depends on WC + help + Use "unsigned long long" in wc for count variables + +config WHO + bool "who" + default n + select FEATURE_UTMP + help + who is used to show who is logged on. + +config WHOAMI + bool "whoami" + default n + help + whoami is used to print the username of the current + user id (same as id -un). + +config YES + bool "yes" + default n + help + yes is used to repeatedly output a specific string, or + the default string `y'. + +comment "Common options for cp and mv" + depends on CP || MV + +config FEATURE_PRESERVE_HARDLINKS + bool "Preserve hard links" + default n + depends on CP || MV + help + Allow cp and mv to preserve hard links. + +comment "Common options for ls, more and telnet" + depends on LS || MORE || TELNET + +config FEATURE_AUTOWIDTH + bool "Calculate terminal & column widths" + default y + depends on LS || MORE || TELNET + help + This option allows utilities such as 'ls', 'more' and 'telnet' + to determine the width of the screen, which can allow them to + display additional text or avoid wrapping text onto the next line. + If you leave this disabled, your utilities will be especially + primitive and will be unable to determine the current screen width. + +comment "Common options for df, du, ls" + depends on DF || DU || LS + +config FEATURE_HUMAN_READABLE + bool "Support for human readable output (example 13k, 23M, 235G)" + default n + depends on DF || DU || LS + help + Allow df, du, and ls to have human readable output. + +comment "Common options for md5sum, sha1sum" + depends on MD5SUM || SHA1SUM + +config FEATURE_MD5_SHA1_SUM_CHECK + bool "Enable -c, -s and -w options" + default n + depends on MD5SUM || SHA1SUM + help + Enabling the -c options allows files to be checked + against pre-calculated hash values. + + -s and -w are useful options when verifying checksums. + +endmenu diff --git a/coreutils/Kbuild b/coreutils/Kbuild new file mode 100644 index 000000000..cfb508d81 --- /dev/null +++ b/coreutils/Kbuild @@ -0,0 +1,81 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +libs-y += libcoreutils/ + +lib-y:= +lib-$(CONFIG_BASENAME) += basename.o +lib-$(CONFIG_CAL) += cal.o +lib-$(CONFIG_CAT) += cat.o +lib-$(CONFIG_CATV) += catv.o +lib-$(CONFIG_CHGRP) += chgrp.o chown.o +lib-$(CONFIG_CHMOD) += chmod.o +lib-$(CONFIG_CHOWN) += chown.o +lib-$(CONFIG_CHROOT) += chroot.o +lib-$(CONFIG_CKSUM) += cksum.o +lib-$(CONFIG_CMP) += cmp.o +lib-$(CONFIG_COMM) += comm.o +lib-$(CONFIG_CP) += cp.o +lib-$(CONFIG_CUT) += cut.o +lib-$(CONFIG_DATE) += date.o +lib-$(CONFIG_DD) += dd.o +lib-$(CONFIG_DF) += df.o +lib-$(CONFIG_DIFF) += diff.o +lib-$(CONFIG_DIRNAME) += dirname.o +lib-$(CONFIG_DOS2UNIX) += dos2unix.o +lib-$(CONFIG_DU) += du.o +lib-$(CONFIG_ECHO) += echo.o +lib-$(CONFIG_ENV) += env.o +lib-$(CONFIG_EXPR) += expr.o +lib-$(CONFIG_FALSE) += false.o +lib-$(CONFIG_FOLD) += fold.o +lib-$(CONFIG_HEAD) += head.o +lib-$(CONFIG_HOSTID) += hostid.o +lib-$(CONFIG_ID) += id.o +lib-$(CONFIG_INSTALL) += install.o +lib-$(CONFIG_LENGTH) += length.o +lib-$(CONFIG_LN) += ln.o +lib-$(CONFIG_LOGNAME) += logname.o +lib-$(CONFIG_LS) += ls.o +lib-$(CONFIG_MD5SUM) += md5_sha1_sum.o +lib-$(CONFIG_MKDIR) += mkdir.o +lib-$(CONFIG_MKFIFO) += mkfifo.o +lib-$(CONFIG_MKNOD) += mknod.o +lib-$(CONFIG_MV) += mv.o +lib-$(CONFIG_NICE) += nice.o +lib-$(CONFIG_NOHUP) += nohup.o +lib-$(CONFIG_OD) += od.o +lib-$(CONFIG_PRINTENV) += printenv.o +lib-$(CONFIG_PRINTF) += printf.o +lib-$(CONFIG_PWD) += pwd.o +lib-$(CONFIG_REALPATH) += realpath.o +lib-$(CONFIG_RM) += rm.o +lib-$(CONFIG_RMDIR) += rmdir.o +lib-$(CONFIG_SEQ) += seq.o +lib-$(CONFIG_SHA1SUM) += md5_sha1_sum.o +lib-$(CONFIG_SLEEP) += sleep.o +lib-$(CONFIG_SORT) += sort.o +lib-$(CONFIG_STAT) += stat.o +lib-$(CONFIG_STTY) += stty.o +lib-$(CONFIG_SUM) += sum.o +lib-$(CONFIG_SYNC) += sync.o +lib-$(CONFIG_TAIL) += tail.o +lib-$(CONFIG_TEE) += tee.o +lib-$(CONFIG_TEST) += test.o +lib-$(CONFIG_TOUCH) += touch.o +lib-$(CONFIG_TR) += tr.o +lib-$(CONFIG_TRUE) += true.o +lib-$(CONFIG_TTY) += tty.o +lib-$(CONFIG_UNAME) += uname.o +lib-$(CONFIG_UNIQ) += uniq.o +lib-$(CONFIG_USLEEP) += usleep.o +lib-$(CONFIG_UUDECODE) += uudecode.o +lib-$(CONFIG_UUENCODE) += uuencode.o +lib-$(CONFIG_WATCH) += watch.o +lib-$(CONFIG_WC) += wc.o +lib-$(CONFIG_WHO) += who.o +lib-$(CONFIG_WHOAMI) += whoami.o +lib-$(CONFIG_YES) += yes.o diff --git a/coreutils/basename.c b/coreutils/basename.c new file mode 100644 index 000000000..30f76dc12 --- /dev/null +++ b/coreutils/basename.c @@ -0,0 +1,50 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini basename implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/basename.html */ + + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Changes: + * 1) Now checks for too many args. Need at least one and at most two. + * 2) Don't check for options, as per SUSv3. + * 3) Save some space by using strcmp(). Calling strncmp() here was silly. + */ + +#include +#include +#include +#include "busybox.h" + +int basename_main(int argc, char **argv) +{ + size_t m, n; + char *s; + + if (((unsigned int)(argc-2)) >= 2) { + bb_show_usage(); + } + + s = bb_get_last_path_component(*++argv); + + if (*++argv) { + n = strlen(*argv); + m = strlen(s); + if ((m > n) && ((strcmp)(s+m-n, *argv) == 0)) { + s[m-n] = '\0'; + } + } + + puts(s); + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/coreutils/cal.c b/coreutils/cal.c new file mode 100644 index 000000000..681a88d90 --- /dev/null +++ b/coreutils/cal.c @@ -0,0 +1,356 @@ +/* vi: set sw=4 ts=4: */ +/* + * Calendar implementation for busybox + * + * See original copyright at the end of this file + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant with -j and -y extensions (from util-linux). */ +/* BB_AUDIT BUG: The output of 'cal -j 1752' is incorrect. The upstream + * BB_AUDIT BUG: version in util-linux seems to be broken as well. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/cal.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Major size reduction... over 50% (>1.5k) on i386. + */ + +#include "busybox.h" + +#define THURSDAY 4 /* for reformation */ +#define SATURDAY 6 /* 1 Jan 1 was a Saturday */ + +#define FIRST_MISSING_DAY 639787 /* 3 Sep 1752 */ +#define NUMBER_MISSING_DAYS 11 /* 11 day correction */ + +#define MAXDAYS 42 /* max slots in a month array */ +#define SPACE -1 /* used in day array */ + +static const char days_in_month[] = { + 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; + +static const char sep1752[] = { + 1, 2, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30 +}; + +static int julian; + +/* leap year -- account for Gregorian reformation in 1752 */ +#define leap_year(yr) \ + ((yr) <= 1752 ? !((yr) % 4) : \ + (!((yr) % 4) && ((yr) % 100)) || !((yr) % 400)) + +static int is_leap_year(int year) +{ + return leap_year(year); +} +#undef leap_year +#define leap_year(yr) is_leap_year(yr) + +/* number of centuries since 1700, not inclusive */ +#define centuries_since_1700(yr) \ + ((yr) > 1700 ? (yr) / 100 - 17 : 0) + +/* number of centuries since 1700 whose modulo of 400 is 0 */ +#define quad_centuries_since_1700(yr) \ + ((yr) > 1600 ? ((yr) - 1600) / 400 : 0) + +/* number of leap years between year 1 and this year, not inclusive */ +#define leap_years_since_year_1(yr) \ + ((yr) / 4 - centuries_since_1700(yr) + quad_centuries_since_1700(yr)) + +static void center (char *, int, int); +static void day_array (int, int, int *); +static void trim_trailing_spaces_and_print (char *); + +static void blank_string(char *buf, size_t buflen); +static char *build_row(char *p, int *dp); + +#define DAY_LEN 3 /* 3 spaces per day */ +#define J_DAY_LEN (DAY_LEN + 1) +#define WEEK_LEN 20 /* 7 * 3 - one space at the end */ +#define J_WEEK_LEN (WEEK_LEN + 7) +#define HEAD_SEP 2 /* spaces between day headings */ + +int cal_main(int argc, char **argv) +{ + struct tm *local_time; + struct tm zero_tm; + time_t now; + int month, year, flags, i; + char *month_names[12]; + char day_headings[28]; /* 28 for julian, 21 for nonjulian */ + char buf[40]; + +// Done in busybox.c, ok to remove? +//#ifdef CONFIG_LOCALE_SUPPORT +// setlocale(LC_TIME, ""); +//#endif + + flags = getopt32(argc, argv, "jy"); + + julian = flags & 1; + + argv += optind; + + month = 0; + + if ((argc -= optind) > 2) { + bb_show_usage(); + } + + if (!argc) { + time(&now); + local_time = localtime(&now); + year = local_time->tm_year + 1900; + if (!(flags & 2)) { + month = local_time->tm_mon + 1; + } + } else { + if (argc == 2) { + month = xatoul_range(*argv++, 1, 12); + } + year = xatoul_range(*argv, 1, 9999); + } + + blank_string(day_headings, sizeof(day_headings) - 7 + 7*julian); + + i = 0; + do { + zero_tm.tm_mon = i; + strftime(buf, sizeof(buf), "%B", &zero_tm); + month_names[i] = xstrdup(buf); + + if (i < 7) { + zero_tm.tm_wday = i; + strftime(buf, sizeof(buf), "%a", &zero_tm); + strncpy(day_headings + i * (3+julian) + julian, buf, 2); + } + } while (++i < 12); + + if (month) { + int row, len, days[MAXDAYS]; + int *dp = days; + char lineout[30]; + + day_array(month, year, dp); + len = sprintf(lineout, "%s %d", month_names[month - 1], year); + printf("%*s%s\n%s\n", + ((7*julian + WEEK_LEN) - len) / 2, "", + lineout, day_headings); + for (row = 0; row < 6; row++) { + build_row(lineout, dp)[0] = '\0'; + dp += 7; + trim_trailing_spaces_and_print(lineout); + } + } else { + int row, which_cal, week_len, days[12][MAXDAYS]; + int *dp; + char lineout[80]; + + sprintf(lineout, "%d", year); + center(lineout, + (WEEK_LEN * 3 + HEAD_SEP * 2) + + julian * (J_WEEK_LEN * 2 + HEAD_SEP + - (WEEK_LEN * 3 + HEAD_SEP * 2)), + 0); + puts("\n"); /* two \n's */ + for (i = 0; i < 12; i++) { + day_array(i + 1, year, days[i]); + } + blank_string(lineout, sizeof(lineout)); + week_len = WEEK_LEN + julian * (J_WEEK_LEN - WEEK_LEN); + for (month = 0; month < 12; month += 3-julian) { + center(month_names[month], week_len, HEAD_SEP); + if (!julian) { + center(month_names[month + 1], week_len, HEAD_SEP); + } + center(month_names[month + 2 - julian], week_len, 0); + printf("\n%s%*s%s", day_headings, HEAD_SEP, "", day_headings); + if (!julian) { + printf("%*s%s", HEAD_SEP, "", day_headings); + } + putchar('\n'); + for (row = 0; row < (6*7); row += 7) { + for (which_cal = 0; which_cal < 3-julian; which_cal++) { + dp = days[month + which_cal] + row; + build_row(lineout + which_cal * (week_len + 2), dp); + } + /* blank_string took care of nul termination. */ + trim_trailing_spaces_and_print(lineout); + } + } + } + + fflush_stdout_and_exit(0); +} + +/* + * day_array -- + * Fill in an array of 42 integers with a calendar. Assume for a moment + * that you took the (maximum) 6 rows in a calendar and stretched them + * out end to end. You would have 42 numbers or spaces. This routine + * builds that array for any month from Jan. 1 through Dec. 9999. + */ +static void day_array(int month, int year, int *days) +{ + long temp; + int i; + int j_offset; + int day, dw, dm; + + memset(days, SPACE, MAXDAYS * sizeof(int)); + + if ((month == 9) && (year == 1752)) { + size_t oday = 0; + + j_offset = julian * 244; + do { + days[oday+2] = sep1752[oday] + j_offset; + } while (++oday < sizeof(sep1752)); + + return; + } + + /* day_in_year + * return the 1 based day number within the year + */ + day = 1; + if ((month > 2) && leap_year(year)) { + ++day; + } + + i = month; + while (i) { + day += days_in_month[--i]; + } + + /* day_in_week + * return the 0 based day number for any date from 1 Jan. 1 to + * 31 Dec. 9999. Assumes the Gregorian reformation eliminates + * 3 Sep. 1752 through 13 Sep. 1752. Returns Thursday for all + * missing days. + */ + dw = THURSDAY; + temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1) + + day; + if (temp < FIRST_MISSING_DAY) { + dw = ((temp - 1 + SATURDAY) % 7); + } else if (temp >= (FIRST_MISSING_DAY + NUMBER_MISSING_DAYS)) { + dw = (((temp - 1 + SATURDAY) - NUMBER_MISSING_DAYS) % 7); + } + + if (!julian) { + day = 1; + } + + dm = days_in_month[month]; + if ((month == 2) && leap_year(year)) { + ++dm; + } + + while (dm) { + days[dw++] = day++; + --dm; + } +} + +static void trim_trailing_spaces_and_print(char *s) +{ + char *p = s; + + while (*p) { + ++p; + } + while (p > s) { + --p; + if (!(isspace)(*p)) { /* We want the function... not the inline. */ + p[1] = '\0'; + break; + } + } + + puts(s); +} + +static void center(char *str, int len, int separate) +{ + int n = strlen(str); + len -= n; + printf("%*s%*s", (len/2) + n, str, (len/2) + (len % 2) + separate, ""); +} + +static void blank_string(char *buf, size_t buflen) +{ + memset(buf, ' ', buflen); + buf[buflen-1] = '\0'; +} + +static char *build_row(char *p, int *dp) +{ + int col, val, day; + + memset(p, ' ', (julian + DAY_LEN) * 7); + + col = 0; + do { + if ((day = *dp++) != SPACE) { + if (julian) { + ++p; + if (day >= 100) { + *p = '0'; + p[-1] = (day / 100) + '0'; + day %= 100; + } + } + if ((val = day / 10) > 0) { + *p = val + '0'; + } + *++p = day % 10 + '0'; + p += 2; + } else { + p += DAY_LEN + julian; + } + } while (++col < 7); + + return p; +} + +/* + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kim Letkeman. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + + diff --git a/coreutils/cat.c b/coreutils/cat.c new file mode 100644 index 000000000..a95980552 --- /dev/null +++ b/coreutils/cat.c @@ -0,0 +1,41 @@ +/* vi: set sw=4 ts=4: */ +/* + * cat implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2, see file License in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/cat.html */ + +#include "busybox.h" +#include + +int cat_main(int argc, char **argv) +{ + FILE *f; + int retval = EXIT_SUCCESS; + + getopt32(argc, argv, "u"); + + argv += optind; + if (!*argv) { + *--argv = "-"; + } + + do { + f = fopen_or_warn_stdin(*argv); + if (f) { + off_t r = bb_copyfd_eof(fileno(f), STDOUT_FILENO); + fclose_if_not_stdin(f); + if (r >= 0) { + continue; + } + } + retval = EXIT_FAILURE; + } while (*++argv); + + return retval; +} diff --git a/coreutils/catv.c b/coreutils/catv.c new file mode 100644 index 000000000..66f30693a --- /dev/null +++ b/coreutils/catv.c @@ -0,0 +1,69 @@ +/* vi: set sw=4 ts=4: */ +/* + * cat -v implementation for busybox + * + * Copyright (C) 2006 Rob Landley + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* See "Cat -v considered harmful" at + * http://cm.bell-labs.com/cm/cs/doc/84/kp.ps.gz */ + +#include "busybox.h" + +int catv_main(int argc, char **argv) +{ + int retval = EXIT_SUCCESS, fd; + unsigned flags; + + flags = getopt32(argc, argv, "etv"); +#define CATV_OPT_e (1<<0) +#define CATV_OPT_t (1<<1) +#define CATV_OPT_v (1<<2) + flags ^= CATV_OPT_v; + + argv += optind; + do { + /* Read from stdin if there's nothing else to do. */ + fd = 0; + if (*argv && 0 > (fd = xopen(*argv, O_RDONLY))) + retval = EXIT_FAILURE; + else for (;;) { + int i, res; + + res = read(fd, bb_common_bufsiz1, sizeof(bb_common_bufsiz1)); + if (res < 0) + retval = EXIT_FAILURE; + if (res < 1) + break; + for (i = 0; i < res; i++) { + char c = bb_common_bufsiz1[i]; + + if (c > 126 && (flags & CATV_OPT_v)) { + if (c == 127) { + printf("^?"); + continue; + } else { + printf("M-"); + c -= 128; + } + } + if (c < 32) { + if (c == 10) { + if (flags & CATV_OPT_e) + putchar('$'); + } else if (flags & (c==9 ? CATV_OPT_t : CATV_OPT_v)) { + printf("^%c", c+'@'); + continue; + } + } + putchar(c); + } + } + if (ENABLE_FEATURE_CLEAN_UP && fd) + close(fd); + } while (*++argv); + + fflush_stdout_and_exit(retval); +} diff --git a/coreutils/chgrp.c b/coreutils/chgrp.c new file mode 100644 index 000000000..da5b12964 --- /dev/null +++ b/coreutils/chgrp.c @@ -0,0 +1,27 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini chgrp implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 defects - unsupported options -H, -L, and -P. */ +/* BB_AUDIT GNU defects - unsupported long options. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/chgrp.html */ + +#include "busybox.h" + +int chgrp_main(int argc, char **argv) +{ + /* "chgrp [opts] abc file(s)" == "chown [opts] :abc file(s)" */ + char **p = argv; + while (*++p) { + if (p[0][0] != '-') { + p[0] = xasprintf(":%s", p[0]); + break; + } + } + return chown_main(argc, argv); +} diff --git a/coreutils/chmod.c b/coreutils/chmod.c new file mode 100644 index 000000000..990e9b4ce --- /dev/null +++ b/coreutils/chmod.c @@ -0,0 +1,157 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini chmod implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Reworked by (C) 2002 Vladimir Oleynik + * to correctly parse '-rwxgoa' + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* BB_AUDIT GNU defects - unsupported long options. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/chmod.html */ + +#include "busybox.h" + +#define OPT_RECURSE (option_mask32 & 1) +#define OPT_VERBOSE (USE_DESKTOP(option_mask32 & 2) SKIP_DESKTOP(0)) +#define OPT_CHANGED (USE_DESKTOP(option_mask32 & 4) SKIP_DESKTOP(0)) +#define OPT_QUIET (USE_DESKTOP(option_mask32 & 8) SKIP_DESKTOP(0)) +#define OPT_STR "R" USE_DESKTOP("vcf") + +/* coreutils: + * chmod never changes the permissions of symbolic links; the chmod + * system call cannot change their permissions. This is not a problem + * since the permissions of symbolic links are never used. + * However, for each symbolic link listed on the command line, chmod changes + * the permissions of the pointed-to file. In contrast, chmod ignores + * symbolic links encountered during recursive directory traversals. + */ + +static int fileAction(const char *fileName, struct stat *statbuf, void* junk, int depth) +{ + mode_t newmode; + + /* match coreutils behavior */ + if (depth == 0) { + /* statbuf holds lstat result, but we need stat (follow link) */ + if (stat(fileName, statbuf)) + goto err; + } else { /* depth > 0: skip links */ + if (S_ISLNK(statbuf->st_mode)) + return TRUE; + } + newmode = statbuf->st_mode; + + if (!bb_parse_mode((char *)junk, &newmode)) + bb_error_msg_and_die("invalid mode: %s", (char *)junk); + + if (chmod(fileName, newmode) == 0) { + if (OPT_VERBOSE + || (OPT_CHANGED && statbuf->st_mode != newmode) + ) { + printf("mode of '%s' changed to %04o (%s)\n", fileName, + newmode & 07777, bb_mode_string(newmode)+1); + } + return TRUE; + } + err: + if (!OPT_QUIET) + bb_perror_msg("%s", fileName); + return FALSE; +} + +int chmod_main(int argc, char **argv) +{ + int retval = EXIT_SUCCESS; + char *arg, **argp; + char *smode; + + /* Convert first encountered -r into ar, -w into aw etc + * so that getopt would not eat it */ + argp = argv; + while ((arg = *++argp)) { + /* Mode spec must be the first arg (sans -R etc) */ + /* (protect against mishandling e.g. "chmod 644 -r") */ + if (arg[0] != '-') { + arg = NULL; + break; + } + /* An option. Not a -- or valid option? */ + if (arg[1] && !strchr("-"OPT_STR, arg[1])) { + arg[0] = 'a'; + break; + } + } + + /* Parse options */ + opt_complementary = "-2"; + getopt32(argc, argv, ("-"OPT_STR) + 1); /* Reuse string */ + argv += optind; + + /* Restore option-like mode if needed */ + if (arg) arg[0] = '-'; + + /* Ok, ready to do the deed now */ + smode = *argv++; + do { + if (!recursive_action(*argv, + OPT_RECURSE, // recurse + FALSE, // follow links: coreutils doesn't + FALSE, // depth first + fileAction, // file action + fileAction, // dir action + smode, // user data + 0) // depth + ) { + retval = EXIT_FAILURE; + } + } while (*++argv); + + return retval; +} + +/* +Security: chmod is too important and too subtle. +This is a test script (busybox chmod versus coreutils). +Run it in empty dir. Probably requires bash. + +#!/bin/sh +function create() { + rm -rf $1; mkdir $1 + ( + cd $1 || exit 1 + mkdir dir + >up + >file + >dir/file + ln -s dir linkdir + ln -s file linkfile + ln -s ../up dir/up + ) +} +function tst() { + (cd test1; $t1 $1) + (cd test2; $t2 $1) + (cd test1; ls -lR) >out1 + (cd test2; ls -lR) >out2 + echo "chmod $1" >out.diff + if ! diff -u out1 out2 >>out.diff; then exit 1; fi + mv out.diff out1.diff +} +t1="/tmp/busybox chmod" +t2="/usr/bin/chmod" +create test1; create test2 +tst "a+w file" +tst "a-w dir" +tst "a+w linkfile" +tst "a-w linkdir" +tst "-R a+w file" +tst "-R a-w dir" +tst "-R a+w linkfile" +tst "-R a-w linkdir" +tst "a-r,a+x linkfile" +*/ diff --git a/coreutils/chown.c b/coreutils/chown.c new file mode 100644 index 000000000..fddce7cf1 --- /dev/null +++ b/coreutils/chown.c @@ -0,0 +1,98 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini chown implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 defects - unsupported options -H, -L, and -P. */ +/* BB_AUDIT GNU defects - unsupported long options. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/chown.html */ + +#include "busybox.h" + +static uid_t uid = -1; +static gid_t gid = -1; + +static int (*chown_func)(const char *, uid_t, gid_t) = chown; + +#define OPT_RECURSE (option_mask32 & 1) +#define OPT_NODEREF (option_mask32 & 2) +#define OPT_VERBOSE (USE_DESKTOP(option_mask32 & 4) SKIP_DESKTOP(0)) +#define OPT_CHANGED (USE_DESKTOP(option_mask32 & 8) SKIP_DESKTOP(0)) +#define OPT_QUIET (USE_DESKTOP(option_mask32 & 0x10) SKIP_DESKTOP(0)) +#define OPT_STR ("Rh" USE_DESKTOP("vcf")) + +/* TODO: + * -H if a command line argument is a symbolic link to a directory, traverse it + * -L traverse every symbolic link to a directory encountered + * -P do not traverse any symbolic links (default) + */ + +static int fileAction(const char *fileName, struct stat *statbuf, + void ATTRIBUTE_UNUSED *junk, int depth) +{ + // TODO: -H/-L/-P + // if (depth ... && S_ISLNK(statbuf->st_mode)) .... + + if (!chown_func(fileName, + (uid == (uid_t)-1) ? statbuf->st_uid : uid, + (gid == (gid_t)-1) ? statbuf->st_gid : gid)) { + if (OPT_VERBOSE + || (OPT_CHANGED && (statbuf->st_uid != uid || statbuf->st_gid != gid)) + ) { + printf("changed ownership of '%s' to %u:%u\n", fileName, uid, gid); + } + return TRUE; + } + if (!OPT_QUIET) + bb_perror_msg("%s", fileName); /* A filename can have % in it... */ + return FALSE; +} + +int chown_main(int argc, char **argv) +{ + int retval = EXIT_SUCCESS; + char *groupName; + + opt_complementary = "-2"; + getopt32(argc, argv, OPT_STR); + + if (OPT_NODEREF) chown_func = lchown; + + argv += optind; + + /* First, check if there is a group name here */ + groupName = strchr(*argv, '.'); + if (!groupName) { + groupName = strchr(*argv, ':'); + } + + /* Check for the username and groupname */ + if (groupName) { + *groupName++ = '\0'; + gid = get_ug_id(groupName, bb_xgetgrnam); + } + if (--groupName != *argv) + uid = get_ug_id(*argv, bb_xgetpwnam); + ++argv; + + /* Ok, ready to do the deed now */ + do { + if (!recursive_action(*argv, + OPT_RECURSE, // recurse + FALSE, // follow links: TODO: -H/-L/-P + FALSE, // depth first + fileAction, // file action + fileAction, // dir action + NULL, // user data + 0) // depth + ) { + retval = EXIT_FAILURE; + } + } while (*++argv); + + return retval; +} diff --git a/coreutils/chroot.c b/coreutils/chroot.c new file mode 100644 index 000000000..62cfdc244 --- /dev/null +++ b/coreutils/chroot.c @@ -0,0 +1,37 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini chroot implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 N/A -- Matches GNU behavior. */ + +#include "busybox.h" + +int chroot_main(int argc, char **argv) +{ + if (argc < 2) { + bb_show_usage(); + } + + ++argv; + if (chroot(*argv)) { + bb_perror_msg_and_die("cannot change root directory to %s", *argv); + } + xchdir("/"); + + ++argv; + if (argc == 2) { + argv -= 2; + if (!(*argv = getenv("SHELL"))) { + *argv = (char *) DEFAULT_SHELL; + } + argv[1] = (char *) "-i"; + } + + execvp(*argv, argv); + bb_perror_msg_and_die("cannot execute %s", *argv); +} diff --git a/coreutils/cksum.c b/coreutils/cksum.c new file mode 100644 index 000000000..52213328c --- /dev/null +++ b/coreutils/cksum.c @@ -0,0 +1,53 @@ +/* vi: set sw=4 ts=4: */ +/* + * cksum - calculate the CRC32 checksum of a file + * + * Copyright (C) 2006 by Rob Sullivan, with ideas from code by Walter Harms + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. */ + +#include "busybox.h" + +int cksum_main(int argc, char **argv) +{ + + uint32_t *crc32_table = crc32_filltable(1); + + FILE *fp; + uint32_t crc; + long length, filesize; + int bytes_read; + char *cp; + + int inp_stdin = (argc == optind) ? 1 : 0; + + do { + fp = fopen_or_warn_stdin((inp_stdin) ? bb_msg_standard_input : *++argv); + + crc = 0; + length = 0; + + while ((bytes_read = fread(bb_common_bufsiz1, 1, BUFSIZ, fp)) > 0) { + cp = bb_common_bufsiz1; + length += bytes_read; + while (bytes_read--) + crc = (crc << 8) ^ crc32_table[((crc >> 24) ^ (*cp++)) & 0xffL]; + } + + filesize = length; + + for (; length; length >>= 8) + crc = (crc << 8) ^ crc32_table[((crc >> 24) ^ length) & 0xffL]; + crc ^= 0xffffffffL; + + if (inp_stdin) { + printf("%" PRIu32 " %li\n", crc, filesize); + break; + } + + printf("%" PRIu32 " %li %s\n", crc, filesize, *argv); + fclose(fp); + } while (*(argv + 1)); + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/coreutils/cmp.c b/coreutils/cmp.c new file mode 100644 index 000000000..71007eac1 --- /dev/null +++ b/coreutils/cmp.c @@ -0,0 +1,127 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini cmp implementation for busybox + * + * Copyright (C) 2000,2001 by Matt Kraai + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 (virtually) compliant -- uses nicer GNU format for -l. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/cmp.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Original version majorly reworked for SUSv3 compliance, bug fixes, and + * size optimizations. Changes include: + * 1) Now correctly distinguishes between errors and actual file differences. + * 2) Proper handling of '-' args. + * 3) Actual error checking of i/o. + * 4) Accept SUSv3 -l option. Note that we use the slightly nicer gnu format + * in the '-l' case. + */ + +#include "busybox.h" + +static FILE *cmp_xfopen_input(const char * const filename) +{ + FILE *fp; + + fp = fopen_or_warn_stdin(filename); + if (fp) + return fp; + exit(xfunc_error_retval); /* We already output an error message. */ +} + +static const char fmt_eof[] = "cmp: EOF on %s\n"; +static const char fmt_differ[] = "%s %s differ: char %d, line %d\n"; +// This fmt_l_opt uses gnu-isms. SUSv3 would be "%.0s%.0s%d %o %o\n" +static const char fmt_l_opt[] = "%.0s%.0s%d %3o %3o\n"; + +static const char opt_chars[] = "sl"; +#define CMP_OPT_s (1<<0) +#define CMP_OPT_l (1<<1) + +int cmp_main(int argc, char **argv) +{ + FILE *fp1, *fp2, *outfile = stdout; + const char *filename1, *filename2 = "-"; + const char *fmt; + int c1, c2, char_pos = 0, line_pos = 1; + unsigned opt; + int retval = 0; + + xfunc_error_retval = 2; /* 1 is returned if files are different. */ + + opt = getopt32(argc, argv, opt_chars); + + if (((opt & (CMP_OPT_s|CMP_OPT_l)) == (CMP_OPT_s|CMP_OPT_l)) + || (((unsigned int)(--argc - optind)) > 1)) + bb_show_usage(); + + fp1 = cmp_xfopen_input(filename1 = *(argv += optind)); + + if (*++argv) { + filename2 = *argv; + } + fp2 = cmp_xfopen_input(filename2); + + if (fp1 == fp2) { /* Paranioa check... stdin == stdin? */ + /* Note that we don't bother reading stdin. Neither does gnu wc. + * But perhaps we should, so that other apps down the chain don't + * get the input. Consider 'echo hello | (cmp - - && cat -)'. + */ + return 0; + } + + if (opt & CMP_OPT_l) + fmt = fmt_l_opt; + else + fmt = fmt_differ; + + do { + c1 = getc(fp1); + c2 = getc(fp2); + ++char_pos; + if (c1 != c2) { /* Remember: a read error may have occurred. */ + retval = 1; /* But assume the files are different for now. */ + if (c2 == EOF) { + /* We know that fp1 isn't at EOF or in an error state. But to + * save space below, things are setup to expect an EOF in fp1 + * if an EOF occurred. So, swap things around. + */ + fp1 = fp2; + filename1 = filename2; + c1 = c2; + } + if (c1 == EOF) { + die_if_ferror(fp1, filename1); + fmt = fmt_eof; /* Well, no error, so it must really be EOF. */ + outfile = stderr; + /* There may have been output to stdout (option -l), so + * make sure we fflush before writing to stderr. */ + xfflush_stdout(); + } + if (!(opt & CMP_OPT_s)) { + if (opt & CMP_OPT_l) { + line_pos = c1; /* line_pos is unused in the -l case. */ + } + fprintf(outfile, fmt, filename1, filename2, char_pos, line_pos, c2); + if (opt) { /* This must be -l since not -s. */ + /* If we encountered an EOF, + * the while check will catch it. */ + continue; + } + } + break; + } + if (c1 == '\n') { + ++line_pos; + } + } while (c1 != EOF); + + die_if_ferror(fp1, filename1); + die_if_ferror(fp2, filename2); + + fflush_stdout_and_exit(retval); +} diff --git a/coreutils/comm.c b/coreutils/comm.c new file mode 100644 index 000000000..91f017753 --- /dev/null +++ b/coreutils/comm.c @@ -0,0 +1,126 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini comm implementation for busybox + * + * Copyright (C) 2005 by Robert Sullivan + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +#define COMM_OPT_1 0x01 +#define COMM_OPT_2 0x02 +#define COMM_OPT_3 0x04 + +/* These three variables control behaviour if non-zero */ + +static int only_file_1; +static int only_file_2; +static int both; + +/* writeline outputs the input given, appropriately aligned according to class */ +static void writeline(char *line, int class) +{ + if (class == 0) { + if (!only_file_1) + return; + } else if (class == 1) { + if (!only_file_2) + return; + if (only_file_1) + putchar('\t'); + } + else /*if (class == 2)*/ { + if (!both) + return; + if (only_file_1) + putchar('\t'); + if (only_file_2) + putchar('\t'); + } + fputs(line, stdout); +} + +/* This is the real core of the program - lines are compared here */ +static void cmp_files(char **infiles) +{ +#define LINE_LEN 100 +#define BB_EOF_0 0x1 +#define BB_EOF_1 0x2 + char thisline[2][LINE_LEN]; + FILE *streams[2]; + int i; + + for (i = 0; i < 2; ++i) { + streams[i] = ((infiles[i][0] == '=' && infiles[i][1]) ? stdin : xfopen(infiles[i], "r")); + fgets(thisline[i], LINE_LEN, streams[i]); + } + + while (*thisline[0] || *thisline[1]) { + int order = 0; + + i = 0; + if (feof(streams[0])) i |= BB_EOF_0; + if (feof(streams[1])) i |= BB_EOF_1; + + if (!*thisline[0]) + order = 1; + else if (!*thisline[1]) + order = -1; + else { + int tl0_len, tl1_len; + tl0_len = strlen(thisline[0]); + tl1_len = strlen(thisline[1]); + order = memcmp(thisline[0], thisline[1], tl0_len < tl1_len ? tl0_len : tl1_len); + if (!order) + order = tl0_len < tl1_len ? -1 : tl0_len != tl1_len; + } + + if (order == 0 && !i) + writeline(thisline[1], 2); + else if (order > 0 && !(i & BB_EOF_1)) + writeline(thisline[1], 1); + else if (order < 0 && !(i & BB_EOF_0)) + writeline(thisline[0], 0); + + if (i & BB_EOF_0 & BB_EOF_1) { + break; + + } else if (i) { + i = (i & BB_EOF_0 ? 1 : 0); + while (!feof(streams[i])) { + if ((order < 0 && i) || (order > 0 && !i)) + writeline(thisline[i], i); + fgets(thisline[i], LINE_LEN, streams[i]); + } + break; + + } else { + if (order >= 0) + fgets(thisline[1], LINE_LEN, streams[1]); + if (order <= 0) + fgets(thisline[0], LINE_LEN, streams[0]); + } + } + + fclose(streams[0]); + fclose(streams[1]); +} + +int comm_main(int argc, char **argv) +{ + unsigned long flags; + + flags = getopt32(argc, argv, "123"); + + if (optind + 2 != argc) + bb_show_usage(); + + only_file_1 = !(flags & COMM_OPT_1); + only_file_2 = !(flags & COMM_OPT_2); + both = !(flags & COMM_OPT_3); + + cmp_files(argv + optind); + exit(EXIT_SUCCESS); +} diff --git a/coreutils/cp.c b/coreutils/cp.c new file mode 100644 index 000000000..47ad85ecf --- /dev/null +++ b/coreutils/cp.c @@ -0,0 +1,92 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini cp implementation for busybox + * + * Copyright (C) 2000 by Matt Kraai + * + * Licensed under GPL v2 or later, see file LICENSE in this tarball for details. + */ + +/* http://www.opengroup.org/onlinepubs/007904975/utilities/cp.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Size reduction. + */ + +#include "busybox.h" +#include "libcoreutils/coreutils.h" + +int cp_main(int argc, char **argv) +{ + struct stat source_stat; + struct stat dest_stat; + const char *last; + const char *dest; + int s_flags; + int d_flags; + int flags; + int status = 0; + enum { + OPT_a = 1 << (sizeof(FILEUTILS_CP_OPTSTR)-1), + OPT_r = 1 << (sizeof(FILEUTILS_CP_OPTSTR)), + OPT_P = 1 << (sizeof(FILEUTILS_CP_OPTSTR)+1), + OPT_H = 1 << (sizeof(FILEUTILS_CP_OPTSTR)+2), + OPT_L = 1 << (sizeof(FILEUTILS_CP_OPTSTR)+3), + }; + + // Soft- and hardlinking don't mix + // -P and -d are the same (-P is POSIX, -d is GNU) + // -r and -R are the same + // -a = -pdR + opt_complementary = "?:l--s:s--l:Pd:rR:apdR"; + flags = getopt32(argc, argv, FILEUTILS_CP_OPTSTR "arPHL"); + /* Default behavior of cp is to dereference, so we don't have to do + * anything special when we are given -L. + * The behavior of -H is *almost* like -L, but not quite, so let's + * just ignore it too for fun. + if (flags & OPT_L) ... + if (flags & OPT_H) ... // deref command-line params only + */ + + flags ^= FILEUTILS_DEREFERENCE; /* The sense of this flag was reversed. */ + + if (optind + 2 > argc) { + bb_show_usage(); + } + + last = argv[argc - 1]; + argv += optind; + + /* If there are only two arguments and... */ + if (optind + 2 == argc) { + s_flags = cp_mv_stat2(*argv, &source_stat, + (flags & FILEUTILS_DEREFERENCE) ? stat : lstat); + if (s_flags < 0) + return EXIT_FAILURE; + d_flags = cp_mv_stat(last, &dest_stat); + if (d_flags < 0) + return EXIT_FAILURE; + + /* ...if neither is a directory or... */ + if ( !((s_flags | d_flags) & 2) || + /* ...recursing, the 1st is a directory, and the 2nd doesn't exist... */ + ((flags & FILEUTILS_RECUR) && (s_flags & 2) && !d_flags) + ) { + /* ...do a simple copy. */ + dest = xstrdup(last); + goto DO_COPY; /* Note: optind+2==argc implies argv[1]==last below. */ + } + } + + do { + dest = concat_path_file(last, bb_get_last_path_component(*argv)); + DO_COPY: + if (copy_file(*argv, dest, flags) < 0) { + status = 1; + } + free((void*)dest); + } while (*++argv != last); + + return status; +} diff --git a/coreutils/cut.c b/coreutils/cut.c new file mode 100644 index 000000000..a538e3d20 --- /dev/null +++ b/coreutils/cut.c @@ -0,0 +1,285 @@ +/* vi: set sw=4 ts=4: */ +/* + * cut.c - minimalist version of cut + * + * Copyright (C) 1999,2000,2001 by Lineo, inc. + * Written by Mark Whitley + * debloated by Bernhard Fischer + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +/* option vars */ +static const char optstring[] = "b:c:f:d:sn"; +#define CUT_OPT_BYTE_FLGS (1<<0) +#define CUT_OPT_CHAR_FLGS (1<<1) +#define CUT_OPT_FIELDS_FLGS (1<<2) +#define CUT_OPT_DELIM_FLGS (1<<3) +#define CUT_OPT_SUPPRESS_FLGS (1<<4) + +static char delim = '\t'; /* delimiter, default is tab */ + +struct cut_list { + int startpos; + int endpos; +}; + +enum { + BOL = 0, + EOL = INT_MAX, + NON_RANGE = -1 +}; + +/* growable array holding a series of lists */ +static struct cut_list *cut_lists; +static unsigned int nlists; /* number of elements in above list */ + + +static int cmpfunc(const void *a, const void *b) +{ + return (((struct cut_list *) a)->startpos - + ((struct cut_list *) b)->startpos); + +} + +static void cut_file(FILE * file) +{ + char *line = NULL; + unsigned int linenum = 0; /* keep these zero-based to be consistent */ + + /* go through every line in the file */ + while ((line = xmalloc_getline(file)) != NULL) { + + /* set up a list so we can keep track of what's been printed */ + char * printed = xzalloc(strlen(line) * sizeof(char)); + char * orig_line = line; + unsigned int cl_pos = 0; + int spos; + + /* cut based on chars/bytes XXX: only works when sizeof(char) == byte */ + if (option_mask32 & (CUT_OPT_CHAR_FLGS | CUT_OPT_BYTE_FLGS)) { + /* print the chars specified in each cut list */ + for (; cl_pos < nlists; cl_pos++) { + spos = cut_lists[cl_pos].startpos; + while (spos < strlen(line)) { + if (!printed[spos]) { + printed[spos] = 'X'; + putchar(line[spos]); + } + spos++; + if (spos > cut_lists[cl_pos].endpos + || cut_lists[cl_pos].endpos == NON_RANGE) + break; + } + } + } else if (delim == '\n') { /* cut by lines */ + spos = cut_lists[cl_pos].startpos; + + /* get out if we have no more lists to process or if the lines + * are lower than what we're interested in */ + if (linenum < spos || cl_pos >= nlists) + goto next_line; + + /* if the line we're looking for is lower than the one we were + * passed, it means we displayed it already, so move on */ + while (spos < linenum) { + spos++; + /* go to the next list if we're at the end of this one */ + if (spos > cut_lists[cl_pos].endpos + || cut_lists[cl_pos].endpos == NON_RANGE) { + cl_pos++; + /* get out if there's no more lists to process */ + if (cl_pos >= nlists) + goto next_line; + spos = cut_lists[cl_pos].startpos; + /* get out if the current line is lower than the one + * we just became interested in */ + if (linenum < spos) + goto next_line; + } + } + + /* If we made it here, it means we've found the line we're + * looking for, so print it */ + puts(line); + goto next_line; + } else { /* cut by fields */ + int ndelim = -1; /* zero-based / one-based problem */ + int nfields_printed = 0; + char *field = NULL; + const char delimiter[2] = { delim, 0 }; + + /* does this line contain any delimiters? */ + if (strchr(line, delim) == NULL) { + if (!(option_mask32 & CUT_OPT_SUPPRESS_FLGS)) + puts(line); + goto next_line; + } + + /* process each list on this line, for as long as we've got + * a line to process */ + for (; cl_pos < nlists && line; cl_pos++) { + spos = cut_lists[cl_pos].startpos; + do { + /* find the field we're looking for */ + while (line && ndelim < spos) { + field = strsep(&line, delimiter); + ndelim++; + } + + /* we found it, and it hasn't been printed yet */ + if (field && ndelim == spos && !printed[ndelim]) { + /* if this isn't our first time through, we need to + * print the delimiter after the last field that was + * printed */ + if (nfields_printed > 0) + putchar(delim); + fputs(field, stdout); + printed[ndelim] = 'X'; + nfields_printed++; /* shouldn't overflow.. */ + } + + spos++; + + /* keep going as long as we have a line to work with, + * this is a list, and we're not at the end of that + * list */ + } while (spos <= cut_lists[cl_pos].endpos && line + && cut_lists[cl_pos].endpos != NON_RANGE); + } + } + /* if we printed anything at all, we need to finish it with a + * newline cuz we were handed a chomped line */ + putchar('\n'); + next_line: + linenum++; + free(printed); + free(orig_line); + } +} + +static const char _op_on_field[] = " only when operating on fields"; + +int cut_main(int argc, char **argv) +{ + char *sopt, *ltok; + + opt_complementary = "b--bcf:c--bcf:f--bcf"; + getopt32(argc, argv, optstring, &sopt, &sopt, &sopt, <ok); + if (!(option_mask32 & (CUT_OPT_BYTE_FLGS | CUT_OPT_CHAR_FLGS | CUT_OPT_FIELDS_FLGS))) + bb_error_msg_and_die("expected a list of bytes, characters, or fields"); + if (option_mask32 & BB_GETOPT_ERROR) + bb_error_msg_and_die("only one type of list may be specified"); + + if (option_mask32 & CUT_OPT_DELIM_FLGS) { + if (strlen(ltok) > 1) { + bb_error_msg_and_die("the delimiter must be a single character"); + } + delim = ltok[0]; + } + + /* non-field (char or byte) cutting has some special handling */ + if (!(option_mask32 & CUT_OPT_FIELDS_FLGS)) { + if (option_mask32 & CUT_OPT_SUPPRESS_FLGS) { + bb_error_msg_and_die + ("suppressing non-delimited lines makes sense%s", + _op_on_field); + } + if (delim != '\t') { + bb_error_msg_and_die + ("a delimiter may be specified%s", _op_on_field); + } + } + + /* + * parse list and put values into startpos and endpos. + * valid list formats: N, N-, N-M, -M + * more than one list can be separated by commas + */ + { + char *ntok; + int s = 0, e = 0; + + /* take apart the lists, one by one (they are separated with commas */ + while ((ltok = strsep(&sopt, ",")) != NULL) { + + /* it's actually legal to pass an empty list */ + if (strlen(ltok) == 0) + continue; + + /* get the start pos */ + ntok = strsep(<ok, "-"); + if (ntok == NULL) { + bb_error_msg + ("internal error: ntok is null for start pos!?\n"); + } else if (strlen(ntok) == 0) { + s = BOL; + } else { + s = xatoi_u(ntok); + /* account for the fact that arrays are zero based, while + * the user expects the first char on the line to be char #1 */ + if (s != 0) + s--; + } + + /* get the end pos */ + ntok = strsep(<ok, "-"); + if (ntok == NULL) { + e = NON_RANGE; + } else if (strlen(ntok) == 0) { + e = EOL; + } else { + e = xatoi_u(ntok); + /* if the user specified and end position of 0, that means "til the + * end of the line */ + if (e == 0) + e = EOL; + e--; /* again, arrays are zero based, lines are 1 based */ + if (e == s) + e = NON_RANGE; + } + + /* if there's something left to tokenize, the user passed + * an invalid list */ + if (ltok) + bb_error_msg_and_die("invalid byte or field list"); + + /* add the new list */ + cut_lists = xrealloc(cut_lists, sizeof(struct cut_list) * (++nlists)); + cut_lists[nlists-1].startpos = s; + cut_lists[nlists-1].endpos = e; + } + + /* make sure we got some cut positions out of all that */ + if (nlists == 0) + bb_error_msg_and_die("missing list of positions"); + + /* now that the lists are parsed, we need to sort them to make life + * easier on us when it comes time to print the chars / fields / lines + */ + qsort(cut_lists, nlists, sizeof(struct cut_list), cmpfunc); + } + + /* argv[(optind)..(argc-1)] should be names of file to process. If no + * files were specified or '-' was specified, take input from stdin. + * Otherwise, we process all the files specified. */ + if (argv[optind] == NULL + || (argv[optind][0] == '-' && argv[optind][1] == '\0')) { + cut_file(stdin); + } else { + FILE *file; + + for (; optind < argc; optind++) { + file = fopen_or_warn(argv[optind], "r"); + if (file) { + cut_file(file); + fclose(file); + } + } + } + if (ENABLE_FEATURE_CLEAN_UP) + free(cut_lists); + return EXIT_SUCCESS; +} diff --git a/coreutils/date.c b/coreutils/date.c new file mode 100644 index 000000000..37ccfd5ba --- /dev/null +++ b/coreutils/date.c @@ -0,0 +1,239 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini date implementation for busybox + * + * by Matthew Grant + * + * iso-format handling added by Robert Griebl + * bugfixes and cleanup by Bernhard Fischer + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. +*/ + +#include "busybox.h" + +/* This 'date' command supports only 2 time setting formats, + all the GNU strftime stuff (its in libc, lets use it), + setting time using UTC and displaying it, as well as + an RFC 2822 compliant date output for shell scripting + mail commands */ + +/* Input parsing code is always bulky - used heavy duty libc stuff as + much as possible, missed out a lot of bounds checking */ + +/* Default input handling to save surprising some people */ + + +#define DATE_OPT_RFC2822 0x01 +#define DATE_OPT_SET 0x02 +#define DATE_OPT_UTC 0x04 +#define DATE_OPT_DATE 0x08 +#define DATE_OPT_REFERENCE 0x10 +#define DATE_OPT_TIMESPEC 0x20 +#define DATE_OPT_HINT 0x40 + +static void xputenv(char *s) +{ + if (putenv(s) != 0) + bb_error_msg_and_die(bb_msg_memory_exhausted); +} + +static void maybe_set_utc(int opt) +{ + if (opt & DATE_OPT_UTC) + xputenv("TZ=UTC0"); +} + +int date_main(int argc, char **argv) +{ + time_t tm; + struct tm tm_time; + unsigned opt; + int ifmt = -1; + char *date_str = NULL; + char *date_fmt = NULL; + char *filename = NULL; + char *isofmt_arg; + char *hintfmt_arg; + + opt_complementary = "?:d--s:s--d" + USE_FEATURE_DATE_ISOFMT(":R--I:I--R"); + opt = getopt32(argc, argv, "Rs:ud:r:" + USE_FEATURE_DATE_ISOFMT("I::D:"), + &date_str, &date_str, &filename + USE_FEATURE_DATE_ISOFMT(, &isofmt_arg, &hintfmt_arg)); + maybe_set_utc(opt); + + if (ENABLE_FEATURE_DATE_ISOFMT && (opt & DATE_OPT_TIMESPEC)) { + if (!isofmt_arg) { + ifmt = 0; /* default is date */ + } else { + const char * const isoformats[] = + {"date", "hours", "minutes", "seconds"}; + + for (ifmt = 0; ifmt < 4; ifmt++) + if (!strcmp(isofmt_arg, isoformats[ifmt])) { + break; + } + if (ifmt == 4) /* parse error */ + bb_show_usage(); + } + } + + /* XXX, date_fmt == NULL from this always */ + if ((date_fmt == NULL) && (optind < argc) && (argv[optind][0] == '+')) { + date_fmt = &argv[optind][1]; /* Skip over the '+' */ + } else if (date_str == NULL) { + opt |= DATE_OPT_SET; + date_str = argv[optind]; + } + + /* Now we have parsed all the information except the date format + which depends on whether the clock is being set or read */ + + if (filename) { + struct stat statbuf; + xstat(filename, &statbuf); + tm = statbuf.st_mtime; + } else + time(&tm); + memcpy(&tm_time, localtime(&tm), sizeof(tm_time)); + /* Zero out fields - take her back to midnight! */ + if (date_str != NULL) { + tm_time.tm_sec = 0; + tm_time.tm_min = 0; + tm_time.tm_hour = 0; + + /* Process any date input to UNIX time since 1 Jan 1970 */ + if (ENABLE_FEATURE_DATE_ISOFMT && (opt & DATE_OPT_HINT)) { + strptime(date_str, hintfmt_arg, &tm_time); + } else if (strchr(date_str, ':') != NULL) { + /* Parse input and assign appropriately to tm_time */ + + if (sscanf(date_str, "%d:%d:%d", &tm_time.tm_hour, &tm_time.tm_min, + &tm_time.tm_sec) == 3) { + /* no adjustments needed */ + } else if (sscanf(date_str, "%d:%d", &tm_time.tm_hour, + &tm_time.tm_min) == 2) { + /* no adjustments needed */ + } else if (sscanf(date_str, "%d.%d-%d:%d:%d", &tm_time.tm_mon, + &tm_time.tm_mday, &tm_time.tm_hour, + &tm_time.tm_min, &tm_time.tm_sec) == 5) { + /* Adjust dates from 1-12 to 0-11 */ + tm_time.tm_mon -= 1; + } else if (sscanf(date_str, "%d.%d-%d:%d", &tm_time.tm_mon, + &tm_time.tm_mday, + &tm_time.tm_hour, &tm_time.tm_min) == 4) { + /* Adjust dates from 1-12 to 0-11 */ + tm_time.tm_mon -= 1; + } else if (sscanf(date_str, "%d.%d.%d-%d:%d:%d", &tm_time.tm_year, + &tm_time.tm_mon, &tm_time.tm_mday, + &tm_time.tm_hour, &tm_time.tm_min, + &tm_time.tm_sec) == 6) { + tm_time.tm_year -= 1900; /* Adjust years */ + tm_time.tm_mon -= 1; /* Adjust dates from 1-12 to 0-11 */ + } else if (sscanf(date_str, "%d.%d.%d-%d:%d", &tm_time.tm_year, + &tm_time.tm_mon, &tm_time.tm_mday, + &tm_time.tm_hour, &tm_time.tm_min) == 5) { + tm_time.tm_year -= 1900; /* Adjust years */ + tm_time.tm_mon -= 1; /* Adjust dates from 1-12 to 0-11 */ + } else { + bb_error_msg_and_die(bb_msg_invalid_date, date_str); + } + } else { + int nr; + char *cp; + + nr = sscanf(date_str, "%2d%2d%2d%2d%d", &tm_time.tm_mon, + &tm_time.tm_mday, &tm_time.tm_hour, &tm_time.tm_min, + &tm_time.tm_year); + + if (nr < 4 || nr > 5) { + bb_error_msg_and_die(bb_msg_invalid_date, date_str); + } + + cp = strchr(date_str, '.'); + if (cp) { + nr = sscanf(cp + 1, "%2d", &tm_time.tm_sec); + if (nr != 1) { + bb_error_msg_and_die(bb_msg_invalid_date, date_str); + } + } + + /* correct for century - minor Y2K problem here? */ + if (tm_time.tm_year >= 1900) { + tm_time.tm_year -= 1900; + } + /* adjust date */ + tm_time.tm_mon -= 1; + } + + /* Correct any day of week and day of year etc. fields */ + tm_time.tm_isdst = -1; /* Be sure to recheck dst. */ + tm = mktime(&tm_time); + if (tm < 0) { + bb_error_msg_and_die(bb_msg_invalid_date, date_str); + } + maybe_set_utc(opt); + + /* if setting time, set it */ + if ((opt & DATE_OPT_SET) && stime(&tm) < 0) { + bb_perror_msg("cannot set date"); + } + } + + /* Display output */ + + /* Deal with format string */ + + if (date_fmt == NULL) { + int i; + date_fmt = xzalloc(32); + if (ENABLE_FEATURE_DATE_ISOFMT && ifmt >= 0) { + strcpy(date_fmt, "%Y-%m-%d"); + if (ifmt > 0) { + i = 8; + date_fmt[i++] = 'T'; + date_fmt[i++] = '%'; + date_fmt[i++] = 'H'; + if (ifmt > 1) { + date_fmt[i++] = ':'; + date_fmt[i++] = '%'; + date_fmt[i++] = 'M'; + } + if (ifmt > 2) { + date_fmt[i++] = ':'; + date_fmt[i++] = '%'; + date_fmt[i++] = 'S'; + } +format_utc: + date_fmt[i++] = '%'; + date_fmt[i] = (opt & DATE_OPT_UTC) ? 'Z' : 'z'; + } + } else if (opt & DATE_OPT_RFC2822) { + /* Undo busybox.c for date -R */ + setlocale(LC_TIME, "C"); + strcpy(date_fmt, "%a, %d %b %Y %H:%M:%S "); + i = 22; + goto format_utc; + } else /* default case */ + date_fmt = "%a %b %e %H:%M:%S %Z %Y"; + } + + if (*date_fmt == '\0') { + /* With no format string, just print a blank line */ + *bb_common_bufsiz1 = 0; + } else { + /* Handle special conversions */ + + if (strncmp(date_fmt, "%f", 2) == 0) { + date_fmt = "%Y.%m.%d-%H:%M:%S"; + } + + /* Generate output string */ + strftime(bb_common_bufsiz1, 200, date_fmt, &tm_time); + } + puts(bb_common_bufsiz1); + + return EXIT_SUCCESS; +} diff --git a/coreutils/dd.c b/coreutils/dd.c new file mode 100644 index 000000000..01f37abeb --- /dev/null +++ b/coreutils/dd.c @@ -0,0 +1,248 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini dd implementation for busybox + * + * + * Copyright (C) 2000,2001 Matt Kraai + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include /* For FEATURE_DD_SIGNAL_HANDLING */ + +static const struct suffix_mult dd_suffixes[] = { + { "c", 1 }, + { "w", 2 }, + { "b", 512 }, + { "kD", 1000 }, + { "k", 1024 }, + { "K", 1024 }, // compat with coreutils dd + { "MD", 1000000 }, + { "M", 1048576 }, + { "GD", 1000000000 }, + { "G", 1073741824 }, + { NULL, 0 } +}; + +static off_t out_full, out_part, in_full, in_part; + +static void dd_output_status(int ATTRIBUTE_UNUSED cur_signal) +{ + fprintf(stderr, "%"OFF_FMT"d+%"OFF_FMT"d records in\n" + "%"OFF_FMT"d+%"OFF_FMT"d records out\n", + in_full, in_part, + out_full, out_part); +} + +static ssize_t full_write_or_warn(int fd, const void *buf, size_t len, + const char* filename) +{ + ssize_t n = full_write(fd, buf, len); + if (n < 0) + bb_perror_msg("writing '%s'", filename); + return n; +} + +#if ENABLE_LFS +#define XATOU_SFX xatoull_sfx +#else +#define XATOU_SFX xatoul_sfx +#endif + +int dd_main(int argc, char **argv) +{ + enum { + sync_flag = 1 << 0, + noerror = 1 << 1, + trunc_flag = 1 << 2, + twobufs_flag = 1 << 3, + }; + int flags = trunc_flag; + size_t oc = 0, ibs = 512, obs = 512; + ssize_t n, w; + off_t seek = 0, skip = 0, count = OFF_T_MAX; + int oflag, ifd, ofd; + const char *infile = NULL, *outfile = NULL; + char *ibuf, *obuf; + + if (ENABLE_FEATURE_DD_SIGNAL_HANDLING) { + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = dd_output_status; + sa.sa_flags = SA_RESTART; + sigemptyset(&sa.sa_mask); + sigaction(SIGUSR1, &sa, 0); + } + + for (n = 1; n < argc; n++) { + char *arg = argv[n]; + /* Must fit into positive ssize_t */ + if (ENABLE_FEATURE_DD_IBS_OBS && !strncmp("ibs=", arg, 4)) + ibs = xatoul_range_sfx(arg+4, 0, ((size_t)-1L)/2, dd_suffixes); + else if (ENABLE_FEATURE_DD_IBS_OBS && !strncmp("obs=", arg, 4)) + obs = xatoul_range_sfx(arg+4, 0, ((size_t)-1L)/2, dd_suffixes); + else if (!strncmp("bs=", arg, 3)) + ibs = obs = xatoul_range_sfx(arg+3, 0, ((size_t)-1L)/2, dd_suffixes); + /* These can be large: */ + else if (!strncmp("count=", arg, 6)) + count = XATOU_SFX(arg+6, dd_suffixes); + else if (!strncmp("seek=", arg, 5)) + seek = XATOU_SFX(arg+5, dd_suffixes); + else if (!strncmp("skip=", arg, 5)) + skip = XATOU_SFX(arg+5, dd_suffixes); + + else if (!strncmp("if=", arg, 3)) + infile = arg+3; + else if (!strncmp("of=", arg, 3)) + outfile = arg+3; + else if (ENABLE_FEATURE_DD_IBS_OBS && !strncmp("conv=", arg, 5)) { + arg += 5; + while (1) { + if (!strncmp("notrunc", arg, 7)) { + flags &= ~trunc_flag; + arg += 7; + } else if (!strncmp("sync", arg, 4)) { + flags |= sync_flag; + arg += 4; + } else if (!strncmp("noerror", arg, 7)) { + flags |= noerror; + arg += 7; + } else { + bb_error_msg_and_die(bb_msg_invalid_arg, arg, "conv"); + } + if (arg[0] == '\0') break; + if (*arg++ != ',') bb_show_usage(); + } + } else + bb_show_usage(); + } + + ibuf = obuf = xmalloc(ibs); + if (ibs != obs) { + flags |= twobufs_flag; + obuf = xmalloc(obs); + } + + if (infile != NULL) + ifd = xopen(infile, O_RDONLY); + else { + ifd = STDIN_FILENO; + infile = bb_msg_standard_input; + } + + if (outfile != NULL) { + oflag = O_WRONLY | O_CREAT; + + if (!seek && (flags & trunc_flag)) + oflag |= O_TRUNC; + + ofd = xopen(outfile, oflag); + + if (seek && (flags & trunc_flag)) { + if (ftruncate(ofd, seek * obs) < 0) { + struct stat st; + + if (fstat(ofd, &st) < 0 || S_ISREG(st.st_mode) || + S_ISDIR(st.st_mode)) + goto die_outfile; + } + } + } else { + ofd = STDOUT_FILENO; + outfile = bb_msg_standard_output; + } + + if (skip) { + if (lseek(ifd, skip * ibs, SEEK_CUR) < 0) { + while (skip-- > 0) { + n = safe_read(ifd, ibuf, ibs); + if (n < 0) + bb_perror_msg_and_die("%s", infile); + if (n == 0) + break; + } + } + } + + if (seek) { + if (lseek(ofd, seek * obs, SEEK_CUR) < 0) + goto die_outfile; + } + + while (in_full + in_part != count) { + if (flags & noerror) { + /* Pre-zero the buffer when doing the noerror thing */ + memset(ibuf, '\0', ibs); + } + + n = safe_read(ifd, ibuf, ibs); + if (n == 0) + break; + if (n < 0) { + if (flags & noerror) { + n = ibs; + bb_perror_msg("%s", infile); + } else + bb_perror_msg_and_die("%s", infile); + } + if ((size_t)n == ibs) + in_full++; + else { + in_part++; + if (flags & sync_flag) { + memset(ibuf + n, '\0', ibs - n); + n = ibs; + } + } + if (flags & twobufs_flag) { + char *tmp = ibuf; + while (n) { + size_t d = obs - oc; + + if (d > n) + d = n; + memcpy(obuf + oc, tmp, d); + n -= d; + tmp += d; + oc += d; + if (oc == obs) { + w = full_write_or_warn(ofd, obuf, obs, outfile); + if (w < 0) goto out_status; + if (w == obs) + out_full++; + else if (w > 0) + out_part++; + oc = 0; + } + } + } else { + w = full_write_or_warn(ofd, ibuf, n, outfile); + if (w < 0) goto out_status; + if (w == obs) + out_full++; + else if (w > 0) + out_part++; + } + } + + if (ENABLE_FEATURE_DD_IBS_OBS && oc) { + w = full_write_or_warn(ofd, obuf, oc, outfile); + if (w < 0) goto out_status; + if (w > 0) + out_part++; + } + if (close(ifd) < 0) { + bb_perror_msg_and_die("%s", infile); + } + + if (close(ofd) < 0) { + die_outfile: + bb_perror_msg_and_die("%s", outfile); + } + out_status: + dd_output_status(0); + + return EXIT_SUCCESS; +} diff --git a/coreutils/df.c b/coreutils/df.c new file mode 100644 index 000000000..6ae3eed33 --- /dev/null +++ b/coreutils/df.c @@ -0,0 +1,154 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini df implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * based on original code by (I think) Bruce Perens . + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 _NOT_ compliant -- options -P and -t missing. Also blocksize. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/df.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Size reduction. Removed floating point dependency. Added error checking + * on output. Output stats on 0-sized filesystems if specifically listed on + * the command line. Properly round *-blocks, Used, and Available quantities. + */ + +#include +#include +#include +#include +#include +#include +#include "busybox.h" + +#ifndef CONFIG_FEATURE_HUMAN_READABLE +static long kscale(long b, long bs) +{ + return ( b * (long long) bs + 1024/2 ) / 1024; +} +#endif + +int df_main(int argc, char **argv) +{ + long blocks_used; + long blocks_percent_used; +#ifdef CONFIG_FEATURE_HUMAN_READABLE + unsigned long df_disp_hr = 1024; +#endif + int status = EXIT_SUCCESS; + unsigned opt; + FILE *mount_table; + struct mntent *mount_entry; + struct statfs s; + static const char hdr_1k[] = "1k-blocks"; /* default display is kilobytes */ + const char *disp_units_hdr = hdr_1k; + +#ifdef CONFIG_FEATURE_HUMAN_READABLE + opt_complementary = "h-km:k-hm:m-hk"; + opt = getopt32(argc, argv, "hmk"); + if (opt & 1) { + df_disp_hr = 0; + disp_units_hdr = " Size"; + } + if (opt & 2) { + df_disp_hr = 1024*1024; + disp_units_hdr = "1M-blocks"; + } +#else + opt = getopt32(argc, argv, "k"); +#endif + + printf("Filesystem%11s%-15sUsed Available Use%% Mounted on\n", + "", disp_units_hdr); + + mount_table = NULL; + argv += optind; + if (optind >= argc) { + mount_table = setmntent(bb_path_mtab_file, "r"); + if (!mount_table) { + bb_perror_msg_and_die(bb_path_mtab_file); + } + } + + do { + const char *device; + const char *mount_point; + + if (mount_table) { + mount_entry = getmntent(mount_table); + if (!mount_entry) { + endmntent(mount_table); + break; + } + } else { + mount_point = *argv++; + if (!mount_point) { + break; + } + mount_entry = find_mount_point(mount_point, bb_path_mtab_file); + if (!mount_entry) { + bb_error_msg("%s: can't find mount point", mount_point); + SET_ERROR: + status = EXIT_FAILURE; + continue; + } + } + + device = mount_entry->mnt_fsname; + mount_point = mount_entry->mnt_dir; + + if (statfs(mount_point, &s) != 0) { + bb_perror_msg("%s", mount_point); + goto SET_ERROR; + } + + if ((s.f_blocks > 0) || !mount_table){ + blocks_used = s.f_blocks - s.f_bfree; + blocks_percent_used = 0; + if (blocks_used + s.f_bavail) { + blocks_percent_used = (((long long) blocks_used) * 100 + + (blocks_used + s.f_bavail)/2 + ) / (blocks_used + s.f_bavail); + } + + if (strcmp(device, "rootfs") == 0) { + continue; + } else if (strcmp(device, "/dev/root") == 0) { + /* Adjusts device to be the real root device, + * or leaves device alone if it can't find it */ + device = find_block_device("/"); + if (!device) { + goto SET_ERROR; + } + } + +#ifdef CONFIG_FEATURE_HUMAN_READABLE + printf("%-20s %9s ", device, + make_human_readable_str(s.f_blocks, s.f_bsize, df_disp_hr)); + + printf("%9s ", + make_human_readable_str( (s.f_blocks - s.f_bfree), + s.f_bsize, df_disp_hr)); + + printf("%9s %3ld%% %s\n", + make_human_readable_str(s.f_bavail, s.f_bsize, df_disp_hr), + blocks_percent_used, mount_point); +#else + printf("%-20s %9ld %9ld %9ld %3ld%% %s\n", + device, + kscale(s.f_blocks, s.f_bsize), + kscale(s.f_blocks-s.f_bfree, s.f_bsize), + kscale(s.f_bavail, s.f_bsize), + blocks_percent_used, mount_point); +#endif + } + + } while (1); + + fflush_stdout_and_exit(status); +} diff --git a/coreutils/diff.c b/coreutils/diff.c new file mode 100644 index 000000000..2ed26babe --- /dev/null +++ b/coreutils/diff.c @@ -0,0 +1,1243 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini diff implementation for busybox, adapted from OpenBSD diff. + * + * Copyright (C) 2006 by Robert Sullivan + * Copyright (c) 2003 Todd C. Miller + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +#define FSIZE_MAX 32768 + +/* + * Output flags + */ +#define D_HEADER 1 /* Print a header/footer between files */ +#define D_EMPTY1 2 /* Treat first file as empty (/dev/null) */ +#define D_EMPTY2 4 /* Treat second file as empty (/dev/null) */ + +/* + * Status values for print_status() and diffreg() return values + * Guide: + * D_SAME - files are the same + * D_DIFFER - files differ + * D_BINARY - binary files differ + * D_COMMON - subdirectory common to both dirs + * D_ONLY - file only exists in one dir + * D_MISMATCH1 - path1 a dir, path2 a file + * D_MISMATCH2 - path1 a file, path2 a dir + * D_ERROR - error occurred + * D_SKIPPED1 - skipped path1 as it is a special file + * D_SKIPPED2 - skipped path2 as it is a special file + */ + +#define D_SAME 0 +#define D_DIFFER (1<<0) +#define D_BINARY (1<<1) +#define D_COMMON (1<<2) +#define D_ONLY (1<<3) +#define D_MISMATCH1 (1<<4) +#define D_MISMATCH2 (1<<5) +#define D_ERROR (1<<6) +#define D_SKIPPED1 (1<<7) +#define D_SKIPPED2 (1<<8) + +/* Command line options */ +static unsigned long cmd_flags; + +#define FLAG_a (1<<0) +#define FLAG_b (1<<1) +#define FLAG_d (1<<2) +#define FLAG_i (1<<3) +#define FLAG_L (1<<4) +#define FLAG_N (1<<5) +#define FLAG_q (1<<6) +#define FLAG_r (1<<7) +#define FLAG_s (1<<8) +#define FLAG_S (1<<9) +#define FLAG_t (1<<10) +#define FLAG_T (1<<11) +#define FLAG_U (1<<12) +#define FLAG_w (1<<13) + +/* XXX: FIXME: the following variables should be static, but gcc currently + * creates a much bigger object if we do this. */ +int context, status; +char *start, *label[2]; +struct stat stb1, stb2; +char **dl; +static int dl_count = 0; + +struct cand { + int x; + int y; + int pred; +}; + +struct line { + int serial; + int value; +} *file[2]; + +/* + * The following struct is used to record change information + * doing a "context" or "unified" diff. (see routine "change" to + * understand the highly mnemonic field names) + */ +struct context_vec { + int a; /* start line in old file */ + int b; /* end line in old file */ + int c; /* start line in new file */ + int d; /* end line in new file */ +}; + +static int *J; /* will be overlaid on class */ +static int *class; /* will be overlaid on file[0] */ +static int *klist; /* will be overlaid on file[0] after class */ +static int *member; /* will be overlaid on file[1] */ +static int clen; +static int len[2]; +static int pref, suff; /* length of prefix and suffix */ +static int slen[2]; +static int anychange; +static long *ixnew; /* will be overlaid on file[1] */ +static long *ixold; /* will be overlaid on klist */ +static struct cand *clist; /* merely a free storage pot for candidates */ +static int clistlen; /* the length of clist */ +static struct line *sfile[2]; /* shortened by pruning common prefix/suffix */ +static struct context_vec *context_vec_start; +static struct context_vec *context_vec_end; +static struct context_vec *context_vec_ptr; + +static void print_only(const char *path, size_t dirlen, const char *entry) +{ + if (dirlen > 1) + dirlen--; + printf("Only in %.*s: %s\n", (int) dirlen, path, entry); +} + +static void print_status(int val, char *path1, char *path2, char *entry) +{ + const char *const _entry = entry ? entry : ""; + char *_path1 = entry ? concat_path_file(path1, _entry) : path1; + char *_path2 = entry ? concat_path_file(path2, _entry) : path2; + + switch (val) { + case D_ONLY: + print_only(path1, strlen(path1), entry); + break; + case D_COMMON: + printf("Common subdirectories: %s and %s\n", _path1, _path2); + break; + case D_BINARY: + printf("Binary files %s and %s differ\n", _path1, _path2); + break; + case D_DIFFER: + if (cmd_flags & FLAG_q) + printf("Files %s and %s differ\n", _path1, _path2); + break; + case D_SAME: + if (cmd_flags & FLAG_s) + printf("Files %s and %s are identical\n", _path1, _path2); + break; + case D_MISMATCH1: + printf("File %s is a directory while file %s is a regular file\n", + _path1, _path2); + break; + case D_MISMATCH2: + printf("File %s is a regular file while file %s is a directory\n", + _path1, _path2); + break; + case D_SKIPPED1: + printf("File %s is not a regular file or directory and was skipped\n", + _path1); + break; + case D_SKIPPED2: + printf("File %s is not a regular file or directory and was skipped\n", + _path2); + break; + } + if (entry) { + free(_path1); + free(_path2); + } +} + +/* + * Hash function taken from Robert Sedgewick, Algorithms in C, 3d ed., p 578. + */ +static int readhash(FILE * f) +{ + int i, t, space; + int sum; + + sum = 1; + space = 0; + if (!(cmd_flags & FLAG_b) && !(cmd_flags & FLAG_w)) { + if (FLAG_i) + for (i = 0; (t = getc(f)) != '\n'; i++) { + if (t == EOF) { + if (i == 0) + return 0; + break; + } + sum = sum * 127 + t; + } else + for (i = 0; (t = getc(f)) != '\n'; i++) { + if (t == EOF) { + if (i == 0) + return 0; + break; + } + sum = sum * 127 + t; + } + } else { + for (i = 0;;) { + switch (t = getc(f)) { + case '\t': + case '\r': + case '\v': + case '\f': + case ' ': + space++; + continue; + default: + if (space && !(cmd_flags & FLAG_w)) { + i++; + space = 0; + } + sum = sum * 127 + t; + i++; + continue; + case EOF: + if (i == 0) + return 0; + /* FALLTHROUGH */ + case '\n': + break; + } + break; + } + } + /* + * There is a remote possibility that we end up with a zero sum. + * Zero is used as an EOF marker, so return 1 instead. + */ + return (sum == 0 ? 1 : sum); +} + + + +/* + * Check to see if the given files differ. + * Returns 0 if they are the same, 1 if different, and -1 on error. + */ +static int files_differ(FILE * f1, FILE * f2, int flags) +{ + char buf1[BUFSIZ], buf2[BUFSIZ]; + size_t i, j; + + if ((flags & (D_EMPTY1 | D_EMPTY2)) || stb1.st_size != stb2.st_size || + (stb1.st_mode & S_IFMT) != (stb2.st_mode & S_IFMT)) + return 1; + while (1) { + i = fread(buf1, 1, sizeof(buf1), f1); + j = fread(buf2, 1, sizeof(buf2), f2); + if (i != j) + return 1; + if (i == 0 && j == 0) { + if (ferror(f1) || ferror(f2)) + return 1; + return 0; + } + if (memcmp(buf1, buf2, i) != 0) + return 1; + } +} + +static void prepare(int i, FILE * fd, off_t filesize) +{ + struct line *p; + int h; + size_t j, sz; + + rewind(fd); + + sz = (filesize <= FSIZE_MAX ? filesize : FSIZE_MAX) / 25; + if (sz < 100) + sz = 100; + + p = xmalloc((sz + 3) * sizeof(struct line)); + for (j = 0; (h = readhash(fd));) { + if (j == sz) { + sz = sz * 3 / 2; + p = xrealloc(p, (sz + 3) * sizeof(struct line)); + } + p[++j].value = h; + } + len[i] = j; + file[i] = p; +} + +static void prune(void) +{ + int i, j; + + for (pref = 0; pref < len[0] && pref < len[1] && + file[0][pref + 1].value == file[1][pref + 1].value; pref++); + for (suff = 0; suff < len[0] - pref && suff < len[1] - pref && + file[0][len[0] - suff].value == file[1][len[1] - suff].value; + suff++); + for (j = 0; j < 2; j++) { + sfile[j] = file[j] + pref; + slen[j] = len[j] - pref - suff; + for (i = 0; i <= slen[j]; i++) + sfile[j][i].serial = i; + } +} + +static void equiv(struct line *a, int n, struct line *b, int m, int *c) +{ + int i, j; + + i = j = 1; + while (i <= n && j <= m) { + if (a[i].value < b[j].value) + a[i++].value = 0; + else if (a[i].value == b[j].value) + a[i++].value = j; + else + j++; + } + while (i <= n) + a[i++].value = 0; + b[m + 1].value = 0; + j = 0; + while (++j <= m) { + c[j] = -b[j].serial; + while (b[j + 1].value == b[j].value) { + j++; + c[j] = b[j].serial; + } + } + c[j] = -1; +} + +static int isqrt(int n) +{ + int y, x = 1; + + if (n == 0) + return 0; + + do { + y = x; + x = n / x; + x += y; + x /= 2; + } while ((x - y) > 1 || (x - y) < -1); + + return x; +} + +static int newcand(int x, int y, int pred) +{ + struct cand *q; + + if (clen == clistlen) { + clistlen = clistlen * 11 / 10; + clist = xrealloc(clist, clistlen * sizeof(struct cand)); + } + q = clist + clen; + q->x = x; + q->y = y; + q->pred = pred; + return clen++; +} + + +static int search(int *c, int k, int y) +{ + int i, j, l, t; + + if (clist[c[k]].y < y) /* quick look for typical case */ + return k + 1; + i = 0; + j = k + 1; + while (1) { + l = i + j; + if ((l >>= 1) <= i) + break; + t = clist[c[l]].y; + if (t > y) + j = l; + else if (t < y) + i = l; + else + return l; + } + return l + 1; +} + + +static int stone(int *a, int n, int *b, int *c) +{ + int i, k, y, j, l; + int oldc, tc, oldl; + unsigned int numtries; + +#if ENABLE_FEATURE_DIFF_MINIMAL + const unsigned int bound = + (cmd_flags & FLAG_d) ? UINT_MAX : MAX(256, isqrt(n)); +#else + const unsigned int bound = MAX(256, isqrt(n)); +#endif + k = 0; + c[0] = newcand(0, 0, 0); + for (i = 1; i <= n; i++) { + j = a[i]; + if (j == 0) + continue; + y = -b[j]; + oldl = 0; + oldc = c[0]; + numtries = 0; + do { + if (y <= clist[oldc].y) + continue; + l = search(c, k, y); + if (l != oldl + 1) + oldc = c[l - 1]; + if (l <= k) { + if (clist[c[l]].y <= y) + continue; + tc = c[l]; + c[l] = newcand(i, y, oldc); + oldc = tc; + oldl = l; + numtries++; + } else { + c[l] = newcand(i, y, oldc); + k++; + break; + } + } while ((y = b[++j]) > 0 && numtries < bound); + } + return k; +} + +static void unravel(int p) +{ + struct cand *q; + int i; + + for (i = 0; i <= len[0]; i++) + J[i] = i <= pref ? i : i > len[0] - suff ? i + len[1] - len[0] : 0; + for (q = clist + p; q->y != 0; q = clist + q->pred) + J[q->x + pref] = q->y + pref; +} + + +static void unsort(struct line *f, int l, int *b) +{ + int *a, i; + + a = xmalloc((l + 1) * sizeof(int)); + for (i = 1; i <= l; i++) + a[f[i].serial] = f[i].value; + for (i = 1; i <= l; i++) + b[i] = a[i]; + free(a); +} + +static int skipline(FILE * f) +{ + int i, c; + + for (i = 1; (c = getc(f)) != '\n' && c != EOF; i++) + continue; + return i; +} + + +/* + * Check does double duty: + * 1. ferret out any fortuitous correspondences due + * to confounding by hashing (which result in "jackpot") + * 2. collect random access indexes to the two files + */ +static void check(FILE * f1, FILE * f2) +{ + int i, j, jackpot, c, d; + long ctold, ctnew; + + rewind(f1); + rewind(f2); + j = 1; + ixold[0] = ixnew[0] = 0; + jackpot = 0; + ctold = ctnew = 0; + for (i = 1; i <= len[0]; i++) { + if (J[i] == 0) { + ixold[i] = ctold += skipline(f1); + continue; + } + while (j < J[i]) { + ixnew[j] = ctnew += skipline(f2); + j++; + } + if ((cmd_flags & FLAG_b) || (cmd_flags & FLAG_w) + || (cmd_flags & FLAG_i)) { + while (1) { + c = getc(f1); + d = getc(f2); + /* + * GNU diff ignores a missing newline + * in one file if bflag || wflag. + */ + if (((cmd_flags & FLAG_b) || (cmd_flags & FLAG_w)) && + ((c == EOF && d == '\n') || (c == '\n' && d == EOF))) { + break; + } + ctold++; + ctnew++; + if ((cmd_flags & FLAG_b) && isspace(c) && isspace(d)) { + do { + if (c == '\n') + break; + ctold++; + } while (isspace(c = getc(f1))); + do { + if (d == '\n') + break; + ctnew++; + } while (isspace(d = getc(f2))); + } else if (cmd_flags & FLAG_w) { + while (isspace(c) && c != '\n') { + c = getc(f1); + ctold++; + } + while (isspace(d) && d != '\n') { + d = getc(f2); + ctnew++; + } + } + if (c != d) { + jackpot++; + J[i] = 0; + if (c != '\n' && c != EOF) + ctold += skipline(f1); + if (d != '\n' && c != EOF) + ctnew += skipline(f2); + break; + } + if (c == '\n' || c == EOF) + break; + } + } else { + while (1) { + ctold++; + ctnew++; + if ((c = getc(f1)) != (d = getc(f2))) { + J[i] = 0; + if (c != '\n' && c != EOF) + ctold += skipline(f1); + if (d != '\n' && c != EOF) + ctnew += skipline(f2); + break; + } + if (c == '\n' || c == EOF) + break; + } + } + ixold[i] = ctold; + ixnew[j] = ctnew; + j++; + } + for (; j <= len[1]; j++) + ixnew[j] = ctnew += skipline(f2); +} + +/* shellsort CACM #201 */ +static void sort(struct line *a, int n) +{ + struct line *ai, *aim, w; + int j, m = 0, k; + + if (n == 0) + return; + for (j = 1; j <= n; j *= 2) + m = 2 * j - 1; + for (m /= 2; m != 0; m /= 2) { + k = n - m; + for (j = 1; j <= k; j++) { + for (ai = &a[j]; ai > a; ai -= m) { + aim = &ai[m]; + if (aim < ai) + break; /* wraparound */ + if (aim->value > ai[0].value || + (aim->value == ai[0].value && aim->serial > ai[0].serial)) + break; + w.value = ai[0].value; + ai[0].value = aim->value; + aim->value = w.value; + w.serial = ai[0].serial; + ai[0].serial = aim->serial; + aim->serial = w.serial; + } + } + } +} + + +static void uni_range(int a, int b) +{ + if (a < b) + printf("%d,%d", a, b - a + 1); + else if (a == b) + printf("%d", b); + else + printf("%d,0", b); +} + +static int fetch(long *f, int a, int b, FILE * lb, int ch) +{ + int i, j, c, lastc, col, nc; + + if (a > b) + return 0; + for (i = a; i <= b; i++) { + fseek(lb, f[i - 1], SEEK_SET); + nc = f[i] - f[i - 1]; + if (ch != '\0') { + putchar(ch); + if (cmd_flags & FLAG_T) + putchar('\t'); + } + col = 0; + for (j = 0, lastc = '\0'; j < nc; j++, lastc = c) { + if ((c = getc(lb)) == EOF) { + puts("\n\\ No newline at end of file"); + return 0; + } + if (c == '\t' && (cmd_flags & FLAG_t)) { + do { + putchar(' '); + } while (++col & 7); + } else { + putchar(c); + col++; + } + } + } + return 0; +} + +static int asciifile(FILE * f) +{ +#if ENABLE_FEATURE_DIFF_BINARY + unsigned char buf[BUFSIZ]; + int i, cnt; +#endif + + if ((cmd_flags & FLAG_a) || f == NULL) + return 1; + +#if ENABLE_FEATURE_DIFF_BINARY + rewind(f); + cnt = fread(buf, 1, sizeof(buf), f); + for (i = 0; i < cnt; i++) { + if (!isprint(buf[i]) && !isspace(buf[i])) { + return 0; + } + } +#endif + return 1; +} + +/* dump accumulated "unified" diff changes */ +static void dump_unified_vec(FILE * f1, FILE * f2) +{ + struct context_vec *cvp = context_vec_start; + int lowa, upb, lowc, upd; + int a, b, c, d; + char ch; + + if (context_vec_start > context_vec_ptr) + return; + + b = d = 0; /* gcc */ + lowa = MAX(1, cvp->a - context); + upb = MIN(len[0], context_vec_ptr->b + context); + lowc = MAX(1, cvp->c - context); + upd = MIN(len[1], context_vec_ptr->d + context); + + fputs("@@ -", stdout); + uni_range(lowa, upb); + fputs(" +", stdout); + uni_range(lowc, upd); + fputs(" @@", stdout); + putchar('\n'); + + /* + * Output changes in "unified" diff format--the old and new lines + * are printed together. + */ + for (; cvp <= context_vec_ptr; cvp++) { + a = cvp->a; + b = cvp->b; + c = cvp->c; + d = cvp->d; + + /* + * c: both new and old changes + * d: only changes in the old file + * a: only changes in the new file + */ + if (a <= b && c <= d) + ch = 'c'; + else + ch = (a <= b) ? 'd' : 'a'; + if (ch == 'c' || ch == 'd') { + fetch(ixold, lowa, a - 1, f1, ' '); + fetch(ixold, a, b, f1, '-'); + } + if (ch == 'a') + fetch(ixnew, lowc, c - 1, f2, ' '); + if (ch == 'c' || ch == 'a') + fetch(ixnew, c, d, f2, '+'); + lowa = b + 1; + lowc = d + 1; + } + fetch(ixnew, d + 1, upd, f2, ' '); + + context_vec_ptr = context_vec_start - 1; +} + + +static void print_header(const char *file1, const char *file2) +{ + if (label[0] != NULL) + printf("%s %s\n", "---", label[0]); + else + printf("%s %s\t%s", "---", file1, ctime(&stb1.st_mtime)); + if (label[1] != NULL) + printf("%s %s\n", "+++", label[1]); + else + printf("%s %s\t%s", "+++", file2, ctime(&stb2.st_mtime)); +} + + + +/* + * Indicate that there is a difference between lines a and b of the from file + * to get to lines c to d of the to file. If a is greater then b then there + * are no lines in the from file involved and this means that there were + * lines appended (beginning at b). If c is greater than d then there are + * lines missing from the to file. + */ +static void change(char *file1, FILE * f1, char *file2, FILE * f2, int a, + int b, int c, int d) +{ + static size_t max_context = 64; + + if (a > b && c > d) + return; + if (cmd_flags & FLAG_q) + return; + + /* + * Allocate change records as needed. + */ + if (context_vec_ptr == context_vec_end - 1) { + ptrdiff_t offset = context_vec_ptr - context_vec_start; + + max_context <<= 1; + context_vec_start = xrealloc(context_vec_start, + max_context * + sizeof(struct context_vec)); + context_vec_end = context_vec_start + max_context; + context_vec_ptr = context_vec_start + offset; + } + if (anychange == 0) { + /* + * Print the context/unidiff header first time through. + */ + print_header(file1, file2); + anychange = 1; + } else if (a > context_vec_ptr->b + (2 * context) + 1 && + c > context_vec_ptr->d + (2 * context) + 1) { + /* + * If this change is more than 'context' lines from the + * previous change, dump the record and reset it. + */ + dump_unified_vec(f1, f2); + } + context_vec_ptr++; + context_vec_ptr->a = a; + context_vec_ptr->b = b; + context_vec_ptr->c = c; + context_vec_ptr->d = d; + return; + +} + + +static void output(char *file1, FILE * f1, char *file2, FILE * f2) +{ + + /* Note that j0 and j1 can't be used as they are defined in math.h. + * This also allows the rather amusing variable 'j00'... */ + int m, i0, i1, j00, j01; + + rewind(f1); + rewind(f2); + m = len[0]; + J[0] = 0; + J[m + 1] = len[1] + 1; + for (i0 = 1; i0 <= m; i0 = i1 + 1) { + while (i0 <= m && J[i0] == J[i0 - 1] + 1) + i0++; + j00 = J[i0 - 1] + 1; + i1 = i0 - 1; + while (i1 < m && J[i1 + 1] == 0) + i1++; + j01 = J[i1 + 1] - 1; + J[i1] = j01; + change(file1, f1, file2, f2, i0, i1, j00, j01); + } + if (m == 0) { + change(file1, f1, file2, f2, 1, 0, 1, len[1]); + } + if (anychange != 0) { + dump_unified_vec(f1, f2); + } +} + +/* + * The following code uses an algorithm due to Harold Stone, + * which finds a pair of longest identical subsequences in + * the two files. + * + * The major goal is to generate the match vector J. + * J[i] is the index of the line in file1 corresponding + * to line i file0. J[i] = 0 if there is no + * such line in file1. + * + * Lines are hashed so as to work in core. All potential + * matches are located by sorting the lines of each file + * on the hash (called ``value''). In particular, this + * collects the equivalence classes in file1 together. + * Subroutine equiv replaces the value of each line in + * file0 by the index of the first element of its + * matching equivalence in (the reordered) file1. + * To save space equiv squeezes file1 into a single + * array member in which the equivalence classes + * are simply concatenated, except that their first + * members are flagged by changing sign. + * + * Next the indices that point into member are unsorted into + * array class according to the original order of file0. + * + * The cleverness lies in routine stone. This marches + * through the lines of file0, developing a vector klist + * of "k-candidates". At step i a k-candidate is a matched + * pair of lines x,y (x in file0 y in file1) such that + * there is a common subsequence of length k + * between the first i lines of file0 and the first y + * lines of file1, but there is no such subsequence for + * any smaller y. x is the earliest possible mate to y + * that occurs in such a subsequence. + * + * Whenever any of the members of the equivalence class of + * lines in file1 matable to a line in file0 has serial number + * less than the y of some k-candidate, that k-candidate + * with the smallest such y is replaced. The new + * k-candidate is chained (via pred) to the current + * k-1 candidate so that the actual subsequence can + * be recovered. When a member has serial number greater + * that the y of all k-candidates, the klist is extended. + * At the end, the longest subsequence is pulled out + * and placed in the array J by unravel + * + * With J in hand, the matches there recorded are + * checked against reality to assure that no spurious + * matches have crept in due to hashing. If they have, + * they are broken, and "jackpot" is recorded--a harmless + * matter except that a true match for a spuriously + * mated line may now be unnecessarily reported as a change. + * + * Much of the complexity of the program comes simply + * from trying to minimize core utilization and + * maximize the range of doable problems by dynamically + * allocating what is needed and reusing what is not. + * The core requirements for problems larger than somewhat + * are (in words) 2*length(file0) + length(file1) + + * 3*(number of k-candidates installed), typically about + * 6n words for files of length n. + */ + +static int diffreg(char *ofile1, char *ofile2, int flags) +{ + char *file1 = ofile1; + char *file2 = ofile2; + FILE *f1 = NULL; + FILE *f2 = NULL; + int rval = D_SAME; + int i; + + anychange = 0; + context_vec_ptr = context_vec_start - 1; + + if (S_ISDIR(stb1.st_mode) != S_ISDIR(stb2.st_mode)) + return (S_ISDIR(stb1.st_mode) ? D_MISMATCH1 : D_MISMATCH2); + if (strcmp(file1, "-") == 0 && strcmp(file2, "-") == 0) + goto closem; + + if (flags & D_EMPTY1) + f1 = xfopen(bb_dev_null, "r"); + else { + if (strcmp(file1, "-") == 0) + f1 = stdin; + else + f1 = xfopen(file1, "r"); + } + + if (flags & D_EMPTY2) + f2 = xfopen(bb_dev_null, "r"); + else { + if (strcmp(file2, "-") == 0) + f2 = stdin; + else + f2 = xfopen(file2, "r"); + } + + if ((i = files_differ(f1, f2, flags)) == 0) + goto closem; + else if (i != 1) { /* 1 == ok */ + /* error */ + status |= 2; + goto closem; + } + + if (!asciifile(f1) || !asciifile(f2)) { + rval = D_BINARY; + status |= 1; + goto closem; + } + + prepare(0, f1, stb1.st_size); + prepare(1, f2, stb2.st_size); + prune(); + sort(sfile[0], slen[0]); + sort(sfile[1], slen[1]); + + member = (int *) file[1]; + equiv(sfile[0], slen[0], sfile[1], slen[1], member); + member = xrealloc(member, (slen[1] + 2) * sizeof(int)); + + class = (int *) file[0]; + unsort(sfile[0], slen[0], class); + class = xrealloc(class, (slen[0] + 2) * sizeof(int)); + + klist = xmalloc((slen[0] + 2) * sizeof(int)); + clen = 0; + clistlen = 100; + clist = xmalloc(clistlen * sizeof(struct cand)); + i = stone(class, slen[0], member, klist); + free(member); + free(class); + + J = xrealloc(J, (len[0] + 2) * sizeof(int)); + unravel(klist[i]); + free(clist); + free(klist); + + ixold = xrealloc(ixold, (len[0] + 2) * sizeof(long)); + ixnew = xrealloc(ixnew, (len[1] + 2) * sizeof(long)); + check(f1, f2); + output(file1, f1, file2, f2); + + closem: + if (anychange) { + status |= 1; + if (rval == D_SAME) + rval = D_DIFFER; + } + if (f1 != NULL) + fclose(f1); + if (f2 != NULL) + fclose(f2); + if (file1 != ofile1) + free(file1); + if (file2 != ofile2) + free(file2); + return rval; +} + +#if ENABLE_FEATURE_DIFF_DIR +static void do_diff(char *dir1, char *path1, char *dir2, char *path2) +{ + + int flags = D_HEADER; + int val; + + char *fullpath1 = xasprintf("%s/%s", dir1, path1); + char *fullpath2 = xasprintf("%s/%s", dir2, path2); + + if (stat(fullpath1, &stb1) != 0) { + flags |= D_EMPTY1; + memset(&stb1, 0, sizeof(stb1)); + fullpath1 = xasprintf("%s/%s", dir1, path2); + } + if (stat(fullpath2, &stb2) != 0) { + flags |= D_EMPTY2; + memset(&stb2, 0, sizeof(stb2)); + stb2.st_mode = stb1.st_mode; + fullpath2 = xasprintf("%s/%s", dir2, path1); + } + + if (stb1.st_mode == 0) + stb1.st_mode = stb2.st_mode; + + if (S_ISDIR(stb1.st_mode) && S_ISDIR(stb2.st_mode)) { + printf("Common subdirectories: %s and %s\n", fullpath1, fullpath2); + return; + } + + if (!S_ISREG(stb1.st_mode) && !S_ISDIR(stb1.st_mode)) + val = D_SKIPPED1; + else if (!S_ISREG(stb2.st_mode) && !S_ISDIR(stb2.st_mode)) + val = D_SKIPPED2; + else + val = diffreg(fullpath1, fullpath2, flags); + + print_status(val, fullpath1, fullpath2, NULL); +} +#endif + +#if ENABLE_FEATURE_DIFF_DIR +static int dir_strcmp(const void *p1, const void *p2) +{ + return strcmp(*(char *const *) p1, *(char *const *) p2); +} + +/* This function adds a filename to dl, the directory listing. */ + +static int add_to_dirlist(const char *filename, + struct stat ATTRIBUTE_UNUSED * sb, void *userdata, + int depth ATTRIBUTE_UNUSED) +{ + dl_count++; + dl = xrealloc(dl, dl_count * sizeof(char *)); + dl[dl_count - 1] = xstrdup(filename); + if (cmd_flags & FLAG_r) { + int *pp = (int *) userdata; + int path_len = *pp + 1; + + dl[dl_count - 1] = &(dl[dl_count - 1])[path_len]; + } + return TRUE; +} + +/* This returns a sorted directory listing. */ +static char **get_dir(char *path) +{ + + int i; + char **retval; + + /* If -r has been set, then the recursive_action function will be + * used. Unfortunately, this outputs the root directory along with + * the recursed paths, so use void *userdata to specify the string + * length of the root directory. It can then be removed in + * add_to_dirlist. */ + + int path_len = strlen(path); + void *userdata = &path_len; + + /* Reset dl_count - there's no need to free dl as xrealloc does + * the job nicely. */ + dl_count = 0; + + /* Now fill dl with a listing. */ + if (cmd_flags & FLAG_r) + recursive_action(path, TRUE, TRUE, FALSE, add_to_dirlist, NULL, + userdata, 0); + else { + DIR *dp; + struct dirent *ep; + + dp = warn_opendir(path); + while ((ep = readdir(dp))) { + if ((!strcmp(ep->d_name, "..")) || (!strcmp(ep->d_name, "."))) + continue; + add_to_dirlist(ep->d_name, NULL, NULL, 0); + } + closedir(dp); + } + + /* Sort dl alphabetically. */ + qsort(dl, dl_count, sizeof(char *), dir_strcmp); + + /* Copy dl so that we can return it. */ + retval = xmalloc(dl_count * sizeof(char *)); + for (i = 0; i < dl_count; i++) + retval[i] = xstrdup(dl[i]); + + return retval; +} + +static void diffdir(char *p1, char *p2) +{ + + char **dirlist1, **dirlist2; + char *dp1, *dp2; + int dirlist1_count, dirlist2_count; + int pos; + + /* Check for trailing slashes. */ + + dp1 = last_char_is(p1, '/'); + if (dp1 != NULL) + *dp1 = '\0'; + dp2 = last_char_is(p2, '/'); + if (dp2 != NULL) + *dp2 = '\0'; + + /* Get directory listings for p1 and p2. */ + + dirlist1 = get_dir(p1); + dirlist1_count = dl_count; + dirlist1[dirlist1_count] = NULL; + dirlist2 = get_dir(p2); + dirlist2_count = dl_count; + dirlist2[dirlist2_count] = NULL; + + /* If -S was set, find the starting point. */ + if (start) { + while (*dirlist1 != NULL && strcmp(*dirlist1, start) < 0) + dirlist1++; + while (*dirlist2 != NULL && strcmp(*dirlist2, start) < 0) + dirlist2++; + if ((*dirlist1 == NULL) || (*dirlist2 == NULL)) + bb_error_msg(bb_msg_invalid_arg, "NULL", "-S"); + } + + /* Now that both dirlist1 and dirlist2 contain sorted directory + * listings, we can start to go through dirlist1. If both listings + * contain the same file, then do a normal diff. Otherwise, behaviour + * is determined by whether the -N flag is set. */ + while (*dirlist1 != NULL || *dirlist2 != NULL) { + dp1 = *dirlist1; + dp2 = *dirlist2; + pos = dp1 == NULL ? 1 : dp2 == NULL ? -1 : strcmp(dp1, dp2); + if (pos == 0) { + do_diff(p1, dp1, p2, dp2); + dirlist1++; + dirlist2++; + } else if (pos < 0) { + if (cmd_flags & FLAG_N) + do_diff(p1, dp1, p2, NULL); + else + print_only(p1, strlen(p1) + 1, dp1); + dirlist1++; + } else { + if (cmd_flags & FLAG_N) + do_diff(p1, NULL, p2, dp2); + else + print_only(p2, strlen(p2) + 1, dp2); + dirlist2++; + } + } +} +#endif + + + +int diff_main(int argc, char **argv) +{ + int gotstdin = 0; + + char *U_opt; + llist_t *L_arg = NULL; + + opt_complementary = "L::"; + cmd_flags = getopt32(argc, argv, "abdiL:NqrsS:tTU:wu", + &L_arg, &start, &U_opt); + + if (cmd_flags & FLAG_L) { + while (L_arg) { + if (label[0] == NULL) + label[0] = L_arg->data; + else if (label[1] == NULL) + label[1] = L_arg->data; + else + bb_show_usage(); + + L_arg = L_arg->link; + } + + /* If both label[0] and label[1] were set, they need to be swapped. */ + if (label[0] && label[1]) { + char *tmp; + + tmp = label[1]; + label[1] = label[0]; + label[0] = tmp; + } + } + + context = 3; /* This is the default number of lines of context. */ + if (cmd_flags & FLAG_U) { + context = xatoul_range(U_opt, 1, INT_MAX); + } + argc -= optind; + argv += optind; + + /* + * Do sanity checks, fill in stb1 and stb2 and call the appropriate + * driver routine. Both drivers use the contents of stb1 and stb2. + */ + if (argc < 2) { + bb_error_msg("missing filename"); + bb_show_usage(); + } + if (strcmp(argv[0], "-") == 0) { + fstat(STDIN_FILENO, &stb1); + gotstdin = 1; + } else + xstat(argv[0], &stb1); + if (strcmp(argv[1], "-") == 0) { + fstat(STDIN_FILENO, &stb2); + gotstdin = 1; + } else + xstat(argv[1], &stb2); + if (gotstdin && (S_ISDIR(stb1.st_mode) || S_ISDIR(stb2.st_mode))) + bb_error_msg_and_die("can't compare - to a directory"); + if (S_ISDIR(stb1.st_mode) && S_ISDIR(stb2.st_mode)) { +#if ENABLE_FEATURE_DIFF_DIR + diffdir(argv[0], argv[1]); +#else + bb_error_msg_and_die("directory comparison not supported"); +#endif + } else { + if (S_ISDIR(stb1.st_mode)) { + argv[0] = concat_path_file(argv[0], argv[1]); + xstat(argv[0], &stb1); + } + if (S_ISDIR(stb2.st_mode)) { + argv[1] = concat_path_file(argv[1], argv[0]); + xstat(argv[1], &stb2); + } + print_status(diffreg(argv[0], argv[1], 0), argv[0], argv[1], NULL); + } + exit(status); +} diff --git a/coreutils/dirname.c b/coreutils/dirname.c new file mode 100644 index 000000000..e986a9701 --- /dev/null +++ b/coreutils/dirname.c @@ -0,0 +1,26 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini dirname implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/dirname.html */ + +#include +#include +#include "busybox.h" + +int dirname_main(int argc, char **argv) +{ + if (argc != 2) { + bb_show_usage(); + } + + puts(dirname(argv[1])); + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/coreutils/dos2unix.c b/coreutils/dos2unix.c new file mode 100644 index 000000000..1ed8771c0 --- /dev/null +++ b/coreutils/dos2unix.c @@ -0,0 +1,115 @@ +/* vi: set sw=4 ts=4: */ +/* + * dos2unix for BusyBox + * + * dos2unix '\n' convertor 0.5.0 + * based on Unix2Dos 0.9.0 by Peter Hanecak (made 19.2.1997) + * Copyright 1997,.. by Peter Hanecak . + * All rights reserved. + * + * dos2unix filters reading input from stdin and writing output to stdout. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. +*/ + +#include "busybox.h" + +enum ConvType { + CT_UNIX2DOS = 1, + CT_DOS2UNIX +} ConvType; + +/* if fn is NULL then input is stdin and output is stdout */ +static int convert(char *fn) +{ + FILE *in, *out; + int i; + + if (fn != NULL) { + in = xfopen(fn, "rw"); + /* + The file is then created with mode read/write and + permissions 0666 for glibc 2.0.6 and earlier or + 0600 for glibc 2.0.7 and later. + */ + snprintf(bb_common_bufsiz1, sizeof(bb_common_bufsiz1), "%sXXXXXX", fn); + /* + sizeof bb_common_bufsiz1 is 4096, so it should be big enough to + hold the full path. However if the output is truncated the + subsequent call to mkstemp would fail. + */ + if ((i = mkstemp(&bb_common_bufsiz1[0])) == -1 + || chmod(bb_common_bufsiz1, 0600) == -1) { + bb_perror_nomsg_and_die(); + } + out = fdopen(i, "w+"); + if (!out) { + close(i); + remove(bb_common_bufsiz1); + } + } else { + in = stdin; + out = stdout; + } + + while ((i = fgetc(in)) != EOF) { + if (i == '\r') + continue; + if (i == '\n') { + if (ConvType == CT_UNIX2DOS) + fputc('\r', out); + fputc('\n', out); + continue; + } + fputc(i, out); + } + + if (fn != NULL) { + if (fclose(in) < 0 || fclose(out) < 0) { + bb_perror_nomsg(); + remove(bb_common_bufsiz1); + return -2; + } + /* Assume they are both on the same filesystem (which + * should be true since we put them into the same directory + * so we _should_ be ok, but you never know... */ + if (rename(bb_common_bufsiz1, fn) < 0) { + bb_perror_msg("cannot rename '%s' as '%s'", bb_common_bufsiz1, fn); + return -1; + } + } + + return 0; +} + +int dos2unix_main(int argc, char *argv[]) +{ + int o; + + /* See if we are supposed to be doing dos2unix or unix2dos */ + if (applet_name[0] == 'd') { + ConvType = CT_DOS2UNIX; /*2 */ + } else { + ConvType = CT_UNIX2DOS; /*1 */ + } + /* -u and -d are mutally exclusive */ + opt_complementary = "?:u--d:d--u"; + /* process parameters */ + /* -u convert to unix */ + /* -d convert to dos */ + o = getopt32(argc, argv, "du"); + + /* Do the conversion requested by an argument else do the default + * conversion depending on our name. */ + if (o) + ConvType = o; + + if (optind < argc) { + while (optind < argc) + if ((o = convert(argv[optind++])) < 0) + break; + } else + o = convert(NULL); + + return o; +} diff --git a/coreutils/du.c b/coreutils/du.c new file mode 100644 index 000000000..a547b1e14 --- /dev/null +++ b/coreutils/du.c @@ -0,0 +1,250 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini du implementation for busybox + * + * Copyright (C) 1999,2000,2001 by Lineo, inc. and John Beppu + * Copyright (C) 1999,2000,2001 by John Beppu + * Copyright (C) 2002 Edward Betts + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant (unless default blocksize set to 1k) */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/du.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Mostly rewritten for SUSv3 compliance and to fix bugs/defects. + * 1) Added support for SUSv3 -a, -H, -L, gnu -c, and (busybox) -d options. + * The -d option allows setting of max depth (similar to gnu --max-depth). + * 2) Fixed incorrect size calculations for links and directories, especially + * when errors occurred. Calculates sizes should now match gnu du output. + * 3) Added error checking of output. + * 4) Fixed busybox bug #1284 involving long overflow with human_readable. + */ + +#include "busybox.h" + +#ifdef CONFIG_FEATURE_HUMAN_READABLE +# ifdef CONFIG_FEATURE_DU_DEFAULT_BLOCKSIZE_1K +static unsigned long disp_hr = 1024; +# else +static unsigned long disp_hr = 512; +# endif +#elif defined CONFIG_FEATURE_DU_DEFAULT_BLOCKSIZE_1K +static unsigned int disp_k = 1; +#else +static unsigned int disp_k; /* bss inits to 0 */ +#endif + +static int max_print_depth = INT_MAX; +static nlink_t count_hardlinks = 1; + +static int status +#if EXIT_SUCCESS == 0 + = EXIT_SUCCESS +#endif + ; +static int print_files; +static int slink_depth; +static int du_depth; +static int one_file_system; +static dev_t dir_dev; + + +static void print(long size, const char * const filename) +{ + /* TODO - May not want to defer error checking here. */ +#ifdef CONFIG_FEATURE_HUMAN_READABLE + printf("%s\t%s\n", make_human_readable_str(size, 512, disp_hr), + filename); +#else + if (disp_k) { + size++; + size >>= 1; + } + printf("%ld\t%s\n", size, filename); +#endif +} + +/* tiny recursive du */ +static long du(const char * const filename) +{ + struct stat statbuf; + long sum; + + if ((lstat(filename, &statbuf)) != 0) { + bb_perror_msg("%s", filename); + status = EXIT_FAILURE; + return 0; + } + + if (one_file_system) { + if (du_depth == 0) { + dir_dev = statbuf.st_dev; + } else if (dir_dev != statbuf.st_dev) { + return 0; + } + } + + sum = statbuf.st_blocks; + + if (S_ISLNK(statbuf.st_mode)) { + if (slink_depth > du_depth) { /* -H or -L */ + if ((stat(filename, &statbuf)) != 0) { + bb_perror_msg("%s", filename); + status = EXIT_FAILURE; + return 0; + } + sum = statbuf.st_blocks; + if (slink_depth == 1) { + slink_depth = INT_MAX; /* Convert -H to -L. */ + } + } + } + + if (statbuf.st_nlink > count_hardlinks) { + /* Add files/directories with links only once */ + if (is_in_ino_dev_hashtable(&statbuf, NULL)) { + return 0; + } + add_to_ino_dev_hashtable(&statbuf, NULL); + } + + if (S_ISDIR(statbuf.st_mode)) { + DIR *dir; + struct dirent *entry; + char *newfile; + + dir = warn_opendir(filename); + if (!dir) { + status = EXIT_FAILURE; + return sum; + } + + newfile = last_char_is(filename, '/'); + if (newfile) + *newfile = '\0'; + + while ((entry = readdir(dir))) { + char *name = entry->d_name; + + newfile = concat_subpath_file(filename, name); + if(newfile == NULL) + continue; + ++du_depth; + sum += du(newfile); + --du_depth; + free(newfile); + } + closedir(dir); + } else if (du_depth > print_files) { + return sum; + } + if (du_depth <= max_print_depth) { + print(sum, filename); + } + return sum; +} + +int du_main(int argc, char **argv) +{ + long total; + int slink_depth_save; + int print_final_total; + char *smax_print_depth; + unsigned opt; + +#ifdef CONFIG_FEATURE_DU_DEFUALT_BLOCKSIZE_1K + if (getenv("POSIXLY_CORRECT")) { /* TODO - a new libbb function? */ +#ifdef CONFIG_FEATURE_HUMAN_READABLE + disp_hr = 512; +#else + disp_k = 0; +#endif + } +#endif + + /* Note: SUSv3 specifies that -a and -s options cannot be used together + * in strictly conforming applications. However, it also says that some + * du implementations may produce output when -a and -s are used together. + * gnu du exits with an error code in this case. We choose to simply + * ignore -a. This is consistent with -s being equivalent to -d 0. + */ +#ifdef CONFIG_FEATURE_HUMAN_READABLE + opt_complementary = "h-km:k-hm:m-hk:H-L:L-H:s-d:d-s"; + opt = getopt32(argc, argv, "aHkLsx" "d:" "lc" "hm", &smax_print_depth); + if((opt & (1 << 9))) { + /* -h opt */ + disp_hr = 0; + } + if((opt & (1 << 10))) { + /* -m opt */ + disp_hr = 1024*1024; + } + if((opt & (1 << 2))) { + /* -k opt */ + disp_hr = 1024; + } +#else + opt_complementary = "H-L:L-H:s-d:d-s"; + opt = getopt32(argc, argv, "aHkLsx" "d:" "lc", &smax_print_depth); +#if !defined CONFIG_FEATURE_DU_DEFAULT_BLOCKSIZE_1K + if((opt & (1 << 2))) { + /* -k opt */ + disp_k = 1; + } +#endif +#endif + if((opt & (1 << 0))) { + /* -a opt */ + print_files = INT_MAX; + } + if((opt & (1 << 1))) { + /* -H opt */ + slink_depth = 1; + } + if((opt & (1 << 3))) { + /* -L opt */ + slink_depth = INT_MAX; + } + if((opt & (1 << 4))) { + /* -s opt */ + max_print_depth = 0; + } + one_file_system = opt & (1 << 5); /* -x opt */ + if((opt & (1 << 6))) { + /* -d opt */ + max_print_depth = xatoi_u(smax_print_depth); + } + if((opt & (1 << 7))) { + /* -l opt */ + count_hardlinks = INT_MAX; + } + print_final_total = opt & (1 << 8); /* -c opt */ + + /* go through remaining args (if any) */ + argv += optind; + if (optind >= argc) { + *--argv = "."; + if (slink_depth == 1) { + slink_depth = 0; + } + } + + slink_depth_save = slink_depth; + total = 0; + do { + total += du(*argv); + slink_depth = slink_depth_save; + } while (*++argv); +#ifdef CONFIG_FEATURE_CLEAN_UP + reset_ino_dev_hashtable(); +#endif + + if (print_final_total) { + print(total, "total"); + } + + fflush_stdout_and_exit(status); +} diff --git a/coreutils/echo.c b/coreutils/echo.c new file mode 100644 index 000000000..99063ae52 --- /dev/null +++ b/coreutils/echo.c @@ -0,0 +1,158 @@ +/* vi: set sw=4 ts=4: */ +/* + * echo implementation for busybox + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Original copyright notice is retained at the end of this file. + */ + +/* BB_AUDIT SUSv3 compliant -- unless configured as fancy echo. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/echo.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Because of behavioral differences, implemented configurable SUSv3 + * or 'fancy' gnu-ish behaviors. Also, reduced size and fixed bugs. + * 1) In handling '\c' escape, the previous version only suppressed the + * trailing newline. SUSv3 specifies _no_ output after '\c'. + * 2) SUSv3 specifies that octal escapes are of the form \0{#{#{#}}}. + * The previous version version did not allow 4-digit octals. + */ + + +#include +#include +#include +#include "busybox.h" + +int bb_echo(int ATTRIBUTE_UNUSED argc, char **argv) +{ +#ifndef CONFIG_FEATURE_FANCY_ECHO +#define eflag '\\' + ++argv; +#else + const char *p; + int nflag = 1; + int eflag = 0; + + while (*++argv && (**argv == '-')) { + /* If it appears that we are handling options, then make sure + * that all of the options specified are actually valid. + * Otherwise, the string should just be echoed. + */ + + if (!*(p = *argv + 1)) { /* A single '-', so echo it. */ + goto just_echo; + } + + do { + if (strrchr("neE", *p) == 0) { + goto just_echo; + } + } while (*++p); + + /* All of the options in this arg are valid, so handle them. */ + p = *argv + 1; + do { + if (*p == 'n') { + nflag = 0; + } else if (*p == 'e') { + eflag = '\\'; + } else { + eflag = 0; + } + } while (*++p); + } + +just_echo: +#endif + while (*argv) { + int c; + + while ((c = *(*argv)++)) { + if (c == eflag) { /* Check for escape seq. */ + if (**argv == 'c') { + /* '\c' means cancel newline and + * ignore all subsequent chars. */ + return 0; + } +#ifndef CONFIG_FEATURE_FANCY_ECHO + /* SUSv3 specifies that octal escapes must begin with '0'. */ + if (((unsigned int)(**argv - '1')) >= 7) +#endif + { + /* Since SUSv3 mandates a first digit of 0, 4-digit octals + * of the form \0### are accepted. */ + if ((**argv == '0') && (((unsigned int)(argv[0][1] - '0')) < 8)) { + (*argv)++; + } + /* bb_process_escape_sequence can handle nul correctly */ + c = bb_process_escape_sequence((const char **) argv); + } + } + putchar(c); + } + + if (*++argv) { + putchar(' '); + } + } + +#ifdef CONFIG_FEATURE_FANCY_ECHO + if (nflag) { + putchar('\n'); + } +#else + putchar('\n'); +#endif + return 0; +} + +int echo_main(int argc, char** argv) +{ + (void)bb_echo(argc, argv); + fflush_stdout_and_exit(EXIT_SUCCESS); +} + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. + * + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)echo.c 8.1 (Berkeley) 5/31/93 + */ diff --git a/coreutils/env.c b/coreutils/env.c new file mode 100644 index 000000000..2ce99b0ad --- /dev/null +++ b/coreutils/env.c @@ -0,0 +1,129 @@ +/* vi: set sw=4 ts=4: */ +/* + * env implementation for busybox + * + * Copyright (c) 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Original copyright notice is retained at the end of this file. + * + * Modified for BusyBox by Erik Andersen + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/env.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Fixed bug involving exit return codes if execvp fails. Also added + * output error checking. + */ + +/* + * Modified by Vladimir Oleynik (C) 2003 + * - correct "-" option usage + * - multiple "-u unsetenv" support + * - GNU long option support + * - use xfunc_error_retval + */ + +#include "busybox.h" +#include +#include /* struct option */ + +#if ENABLE_FEATURE_ENV_LONG_OPTIONS +static const struct option env_long_options[] = { + { "ignore-environment", 0, NULL, 'i' }, + { "unset", 1, NULL, 'u' }, + { 0, 0, 0, 0 } +}; +#endif + +int env_main(int argc, char** argv) +{ + static char *cleanenv[1] = { NULL }; + + char **ep; + unsigned opt; + llist_t *unset_env = NULL; + extern char **environ; + + opt_complementary = "u::"; +#if ENABLE_FEATURE_ENV_LONG_OPTIONS + applet_long_options = env_long_options; +#endif + + opt = getopt32(argc, argv, "+iu:", &unset_env); + + argv += optind; + if (*argv && (argv[0][0] == '-') && !argv[0][1]) { + opt |= 1; + ++argv; + } + + if (opt & 1) + environ = cleanenv; + else if (opt & 2) { + while (unset_env) { + unsetenv(unset_env->data); + unset_env = unset_env->link; + } + } + + while (*argv && (strchr(*argv, '=') != NULL)) { + if (putenv(*argv) < 0) { + bb_perror_msg_and_die("putenv"); + } + ++argv; + } + + if (*argv) { + execvp(*argv, argv); + /* SUSv3-mandated exit codes. */ + xfunc_error_retval = (errno == ENOENT) ? 127 : 126; + bb_perror_msg_and_die("%s", *argv); + } + + for (ep = environ; *ep; ep++) { + puts(*ep); + } + + fflush_stdout_and_exit(0); +} + +/* + * Copyright (c) 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. + * + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + + diff --git a/coreutils/expr.c b/coreutils/expr.c new file mode 100644 index 000000000..191473446 --- /dev/null +++ b/coreutils/expr.c @@ -0,0 +1,505 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini expr implementation for busybox + * + * based on GNU expr Mike Parker. + * Copyright (C) 86, 1991-1997, 1999 Free Software Foundation, Inc. + * + * Busybox modifications + * Copyright (c) 2000 Edward Betts . + * Copyright (C) 2003-2005 Vladimir Oleynik + * - reduced 464 bytes. + * - 64 math support + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* This program evaluates expressions. Each token (operator, operand, + * parenthesis) of the expression must be a separate argument. The + * parser used is a reasonably general one, though any incarnation of + * it is language-specific. It is especially nice for expressions. + * + * No parse tree is needed; a new node is evaluated immediately. + * One function can handle multiple operators all of equal precedence, + * provided they all associate ((x op x) op x). */ + +/* no getopt needed */ + +#include "busybox.h" +#include "xregex.h" + +/* The kinds of value we can have. */ +enum valtype { + integer, + string +}; +typedef enum valtype TYPE; + +#if ENABLE_EXPR_MATH_SUPPORT_64 +typedef int64_t arith_t; + +#define PF_REZ "ll" +#define PF_REZ_TYPE (long long) +#define STRTOL(s, e, b) strtoll(s, e, b) +#else +typedef long arith_t; + +#define PF_REZ "l" +#define PF_REZ_TYPE (long) +#define STRTOL(s, e, b) strtol(s, e, b) +#endif + +/* TODO: use bb_strtol[l]? It's easier to check for errors... */ + +/* A value is.... */ +struct valinfo { + TYPE type; /* Which kind. */ + union { /* The value itself. */ + arith_t i; + char *s; + } u; +}; +typedef struct valinfo VALUE; + +/* The arguments given to the program, minus the program name. */ +static char **args; + +static VALUE *docolon(VALUE * sv, VALUE * pv); +static VALUE *eval(void); +static VALUE *int_value(arith_t i); +static VALUE *str_value(char *s); +static int nextarg(char *str); +static int null(VALUE * v); +static int toarith(VALUE * v); +static void freev(VALUE * v); +static void tostring(VALUE * v); + +int expr_main(int argc, char **argv) +{ + VALUE *v; + + if (argc == 1) { + bb_error_msg_and_die("too few arguments"); + } + + args = argv + 1; + + v = eval(); + if (*args) + bb_error_msg_and_die("syntax error"); + + if (v->type == integer) + printf("%" PF_REZ "d\n", PF_REZ_TYPE v->u.i); + else + puts(v->u.s); + + fflush_stdout_and_exit(null(v)); +} + +/* Return a VALUE for I. */ + +static VALUE *int_value(arith_t i) +{ + VALUE *v; + + v = xmalloc(sizeof(VALUE)); + v->type = integer; + v->u.i = i; + return v; +} + +/* Return a VALUE for S. */ + +static VALUE *str_value(char *s) +{ + VALUE *v; + + v = xmalloc(sizeof(VALUE)); + v->type = string; + v->u.s = xstrdup(s); + return v; +} + +/* Free VALUE V, including structure components. */ + +static void freev(VALUE * v) +{ + if (v->type == string) + free(v->u.s); + free(v); +} + +/* Return nonzero if V is a null-string or zero-number. */ + +static int null(VALUE * v) +{ + if (v->type == integer) + return v->u.i == 0; + else /* string: */ + return v->u.s[0] == '\0' || strcmp(v->u.s, "0") == 0; +} + +/* Coerce V to a string value (can't fail). */ + +static void tostring(VALUE * v) +{ + if (v->type == integer) { + v->u.s = xasprintf("%" PF_REZ "d", PF_REZ_TYPE v->u.i); + v->type = string; + } +} + +/* Coerce V to an integer value. Return 1 on success, 0 on failure. */ + +static int toarith(VALUE * v) +{ + if (v->type == string) { + arith_t i; + char *e; + + /* Don't interpret the empty string as an integer. */ + /* Currently does not worry about overflow or int/long differences. */ + i = STRTOL(v->u.s, &e, 10); + if ((v->u.s == e) || *e) + return 0; + free(v->u.s); + v->u.i = i; + v->type = integer; + } + return 1; +} + +/* Return nonzero if the next token matches STR exactly. + STR must not be NULL. */ + +static int nextarg(char *str) +{ + if (*args == NULL) + return 0; + return strcmp(*args, str) == 0; +} + +/* The comparison operator handling functions. */ + +static int cmp_common(VALUE * l, VALUE * r, int op) +{ + int cmpval; + + if (l->type == string || r->type == string) { + tostring(l); + tostring(r); + cmpval = strcmp(l->u.s, r->u.s); + } else + cmpval = l->u.i - r->u.i; + if (op == '<') + return cmpval < 0; + else if (op == ('L' + 'E')) + return cmpval <= 0; + else if (op == '=') + return cmpval == 0; + else if (op == '!') + return cmpval != 0; + else if (op == '>') + return cmpval > 0; + else /* >= */ + return cmpval >= 0; +} + +/* The arithmetic operator handling functions. */ + +static arith_t arithmetic_common(VALUE * l, VALUE * r, int op) +{ + arith_t li, ri; + + if (!toarith(l) || !toarith(r)) + bb_error_msg_and_die("non-numeric argument"); + li = l->u.i; + ri = r->u.i; + if ((op == '/' || op == '%') && ri == 0) + bb_error_msg_and_die("division by zero"); + if (op == '+') + return li + ri; + else if (op == '-') + return li - ri; + else if (op == '*') + return li * ri; + else if (op == '/') + return li / ri; + else + return li % ri; +} + +/* Do the : operator. + SV is the VALUE for the lhs (the string), + PV is the VALUE for the rhs (the pattern). */ + +static VALUE *docolon(VALUE * sv, VALUE * pv) +{ + VALUE *v; + regex_t re_buffer; + const int NMATCH = 2; + regmatch_t re_regs[NMATCH]; + + tostring(sv); + tostring(pv); + + if (pv->u.s[0] == '^') { + fprintf(stderr, "\ +warning: unportable BRE: `%s': using `^' as the first character\n\ +of a basic regular expression is not portable; it is being ignored", pv->u.s); + } + + memset(&re_buffer, 0, sizeof(re_buffer)); + memset(re_regs, 0, sizeof(*re_regs)); + if (regcomp(&re_buffer, pv->u.s, 0) != 0) + bb_error_msg_and_die("invalid regular expression"); + + /* expr uses an anchored pattern match, so check that there was a + * match and that the match starts at offset 0. */ + if (regexec(&re_buffer, sv->u.s, NMATCH, re_regs, 0) != REG_NOMATCH && + re_regs[0].rm_so == 0) { + /* Were \(...\) used? */ + if (re_buffer.re_nsub > 0) { + sv->u.s[re_regs[1].rm_eo] = '\0'; + v = str_value(sv->u.s + re_regs[1].rm_so); + } else + v = int_value(re_regs[0].rm_eo); + } else { + /* Match failed -- return the right kind of null. */ + if (re_buffer.re_nsub > 0) + v = str_value(""); + else + v = int_value(0); + } + return v; +} + +/* Handle bare operands and ( expr ) syntax. */ + +static VALUE *eval7(void) +{ + VALUE *v; + + if (!*args) + bb_error_msg_and_die("syntax error"); + + if (nextarg("(")) { + args++; + v = eval(); + if (!nextarg(")")) + bb_error_msg_and_die("syntax error"); + args++; + return v; + } + + if (nextarg(")")) + bb_error_msg_and_die("syntax error"); + + return str_value(*args++); +} + +/* Handle match, substr, index, length, and quote keywords. */ + +static VALUE *eval6(void) +{ + VALUE *l, *r, *v, *i1, *i2; + + if (nextarg("quote")) { + args++; + if (!*args) + bb_error_msg_and_die("syntax error"); + return str_value(*args++); + } else if (nextarg("length")) { + args++; + r = eval6(); + tostring(r); + v = int_value(strlen(r->u.s)); + freev(r); + return v; + } else if (nextarg("match")) { + args++; + l = eval6(); + r = eval6(); + v = docolon(l, r); + freev(l); + freev(r); + return v; + } else if (nextarg("index")) { + args++; + l = eval6(); + r = eval6(); + tostring(l); + tostring(r); + v = int_value(strcspn(l->u.s, r->u.s) + 1); + if (v->u.i == (arith_t) strlen(l->u.s) + 1) + v->u.i = 0; + freev(l); + freev(r); + return v; + } else if (nextarg("substr")) { + args++; + l = eval6(); + i1 = eval6(); + i2 = eval6(); + tostring(l); + if (!toarith(i1) || !toarith(i2) + || i1->u.i > (arith_t) strlen(l->u.s) + || i1->u.i <= 0 || i2->u.i <= 0) + v = str_value(""); + else { + v = xmalloc(sizeof(VALUE)); + v->type = string; + v->u.s = xstrndup(l->u.s + i1->u.i - 1, i2->u.i); + } + freev(l); + freev(i1); + freev(i2); + return v; + } else + return eval7(); +} + +/* Handle : operator (pattern matching). + Calls docolon to do the real work. */ + +static VALUE *eval5(void) +{ + VALUE *l, *r, *v; + + l = eval6(); + while (nextarg(":")) { + args++; + r = eval6(); + v = docolon(l, r); + freev(l); + freev(r); + l = v; + } + return l; +} + +/* Handle *, /, % operators. */ + +static VALUE *eval4(void) +{ + VALUE *l, *r; + int op; + arith_t val; + + l = eval5(); + while (1) { + if (nextarg("*")) + op = '*'; + else if (nextarg("/")) + op = '/'; + else if (nextarg("%")) + op = '%'; + else + return l; + args++; + r = eval5(); + val = arithmetic_common(l, r, op); + freev(l); + freev(r); + l = int_value(val); + } +} + +/* Handle +, - operators. */ + +static VALUE *eval3(void) +{ + VALUE *l, *r; + int op; + arith_t val; + + l = eval4(); + while (1) { + if (nextarg("+")) + op = '+'; + else if (nextarg("-")) + op = '-'; + else + return l; + args++; + r = eval4(); + val = arithmetic_common(l, r, op); + freev(l); + freev(r); + l = int_value(val); + } +} + +/* Handle comparisons. */ + +static VALUE *eval2(void) +{ + VALUE *l, *r; + int op; + arith_t val; + + l = eval3(); + while (1) { + if (nextarg("<")) + op = '<'; + else if (nextarg("<=")) + op = 'L' + 'E'; + else if (nextarg("=") || nextarg("==")) + op = '='; + else if (nextarg("!=")) + op = '!'; + else if (nextarg(">=")) + op = 'G' + 'E'; + else if (nextarg(">")) + op = '>'; + else + return l; + args++; + r = eval3(); + toarith(l); + toarith(r); + val = cmp_common(l, r, op); + freev(l); + freev(r); + l = int_value(val); + } +} + +/* Handle &. */ + +static VALUE *eval1(void) +{ + VALUE *l, *r; + + l = eval2(); + while (nextarg("&")) { + args++; + r = eval2(); + if (null(l) || null(r)) { + freev(l); + freev(r); + l = int_value(0); + } else + freev(r); + } + return l; +} + +/* Handle |. */ + +static VALUE *eval(void) +{ + VALUE *l, *r; + + l = eval1(); + while (nextarg("|")) { + args++; + r = eval1(); + if (null(l)) { + freev(l); + l = r; + } else + freev(r); + } + return l; +} diff --git a/coreutils/false.c b/coreutils/false.c new file mode 100644 index 000000000..084bc0c1e --- /dev/null +++ b/coreutils/false.c @@ -0,0 +1,19 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini false implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/false.html */ + +#include +#include "busybox.h" + +int false_main(int ATTRIBUTE_UNUSED argc, char ATTRIBUTE_UNUSED **argv) +{ + return EXIT_FAILURE; +} diff --git a/coreutils/fold.c b/coreutils/fold.c new file mode 100644 index 000000000..9ca693dff --- /dev/null +++ b/coreutils/fold.c @@ -0,0 +1,154 @@ +/* vi: set sw=4 ts=4: */ +/* fold -- wrap each input line to fit in specified width. + + Written by David MacKenzie, djm@gnu.ai.mit.edu. + Copyright (C) 91, 1995-2002 Free Software Foundation, Inc. + + Modified for busybox based on coreutils v 5.0 + Copyright (C) 2003 Glenn McGrath + + Licensed under the GPL v2 or later, see the file LICENSE in this tarball. +*/ + +#include "busybox.h" + +static unsigned long flags; +#define FLAG_COUNT_BYTES 1 +#define FLAG_BREAK_SPACES 2 +#define FLAG_WIDTH 4 + +/* Assuming the current column is COLUMN, return the column that + printing C will move the cursor to. + The first column is 0. */ + +static int adjust_column(int column, char c) +{ + if (!(flags & FLAG_COUNT_BYTES)) { + if (c == '\b') { + if (column > 0) + column--; + } else if (c == '\r') + column = 0; + else if (c == '\t') + column = column + 8 - column % 8; + else /* if (isprint (c)) */ + column++; + } else + column++; + return column; +} + +int fold_main(int argc, char **argv) +{ + char *w_opt; + int width = 80; + int i; + int errs = 0; + + if(!ENABLE_DEBUG_YANK_SUSv2) { + /* Turn any numeric options into -w options. */ + for (i = 1; i < argc; i++) { + char const *a = argv[i]; + + if (*a++ == '-') { + if (*a == '-' && !a[1]) + break; + if (isdigit(*a)) { + argv[i] = xasprintf("-w%s", a); + } + } + } + } + + flags = getopt32(argc, argv, "bsw:", &w_opt); + if (flags & FLAG_WIDTH) + width = xatoul_range(w_opt, 1, 10000); + + argv += optind; + if (!*argv) { + *--argv = "-"; + } + + do { + FILE *istream = fopen_or_warn_stdin(*argv); + int c; + int column = 0; /* Screen column where next char will go. */ + int offset_out = 0; /* Index in `line_out' for next char. */ + static char *line_out = NULL; + static int allocated_out = 0; + + if (istream == NULL) { + errs |= EXIT_FAILURE; + continue; + } + + while ((c = getc(istream)) != EOF) { + if (offset_out + 1 >= allocated_out) { + allocated_out += 1024; + line_out = xrealloc(line_out, allocated_out); + } + + if (c == '\n') { + line_out[offset_out++] = c; + fwrite(line_out, sizeof(char), (size_t) offset_out, stdout); + column = offset_out = 0; + continue; + } + +rescan: + column = adjust_column(column, c); + + if (column > width) { + /* This character would make the line too long. + Print the line plus a newline, and make this character + start the next line. */ + if (flags & FLAG_BREAK_SPACES) { + /* Look for the last blank. */ + int logical_end; + + for (logical_end = offset_out - 1; logical_end >= 0; logical_end--) { + if (isblank(line_out[logical_end])) { + break; + } + } + if (logical_end >= 0) { + /* Found a blank. Don't output the part after it. */ + logical_end++; + fwrite(line_out, sizeof(char), (size_t) logical_end, stdout); + putchar('\n'); + /* Move the remainder to the beginning of the next line. + The areas being copied here might overlap. */ + memmove(line_out, line_out + logical_end, offset_out - logical_end); + offset_out -= logical_end; + for (column = i = 0; i < offset_out; i++) { + column = adjust_column(column, line_out[i]); + } + goto rescan; + } + } else { + if (offset_out == 0) { + line_out[offset_out++] = c; + continue; + } + } + line_out[offset_out++] = '\n'; + fwrite(line_out, sizeof(char), (size_t) offset_out, stdout); + column = offset_out = 0; + goto rescan; + } + + line_out[offset_out++] = c; + } + + if (offset_out) { + fwrite(line_out, sizeof(char), (size_t) offset_out, stdout); + } + + if (ferror(istream) || fclose_if_not_stdin(istream)) { + bb_perror_msg("%s", *argv); /* Avoid multibyte problems. */ + errs |= EXIT_FAILURE; + } + } while (*++argv); + + fflush_stdout_and_exit(errs); +} diff --git a/coreutils/head.c b/coreutils/head.c new file mode 100644 index 000000000..f2c948300 --- /dev/null +++ b/coreutils/head.c @@ -0,0 +1,139 @@ +/* vi: set sw=4 ts=4: */ +/* + * head implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* BB_AUDIT GNU compatible -c, -q, and -v options in 'fancy' configuration. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/head.html */ + +#include "busybox.h" + +static const char head_opts[] = + "n:" +#if ENABLE_FEATURE_FANCY_HEAD + "c:qv" +#endif + ; + +#if ENABLE_FEATURE_FANCY_HEAD +static const struct suffix_mult head_suffixes[] = { + { "b", 512 }, + { "k", 1024 }, + { "m", 1024*1024 }, + { NULL, 0 } +}; +#endif + +static const char header_fmt_str[] = "\n==> %s <==\n"; + +int head_main(int argc, char **argv) +{ + unsigned long count = 10; + unsigned long i; +#if ENABLE_FEATURE_FANCY_HEAD + int count_bytes = 0; + int header_threshhold = 1; +#endif + + FILE *fp; + const char *fmt; + char *p; + int opt; + int c; + int retval = EXIT_SUCCESS; + +#if !ENABLE_DEBUG_YANK_SUSv2 || ENABLE_FEATURE_FANCY_HEAD + /* Allow legacy syntax of an initial numeric option without -n. */ + if (argc > 1 && argv[1][0] == '-' + && isdigit(argv[1][1]) + ) { + --argc; + ++argv; + p = (*argv) + 1; + goto GET_COUNT; + } +#endif + + /* No size benefit in converting this to getopt32 */ + while ((opt = getopt(argc, argv, head_opts)) > 0) { + switch (opt) { +#if ENABLE_FEATURE_FANCY_HEAD + case 'q': + header_threshhold = INT_MAX; + break; + case 'v': + header_threshhold = -1; + break; + case 'c': + count_bytes = 1; + /* fall through */ +#endif + case 'n': + p = optarg; +#if !ENABLE_DEBUG_YANK_SUSv2 || ENABLE_FEATURE_FANCY_HEAD + GET_COUNT: +#endif + +#if !ENABLE_FEATURE_FANCY_HEAD + count = xatoul(p); +#else + count = xatoul_sfx(p, head_suffixes); +#endif + break; + default: + bb_show_usage(); + } + } + + argv += optind; + if (!*argv) { + *--argv = "-"; + } + + fmt = header_fmt_str + 1; +#if ENABLE_FEATURE_FANCY_HEAD + if (argc - optind <= header_threshhold) { + header_threshhold = 0; + } +#else + if (argc <= optind + 1) { + fmt += 11; + } + /* Now define some things here to avoid #ifdefs in the code below. + * These should optimize out of the if conditions below. */ +#define header_threshhold 1 +#define count_bytes 0 +#endif + + do { + fp = fopen_or_warn_stdin(*argv); + if (fp) { + if (fp == stdin) { + *argv = (char *) bb_msg_standard_input; + } + if (header_threshhold) { + printf(fmt, *argv); + } + i = count; + while (i && ((c = getc(fp)) != EOF)) { + if (count_bytes || (c == '\n')) { + --i; + } + putchar(c); + } + if (fclose_if_not_stdin(fp)) { + bb_perror_msg("%s", *argv); /* Avoid multibyte problems. */ + retval = EXIT_FAILURE; + } + die_if_ferror_stdout(); + } + fmt = header_fmt_str; + } while (*++argv); + + fflush_stdout_and_exit(retval); +} diff --git a/coreutils/hostid.c b/coreutils/hostid.c new file mode 100644 index 000000000..65447aa63 --- /dev/null +++ b/coreutils/hostid.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini hostid implementation for busybox + * + * Copyright (C) 2000 Edward Betts . + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 N/A -- Matches GNU behavior. */ + +#include +#include +#include "busybox.h" + +int hostid_main(int argc, char ATTRIBUTE_UNUSED **argv) +{ + if (argc > 1) { + bb_show_usage(); + } + + printf("%lx\n", gethostid()); + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/coreutils/id.c b/coreutils/id.c new file mode 100644 index 000000000..66874a71e --- /dev/null +++ b/coreutils/id.c @@ -0,0 +1,111 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini id implementation for busybox + * + * Copyright (C) 2000 by Randolph Chung + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 _NOT_ compliant -- option -G is not currently supported. */ +/* Hacked by Tito Ragusa (C) 2004 to handle usernames of whatever length and to + * be more similar to GNU id. + */ + +#include "busybox.h" +#include +#include +#include + +#ifdef CONFIG_SELINUX +#include /* for is_selinux_enabled() */ +#endif + +#define PRINT_REAL 1 +#define NAME_NOT_NUMBER 2 +#define JUST_USER 4 +#define JUST_GROUP 8 + +static short printf_full(unsigned int id, const char *arg, const char prefix) +{ + const char *fmt = "%cid=%u"; + short status = EXIT_FAILURE; + + if (arg) { + fmt = "%cid=%u(%s)"; + status = EXIT_SUCCESS; + } + printf(fmt, prefix, id, arg); + return status; +} + +int id_main(int argc, char **argv) +{ + struct passwd *p; + uid_t uid; + gid_t gid; + unsigned long flags; + short status; + + /* Don't allow -n -r -nr -ug -rug -nug -rnug */ + /* Don't allow more than one username */ + opt_complementary = "?1:?:u--g:g--u:r?ug:n?ug"; + flags = getopt32(argc, argv, "rnug"); + + /* This values could be overwritten later */ + uid = geteuid(); + gid = getegid(); + if (flags & PRINT_REAL) { + uid = getuid(); + gid = getgid(); + } + + if (argv[optind]) { + p = getpwnam(argv[optind]); + /* bb_xgetpwnam is needed because it exits on failure */ + uid = bb_xgetpwnam(argv[optind]); + gid = p->pw_gid; + /* in this case PRINT_REAL is the same */ + } + + if (flags & (JUST_GROUP | JUST_USER)) { + /* JUST_GROUP and JUST_USER are mutually exclusive */ + if (flags & NAME_NOT_NUMBER) { + /* bb_getpwuid and bb_getgrgid exit on failure so puts cannot segfault */ + puts((flags & JUST_USER) ? bb_getpwuid(NULL, uid, -1 ) : bb_getgrgid(NULL, gid, -1 )); + } else { + printf("%u\n", (flags & JUST_USER) ? uid : gid); + } + /* exit */ + fflush_stdout_and_exit(EXIT_SUCCESS); + } + + /* Print full info like GNU id */ + /* bb_getpwuid doesn't exit on failure here */ + status = printf_full(uid, bb_getpwuid(NULL, uid, 0), 'u'); + putchar(' '); + /* bb_getgrgid doesn't exit on failure here */ + status |= printf_full(gid, bb_getgrgid(NULL, gid, 0), 'g'); + +#ifdef CONFIG_SELINUX + if (is_selinux_enabled()) { + security_context_t mysid; + char context[80]; + int len = sizeof(context); + + getcon(&mysid); + context[0] = '\0'; + if (mysid) { + len = strlen(mysid)+1; + safe_strncpy(context, mysid, len); + freecon(mysid); + } else { + safe_strncpy(context, "unknown", 8); + } + printf(" context=%s", context); + } +#endif + + putchar('\n'); + fflush_stdout_and_exit(status); +} diff --git a/coreutils/install.c b/coreutils/install.c new file mode 100644 index 000000000..3e003905e --- /dev/null +++ b/coreutils/install.c @@ -0,0 +1,131 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 2003 by Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * TODO: -d option, need a way of recursively making directories and changing + * owner/group, will probably modify bb_make_directory(...) + */ + +#include "busybox.h" +#include "libcoreutils/coreutils.h" +#include +#include /* struct option */ + +#define INSTALL_OPT_CMD 1 +#define INSTALL_OPT_DIRECTORY 2 +#define INSTALL_OPT_PRESERVE_TIME 4 +#define INSTALL_OPT_STRIP 8 +#define INSTALL_OPT_GROUP 16 +#define INSTALL_OPT_MODE 32 +#define INSTALL_OPT_OWNER 64 + +#if ENABLE_FEATURE_INSTALL_LONG_OPTIONS +static const struct option install_long_options[] = { + { "directory", 0, NULL, 'd' }, + { "preserve-timestamps", 0, NULL, 'p' }, + { "strip", 0, NULL, 's' }, + { "group", 0, NULL, 'g' }, + { "mode", 0, NULL, 'm' }, + { "owner", 0, NULL, 'o' }, + { 0, 0, 0, 0 } +}; +#endif + +int install_main(int argc, char **argv) +{ + mode_t mode; + uid_t uid; + gid_t gid; + char *gid_str = "-1"; + char *uid_str = "-1"; + char *mode_str = "0755"; + int copy_flags = FILEUTILS_DEREFERENCE | FILEUTILS_FORCE; + int ret = EXIT_SUCCESS, flags, i, isdir; + +#if ENABLE_FEATURE_INSTALL_LONG_OPTIONS + applet_long_options = install_long_options; +#endif + opt_complementary = "?:s--d:d--s"; + /* -c exists for backwards compatibility, its needed */ + flags = getopt32(argc, argv, "cdpsg:m:o:", &gid_str, &mode_str, &uid_str); /* 'a' must be 2nd */ + + /* preserve access and modification time, this is GNU behaviour, BSD only preserves modification time */ + if (flags & INSTALL_OPT_PRESERVE_TIME) { + copy_flags |= FILEUTILS_PRESERVE_STATUS; + } + bb_parse_mode(mode_str, &mode); + gid = get_ug_id(gid_str, bb_xgetgrnam); + uid = get_ug_id(uid_str, bb_xgetpwnam); + umask(0); + + /* Create directories + * don't use bb_make_directory() as it can't change uid or gid + * perhaps bb_make_directory() should be improved. + */ + if (flags & INSTALL_OPT_DIRECTORY) { + for (argv += optind; *argv; argv++) { + char *old_argv_ptr = *argv + 1; + char *argv_ptr; + do { + argv_ptr = strchr(old_argv_ptr, '/'); + old_argv_ptr = argv_ptr; + if (argv_ptr) { + *argv_ptr = '\0'; + old_argv_ptr++; + } + if (mkdir(*argv, mode) == -1) { + if (errno != EEXIST) { + bb_perror_msg("cannot create %s", *argv); + ret = EXIT_FAILURE; + break; + } + } + else if (lchown(*argv, uid, gid) == -1) { + bb_perror_msg("cannot change ownership of %s", *argv); + ret = EXIT_FAILURE; + break; + } + if (argv_ptr) { + *argv_ptr = '/'; + } + } while (old_argv_ptr); + } + return ret; + } + + { + struct stat statbuf; + isdir = lstat(argv[argc - 1], &statbuf)<0 + ? 0 : S_ISDIR(statbuf.st_mode); + } + for (i = optind; i < argc - 1; i++) { + char *dest; + + dest = argv[argc - 1]; + if (isdir) dest = concat_path_file(argv[argc - 1], basename(argv[i])); + ret |= copy_file(argv[i], dest, copy_flags); + + /* Set the file mode */ + if (chmod(dest, mode) == -1) { + bb_perror_msg("cannot change permissions of %s", dest); + ret = EXIT_FAILURE; + } + + /* Set the user and group id */ + if (lchown(dest, uid, gid) == -1) { + bb_perror_msg("cannot change ownership of %s", dest); + ret = EXIT_FAILURE; + } + if (flags & INSTALL_OPT_STRIP) { + if (execlp("strip", "strip", dest, NULL) == -1) { + bb_error_msg("strip failed"); + ret = EXIT_FAILURE; + } + } + if(ENABLE_FEATURE_CLEAN_UP && isdir) free(dest); + } + + return ret; +} diff --git a/coreutils/length.c b/coreutils/length.c new file mode 100644 index 000000000..6413be007 --- /dev/null +++ b/coreutils/length.c @@ -0,0 +1,19 @@ +/* vi: set sw=4 ts=4: */ + +/* BB_AUDIT SUSv3 N/A -- Apparently a busybox (obsolete?) extension. */ + +#include +#include +#include +#include "busybox.h" + +int length_main(int argc, char **argv) +{ + if ((argc != 2) || (**(++argv) == '-')) { + bb_show_usage(); + } + + printf("%lu\n", (unsigned long)strlen(*argv)); + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/coreutils/libcoreutils/Kbuild b/coreutils/libcoreutils/Kbuild new file mode 100644 index 000000000..755d01f86 --- /dev/null +++ b/coreutils/libcoreutils/Kbuild @@ -0,0 +1,12 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2004 by Erik Andersen +# +# Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_MKFIFO) += getopt_mk_fifo_nod.o +lib-$(CONFIG_MKNOD) += getopt_mk_fifo_nod.o +lib-$(CONFIG_INSTALL) += cp_mv_stat.o +lib-$(CONFIG_CP) += cp_mv_stat.o +lib-$(CONFIG_MV) += cp_mv_stat.o diff --git a/coreutils/libcoreutils/coreutils.h b/coreutils/libcoreutils/coreutils.h new file mode 100644 index 000000000..102c7b5be --- /dev/null +++ b/coreutils/libcoreutils/coreutils.h @@ -0,0 +1,16 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#ifndef COREUTILS_H +#define COREUTILS_H 1 + +typedef int (*stat_func)(const char *fn, struct stat *ps); + +extern int cp_mv_stat2(const char *fn, struct stat *fn_stat, stat_func sf); +extern int cp_mv_stat(const char *fn, struct stat *fn_stat); + +extern mode_t getopt_mk_fifo_nod(int argc, char **argv); + +#endif diff --git a/coreutils/libcoreutils/cp_mv_stat.c b/coreutils/libcoreutils/cp_mv_stat.c new file mode 100644 index 000000000..2e4f25e21 --- /dev/null +++ b/coreutils/libcoreutils/cp_mv_stat.c @@ -0,0 +1,43 @@ +/* vi: set sw=4 ts=4: */ +/* + * coreutils utility routine + * + * Copyright (C) 2003 Manuel Novoa III + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "libbb.h" +#include "coreutils.h" + +int cp_mv_stat2(const char *fn, struct stat *fn_stat, stat_func sf) +{ + if (sf(fn, fn_stat) < 0) { + if (errno != ENOENT) { + bb_perror_msg("cannot stat '%s'", fn); + return -1; + } + return 0; + } else if (S_ISDIR(fn_stat->st_mode)) { + return 3; + } + return 1; +} + +int cp_mv_stat(const char *fn, struct stat *fn_stat) +{ + return cp_mv_stat2(fn, fn_stat, stat); +} diff --git a/coreutils/libcoreutils/getopt_mk_fifo_nod.c b/coreutils/libcoreutils/getopt_mk_fifo_nod.c new file mode 100644 index 000000000..3a3d34118 --- /dev/null +++ b/coreutils/libcoreutils/getopt_mk_fifo_nod.c @@ -0,0 +1,40 @@ +/* vi: set sw=4 ts=4: */ +/* + * coreutils utility routine + * + * Copyright (C) 2003 Manuel Novoa III + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include "libbb.h" +#include "coreutils.h" + +mode_t getopt_mk_fifo_nod(int argc, char **argv) +{ + mode_t mode = 0666; + char *smode = NULL; + + getopt32(argc, argv, "m:", &smode); + if(smode) { + if (bb_parse_mode(smode, &mode)) + umask(0); + } + return mode; +} diff --git a/coreutils/ln.c b/coreutils/ln.c new file mode 100644 index 000000000..231a3bf03 --- /dev/null +++ b/coreutils/ln.c @@ -0,0 +1,104 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini ln implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* BB_AUDIT GNU options missing: -d, -F, -i, and -v. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/ln.html */ + +#include "busybox.h" + +#define LN_SYMLINK 1 +#define LN_FORCE 2 +#define LN_NODEREFERENCE 4 +#define LN_BACKUP 8 +#define LN_SUFFIX 16 + +int ln_main(int argc, char **argv) +{ + int status = EXIT_SUCCESS; + int flag; + char *last; + char *src_name; + char *src; + char *suffix = "~"; + struct stat statbuf; + int (*link_func)(const char *, const char *); + + flag = getopt32(argc, argv, "sfnbS:", &suffix); + + if (argc == optind) { + bb_show_usage(); + } + + last = argv[argc - 1]; + argv += optind; + + if (argc == optind + 1) { + *--argv = last; + last = bb_get_last_path_component(xstrdup(last)); + } + + do { + src_name = NULL; + src = last; + + if (is_directory(src, + (flag & LN_NODEREFERENCE) ^ LN_NODEREFERENCE, + NULL)) { + src_name = xstrdup(*argv); + src = concat_path_file(src, bb_get_last_path_component(src_name)); + free(src_name); + src_name = src; + } + if (!(flag & LN_SYMLINK) && stat(*argv, &statbuf)) { + // coreutils: "ln dangling_symlink new_hardlink" works + if (lstat(*argv, &statbuf) || !S_ISLNK(statbuf.st_mode)) { + bb_perror_msg("%s", *argv); + status = EXIT_FAILURE; + free(src_name); + continue; + } + } + + if (flag & LN_BACKUP) { + char *backup; + backup = xasprintf("%s%s", src, suffix); + if (rename(src, backup) < 0 && errno != ENOENT) { + bb_perror_msg("%s", src); + status = EXIT_FAILURE; + free(backup); + continue; + } + free(backup); + /* + * When the source and dest are both hard links to the same + * inode, a rename may succeed even though nothing happened. + * Therefore, always unlink(). + */ + unlink(src); + } else if (flag & LN_FORCE) { + unlink(src); + } + + link_func = link; + if (flag & LN_SYMLINK) { + link_func = symlink; + } + + if (link_func(*argv, src) != 0) { + bb_perror_msg("%s", src); + status = EXIT_FAILURE; + } + + free(src_name); + + } while ((++argv)[1]); + + return status; +} diff --git a/coreutils/logname.c b/coreutils/logname.c new file mode 100644 index 000000000..695a736c6 --- /dev/null +++ b/coreutils/logname.c @@ -0,0 +1,42 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini logname implementation for busybox + * + * Copyright (C) 2000 Edward Betts . + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/logname.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * SUSv3 specifies the string used is that returned from getlogin(). + * The previous implementation used getpwuid() for geteuid(), which + * is _not_ the same. Erik apparently made this change almost 3 years + * ago to avoid failing when no utmp was available. However, the + * correct course of action wrt SUSv3 for a failing getlogin() is + * a diagnostic message and an error return. + */ + +#include +#include +#include +#include "busybox.h" + +int logname_main(int argc, char ATTRIBUTE_UNUSED **argv) +{ + const char *p; + + if (argc > 1) { + bb_show_usage(); + } + + if ((p = getlogin()) != NULL) { + puts(p); + fflush_stdout_and_exit(EXIT_SUCCESS); + } + + bb_perror_msg_and_die("getlogin"); +} diff --git a/coreutils/ls.c b/coreutils/ls.c new file mode 100644 index 000000000..960c161b0 --- /dev/null +++ b/coreutils/ls.c @@ -0,0 +1,940 @@ +/* vi: set sw=4 ts=4: */ +/* + * tiny-ls.c version 0.1.0: A minimalist 'ls' + * Copyright (C) 1996 Brian Candler + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* + * To achieve a small memory footprint, this version of 'ls' doesn't do any + * file sorting, and only has the most essential command line switches + * (i.e., the ones I couldn't live without :-) All features which involve + * linking in substantial chunks of libc can be disabled. + * + * Although I don't really want to add new features to this program to + * keep it small, I *am* interested to receive bug fixes and ways to make + * it more portable. + * + * KNOWN BUGS: + * 1. ls -l of a directory doesn't give "total " header + * 2. ls of a symlink to a directory doesn't list directory contents + * 3. hidden files can make column width too large + * + * NON-OPTIMAL BEHAVIOUR: + * 1. autowidth reads directories twice + * 2. if you do a short directory listing without filetype characters + * appended, there's no need to stat each one + * PORTABILITY: + * 1. requires lstat (BSD) - how do you do it without? + */ + +#include "busybox.h" +#include + +enum { + +TERMINAL_WIDTH = 80, /* use 79 if terminal has linefold bug */ +COLUMN_GAP = 2, /* includes the file type char */ + +/* what is the overall style of the listing */ +STYLE_COLUMNS = 1 << 21, /* fill columns */ +STYLE_LONG = 2 << 21, /* one record per line, extended info */ +STYLE_SINGLE = 3 << 21, /* one record per line */ +STYLE_MASK = STYLE_SINGLE, + +/* 51306 lrwxrwxrwx 1 root root 2 May 11 01:43 /bin/view -> vi* */ +/* what file information will be listed */ +LIST_INO = 1 << 0, +LIST_BLOCKS = 1 << 1, +LIST_MODEBITS = 1 << 2, +LIST_NLINKS = 1 << 3, +LIST_ID_NAME = 1 << 4, +LIST_ID_NUMERIC = 1 << 5, +LIST_CONTEXT = 1 << 6, +LIST_SIZE = 1 << 7, +LIST_DEV = 1 << 8, +LIST_DATE_TIME = 1 << 9, +LIST_FULLTIME = 1 << 10, +LIST_FILENAME = 1 << 11, +LIST_SYMLINK = 1 << 12, +LIST_FILETYPE = 1 << 13, +LIST_EXEC = 1 << 14, +LIST_MASK = (LIST_EXEC << 1) - 1, + +/* what files will be displayed */ +DISP_DIRNAME = 1 << 15, /* 2 or more items? label directories */ +DISP_HIDDEN = 1 << 16, /* show filenames starting with . */ +DISP_DOT = 1 << 17, /* show . and .. */ +DISP_NOLIST = 1 << 18, /* show directory as itself, not contents */ +DISP_RECURSIVE = 1 << 19, /* show directory and everything below it */ +DISP_ROWS = 1 << 20, /* print across rows */ +DISP_MASK = ((DISP_ROWS << 1) - 1) & ~(DISP_DIRNAME - 1), + +/* how will the files be sorted (CONFIG_FEATURE_LS_SORTFILES) */ +SORT_FORWARD = 0, /* sort in reverse order */ +SORT_REVERSE = 1 << 27, /* sort in reverse order */ + +SORT_NAME = 0, /* sort by file name */ +SORT_SIZE = 1 << 28, /* sort by file size */ +SORT_ATIME = 2 << 28, /* sort by last access time */ +SORT_CTIME = 3 << 28, /* sort by last change time */ +SORT_MTIME = 4 << 28, /* sort by last modification time */ +SORT_VERSION = 5 << 28, /* sort by version */ +SORT_EXT = 6 << 28, /* sort by file name extension */ +SORT_DIR = 7 << 28, /* sort by file or directory */ +SORT_MASK = (7 << 28) * ENABLE_FEATURE_LS_SORTFILES, + +/* which of the three times will be used */ +TIME_CHANGE = (1 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS, +TIME_ACCESS = (1 << 24) * ENABLE_FEATURE_LS_TIMESTAMPS, +TIME_MASK = (3 << 23) * ENABLE_FEATURE_LS_TIMESTAMPS, + +FOLLOW_LINKS = (1 << 25) * ENABLE_FEATURE_LS_FOLLOWLINKS, + +LS_DISP_HR = (1 << 26) * ENABLE_FEATURE_HUMAN_READABLE, + +LIST_SHORT = LIST_FILENAME, +LIST_LONG = LIST_MODEBITS | LIST_NLINKS | LIST_ID_NAME | LIST_SIZE | \ + LIST_DATE_TIME | LIST_FILENAME | LIST_SYMLINK, + +SPLIT_DIR = 1, +SPLIT_FILE = 0, +SPLIT_SUBDIR = 2, + +}; + +#define TYPEINDEX(mode) (((mode) >> 12) & 0x0f) +#define TYPECHAR(mode) ("0pcCd?bB-?l?s???" [TYPEINDEX(mode)]) +#define APPCHAR(mode) ("\0|\0\0/\0\0\0\0\0@\0=\0\0\0" [TYPEINDEX(mode)]) +#define COLOR(mode) ("\000\043\043\043\042\000\043\043"\ + "\000\000\044\000\043\000\000\040" [TYPEINDEX(mode)]) +#define ATTR(mode) ("\00\00\01\00\01\00\01\00"\ + "\00\00\01\00\01\00\00\01" [TYPEINDEX(mode)]) + +/* colored LS support by JaWi, janwillem.janssen@lxtreme.nl */ +#if ENABLE_FEATURE_LS_COLOR +static int show_color; +/* long option entry used only for --color, which has no short option + * equivalent */ +static const struct option ls_color_opt[] = { + { "color", optional_argument, NULL, 1 }, + { NULL, 0, NULL, 0 } +}; +#else +enum { show_color = 0 }; +#endif + +/* + * a directory entry and its stat info are stored here + */ +struct dnode { /* the basic node */ + char *name; /* the dir entry name */ + char *fullname; /* the dir entry name */ + int allocated; + struct stat dstat; /* the file stat info */ + USE_SELINUX(security_context_t sid;) + struct dnode *next; /* point at the next node */ +}; +typedef struct dnode dnode_t; + +static struct dnode **list_dir(const char *); +static struct dnode **dnalloc(int); +static int list_single(struct dnode *); + +static unsigned all_fmt; + +#if ENABLE_FEATURE_AUTOWIDTH +static unsigned tabstops = COLUMN_GAP; +static unsigned terminal_width = TERMINAL_WIDTH; +#else +enum { + tabstops = COLUMN_GAP, + terminal_width = TERMINAL_WIDTH, +}; +#endif + +static int status = EXIT_SUCCESS; + +static struct dnode *my_stat(char *fullname, char *name) +{ + struct stat dstat; + struct dnode *cur; + USE_SELINUX(security_context_t sid = NULL;) + + if (all_fmt & FOLLOW_LINKS) { +#if ENABLE_SELINUX + if (is_selinux_enabled()) { + getfilecon(fullname, &sid); + } +#endif + if (stat(fullname, &dstat)) { + bb_perror_msg("%s", fullname); + status = EXIT_FAILURE; + return 0; + } + } else { +#if ENABLE_SELINUX + if (is_selinux_enabled()) { + lgetfilecon(fullname,&sid); + } +#endif + if (lstat(fullname, &dstat)) { + bb_perror_msg("%s", fullname); + status = EXIT_FAILURE; + return 0; + } + } + + cur = xmalloc(sizeof(struct dnode)); + cur->fullname = fullname; + cur->name = name; + cur->dstat = dstat; + USE_SELINUX(cur->sid = sid;) + return cur; +} + +#if ENABLE_FEATURE_LS_COLOR +static char fgcolor(mode_t mode) +{ + /* Check wheter the file is existing (if so, color it red!) */ + if (errno == ENOENT) + return '\037'; + if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) + return COLOR(0xF000); /* File is executable ... */ + return COLOR(mode); +} + +static char bgcolor(mode_t mode) +{ + if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) + return ATTR(0xF000); /* File is executable ... */ + return ATTR(mode); +} +#endif + +#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR +static char append_char(mode_t mode) +{ + if (!(all_fmt & LIST_FILETYPE)) + return '\0'; + if (S_ISDIR(mode)) + return '/'; + if (!(all_fmt & LIST_EXEC)) + return '\0'; + if (S_ISREG(mode) && (mode & (S_IXUSR | S_IXGRP | S_IXOTH))) + return '*'; + return APPCHAR(mode); +} +#endif + +#define countdirs(A, B) count_dirs((A), (B), 1) +#define countsubdirs(A, B) count_dirs((A), (B), 0) +static int count_dirs(struct dnode **dn, int nfiles, int notsubdirs) +{ + int i, dirs; + + if (!dn) + return 0; + dirs = 0; + for (i = 0; i < nfiles; i++) { + char *name; + if (!S_ISDIR(dn[i]->dstat.st_mode)) + continue; + name = dn[i]->name; + if (notsubdirs + || name[0]!='.' || (name[1] && (name[1]!='.' || name[2])) + ) { + dirs++; + } + } + return dirs; +} + +static int countfiles(struct dnode **dnp) +{ + int nfiles; + struct dnode *cur; + + if (dnp == NULL) + return 0; + nfiles = 0; + for (cur = dnp[0]; cur->next; cur = cur->next) + nfiles++; + nfiles++; + return nfiles; +} + +/* get memory to hold an array of pointers */ +static struct dnode **dnalloc(int num) +{ + if (num < 1) + return NULL; + + return xzalloc(num * sizeof(struct dnode *)); +} + +#if ENABLE_FEATURE_LS_RECURSIVE +static void dfree(struct dnode **dnp, int nfiles) +{ + int i; + + if (dnp == NULL) + return; + + for (i = 0; i < nfiles; i++) { + struct dnode *cur = dnp[i]; + if (cur->allocated) + free(cur->fullname); /* free the filename */ + free(cur); /* free the dnode */ + } + free(dnp); /* free the array holding the dnode pointers */ +} +#else +#define dfree(...) do {} while(0) +#endif + +static struct dnode **splitdnarray(struct dnode **dn, int nfiles, int which) +{ + int dncnt, i, d; + struct dnode **dnp; + + if (dn == NULL || nfiles < 1) + return NULL; + + /* count how many dirs and regular files there are */ + if (which == SPLIT_SUBDIR) + dncnt = countsubdirs(dn, nfiles); + else { + dncnt = countdirs(dn, nfiles); /* assume we are looking for dirs */ + if (which == SPLIT_FILE) + dncnt = nfiles - dncnt; /* looking for files */ + } + + /* allocate a file array and a dir array */ + dnp = dnalloc(dncnt); + + /* copy the entrys into the file or dir array */ + for (d = i = 0; i < nfiles; i++) { + if (S_ISDIR(dn[i]->dstat.st_mode)) { + char *name; + if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) + continue; + name = dn[i]->name; + if ((which & SPLIT_DIR) + || name[0]!='.' || (name[1] && (name[1]!='.' || name[2])) + ) { + dnp[d++] = dn[i]; + } + } else if (!(which & (SPLIT_DIR|SPLIT_SUBDIR))) { + dnp[d++] = dn[i]; + } + } + return dnp; +} + +#if ENABLE_FEATURE_LS_SORTFILES +static int sortcmp(const void *a, const void *b) +{ + struct dnode *d1 = *(struct dnode **)a; + struct dnode *d2 = *(struct dnode **)b; + unsigned sort_opts = all_fmt & SORT_MASK; + int dif; + + dif = 0; /* assume SORT_NAME */ + // TODO: use pre-initialized function pointer + // instead of branch forest + if (sort_opts == SORT_SIZE) { + dif = (int) (d2->dstat.st_size - d1->dstat.st_size); + } else if (sort_opts == SORT_ATIME) { + dif = (int) (d2->dstat.st_atime - d1->dstat.st_atime); + } else if (sort_opts == SORT_CTIME) { + dif = (int) (d2->dstat.st_ctime - d1->dstat.st_ctime); + } else if (sort_opts == SORT_MTIME) { + dif = (int) (d2->dstat.st_mtime - d1->dstat.st_mtime); + } else if (sort_opts == SORT_DIR) { + dif = S_ISDIR(d2->dstat.st_mode) - S_ISDIR(d1->dstat.st_mode); + /* } else if (sort_opts == SORT_VERSION) { */ + /* } else if (sort_opts == SORT_EXT) { */ + } + + if (dif == 0) { + /* sort by name - may be a tie_breaker for time or size cmp */ + if (ENABLE_LOCALE_SUPPORT) dif = strcoll(d1->name, d2->name); + else dif = strcmp(d1->name, d2->name); + } + + if (all_fmt & SORT_REVERSE) { + dif = -dif; + } + return dif; +} + +static void dnsort(struct dnode **dn, int size) +{ + qsort(dn, size, sizeof(*dn), sortcmp); +} +#else +#define dnsort(dn, size) do {} while(0) +#endif + + +static void showfiles(struct dnode **dn, int nfiles) +{ + int i, ncols, nrows, row, nc; + int column = 0; + int nexttab = 0; + int column_width = 0; /* for STYLE_LONG and STYLE_SINGLE not used */ + + if (dn == NULL || nfiles < 1) + return; + + if (all_fmt & STYLE_LONG) { + ncols = 1; + } else { + /* find the longest file name, use that as the column width */ + for (i = 0; i < nfiles; i++) { + int len = strlen(dn[i]->name); + if (column_width < len) + column_width = len; + } + column_width += tabstops + + USE_SELINUX( ((all_fmt & LIST_CONTEXT) ? 33 : 0) + ) + ((all_fmt & LIST_INO) ? 8 : 0) + + ((all_fmt & LIST_BLOCKS) ? 5 : 0); + ncols = (int) (terminal_width / column_width); + } + + if (ncols > 1) { + nrows = nfiles / ncols; + if (nrows * ncols < nfiles) + nrows++; /* round up fractionals */ + } else { + nrows = nfiles; + ncols = 1; + } + + for (row = 0; row < nrows; row++) { + for (nc = 0; nc < ncols; nc++) { + /* reach into the array based on the column and row */ + i = (nc * nrows) + row; /* assume display by column */ + if (all_fmt & DISP_ROWS) + i = (row * ncols) + nc; /* display across row */ + if (i < nfiles) { + if (column > 0) { + nexttab -= column; + printf("%*s", nexttab, ""); + column += nexttab; + } + nexttab = column + column_width; + column += list_single(dn[i]); + } + } + putchar('\n'); + column = 0; + } +} + + +static void showdirs(struct dnode **dn, int ndirs, int first) +{ + int i, nfiles; + struct dnode **subdnp; + int dndirs; + struct dnode **dnd; + + if (dn == NULL || ndirs < 1) + return; + + for (i = 0; i < ndirs; i++) { + if (all_fmt & (DISP_DIRNAME | DISP_RECURSIVE)) { + if (!first) + puts(""); + first = 0; + printf("%s:\n", dn[i]->fullname); + } + subdnp = list_dir(dn[i]->fullname); + nfiles = countfiles(subdnp); + if (nfiles > 0) { + /* list all files at this level */ + dnsort(subdnp, nfiles); + showfiles(subdnp, nfiles); + if (ENABLE_FEATURE_LS_RECURSIVE) { + if (all_fmt & DISP_RECURSIVE) { + /* recursive- list the sub-dirs */ + dnd = splitdnarray(subdnp, nfiles, SPLIT_SUBDIR); + dndirs = countsubdirs(subdnp, nfiles); + if (dndirs > 0) { + dnsort(dnd, dndirs); + showdirs(dnd, dndirs, 0); + /* free the array of dnode pointers to the dirs */ + free(dnd); + } + } + /* free the dnodes and the fullname mem */ + dfree(subdnp, nfiles); + } + } + } +} + + +static struct dnode **list_dir(const char *path) +{ + struct dnode *dn, *cur, **dnp; + struct dirent *entry; + DIR *dir; + int i, nfiles; + + if (path == NULL) + return NULL; + + dn = NULL; + nfiles = 0; + dir = warn_opendir(path); + if (dir == NULL) { + status = EXIT_FAILURE; + return NULL; /* could not open the dir */ + } + while ((entry = readdir(dir)) != NULL) { + char *fullname; + + /* are we going to list the file- it may be . or .. or a hidden file */ + if (entry->d_name[0] == '.') { + if ((entry->d_name[1] == 0 || ( + entry->d_name[1] == '.' + && entry->d_name[2] == 0)) + && !(all_fmt & DISP_DOT)) + continue; + if (!(all_fmt & DISP_HIDDEN)) + continue; + } + fullname = concat_path_file(path, entry->d_name); + cur = my_stat(fullname, strrchr(fullname, '/') + 1); + if (!cur) { + free(fullname); + continue; + } + cur->allocated = 1; + cur->next = dn; + dn = cur; + nfiles++; + } + closedir(dir); + + /* now that we know how many files there are + * allocate memory for an array to hold dnode pointers + */ + if (dn == NULL) + return NULL; + dnp = dnalloc(nfiles); + for (i = 0, cur = dn; i < nfiles; i++) { + dnp[i] = cur; /* save pointer to node in array */ + cur = cur->next; + } + + return dnp; +} + + +static int list_single(struct dnode *dn) +{ + int i, column = 0; + +#if ENABLE_FEATURE_LS_USERNAME + char scratch[16]; +#endif +#if ENABLE_FEATURE_LS_TIMESTAMPS + char *filetime; + time_t ttime, age; +#endif +#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR + struct stat info; + char append; +#endif + + if (dn->fullname == NULL) + return 0; + +#if ENABLE_FEATURE_LS_TIMESTAMPS + ttime = dn->dstat.st_mtime; /* the default time */ + if (all_fmt & TIME_ACCESS) + ttime = dn->dstat.st_atime; + if (all_fmt & TIME_CHANGE) + ttime = dn->dstat.st_ctime; + filetime = ctime(&ttime); +#endif +#if ENABLE_FEATURE_LS_FILETYPES + append = append_char(dn->dstat.st_mode); +#endif + + for (i = 0; i <= 31; i++) { + switch (all_fmt & (1 << i)) { + case LIST_INO: + column += printf("%7ld ", (long) dn->dstat.st_ino); + break; + case LIST_BLOCKS: + column += printf("%4"OFF_FMT"d ", (off_t) dn->dstat.st_blocks >> 1); + break; + case LIST_MODEBITS: + column += printf("%-10s ", (char *) bb_mode_string(dn->dstat.st_mode)); + break; + case LIST_NLINKS: + column += printf("%4ld ", (long) dn->dstat.st_nlink); + break; + case LIST_ID_NAME: +#if ENABLE_FEATURE_LS_USERNAME + bb_getpwuid(scratch, dn->dstat.st_uid, sizeof(scratch)); + printf("%-8.8s ", scratch); + bb_getgrgid(scratch, dn->dstat.st_gid, sizeof(scratch)); + printf("%-8.8s", scratch); + column += 17; + break; +#endif + case LIST_ID_NUMERIC: + column += printf("%-8d %-8d", dn->dstat.st_uid, dn->dstat.st_gid); + break; + case LIST_SIZE: + case LIST_DEV: + if (S_ISBLK(dn->dstat.st_mode) || S_ISCHR(dn->dstat.st_mode)) { + column += printf("%4d, %3d ", (int) major(dn->dstat.st_rdev), + (int) minor(dn->dstat.st_rdev)); + } else { + if (all_fmt & LS_DISP_HR) { + column += printf("%9s ", + make_human_readable_str(dn->dstat.st_size, 1, 0)); + } else { + column += printf("%9"OFF_FMT"d ", (off_t) dn->dstat.st_size); + } + } + break; +#if ENABLE_FEATURE_LS_TIMESTAMPS + case LIST_FULLTIME: + printf("%24.24s ", filetime); + column += 25; + break; + case LIST_DATE_TIME: + if ((all_fmt & LIST_FULLTIME) == 0) { + age = time(NULL) - ttime; + printf("%6.6s ", filetime + 4); + if (age < 3600L * 24 * 365 / 2 && age > -15 * 60) { + /* hh:mm if less than 6 months old */ + printf("%5.5s ", filetime + 11); + } else { + printf(" %4.4s ", filetime + 20); + } + column += 13; + } + break; +#endif +#if ENABLE_SELINUX + case LIST_CONTEXT: + { + char context[80]; + int len = 0; + + if (dn->sid) { + /* I assume sid initilized with NULL */ + len = strlen(dn->sid) + 1; + safe_strncpy(context, dn->sid, len); + freecon(dn->sid); + } else { + safe_strncpy(context, "unknown", 8); + } + printf("%-32s ", context); + column += MAX(33, len); + } + break; +#endif + case LIST_FILENAME: + errno = 0; +#if ENABLE_FEATURE_LS_COLOR + if (show_color && !lstat(dn->fullname, &info)) { + printf("\033[%d;%dm", bgcolor(info.st_mode), + fgcolor(info.st_mode)); + } +#endif + column += printf("%s", dn->name); + if (show_color) { + printf("\033[0m"); + } + break; + case LIST_SYMLINK: + if (S_ISLNK(dn->dstat.st_mode)) { + char *lpath = xreadlink(dn->fullname); + if (!lpath) break; + printf(" -> "); +#if ENABLE_FEATURE_LS_FILETYPES || ENABLE_FEATURE_LS_COLOR + if (!stat(dn->fullname, &info)) { + append = append_char(info.st_mode); + } +#endif +#if ENABLE_FEATURE_LS_COLOR + if (show_color) { + errno = 0; + printf("\033[%d;%dm", bgcolor(info.st_mode), + fgcolor(info.st_mode)); + } +#endif + column += printf("%s", lpath) + 4; + if (show_color) { + printf("\033[0m"); + } + free(lpath); + } + break; +#if ENABLE_FEATURE_LS_FILETYPES + case LIST_FILETYPE: + if (append) { + putchar(append); + column++; + } + break; +#endif + } + } + + return column; +} + +/* "[-]Cadil1", POSIX mandated options, busybox always supports */ +/* "[-]gnsx", POSIX non-mandated options, busybox always supports */ +/* "[-]Ak" GNU options, busybox always supports */ +/* "[-]FLRctur", POSIX mandated options, busybox optionally supports */ +/* "[-]p", POSIX non-mandated options, busybox optionally supports */ +/* "[-]SXvThw", GNU options, busybox optionally supports */ +/* "[-]K", SELinux mandated options, busybox optionally supports */ +/* "[-]e", I think we made this one up */ +static const char ls_options[] = "Cadil1gnsxAk" + USE_FEATURE_LS_TIMESTAMPS("cetu") + USE_FEATURE_LS_SORTFILES("SXrv") + USE_FEATURE_LS_FILETYPES("Fp") + USE_FEATURE_LS_FOLLOWLINKS("L") + USE_FEATURE_LS_RECURSIVE("R") + USE_FEATURE_HUMAN_READABLE("h") + USE_SELINUX("K") + USE_FEATURE_AUTOWIDTH("T:w:"); + +enum { + LIST_MASK_TRIGGER = 0, + STYLE_MASK_TRIGGER = STYLE_MASK, + DISP_MASK_TRIGGER = DISP_ROWS, + SORT_MASK_TRIGGER = SORT_MASK, +}; + +static const unsigned opt_flags[] = { + LIST_SHORT | STYLE_COLUMNS, /* C */ + DISP_HIDDEN | DISP_DOT, /* a */ + DISP_NOLIST, /* d */ + LIST_INO, /* i */ + LIST_LONG | STYLE_LONG, /* l - remember LS_DISP_HR in mask! */ + LIST_SHORT | STYLE_SINGLE, /* 1 */ + 0, /* g - ingored */ + LIST_ID_NUMERIC, /* n */ + LIST_BLOCKS, /* s */ + DISP_ROWS, /* x */ + DISP_HIDDEN, /* A */ + ENABLE_SELINUX * LIST_CONTEXT, /* k (ignored if !SELINUX) */ +#if ENABLE_FEATURE_LS_TIMESTAMPS + TIME_CHANGE | (ENABLE_FEATURE_LS_SORTFILES * SORT_CTIME), /* c */ + LIST_FULLTIME, /* e */ + ENABLE_FEATURE_LS_SORTFILES * SORT_MTIME, /* t */ + TIME_ACCESS | (ENABLE_FEATURE_LS_SORTFILES * SORT_ATIME), /* u */ +#endif +#if ENABLE_FEATURE_LS_SORTFILES + SORT_SIZE, /* S */ + SORT_EXT, /* X */ + SORT_REVERSE, /* r */ + SORT_VERSION, /* v */ +#endif +#if ENABLE_FEATURE_LS_FILETYPES + LIST_FILETYPE | LIST_EXEC, /* F */ + LIST_FILETYPE, /* p */ +#endif +#if ENABLE_FEATURE_LS_FOLLOWLINKS + FOLLOW_LINKS, /* L */ +#endif +#if ENABLE_FEATURE_LS_RECURSIVE + DISP_RECURSIVE, /* R */ +#endif +#if ENABLE_FEATURE_HUMAN_READABLE + LS_DISP_HR, /* h */ +#endif +#if ENABLE_SELINUX + LIST_MODEBITS|LIST_NLINKS|LIST_CONTEXT|LIST_SIZE|LIST_DATE_TIME, /* K */ +#endif +#if ENABLE_FEATURE_AUTOWIDTH + 0, 0, /* T, w - ignored */ +#endif + (1U<<31) +}; + + +int ls_main(int argc, char **argv) +{ + struct dnode **dnd; + struct dnode **dnf; + struct dnode **dnp; + struct dnode *dn; + struct dnode *cur; + unsigned opt; + int nfiles = 0; + int dnfiles; + int dndirs; + int oi; + int ac; + int i; + char **av; + USE_FEATURE_AUTOWIDTH(char *tabstops_str = NULL;) + USE_FEATURE_AUTOWIDTH(char *terminal_width_str = NULL;) + USE_FEATURE_LS_COLOR(char *color_opt;) + + all_fmt = LIST_SHORT | + (ENABLE_FEATURE_LS_SORTFILES * (SORT_NAME | SORT_FORWARD)); + +#if ENABLE_FEATURE_AUTOWIDTH + /* Obtain the terminal width */ + get_terminal_width_height(STDOUT_FILENO, &terminal_width, NULL); + /* Go one less... */ + terminal_width--; +#endif + + /* process options */ + USE_FEATURE_LS_COLOR(applet_long_options = ls_color_opt;) +#if ENABLE_FEATURE_AUTOWIDTH + opt = getopt32(argc, argv, ls_options, &tabstops_str, &terminal_width_str + USE_FEATURE_LS_COLOR(, &color_opt)); + if (tabstops_str) + tabstops = xatou(tabstops_str); + if (terminal_width_str) + terminal_width = xatou(terminal_width_str); +#else + opt = getopt32(argc, argv, ls_options USE_FEATURE_LS_COLOR(, &color_opt)); +#endif + for (i = 0; opt_flags[i] != (1U<<31); i++) { + if (opt & (1 << i)) { + unsigned flags = opt_flags[i]; + + if (flags & LIST_MASK_TRIGGER) + all_fmt &= ~LIST_MASK; + if (flags & STYLE_MASK_TRIGGER) + all_fmt &= ~STYLE_MASK; + if (flags & SORT_MASK_TRIGGER) + all_fmt &= ~SORT_MASK; + if (flags & DISP_MASK_TRIGGER) + all_fmt &= ~DISP_MASK; + if (flags & TIME_MASK) + all_fmt &= ~TIME_MASK; + if (flags & LIST_CONTEXT) + all_fmt |= STYLE_SINGLE; + if (LS_DISP_HR && opt == 'l') + all_fmt &= ~LS_DISP_HR; + all_fmt |= flags; + } + } + +#if ENABLE_FEATURE_LS_COLOR + /* find color bit value - last position for short getopt */ + if (ENABLE_FEATURE_LS_COLOR_IS_DEFAULT && isatty(STDOUT_FILENO)) { + char *p = getenv("LS_COLORS"); + /* LS_COLORS is unset, or (not empty && not "none") ? */ + if (!p || (p[0] && strcmp(p, "none"))) + show_color = 1; + } + if (opt & (1 << i)) { /* next flag after short options */ + if (!color_opt || !strcmp("always", color_opt)) + show_color = 1; + else if (color_opt && !strcmp("never", color_opt)) + show_color = 0; + else if (color_opt && !strcmp("auto", color_opt) && isatty(STDOUT_FILENO)) + show_color = 1; + } +#endif + + /* sort out which command line options take precedence */ + if (ENABLE_FEATURE_LS_RECURSIVE && (all_fmt & DISP_NOLIST)) + all_fmt &= ~DISP_RECURSIVE; /* no recurse if listing only dir */ + if (ENABLE_FEATURE_LS_TIMESTAMPS && ENABLE_FEATURE_LS_SORTFILES) { + if (all_fmt & TIME_CHANGE) + all_fmt = (all_fmt & ~SORT_MASK) | SORT_CTIME; + if (all_fmt & TIME_ACCESS) + all_fmt = (all_fmt & ~SORT_MASK) | SORT_ATIME; + } + if ((all_fmt & STYLE_MASK) != STYLE_LONG) /* only for long list */ + all_fmt &= ~(LIST_ID_NUMERIC|LIST_FULLTIME|LIST_ID_NAME|LIST_ID_NUMERIC); + if (ENABLE_FEATURE_LS_USERNAME) + if ((all_fmt & STYLE_MASK) == STYLE_LONG && (all_fmt & LIST_ID_NUMERIC)) + all_fmt &= ~LIST_ID_NAME; /* don't list names if numeric uid */ + + /* choose a display format */ + if (!(all_fmt & STYLE_MASK)) + all_fmt |= (isatty(STDOUT_FILENO) ? STYLE_COLUMNS : STYLE_SINGLE); + + /* + * when there are no cmd line args we have to supply a default "." arg. + * we will create a second argv array, "av" that will hold either + * our created "." arg, or the real cmd line args. The av array + * just holds the pointers- we don't move the date the pointers + * point to. + */ + ac = argc - optind; /* how many cmd line args are left */ + if (ac < 1) { + static const char *const dotdir[] = { "." }; + + av = (char **) dotdir; + ac = 1; + } else { + av = argv + optind; + } + + /* now, everything is in the av array */ + if (ac > 1) + all_fmt |= DISP_DIRNAME; /* 2 or more items? label directories */ + + /* stuff the command line file names into a dnode array */ + dn = NULL; + for (oi = 0; oi < ac; oi++) { + cur = my_stat(av[oi], av[oi]); + if (!cur) + continue; + cur->allocated = 0; + cur->next = dn; + dn = cur; + nfiles++; + } + + /* now that we know how many files there are + * allocate memory for an array to hold dnode pointers + */ + dnp = dnalloc(nfiles); + for (i = 0, cur = dn; i < nfiles; i++) { + dnp[i] = cur; /* save pointer to node in array */ + cur = cur->next; + } + + if (all_fmt & DISP_NOLIST) { + dnsort(dnp, nfiles); + if (nfiles > 0) + showfiles(dnp, nfiles); + } else { + dnd = splitdnarray(dnp, nfiles, SPLIT_DIR); + dnf = splitdnarray(dnp, nfiles, SPLIT_FILE); + dndirs = countdirs(dnp, nfiles); + dnfiles = nfiles - dndirs; + if (dnfiles > 0) { + dnsort(dnf, dnfiles); + showfiles(dnf, dnfiles); + if (ENABLE_FEATURE_CLEAN_UP) + free(dnf); + } + if (dndirs > 0) { + dnsort(dnd, dndirs); + showdirs(dnd, dndirs, dnfiles == 0); + if (ENABLE_FEATURE_CLEAN_UP) + free(dnd); + } + } + if (ENABLE_FEATURE_CLEAN_UP) + dfree(dnp, nfiles); + return status; +} diff --git a/coreutils/md5_sha1_sum.c b/coreutils/md5_sha1_sum.c new file mode 100644 index 000000000..e8d3a1509 --- /dev/null +++ b/coreutils/md5_sha1_sum.c @@ -0,0 +1,186 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 2003 Glenn L. McGrath + * Copyright (C) 2003-2004 Erik Andersen + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" + +typedef enum { HASH_SHA1, HASH_MD5 } hash_algo_t; + +#define FLAG_SILENT 1 +#define FLAG_CHECK 2 +#define FLAG_WARN 4 + +/* This might be useful elsewhere */ +static unsigned char *hash_bin_to_hex(unsigned char *hash_value, + unsigned hash_length) +{ + int len = 0; + char *hex_value = xmalloc((hash_length * 2) + 2); + while (hash_length--) { + len += sprintf(hex_value + len, "%02x", *hash_value++); + } + return hex_value; +} + +static uint8_t *hash_file(const char *filename, hash_algo_t hash_algo) +{ + int src_fd, hash_len, count; + union _ctx_ { + sha1_ctx_t sha1; + md5_ctx_t md5; + } context; + uint8_t *hash_value = NULL; + RESERVE_CONFIG_UBUFFER(in_buf, 4096); + void (*update)(const void*, size_t, void*); + void (*final)(void*, void*); + + src_fd = STDIN_FILENO; + if (filename[0] != '-' || filename[1]) { /* not "-" */ + src_fd = open(filename, O_RDONLY); + if (src_fd < 0) { + bb_perror_msg("%s", filename); + return NULL; + } + } + + /* figure specific hash algorithims */ + if (ENABLE_MD5SUM && hash_algo==HASH_MD5) { + md5_begin(&context.md5); + update = (void (*)(const void*, size_t, void*))md5_hash; + final = (void (*)(void*, void*))md5_end; + hash_len = 16; + } else if (ENABLE_SHA1SUM && hash_algo==HASH_SHA1) { + sha1_begin(&context.sha1); + update = (void (*)(const void*, size_t, void*))sha1_hash; + final = (void (*)(void*, void*))sha1_end; + hash_len = 20; + } else { + bb_error_msg_and_die("algorithm not supported"); + } + + while (0 < (count = safe_read(src_fd, in_buf, 4096))) { + update(in_buf, count, &context); + } + + if (count == 0) { + final(in_buf, &context); + hash_value = hash_bin_to_hex(in_buf, hash_len); + } + + RELEASE_CONFIG_BUFFER(in_buf); + + if (src_fd != STDIN_FILENO) { + close(src_fd); + } + + return hash_value; +} + +int md5_sha1_sum_main(int argc, char **argv) +{ + int return_value = EXIT_SUCCESS; + uint8_t *hash_value; + unsigned flags; + hash_algo_t hash_algo = ENABLE_MD5SUM + ? (ENABLE_SHA1SUM ? (**argv=='m' ? HASH_MD5 : HASH_SHA1) : HASH_MD5) + : HASH_SHA1; + + if (ENABLE_FEATURE_MD5_SHA1_SUM_CHECK) + flags = getopt32(argc, argv, "scw"); + else optind = 1; + + if (ENABLE_FEATURE_MD5_SHA1_SUM_CHECK && !(flags & FLAG_CHECK)) { + if (flags & FLAG_SILENT) { + bb_error_msg_and_die + ("-%c is meaningful only when verifying checksums", 's'); + } else if (flags & FLAG_WARN) { + bb_error_msg_and_die + ("-%c is meaningful only when verifying checksums", 'w'); + } + } + + if (argc == optind) { + argv[argc++] = "-"; + } + + if (ENABLE_FEATURE_MD5_SHA1_SUM_CHECK && (flags & FLAG_CHECK)) { + FILE *pre_computed_stream; + int count_total = 0; + int count_failed = 0; + char *file_ptr = argv[optind]; + char *line; + + if (optind + 1 != argc) { + bb_error_msg_and_die + ("only one argument may be specified when using -c"); + } + + pre_computed_stream = stdin; + if (file_ptr[0] != '-' || file_ptr[1]) { /* not "-" */ + pre_computed_stream = xfopen(file_ptr, "r"); + } + + while ((line = xmalloc_getline(pre_computed_stream)) != NULL) { + char *filename_ptr; + + count_total++; + filename_ptr = strstr(line, " "); + /* handle format for binary checksums */ + if (filename_ptr == NULL) { + filename_ptr = strstr(line, " *"); + } + if (filename_ptr == NULL) { + if (flags & FLAG_WARN) { + bb_error_msg("invalid format"); + } + count_failed++; + return_value = EXIT_FAILURE; + free(line); + continue; + } + *filename_ptr = '\0'; + filename_ptr += 2; + + hash_value = hash_file(filename_ptr, hash_algo); + + if (hash_value && (strcmp((char*)hash_value, line) == 0)) { + if (!(flags & FLAG_SILENT)) + printf("%s: OK\n", filename_ptr); + } else { + if (!(flags & FLAG_SILENT)) + printf("%s: FAILED\n", filename_ptr); + count_failed++; + return_value = EXIT_FAILURE; + } + /* possible free(NULL) */ + free(hash_value); + free(line); + } + if (count_failed && !(flags & FLAG_SILENT)) { + bb_error_msg("WARNING: %d of %d computed checksums did NOT match", + count_failed, count_total); + } + /* + if (fclose_if_not_stdin(pre_computed_stream) == EOF) { + bb_perror_msg_and_die("cannot close file %s", file_ptr); + } + */ + } else { + while (optind < argc) { + char *file_ptr = argv[optind++]; + + hash_value = hash_file(file_ptr, hash_algo); + if (hash_value == NULL) { + return_value = EXIT_FAILURE; + } else { + printf("%s %s\n", hash_value, file_ptr); + free(hash_value); + } + } + } + return return_value; +} diff --git a/coreutils/mkdir.c b/coreutils/mkdir.c new file mode 100644 index 000000000..2cc9c7a4c --- /dev/null +++ b/coreutils/mkdir.c @@ -0,0 +1,66 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini mkdir implementation for busybox + * + * Copyright (C) 2001 Matt Kraai + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/mkdir.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Fixed broken permission setting when -p was used; especially in + * conjunction with -m. + */ + +#include +#include +#include /* struct option */ +#include "busybox.h" + +#if ENABLE_FEATURE_MKDIR_LONG_OPTIONS +static const struct option mkdir_long_options[] = { + { "mode", 1, NULL, 'm' }, + { "parents", 0, NULL, 'p' }, + { 0, 0, 0, 0 } +}; +#endif + +int mkdir_main(int argc, char **argv) +{ + mode_t mode = (mode_t)(-1); + int status = EXIT_SUCCESS; + int flags = 0; + unsigned opt; + char *smode; + +#if ENABLE_FEATURE_MKDIR_LONG_OPTIONS + applet_long_options = mkdir_long_options; +#endif + opt = getopt32(argc, argv, "m:p", &smode); + if (opt & 1) { + mode = 0777; + if (!bb_parse_mode(smode, &mode)) { + bb_error_msg_and_die("invalid mode '%s'", smode); + } + } + if (opt & 2) + flags |= FILEUTILS_RECUR; + + if (optind == argc) { + bb_show_usage(); + } + + argv += optind; + + do { + if (bb_make_directory(*argv, mode, flags)) { + status = EXIT_FAILURE; + } + } while (*++argv); + + return status; +} diff --git a/coreutils/mkfifo.c b/coreutils/mkfifo.c new file mode 100644 index 000000000..24d27e7c0 --- /dev/null +++ b/coreutils/mkfifo.c @@ -0,0 +1,38 @@ +/* vi: set sw=4 ts=4: */ +/* + * mkfifo implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/mkfifo.html */ + +#include +#include +#include +#include "busybox.h" +#include "libcoreutils/coreutils.h" + +int mkfifo_main(int argc, char **argv) +{ + mode_t mode; + int retval = EXIT_SUCCESS; + + mode = getopt_mk_fifo_nod(argc, argv); + + if (!*(argv += optind)) { + bb_show_usage(); + } + + do { + if (mkfifo(*argv, mode) < 0) { + bb_perror_msg("%s", *argv); /* Avoid multibyte problems. */ + retval = EXIT_FAILURE; + } + } while (*++argv); + + return retval; +} diff --git a/coreutils/mknod.c b/coreutils/mknod.c new file mode 100644 index 000000000..8ca511cd5 --- /dev/null +++ b/coreutils/mknod.c @@ -0,0 +1,50 @@ +/* vi: set sw=4 ts=4: */ +/* + * mknod implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 N/A -- Matches GNU behavior. */ + +#include // For makedev + +#include "busybox.h" +#include "libcoreutils/coreutils.h" + +static const char modes_chars[] = { 'p', 'c', 'u', 'b', 0, 1, 1, 2 }; +static const mode_t modes_cubp[] = { S_IFIFO, S_IFCHR, S_IFBLK }; + +int mknod_main(int argc, char **argv) +{ + mode_t mode; + dev_t dev; + const char *name; + + mode = getopt_mk_fifo_nod(argc, argv); + argv += optind; + argc -= optind; + + if ((argc >= 2) && ((name = strchr(modes_chars, argv[1][0])) != NULL)) { + mode |= modes_cubp[(int)(name[4])]; + + dev = 0; + if ((*name != 'p') && ((argc -= 2) == 2)) { + /* Autodetect what the system supports; these macros should + * optimize out to two constants. */ + dev = makedev(xatoul_range(argv[2], 0, major(UINT_MAX)), + xatoul_range(argv[3], 0, minor(UINT_MAX))); + } + + if (argc == 2) { + name = *argv; + if (mknod(name, mode, dev) == 0) { + return EXIT_SUCCESS; + } + bb_perror_msg_and_die("%s", name); + } + } + bb_show_usage(); +} diff --git a/coreutils/mv.c b/coreutils/mv.c new file mode 100644 index 000000000..353124b35 --- /dev/null +++ b/coreutils/mv.c @@ -0,0 +1,131 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini mv implementation for busybox + * + * Copyright (C) 2000 by Matt Kraai + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Size reduction and improved error checking. + */ + +#include +#include +#include +#include +#include +#include +#include /* struct option */ +#include "busybox.h" +#include "libcoreutils/coreutils.h" + +#if ENABLE_FEATURE_MV_LONG_OPTIONS +static const struct option mv_long_options[] = { + { "interactive", 0, NULL, 'i' }, + { "force", 0, NULL, 'f' }, + { 0, 0, 0, 0 } +}; +#endif + +#define OPT_FILEUTILS_FORCE 1 +#define OPT_FILEUTILS_INTERACTIVE 2 + +static const char fmt[] = "cannot overwrite %sdirectory with %sdirectory"; + +int mv_main(int argc, char **argv) +{ + struct stat dest_stat; + const char *last; + const char *dest; + unsigned long flags; + int dest_exists; + int status = 0; + +#if ENABLE_FEATURE_MV_LONG_OPTIONS + applet_long_options = mv_long_options; +#endif + opt_complementary = "f-i:i-f"; + flags = getopt32(argc, argv, "fi"); + if (optind + 2 > argc) { + bb_show_usage(); + } + + last = argv[argc - 1]; + argv += optind; + + if (optind + 2 == argc) { + dest_exists = cp_mv_stat(last, &dest_stat); + if (dest_exists < 0) { + return 1; + } + + if (!(dest_exists & 2)) { + dest = last; + goto DO_MOVE; + } + } + + do { + dest = concat_path_file(last, bb_get_last_path_component(*argv)); + dest_exists = cp_mv_stat(dest, &dest_stat); + if (dest_exists < 0) { + goto RET_1; + } + +DO_MOVE: + + if (dest_exists && !(flags & OPT_FILEUTILS_FORCE) && + ((access(dest, W_OK) < 0 && isatty(0)) || + (flags & OPT_FILEUTILS_INTERACTIVE))) { + if (fprintf(stderr, "mv: overwrite '%s'? ", dest) < 0) { + goto RET_1; /* Ouch! fprintf failed! */ + } + if (!bb_ask_confirmation()) { + goto RET_0; + } + } + if (rename(*argv, dest) < 0) { + struct stat source_stat; + int source_exists; + + if (errno != EXDEV || + (source_exists = cp_mv_stat(*argv, &source_stat)) < 1) { + bb_perror_msg("cannot rename '%s'", *argv); + } else { + if (dest_exists) { + if (dest_exists == 3) { + if (source_exists != 3) { + bb_error_msg(fmt, "", "non-"); + goto RET_1; + } + } else { + if (source_exists == 3) { + bb_error_msg(fmt, "non-", ""); + goto RET_1; + } + } + if (unlink(dest) < 0) { + bb_perror_msg("cannot remove '%s'", dest); + goto RET_1; + } + } + if ((copy_file(*argv, dest, + FILEUTILS_RECUR | FILEUTILS_PRESERVE_STATUS) >= 0) && + (remove_file(*argv, FILEUTILS_RECUR | FILEUTILS_FORCE) >= 0)) { + goto RET_0; + } + } +RET_1: + status = 1; + } +RET_0: + if (dest != last) { + free((void *) dest); + } + } while (*++argv != last); + + return status; +} diff --git a/coreutils/nice.c b/coreutils/nice.c new file mode 100644 index 000000000..293861842 --- /dev/null +++ b/coreutils/nice.c @@ -0,0 +1,54 @@ +/* vi: set sw=4 ts=4: */ +/* + * nice implementation for busybox + * + * Copyright (C) 2005 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "busybox.h" + +int nice_main(int argc, char **argv) +{ + int old_priority, adjustment; + + old_priority = getpriority(PRIO_PROCESS, 0); + + if (!*++argv) { /* No args, so (GNU) output current nice value. */ + printf("%d\n", old_priority); + fflush_stdout_and_exit(EXIT_SUCCESS); + } + + adjustment = 10; /* Set default adjustment. */ + + if (argv[0][0] == '-') { + if (argv[0][1] == 'n') { /* -n */ + if (argv[0][2]) { /* -nNNNN (w/o space) */ + argv[0] += 2; argv--; argc++; + } + } else { /* -NNN (NNN may be negative) == -n NNN */ + argv[0] += 1; argv--; argc++; + } + if (argc < 4) { /* Missing priority and/or utility! */ + bb_show_usage(); + } + adjustment = xatoi_range(argv[1], INT_MIN/2, INT_MAX/2); + argv += 2; + } + + { /* Set our priority. */ + int prio = old_priority + adjustment; + + if (setpriority(PRIO_PROCESS, 0, prio) < 0) { + bb_perror_msg_and_die("setpriority(%d)", prio); + } + } + + execvp(*argv, argv); /* Now exec the desired program. */ + + /* The exec failed... */ + xfunc_error_retval = (errno == ENOENT) ? 127 : 126; /* SUSv3 */ + bb_perror_msg_and_die("%s", *argv); +} diff --git a/coreutils/nohup.c b/coreutils/nohup.c new file mode 100644 index 000000000..21adfc1c3 --- /dev/null +++ b/coreutils/nohup.c @@ -0,0 +1,53 @@ +/* vi: set sw=4 ts=4: */ +/* nohup - invoke a utility immune to hangups. + * + * Busybox version based on nohup specification at + * http://www.opengroup.org/onlinepubs/007904975/utilities/nohup.html + * + * Copyright 2006 Rob Landley + * Copyright 2006 Bernhard Fischer + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +int nohup_main(int argc, char **argv) +{ + int temp, nullfd; + char *nohupout, *home = NULL; + + xfunc_error_retval = 127; + + if (argc<2) bb_show_usage(); + + nullfd = xopen(bb_dev_null, O_WRONLY|O_APPEND); + /* If stdin is a tty, detach from it. */ + if (isatty(STDIN_FILENO)) dup2(nullfd, STDIN_FILENO); + + nohupout = "nohup.out"; + /* Redirect stdout to nohup.out, either in "." or in "$HOME". */ + if (isatty(STDOUT_FILENO)) { + close(STDOUT_FILENO); + if (open(nohupout, O_CREAT|O_WRONLY|O_APPEND, S_IRUSR|S_IWUSR) < 0) { + home = getenv("HOME"); + if (home) { + nohupout = concat_path_file(home, nohupout); + xopen3(nohupout, O_CREAT|O_WRONLY|O_APPEND, S_IRUSR|S_IWUSR); + } + } + } else dup2(nullfd, STDOUT_FILENO); + + /* If we have a tty on strderr, announce filename and redirect to stdout. + * Else redirect to /dev/null. + */ + temp = isatty(STDERR_FILENO); + if (temp) bb_error_msg("appending to %s", nohupout); + dup2(temp ? STDOUT_FILENO : nullfd, STDERR_FILENO); + close(nullfd); + signal (SIGHUP, SIG_IGN); + + execvp(argv[1],argv+1); + if (00 && ENABLE_FEATURE_CLEAN_UP && home) free(nohupout); + bb_perror_msg_and_die("%s", argv[1]); +} diff --git a/coreutils/od.c b/coreutils/od.c new file mode 100644 index 000000000..8de866281 --- /dev/null +++ b/coreutils/od.c @@ -0,0 +1,224 @@ +/* vi: set sw=4 ts=4: */ +/* + * od implementation for busybox + * Based on code from util-linux v 2.11l + * + * Copyright (c) 1990 + * The Regents of the University of California. All rights reserved. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Original copyright notice is retained at the end of this file. + */ + + +#if ENABLE_DESKTOP +/* This one provides -t (busybox's own build script needs it) */ +#include "od_bloaty.c" +#else + +#include +#include "busybox.h" +#include "dump.h" + +#define isdecdigit(c) isdigit(c) +#define ishexdigit(c) (isxdigit)(c) + +static void +odoffset(int argc, char ***argvp) +{ + char *num, *p; + int base; + char *end; + + /* + * The offset syntax of od(1) was genuinely bizarre. First, if + * it started with a plus it had to be an offset. Otherwise, if + * there were at least two arguments, a number or lower-case 'x' + * followed by a number makes it an offset. By default it was + * octal; if it started with 'x' or '0x' it was hex. If it ended + * in a '.', it was decimal. If a 'b' or 'B' was appended, it + * multiplied the number by 512 or 1024 byte units. There was + * no way to assign a block count to a hex offset. + * + * We assumes it's a file if the offset is bad. + */ + p = **argvp; + + if (!p) { + /* hey someone is probably piping to us ... */ + return; + } + + if ((*p != '+') + && (argc < 2 + || (!isdecdigit(p[0]) + && ((p[0] != 'x') || !ishexdigit(p[1]))))) + return; + + base = 0; + /* + * bb_dump_skip over leading '+', 'x[0-9a-fA-f]' or '0x', and + * set base. + */ + if (p[0] == '+') + ++p; + if (p[0] == 'x' && ishexdigit(p[1])) { + ++p; + base = 16; + } else if (p[0] == '0' && p[1] == 'x') { + p += 2; + base = 16; + } + + /* bb_dump_skip over the number */ + if (base == 16) + for (num = p; ishexdigit(*p); ++p); + else + for (num = p; isdecdigit(*p); ++p); + + /* check for no number */ + if (num == p) + return; + + /* if terminates with a '.', base is decimal */ + if (*p == '.') { + if (base) + return; + base = 10; + } + + bb_dump_skip = strtol(num, &end, base ? base : 8); + + /* if end isn't the same as p, we got a non-octal digit */ + if (end != p) + bb_dump_skip = 0; + else { + if (*p) { + if (*p == 'b') { + bb_dump_skip *= 512; + ++p; + } else if (*p == 'B') { + bb_dump_skip *= 1024; + ++p; + } + } + if (*p) + bb_dump_skip = 0; + else { + ++*argvp; + /* + * If the offset uses a non-octal base, the base of + * the offset is changed as well. This isn't pretty, + * but it's easy. + */ +#define TYPE_OFFSET 7 + { + char x_or_d; + if (base == 16) { + x_or_d = 'x'; + goto DO_X_OR_D; + } + if (base == 10) { + x_or_d = 'd'; + DO_X_OR_D: + bb_dump_fshead->nextfu->fmt[TYPE_OFFSET] + = bb_dump_fshead->nextfs->nextfu->fmt[TYPE_OFFSET] + = x_or_d; + } + } + } + } +} + +static const char * const add_strings[] = { + "16/1 \"%3_u \" \"\\n\"", /* a */ + "8/2 \" %06o \" \"\\n\"", /* B, o */ + "16/1 \"%03o \" \"\\n\"", /* b */ + "16/1 \"%3_c \" \"\\n\"", /* c */ + "8/2 \" %05u \" \"\\n\"", /* d */ + "4/4 \" %010u \" \"\\n\"", /* D */ + "2/8 \" %21.14e \" \"\\n\"", /* e (undocumented in od), F */ + "4/4 \" %14.7e \" \"\\n\"", /* f */ + "4/4 \" %08x \" \"\\n\"", /* H, X */ + "8/2 \" %04x \" \"\\n\"", /* h, x */ + "4/4 \" %11d \" \"\\n\"", /* I, L, l */ + "8/2 \" %6d \" \"\\n\"", /* i */ + "4/4 \" %011o \" \"\\n\"", /* O */ +}; + +static const char od_opts[] = "aBbcDdeFfHhIiLlOoXxv"; + +static const char od_o2si[] = { + 0, 1, 2, 3, 5, + 4, 6, 6, 7, 8, + 9, 0xa, 0xb, 0xa, 0xa, + 0xb, 1, 8, 9, +}; + +int od_main(int argc, char **argv) +{ + int ch; + int first = 1; + char *p; + bb_dump_vflag = FIRST; + bb_dump_length = -1; + + while ((ch = getopt(argc, argv, od_opts)) > 0) { + if (ch == 'v') { + bb_dump_vflag = ALL; + } else if (((p = strchr(od_opts, ch)) != NULL) && (*p != '\0')) { + if (first) { + first = 0; + bb_dump_add("\"%07.7_Ao\n\""); + bb_dump_add("\"%07.7_ao \""); + } else { + bb_dump_add("\" \""); + } + bb_dump_add(add_strings[(int)od_o2si[(p-od_opts)]]); + } else { /* P, p, s, w, or other unhandled */ + bb_show_usage(); + } + } + if (!bb_dump_fshead) { + bb_dump_add("\"%07.7_Ao\n\""); + bb_dump_add("\"%07.7_ao \" 8/2 \"%06o \" \"\\n\""); + } + + argc -= optind; + argv += optind; + + odoffset(argc, &argv); + + return bb_dump_dump(argv); +} +#endif /* ENABLE_DESKTOP */ + +/*- + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ diff --git a/coreutils/od_bloaty.c b/coreutils/od_bloaty.c new file mode 100644 index 000000000..9f1a582ef --- /dev/null +++ b/coreutils/od_bloaty.c @@ -0,0 +1,1466 @@ +/* od -- dump files in octal and other formats + Copyright (C) 92, 1995-2004 Free Software Foundation, Inc. + + 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +/* Written by Jim Meyering. */ + +/* Busyboxed by Denis Vlasenko + +Based on od.c from coreutils-5.2.1 +Top bloat sources: + +00000073 t parse_old_offset +0000007b t get_lcm +00000090 r long_options +00000092 t print_named_ascii +000000bf t print_ascii +00000168 t write_block +00000366 t decode_format_string +00000a71 T od_main + +Tested for compat with coreutils 6.3 +using this script. Minor differences fixed. + +#!/bin/sh +echo STD +time /path/to/coreutils/od \ +...params... \ +>std +echo Exit code $? +echo BBOX +time ./busybox od \ +...params... \ +>bbox +echo Exit code $? +diff -u -a std bbox >bbox.diff || { echo Different!; sleep 1; } + +*/ + + +#include "busybox.h" +#include + +#define assert(a) ((void)0) + +/* Check for 0x7f is a coreutils 6.3 addition */ +#define ISPRINT(c) (((c)>=' ') && (c) != 0x7f) + +typedef long double longdouble_t; +typedef unsigned long long ulonglong_t; +typedef long long llong; + +#if ENABLE_LFS +# define xstrtooff_sfx xstrtoull_sfx +#else +# define xstrtooff_sfx xstrtoul_sfx +#endif + +/* The default number of input bytes per output line. */ +#define DEFAULT_BYTES_PER_BLOCK 16 + +/* The number of decimal digits of precision in a float. */ +#ifndef FLT_DIG +# define FLT_DIG 7 +#endif + +/* The number of decimal digits of precision in a double. */ +#ifndef DBL_DIG +# define DBL_DIG 15 +#endif + +/* The number of decimal digits of precision in a long double. */ +#ifndef LDBL_DIG +# define LDBL_DIG DBL_DIG +#endif + +enum size_spec { + NO_SIZE, + CHAR, + SHORT, + INT, + LONG, + LONG_LONG, + FLOAT_SINGLE, + FLOAT_DOUBLE, + FLOAT_LONG_DOUBLE, + N_SIZE_SPECS +}; + +enum output_format { + SIGNED_DECIMAL, + UNSIGNED_DECIMAL, + OCTAL, + HEXADECIMAL, + FLOATING_POINT, + NAMED_CHARACTER, + CHARACTER +}; + +/* Each output format specification (from '-t spec' or from + old-style options) is represented by one of these structures. */ +struct tspec { + enum output_format fmt; + enum size_spec size; + void (*print_function) (size_t, const char *, const char *); + char *fmt_string; + int hexl_mode_trailer; + int field_width; +}; + +/* Convert the number of 8-bit bytes of a binary representation to + the number of characters (digits + sign if the type is signed) + required to represent the same quantity in the specified base/type. + For example, a 32-bit (4-byte) quantity may require a field width + as wide as the following for these types: + 11 unsigned octal + 11 signed decimal + 10 unsigned decimal + 8 unsigned hexadecimal */ + +static const uint8_t bytes_to_oct_digits[] = +{0, 3, 6, 8, 11, 14, 16, 19, 22, 25, 27, 30, 32, 35, 38, 41, 43}; + +static const uint8_t bytes_to_signed_dec_digits[] = +{1, 4, 6, 8, 11, 13, 16, 18, 20, 23, 25, 28, 30, 33, 35, 37, 40}; + +static const uint8_t bytes_to_unsigned_dec_digits[] = +{0, 3, 5, 8, 10, 13, 15, 17, 20, 22, 25, 27, 29, 32, 34, 37, 39}; + +static const uint8_t bytes_to_hex_digits[] = +{0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32}; + +/* Convert enum size_spec to the size of the named type. */ +static const signed char width_bytes[] = { + -1, + sizeof(char), + sizeof(short), + sizeof(int), + sizeof(long), + sizeof(ulonglong_t), + sizeof(float), + sizeof(double), + sizeof(longdouble_t) +}; + +/* Ensure that for each member of 'enum size_spec' there is an + initializer in the width_bytes array. */ +struct dummy { + int assert_width_bytes_matches_size_spec_decl + [sizeof width_bytes / sizeof width_bytes[0] == N_SIZE_SPECS ? 1 : -1]; +}; + +static size_t string_min; +static int flag_dump_strings; + +/* Non-zero if an old-style 'pseudo-address' was specified. */ +static int flag_pseudo_start; + +/* The difference between the old-style pseudo starting address and + the number of bytes to skip. */ +static off_t pseudo_offset; + +/* Function that accepts an address and an optional following char, + and prints the address and char to stdout. */ +static void (*format_address) (off_t, char); + +/* The number of input bytes to skip before formatting and writing. */ +static off_t n_bytes_to_skip; // = 0; + +/* When zero, MAX_BYTES_TO_FORMAT and END_OFFSET are ignored, and all + input is formatted. */ +static int limit_bytes_to_format; // = 0; + +/* The maximum number of bytes that will be formatted. */ +static off_t max_bytes_to_format; + +/* The offset of the first byte after the last byte to be formatted. */ +static off_t end_offset; + +/* When nonzero and two or more consecutive blocks are equal, format + only the first block and output an asterisk alone on the following + line to indicate that identical blocks have been elided. */ +static int abbreviate_duplicate_blocks = 1; + +/* An array of specs describing how to format each input block. */ +static size_t n_specs; +static struct tspec *spec; + +/* The number of input bytes formatted per output line. It must be + a multiple of the least common multiple of the sizes associated with + the specified output types. It should be as large as possible, but + no larger than 16 -- unless specified with the -w option. */ +static size_t bytes_per_block; + +/* Human-readable representation of *file_list (for error messages). + It differs from *file_list only when *file_list is "-". */ +static char const *input_filename; + +/* A NULL-terminated list of the file-arguments from the command line. */ +static char const *const *file_list; + +/* Initializer for file_list if no file-arguments + were specified on the command line. */ +static char const *const default_file_list[] = { "-", NULL }; + +/* The input stream associated with the current file. */ +static FILE *in_stream; + +static int ioerror; + +#define MAX_INTEGRAL_TYPE_SIZE sizeof(ulonglong_t) +static unsigned char integral_type_size[MAX_INTEGRAL_TYPE_SIZE + 1] = { + [sizeof(char)] = CHAR, +#if USHRT_MAX != UCHAR_MAX + [sizeof(short)] = SHORT, +#endif +#if UINT_MAX != USHRT_MAX + [sizeof(int)] = INT, +#endif +#if ULONG_MAX != UINT_MAX + [sizeof(long)] = LONG, +#endif +#if ULLONG_MAX != ULONG_MAX + [sizeof(ulonglong_t)] = LONG_LONG, +#endif +}; + +#define MAX_FP_TYPE_SIZE sizeof(longdouble_t) +static unsigned char fp_type_size[MAX_FP_TYPE_SIZE + 1] = { + /* gcc seems to allow repeated indexes. Last one stays */ + [sizeof(longdouble_t)] = FLOAT_LONG_DOUBLE, + [sizeof(double)] = FLOAT_DOUBLE, + [sizeof(float)] = FLOAT_SINGLE, +}; + + +static unsigned +gcd(unsigned u, unsigned v) +{ + unsigned t; + while (v != 0) { + t = u % v; + u = v; + v = t; + } + return u; +} + +/* Compute the least common multiple of U and V. */ +static unsigned +lcm(unsigned u, unsigned v) { + unsigned t = gcd(u, v); + if (t == 0) + return 0; + return u * v / t; +} + +static void +print_s_char(size_t n_bytes, const char *block, const char *fmt_string) +{ + while (n_bytes--) { + int tmp = *(signed char *) block; + printf(fmt_string, tmp); + block += sizeof(unsigned char); + } +} + +static void +print_char(size_t n_bytes, const char *block, const char *fmt_string) +{ + while (n_bytes--) { + unsigned tmp = *(unsigned char *) block; + printf(fmt_string, tmp); + block += sizeof(unsigned char); + } +} + +static void +print_s_short(size_t n_bytes, const char *block, const char *fmt_string) +{ + n_bytes /= sizeof(signed short); + while (n_bytes--) { + int tmp = *(signed short *) block; + printf(fmt_string, tmp); + block += sizeof(unsigned short); + } +} + +static void +print_short(size_t n_bytes, const char *block, const char *fmt_string) +{ + n_bytes /= sizeof(unsigned short); + while (n_bytes--) { + unsigned tmp = *(unsigned short *) block; + printf(fmt_string, tmp); + block += sizeof(unsigned short); + } +} + +static void +print_int(size_t n_bytes, const char *block, const char *fmt_string) +{ + n_bytes /= sizeof(unsigned); + while (n_bytes--) { + unsigned tmp = *(unsigned *) block; + printf(fmt_string, tmp); + block += sizeof(unsigned); + } +} + +#if UINT_MAX == ULONG_MAX +# define print_long print_int +#else +static void +print_long(size_t n_bytes, const char *block, const char *fmt_string) +{ + n_bytes /= sizeof(unsigned long); + while (n_bytes--) { + unsigned long tmp = *(unsigned long *) block; + printf(fmt_string, tmp); + block += sizeof(unsigned long); + } +} +#endif + +#if ULONG_MAX == ULLONG_MAX +# define print_long_long print_long +#else +static void +print_long_long(size_t n_bytes, const char *block, const char *fmt_string) +{ + n_bytes /= sizeof(ulonglong_t); + while (n_bytes--) { + ulonglong_t tmp = *(ulonglong_t *) block; + printf(fmt_string, tmp); + block += sizeof(ulonglong_t); + } +} +#endif + +static void +print_float(size_t n_bytes, const char *block, const char *fmt_string) +{ + n_bytes /= sizeof(float); + while (n_bytes--) { + float tmp = *(float *) block; + printf(fmt_string, tmp); + block += sizeof(float); + } +} + +static void +print_double(size_t n_bytes, const char *block, const char *fmt_string) +{ + n_bytes /= sizeof(double); + while (n_bytes--) { + double tmp = *(double *) block; + printf(fmt_string, tmp); + block += sizeof(double); + } +} + +static void +print_long_double(size_t n_bytes, const char *block, const char *fmt_string) +{ + n_bytes /= sizeof(longdouble_t); + while (n_bytes--) { + longdouble_t tmp = *(longdouble_t *) block; + printf(fmt_string, tmp); + block += sizeof(longdouble_t); + } +} + +/* print_[named]_ascii are optimized for speed. + * Remember, someday you may want to pump gigabytes thru this thing. + * Saving a dozen of .text bytes here is counter-productive */ + +static void +print_named_ascii(size_t n_bytes, const char *block, + const char *unused_fmt_string ATTRIBUTE_UNUSED) +{ + /* Names for some non-printing characters. */ + static const char charname[33][3] = { + "nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel", + " bs", " ht", " nl", " vt", " ff", " cr", " so", " si", + "dle", "dc1", "dc2", "dc3", "dc4", "nak", "syn", "etb", + "can", " em", "sub", "esc", " fs", " gs", " rs", " us", + " sp" + }; + // buf[N] pos: 01234 56789 + char buf[12] = " x\0 0xx\0"; + // actually " x\0 xxx\0", but I want to share the string with below. + // [12] because we take three 32bit stack slots anyway, and + // gcc is too dumb to initialize with constant stores, + // it copies initializer from rodata. Oh well. + + while (n_bytes--) { + unsigned masked_c = *(unsigned char *) block++; + + masked_c &= 0x7f; + if (masked_c == 0x7f) { + fputs(" del", stdout); + continue; + } + if (masked_c > ' ') { + buf[3] = masked_c; + fputs(buf, stdout); + continue; + } + /* Why? Because printf(" %3.3s") is much slower... */ + buf[6] = charname[masked_c][0]; + buf[7] = charname[masked_c][1]; + buf[8] = charname[masked_c][2]; + fputs(buf+5, stdout); + } +} + +static void +print_ascii(size_t n_bytes, const char *block, + const char *unused_fmt_string ATTRIBUTE_UNUSED) +{ + // buf[N] pos: 01234 56789 + char buf[12] = " x\0 0xx\0"; + + while (n_bytes--) { + const char *s; + unsigned c = *(unsigned char *) block++; + + if (ISPRINT(c)) { + buf[3] = c; + fputs(buf, stdout); + continue; + } + switch (c) { + case '\0': + s = " \\0"; + break; + case '\007': + s = " \\a"; + break; + case '\b': + s = " \\b"; + break; + case '\f': + s = " \\f"; + break; + case '\n': + s = " \\n"; + break; + case '\r': + s = " \\r"; + break; + case '\t': + s = " \\t"; + break; + case '\v': + s = " \\v"; + break; + case '\x7f': + s = " 177"; + break; + default: /* c is never larger than 040 */ + buf[7] = (c >> 3) + '0'; + buf[8] = (c & 7) + '0'; + s = buf + 5; + } + fputs(s, stdout); + } +} + +/* Given a list of one or more input filenames FILE_LIST, set the global + file pointer IN_STREAM and the global string INPUT_FILENAME to the + first one that can be successfully opened. Modify FILE_LIST to + reference the next filename in the list. A file name of "-" is + interpreted as standard input. If any file open fails, give an error + message and return nonzero. */ + +static void +open_next_file(void) +{ + while(1) { + input_filename = *file_list; + if (!input_filename) + return; + file_list++; + in_stream = fopen_or_warn_stdin(input_filename); + if (in_stream) { + if (in_stream == stdin) + input_filename = bb_msg_standard_input; + break; + } + ioerror = 1; + } + + if (limit_bytes_to_format && !flag_dump_strings) + setbuf(in_stream, NULL); +} + +/* Test whether there have been errors on in_stream, and close it if + it is not standard input. Return nonzero if there has been an error + on in_stream or stdout; return zero otherwise. This function will + report more than one error only if both a read and a write error + have occurred. IN_ERRNO, if nonzero, is the error number + corresponding to the most recent action for IN_STREAM. */ + +static void +check_and_close(void) +{ + if (in_stream) { + if (ferror(in_stream)) { + bb_error_msg("%s: read error", input_filename); + ioerror = 1; + } + fclose_if_not_stdin(in_stream); + in_stream = NULL; + } + + if (ferror(stdout)) { + bb_error_msg("write error"); + ioerror = 1; + } +} + +/* If S points to a single valid modern od format string, put + a description of that format in *TSPEC, make *NEXT point at the + character following the just-decoded format (if *NEXT is non-NULL), + and return zero. For example, if S were "d4afL" + *NEXT would be set to "afL" and *TSPEC would be + { + fmt = SIGNED_DECIMAL; + size = INT or LONG; (whichever integral_type_size[4] resolves to) + print_function = print_int; (assuming size == INT) + fmt_string = "%011d%c"; + } + S_ORIG is solely for reporting errors. It should be the full format + string argument. */ + +static void +decode_one_format(const char *s_orig, const char *s, const char **next, + struct tspec *tspec) +{ + enum size_spec size_spec; + unsigned size; + enum output_format fmt; + const char *p; + char *end; + char *fmt_string = NULL; + void (*print_function) (size_t, const char *, const char *); + unsigned c; + unsigned field_width = 0; + int pos; + + assert(tspec != NULL); + + switch (*s) { + case 'd': + case 'o': + case 'u': + case 'x': { + static const char CSIL[] = "CSIL"; + + c = *s++; + p = strchr(CSIL, *s); + if (!p) { + size = sizeof(int); + if (isdigit(s[0])) { + size = bb_strtou(s, &end, 0); + if (errno == ERANGE + || MAX_INTEGRAL_TYPE_SIZE < size + || integral_type_size[size] == NO_SIZE + ) { + bb_error_msg_and_die("invalid type string '%s'; " + "%u-byte %s type is not supported", + s_orig, size, "integral"); + } + s = end; + } + } else { + static const uint8_t CSIL_sizeof[] = { + sizeof(char), + sizeof(short), + sizeof(int), + sizeof(long), + }; + size = CSIL_sizeof[p - CSIL]; + } + +#define ISPEC_TO_FORMAT(Spec, Min_format, Long_format, Max_format) \ + ((Spec) == LONG_LONG ? (Max_format) \ + : ((Spec) == LONG ? (Long_format) : (Min_format))) + +#define FMT_BYTES_ALLOCATED 9 + size_spec = integral_type_size[size]; + + { + static const char doux[] = "doux"; + static const char doux_fmt_letter[][4] = { + "lld", "llo", "llu", "llx" + }; + static const enum output_format doux_fmt[] = { + SIGNED_DECIMAL, + OCTAL, + UNSIGNED_DECIMAL, + HEXADECIMAL, + }; + static const uint8_t *const doux_bytes_to_XXX[] = { + bytes_to_signed_dec_digits, + bytes_to_oct_digits, + bytes_to_unsigned_dec_digits, + bytes_to_hex_digits, + }; + static const char doux_fmtstring[][sizeof(" %%0%u%s")] = { + " %%%u%s", + " %%0%u%s", + " %%%u%s", + " %%0%u%s", + }; + + pos = strchr(doux, c) - doux; + fmt = doux_fmt[pos]; + field_width = doux_bytes_to_XXX[pos][size]; + p = doux_fmt_letter[pos] + 2; + if (size_spec == LONG) p--; + if (size_spec == LONG_LONG) p -= 2; + fmt_string = xasprintf(doux_fmtstring[pos], field_width, p); + } + + switch (size_spec) { + case CHAR: + print_function = (fmt == SIGNED_DECIMAL + ? print_s_char + : print_char); + break; + case SHORT: + print_function = (fmt == SIGNED_DECIMAL + ? print_s_short + : print_short); + break; + case INT: + print_function = print_int; + break; + case LONG: + print_function = print_long; + break; + default: /* case LONG_LONG: */ + print_function = print_long_long; + break; + } + break; + } + + case 'f': { + static const char FDL[] = "FDL"; + + fmt = FLOATING_POINT; + ++s; + p = strchr(FDL, *s); + if (!p) { + size = sizeof(double); + if (isdigit(s[0])) { + size = bb_strtou(s, &end, 0); + if (errno == ERANGE || size > MAX_FP_TYPE_SIZE + || fp_type_size[size] == NO_SIZE + ) { + bb_error_msg_and_die("invalid type string '%s'; " + "%u-byte %s type is not supported", + s_orig, size, "floating point"); + } + s = end; + } + } else { + static const uint8_t FDL_sizeof[] = { + sizeof(float), + sizeof(double), + sizeof(longdouble_t), + }; + + size = FDL_sizeof[p - FDL]; + } + + size_spec = fp_type_size[size]; + + switch (size_spec) { + case FLOAT_SINGLE: + print_function = print_float; + field_width = FLT_DIG + 8; + /* Don't use %#e; not all systems support it. */ + fmt_string = xasprintf(" %%%d.%de", field_width, FLT_DIG); + break; + case FLOAT_DOUBLE: + print_function = print_double; + field_width = DBL_DIG + 8; + fmt_string = xasprintf(" %%%d.%de", field_width, DBL_DIG); + break; + default: /* case FLOAT_LONG_DOUBLE: */ + print_function = print_long_double; + field_width = LDBL_DIG + 8; + fmt_string = xasprintf(" %%%d.%dLe", field_width, LDBL_DIG); + break; + } + break; + } + + case 'a': + ++s; + fmt = NAMED_CHARACTER; + size_spec = CHAR; + print_function = print_named_ascii; + field_width = 3; + break; + case 'c': + ++s; + fmt = CHARACTER; + size_spec = CHAR; + print_function = print_ascii; + field_width = 3; + break; + default: + bb_error_msg_and_die("invalid character '%c' " + "in type string '%s'", *s, s_orig); + } + + tspec->size = size_spec; + tspec->fmt = fmt; + tspec->print_function = print_function; + tspec->fmt_string = fmt_string; + + tspec->field_width = field_width; + tspec->hexl_mode_trailer = (*s == 'z'); + if (tspec->hexl_mode_trailer) + s++; + + if (next != NULL) + *next = s; +} + +/* Decode the modern od format string S. Append the decoded + representation to the global array SPEC, reallocating SPEC if + necessary. Return zero if S is valid, nonzero otherwise. */ + +static void +decode_format_string(const char *s) +{ + const char *s_orig = s; + + while (*s != '\0') { + struct tspec tspec; + const char *next; + + decode_one_format(s_orig, s, &next, &tspec); + + assert(s != next); + s = next; + n_specs++; + spec = xrealloc(spec, n_specs * sizeof(*spec)); + memcpy(&spec[n_specs-1], &tspec, sizeof *spec); + } +} + +/* Given a list of one or more input filenames FILE_LIST, set the global + file pointer IN_STREAM to position N_SKIP in the concatenation of + those files. If any file operation fails or if there are fewer than + N_SKIP bytes in the combined input, give an error message and return + nonzero. When possible, use seek rather than read operations to + advance IN_STREAM. */ + +static void +skip(off_t n_skip) +{ + if (n_skip == 0) + return; + + while (in_stream) { /* !EOF */ + struct stat file_stats; + + /* First try seeking. For large offsets, this extra work is + worthwhile. If the offset is below some threshold it may be + more efficient to move the pointer by reading. There are two + issues when trying to seek: + - the file must be seekable. + - before seeking to the specified position, make sure + that the new position is in the current file. + Try to do that by getting file's size using fstat. + But that will work only for regular files. */ + + /* The st_size field is valid only for regular files + (and for symbolic links, which cannot occur here). + If the number of bytes left to skip is at least + as large as the size of the current file, we can + decrement n_skip and go on to the next file. */ + if (fstat(fileno(in_stream), &file_stats) == 0 + && S_ISREG(file_stats.st_mode) && file_stats.st_size >= 0 + ) { + if (file_stats.st_size < n_skip) { + n_skip -= file_stats.st_size; + /* take check&close / open_next route */ + } else { + if (fseeko(in_stream, n_skip, SEEK_CUR) != 0) + ioerror = 1; + return; + } + } else { + /* If it's not a regular file with nonnegative size, + position the file pointer by reading. */ + char buf[BUFSIZ]; + size_t n_bytes_read, n_bytes_to_read = BUFSIZ; + + while (n_skip > 0) { + if (n_skip < n_bytes_to_read) + n_bytes_to_read = n_skip; + n_bytes_read = fread(buf, 1, n_bytes_to_read, in_stream); + n_skip -= n_bytes_read; + if (n_bytes_read != n_bytes_to_read) + break; /* EOF on this file or error */ + } + } + if (n_skip == 0) + return; + + check_and_close(); + open_next_file(); + } + + if (n_skip) + bb_error_msg_and_die("cannot skip past end of combined input"); +} + + +typedef void FN_format_address(off_t address, char c); + +static void +format_address_none(off_t address ATTRIBUTE_UNUSED, char c ATTRIBUTE_UNUSED) +{ +} + +static char address_fmt[] = "%0n"OFF_FMT"xc"; +/* Corresponds to 'x' above */ +#define address_base_char address_fmt[sizeof(address_fmt)-3] +/* Corresponds to 'n' above */ +#define address_pad_len_char address_fmt[2] + +static void +format_address_std(off_t address, char c) +{ + /* Corresponds to 'c' */ + address_fmt[sizeof(address_fmt)-2] = c; + printf(address_fmt, address); +} + +#if ENABLE_GETOPT_LONG +/* only used with --traditional */ +static void +format_address_paren(off_t address, char c) +{ + putchar('('); + format_address_std(address, ')'); + /* BUG in coreutils 5.2.1! must be "if (c) putchar(c);" */ + putchar(c); +} + +static void +format_address_label(off_t address, char c) +{ + format_address_std(address, ' '); + format_address_paren(address + pseudo_offset, c); +} +#endif + +static void +dump_hexl_mode_trailer(size_t n_bytes, const char *block) +{ + fputs(" >", stdout); + while (n_bytes--) { + unsigned c = *(unsigned char *) block++; + c = (ISPRINT(c) ? c : '.'); + putchar(c); + } + putchar('<'); +} + +/* Write N_BYTES bytes from CURR_BLOCK to standard output once for each + of the N_SPEC format specs. CURRENT_OFFSET is the byte address of + CURR_BLOCK in the concatenation of input files, and it is printed + (optionally) only before the output line associated with the first + format spec. When duplicate blocks are being abbreviated, the output + for a sequence of identical input blocks is the output for the first + block followed by an asterisk alone on a line. It is valid to compare + the blocks PREV_BLOCK and CURR_BLOCK only when N_BYTES == BYTES_PER_BLOCK. + That condition may be false only for the last input block -- and then + only when it has not been padded to length BYTES_PER_BLOCK. */ + +static void +write_block(off_t current_offset, size_t n_bytes, + const char *prev_block, const char *curr_block) +{ + static char first = 1; + static char prev_pair_equal = 0; + size_t i; + + if (abbreviate_duplicate_blocks + && !first + && n_bytes == bytes_per_block + && memcmp(prev_block, curr_block, bytes_per_block) == 0 + ) { + if (prev_pair_equal) { + /* The two preceding blocks were equal, and the current + block is the same as the last one, so print nothing. */ + } else { + puts("*"); + prev_pair_equal = 1; + } + } else { + first = 0; + prev_pair_equal = 0; + for (i = 0; i < n_specs; i++) { + if (i == 0) + format_address(current_offset, '\0'); + else + printf("%*s", address_pad_len_char - '0', ""); + (*spec[i].print_function) (n_bytes, curr_block, spec[i].fmt_string); + if (spec[i].hexl_mode_trailer) { + /* space-pad out to full line width, then dump the trailer */ + int datum_width = width_bytes[spec[i].size]; + int blank_fields = (bytes_per_block - n_bytes) / datum_width; + int field_width = spec[i].field_width + 1; + printf("%*s", blank_fields * field_width, ""); + dump_hexl_mode_trailer(n_bytes, curr_block); + } + putchar('\n'); + } + } +} + +static void +read_block(size_t n, char *block, size_t *n_bytes_in_buffer) +{ + assert(0 < n && n <= bytes_per_block); + + *n_bytes_in_buffer = 0; + + if (n == 0) + return; + + while (in_stream != NULL) { /* EOF. */ + size_t n_needed; + size_t n_read; + + n_needed = n - *n_bytes_in_buffer; + n_read = fread(block + *n_bytes_in_buffer, 1, n_needed, in_stream); + *n_bytes_in_buffer += n_read; + if (n_read == n_needed) + break; + /* error check is done in check_and_close */ + check_and_close(); + open_next_file(); + } +} + +/* Return the least common multiple of the sizes associated + with the format specs. */ + +static int +get_lcm(void) +{ + size_t i; + int l_c_m = 1; + + for (i = 0; i < n_specs; i++) + l_c_m = lcm(l_c_m, width_bytes[(int) spec[i].size]); + return l_c_m; +} + +#if ENABLE_GETOPT_LONG +/* If S is a valid traditional offset specification with an optional + leading '+' return nonzero and set *OFFSET to the offset it denotes. */ + +static int +parse_old_offset(const char *s, off_t *offset) +{ + static const struct suffix_mult Bb[] = { + { "B", 1024 }, + { "b", 512 }, + { NULL, 0 } + }; + char *p; + int radix; + + /* Skip over any leading '+'. */ + if (s[0] == '+') ++s; + + /* Determine the radix we'll use to interpret S. If there is a '.', + * it's decimal, otherwise, if the string begins with '0X'or '0x', + * it's hexadecimal, else octal. */ + p = strchr(s, '.'); + radix = 8; + if (p) { + p[0] = '\0'; /* cheating */ + radix = 10; + } else if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) + radix = 16; + + *offset = xstrtooff_sfx(s, radix, Bb); + if (p) p[0] = '.'; + + return (*offset >= 0); +} +#endif + +/* Read a chunk of size BYTES_PER_BLOCK from the input files, write the + formatted block to standard output, and repeat until the specified + maximum number of bytes has been read or until all input has been + processed. If the last block read is smaller than BYTES_PER_BLOCK + and its size is not a multiple of the size associated with a format + spec, extend the input block with zero bytes until its length is a + multiple of all format spec sizes. Write the final block. Finally, + write on a line by itself the offset of the byte after the last byte + read. Accumulate return values from calls to read_block and + check_and_close, and if any was nonzero, return nonzero. + Otherwise, return zero. */ + +static void +dump(void) +{ + char *block[2]; + off_t current_offset; + int idx; + size_t n_bytes_read; + + block[0] = xmalloc(2*bytes_per_block); + block[1] = block[0] + bytes_per_block; + + current_offset = n_bytes_to_skip; + + idx = 0; + if (limit_bytes_to_format) { + while (1) { + size_t n_needed; + if (current_offset >= end_offset) { + n_bytes_read = 0; + break; + } + n_needed = MIN(end_offset - current_offset, + (off_t) bytes_per_block); + read_block(n_needed, block[idx], &n_bytes_read); + if (n_bytes_read < bytes_per_block) + break; + assert(n_bytes_read == bytes_per_block); + write_block(current_offset, n_bytes_read, + block[!idx], block[idx]); + current_offset += n_bytes_read; + idx = !idx; + } + } else { + while (1) { + read_block(bytes_per_block, block[idx], &n_bytes_read); + if (n_bytes_read < bytes_per_block) + break; + assert(n_bytes_read == bytes_per_block); + write_block(current_offset, n_bytes_read, + block[!idx], block[idx]); + current_offset += n_bytes_read; + idx = !idx; + } + } + + if (n_bytes_read > 0) { + int l_c_m; + size_t bytes_to_write; + + l_c_m = get_lcm(); + + /* Make bytes_to_write the smallest multiple of l_c_m that + is at least as large as n_bytes_read. */ + bytes_to_write = l_c_m * ((n_bytes_read + l_c_m - 1) / l_c_m); + + memset(block[idx] + n_bytes_read, 0, bytes_to_write - n_bytes_read); + write_block(current_offset, bytes_to_write, + block[!idx], block[idx]); + current_offset += n_bytes_read; + } + + format_address(current_offset, '\n'); + + if (limit_bytes_to_format && current_offset >= end_offset) + check_and_close(); + + free(block[0]); +} + +/* Read a single byte into *C from the concatenation of the input files + named in the global array FILE_LIST. On the first call to this + function, the global variable IN_STREAM is expected to be an open + stream associated with the input file INPUT_FILENAME. If IN_STREAM + is at end-of-file, close it and update the global variables IN_STREAM + and INPUT_FILENAME so they correspond to the next file in the list. + Then try to read a byte from the newly opened file. Repeat if + necessary until EOF is reached for the last file in FILE_LIST, then + set *C to EOF and return. Subsequent calls do likewise. The return + value is nonzero if any errors occured, zero otherwise. */ + +static void +read_char(int *c) +{ + while (in_stream) { /* !EOF */ + *c = fgetc(in_stream); + if (*c != EOF) + return; + check_and_close(); + open_next_file(); + } + *c = EOF; +} + +/* Read N bytes into BLOCK from the concatenation of the input files + named in the global array FILE_LIST. On the first call to this + function, the global variable IN_STREAM is expected to be an open + stream associated with the input file INPUT_FILENAME. If all N + bytes cannot be read from IN_STREAM, close IN_STREAM and update + the global variables IN_STREAM and INPUT_FILENAME. Then try to + read the remaining bytes from the newly opened file. Repeat if + necessary until EOF is reached for the last file in FILE_LIST. + On subsequent calls, don't modify BLOCK and return zero. Set + *N_BYTES_IN_BUFFER to the number of bytes read. If an error occurs, + it will be detected through ferror when the stream is about to be + closed. If there is an error, give a message but continue reading + as usual and return nonzero. Otherwise return zero. */ + +/* STRINGS mode. Find each "string constant" in the input. + A string constant is a run of at least 'string_min' ASCII + graphic (or formatting) characters terminated by a null. + Based on a function written by Richard Stallman for a + traditional version of od. Return nonzero if an error + occurs. Otherwise, return zero. */ + +static void +dump_strings(void) +{ + size_t bufsize = MAX(100, string_min); + char *buf = xmalloc(bufsize); + off_t address = n_bytes_to_skip; + + while (1) { + size_t i; + int c; + + /* See if the next 'string_min' chars are all printing chars. */ + tryline: + if (limit_bytes_to_format && (end_offset - string_min <= address)) + break; + i = 0; + while (!limit_bytes_to_format || address < end_offset) { + if (i == bufsize) { + bufsize += bufsize/8; + buf = xrealloc(buf, bufsize); + } + read_char(&c); + if (c < 0) { /* EOF */ + free(buf); + return; + } + address++; + if (!c) + break; + if (!ISPRINT(c)) + goto tryline; /* It isn't; give up on this string. */ + buf[i++] = c; /* String continues; store it all. */ + } + + if (i < string_min) /* Too short! */ + goto tryline; + + /* If we get here, the string is all printable and null-terminated, + * so print it. It is all in 'buf' and 'i' is its length. */ + buf[i] = 0; + format_address(address - i - 1, ' '); + + for (i = 0; (c = buf[i]); i++) { + switch (c) { + case '\007': fputs("\\a", stdout); break; + case '\b': fputs("\\b", stdout); break; + case '\f': fputs("\\f", stdout); break; + case '\n': fputs("\\n", stdout); break; + case '\r': fputs("\\r", stdout); break; + case '\t': fputs("\\t", stdout); break; + case '\v': fputs("\\v", stdout); break; + default: putc(c, stdout); + } + } + putchar('\n'); + } + + /* We reach this point only if we search through + (max_bytes_to_format - string_min) bytes before reaching EOF. */ + free(buf); + + check_and_close(); +} + +int +od_main(int argc, char **argv) +{ + static const struct suffix_mult bkm[] = { + { "b", 512 }, + { "k", 1024 }, + { "m", 1024*1024 }, + { NULL, 0 } + }; + unsigned opt; + int l_c_m; + /* The old-style 'pseudo starting address' to be printed in parentheses + after any true address. */ + off_t pseudo_start = 0; // only for gcc + enum { + OPT_A = 1 << 0, + OPT_N = 1 << 1, + OPT_a = 1 << 2, + OPT_b = 1 << 3, + OPT_c = 1 << 4, + OPT_d = 1 << 5, + OPT_f = 1 << 6, + OPT_h = 1 << 7, + OPT_i = 1 << 8, + OPT_j = 1 << 9, + OPT_l = 1 << 10, + OPT_o = 1 << 11, + OPT_t = 1 << 12, + OPT_v = 1 << 13, + OPT_x = 1 << 14, + OPT_s = 1 << 15, + OPT_S = 1 << 16, + OPT_w = 1 << 17, + OPT_traditional = (1 << 18) * ENABLE_GETOPT_LONG, + }; +#if ENABLE_GETOPT_LONG + static const struct option long_options[] = { + { "skip-bytes", required_argument, NULL, 'j' }, + { "address-radix", required_argument, NULL, 'A' }, + { "read-bytes", required_argument, NULL, 'N' }, + { "format", required_argument, NULL, 't' }, + { "output-duplicates", no_argument, NULL, 'v' }, + { "strings", optional_argument, NULL, 'S' }, + { "width", optional_argument, NULL, 'w' }, + { "traditional", no_argument, NULL, 0xff }, + { NULL, 0, NULL, 0 } + }; +#endif + char *str_A, *str_N, *str_j, *str_S; + char *str_w = NULL; + llist_t *lst_t = NULL; + + spec = NULL; + format_address = format_address_std; + address_base_char = 'o'; + address_pad_len_char = '7'; + flag_dump_strings = 0; + + /* Parse command line */ + opt_complementary = "t::"; // list +#if ENABLE_GETOPT_LONG + applet_long_options = long_options; +#endif + opt = getopt32(argc, argv, "A:N:abcdfhij:lot:vxsS:" + "w::", // -w with optional param + // -S was -s and also had optional parameter + // but in coreutils 6.3 it was renamed and now has + // _mandatory_ parameter + &str_A, &str_N, &str_j, &lst_t, &str_S, &str_w); + argc -= optind; + argv += optind; + if (opt & OPT_A) { + static const char doxn[] = "doxn"; + static const char doxn_address_base_char[] = { + 'u', 'o', 'x', /* '?' fourth one is not important */ + }; + static const uint8_t doxn_address_pad_len_char[] = { + '7', '7', '6', /* '?' */ + }; + char *p; + int pos; + p = strchr(doxn, str_A[0]); + if (!p) + bb_error_msg_and_die("bad output address radix " + "'%c' (must be [doxn])", str_A[0]); + pos = p - doxn; + if (pos == 3) format_address = format_address_none; + address_base_char = doxn_address_base_char[pos]; + address_pad_len_char = doxn_address_pad_len_char[pos]; + } + if (opt & OPT_N) { + limit_bytes_to_format = 1; + max_bytes_to_format = xstrtooff_sfx(str_N, 0, bkm); + } + if (opt & OPT_a) decode_format_string("a"); + if (opt & OPT_b) decode_format_string("oC"); + if (opt & OPT_c) decode_format_string("c"); + if (opt & OPT_d) decode_format_string("u2"); + if (opt & OPT_f) decode_format_string("fF"); + if (opt & OPT_h) decode_format_string("x2"); + if (opt & OPT_i) decode_format_string("d2"); + if (opt & OPT_j) n_bytes_to_skip = xstrtooff_sfx(str_j, 0, bkm); + if (opt & OPT_l) decode_format_string("d4"); + if (opt & OPT_o) decode_format_string("o2"); + //if (opt & OPT_t)... + lst_t = rev_llist(lst_t); + while (lst_t) { + decode_format_string(lst_t->data); + lst_t = lst_t->link; + } + if (opt & OPT_v) abbreviate_duplicate_blocks = 0; + if (opt & OPT_x) decode_format_string("x2"); + if (opt & OPT_s) decode_format_string("d2"); + if (opt & OPT_S) { + string_min = 3; + string_min = xstrtou_sfx(str_S, 0, bkm); + flag_dump_strings = 1; + } + //if (opt & OPT_w)... + //if (opt & OPT_traditional)... + + if (flag_dump_strings && n_specs > 0) + bb_error_msg_and_die("no type may be specified when dumping strings"); + + /* If the --traditional option is used, there may be from + * 0 to 3 remaining command line arguments; handle each case + * separately. + * od [file] [[+]offset[.][b] [[+]label[.][b]]] + * The offset and pseudo_start have the same syntax. + * + * FIXME: POSIX 1003.1-2001 with XSI requires support for the + * traditional syntax even if --traditional is not given. */ + +#if ENABLE_GETOPT_LONG + if (opt & OPT_traditional) { + off_t o1, o2; + + if (argc == 1) { + if (parse_old_offset(argv[0], &o1)) { + n_bytes_to_skip = o1; + --argc; + ++argv; + } + } else if (argc == 2) { + if (parse_old_offset(argv[0], &o1) + && parse_old_offset(argv[1], &o2) + ) { + n_bytes_to_skip = o1; + flag_pseudo_start = 1; + pseudo_start = o2; + argv += 2; + argc -= 2; + } else if (parse_old_offset(argv[1], &o2)) { + n_bytes_to_skip = o2; + --argc; + argv[1] = argv[0]; + ++argv; + } else { + bb_error_msg_and_die("invalid second operand " + "in compatibility mode '%s'", argv[1]); + } + } else if (argc == 3) { + if (parse_old_offset(argv[1], &o1) + && parse_old_offset(argv[2], &o2) + ) { + n_bytes_to_skip = o1; + flag_pseudo_start = 1; + pseudo_start = o2; + argv[2] = argv[0]; + argv += 2; + argc -= 2; + } else { + bb_error_msg_and_die("in compatibility mode " + "the last two arguments must be offsets"); + } + } else if (argc > 3) { + bb_error_msg_and_die("compatibility mode supports " + "at most three arguments"); + } + + if (flag_pseudo_start) { + if (format_address == format_address_none) { + address_base_char = 'o'; + address_pad_len_char = '7'; + format_address = format_address_paren; + } else + format_address = format_address_label; + } + } +#endif + + if (limit_bytes_to_format) { + end_offset = n_bytes_to_skip + max_bytes_to_format; + if (end_offset < n_bytes_to_skip) + bb_error_msg_and_die("skip-bytes + read-bytes is too large"); + } + + if (n_specs == 0) { + decode_format_string("o2"); + n_specs = 1; + } + + /* If no files were listed on the command line, + set the global pointer FILE_LIST so that it + references the null-terminated list of one name: "-". */ + file_list = default_file_list; + if (argc > 0) { + /* Set the global pointer FILE_LIST so that it + references the first file-argument on the command-line. */ + file_list = (char const *const *) argv; + } + + /* open the first input file */ + open_next_file(); + /* skip over any unwanted header bytes */ + skip(n_bytes_to_skip); + if (!in_stream) + return 1; + + pseudo_offset = (flag_pseudo_start ? pseudo_start - n_bytes_to_skip : 0); + + /* Compute output block length. */ + l_c_m = get_lcm(); + + if (opt & OPT_w) { /* -w: width */ + bytes_per_block = 32; + if (str_w) + bytes_per_block = xatou(str_w); + if (!bytes_per_block || bytes_per_block % l_c_m != 0) { + bb_error_msg("warning: invalid width %u; using %d instead", + bytes_per_block, l_c_m); + bytes_per_block = l_c_m; + } + } else { + bytes_per_block = l_c_m; + if (l_c_m < DEFAULT_BYTES_PER_BLOCK) + bytes_per_block *= DEFAULT_BYTES_PER_BLOCK / l_c_m; + } + +#ifdef DEBUG + for (i = 0; i < n_specs; i++) { + printf("%d: fmt=\"%s\" width=%d\n", + i, spec[i].fmt_string, width_bytes[spec[i].size]); + } +#endif + + if (flag_dump_strings) + dump_strings(); + else + dump(); + + if (fclose(stdin) == EOF) + bb_perror_msg_and_die(bb_msg_standard_input); + + return (ioerror != 0); /* err != 0 - return 1 (failure) */ +} diff --git a/coreutils/printenv.c b/coreutils/printenv.c new file mode 100644 index 000000000..ec50f7151 --- /dev/null +++ b/coreutils/printenv.c @@ -0,0 +1,41 @@ +/* vi: set sw=4 ts=4: */ +/* + * printenv implementation for busybox + * + * Copyright (C) 2005 by Erik Andersen + * Copyright (C) 2005 by Mike Frysinger + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include "busybox.h" + +int printenv_main(int argc, char **argv) +{ + extern char **environ; + int e = 0; + + /* no variables specified, show whole env */ + if (argc == 1) + while (environ[e]) + puts(environ[e++]); + + /* search for specified variables and print them out if found */ + else { + int i; + size_t l; + char *arg, *env; + + for (i=1; (arg = argv[i]); ++i) + for (; (env = environ[e]); ++e) { + l = strlen(arg); + if (!strncmp(env, arg, l) && env[l] == '=') + puts(env + l + 1); + } + } + + fflush_stdout_and_exit(0); +} diff --git a/coreutils/printf.c b/coreutils/printf.c new file mode 100644 index 000000000..0e818354f --- /dev/null +++ b/coreutils/printf.c @@ -0,0 +1,321 @@ +/* vi: set sw=4 ts=4: */ +/* printf - format and print data + + Copyright 1999 Dave Cinege + Portions copyright (C) 1990-1996 Free Software Foundation, Inc. + + Licensed under GPL v2 or later, see file LICENSE in this tarball for details. +*/ + +/* Usage: printf format [argument...] + + A front end to the printf function that lets it be used from the shell. + + Backslash escapes: + + \" = double quote + \\ = backslash + \a = alert (bell) + \b = backspace + \c = produce no further output + \f = form feed + \n = new line + \r = carriage return + \t = horizontal tab + \v = vertical tab + \0ooo = octal number (ooo is 0 to 3 digits) + \xhhh = hexadecimal number (hhh is 1 to 3 digits) + + Additional directive: + + %b = print an argument string, interpreting backslash escapes + + The 'format' argument is re-used as many times as necessary + to convert all of the given arguments. + + David MacKenzie */ + + +// 19990508 Busy Boxed! Dave Cinege + +#include "busybox.h" + +static int print_formatted(char *format, int argc, char **argv); +static void print_direc(char *start, size_t length, + int field_width, int precision, char *argument); + +typedef void (*converter)(char *arg, void *result); + +static void multiconvert(char *arg, void *result, converter convert) +{ + char s[16]; + if (*arg == '"' || *arg == '\'') { + sprintf(s, "%d", (unsigned char)arg[1]); + arg = s; + } + convert(arg, result); + if (errno) /* Huh, looks strange... bug? */ + fputs(arg, stderr); +} + +static void conv_strtoul(char *arg, void *result) +{ + *(unsigned long*)result = bb_strtoul(arg, NULL, 10); +} +static void conv_strtol(char *arg, void *result) +{ + *(long*)result = bb_strtol(arg, NULL, 10); +} +static void conv_strtod(char *arg, void *result) +{ + char *end; + /* Well, this one allows leading whitespace... so what */ + /* What I like much less is that "-" is accepted too! :( */ + *(double*)result = strtod(arg, &end); + if (end[0]) errno = ERANGE; +} + +static unsigned long my_xstrtoul(char *arg) +{ + unsigned long result; + multiconvert(arg, &result, conv_strtoul); + return result; +} + +static long my_xstrtol(char *arg) +{ + long result; + multiconvert(arg, &result, conv_strtol); + return result; +} + +static double my_xstrtod(char *arg) +{ + double result; + multiconvert(arg, &result, conv_strtod); + return result; +} + +static void print_esc_string(char *str) +{ + for (; *str; str++) { + if (*str == '\\') { + str++; + putchar(bb_process_escape_sequence((const char **)&str)); + } else { + putchar(*str); + } + + } +} + +int printf_main(int argc, char **argv) +{ + char *format; + int args_used; + + if (argc <= 1 || argv[1][0] == '-') { + bb_show_usage(); + } + + format = argv[1]; + argc -= 2; + argv += 2; + + do { + args_used = print_formatted(format, argc, argv); + argc -= args_used; + argv += args_used; + } + while (args_used > 0 && argc > 0); + +/* if (argc > 0) + fprintf(stderr, "excess args ignored"); +*/ + + return EXIT_SUCCESS; +} + +/* Print the text in FORMAT, using ARGV (with ARGC elements) for + arguments to any '%' directives. + Return the number of elements of ARGV used. */ + +static int print_formatted(char *format, int argc, char **argv) +{ + int save_argc = argc; /* Preserve original value. */ + char *f; /* Pointer into 'format'. */ + char *direc_start; /* Start of % directive. */ + size_t direc_length; /* Length of % directive. */ + int field_width; /* Arg to first '*', or -1 if none. */ + int precision; /* Arg to second '*', or -1 if none. */ + + for (f = format; *f; ++f) { + switch (*f) { + case '%': + direc_start = f++; + direc_length = 1; + field_width = precision = -1; + if (*f == '%') { + putchar('%'); + break; + } + if (*f == 'b') { + if (argc > 0) { + print_esc_string(*argv); + ++argv; + --argc; + } + break; + } + if (strchr("-+ #", *f)) { + ++f; + ++direc_length; + } + if (*f == '*') { + ++f; + ++direc_length; + if (argc > 0) { + field_width = my_xstrtoul(*argv); + ++argv; + --argc; + } else + field_width = 0; + } else + while (isdigit(*f)) { + ++f; + ++direc_length; + } + if (*f == '.') { + ++f; + ++direc_length; + if (*f == '*') { + ++f; + ++direc_length; + if (argc > 0) { + precision = my_xstrtoul(*argv); + ++argv; + --argc; + } else + precision = 0; + } else + while (isdigit(*f)) { + ++f; + ++direc_length; + } + } + if (*f == 'l' || *f == 'L' || *f == 'h') { + ++f; + ++direc_length; + } + /* + if (!strchr ("diouxXfeEgGcs", *f)) + fprintf(stderr, "%%%c: invalid directive", *f); + */ + ++direc_length; + if (argc > 0) { + print_direc(direc_start, direc_length, field_width, + precision, *argv); + ++argv; + --argc; + } else + print_direc(direc_start, direc_length, field_width, + precision, ""); + break; + + case '\\': + if (*++f == 'c') + exit(0); + putchar(bb_process_escape_sequence((const char **)&f)); + f--; + break; + + default: + putchar(*f); + } + } + + return save_argc - argc; +} + +static void +print_direc(char *start, size_t length, int field_width, int precision, + char *argument) +{ + char *p; /* Null-terminated copy of % directive. */ + + p = xmalloc((unsigned) (length + 1)); + strncpy(p, start, length); + p[length] = 0; + + switch (p[length - 1]) { + case 'd': + case 'i': + if (field_width < 0) { + if (precision < 0) + printf(p, my_xstrtol(argument)); + else + printf(p, precision, my_xstrtol(argument)); + } else { + if (precision < 0) + printf(p, field_width, my_xstrtol(argument)); + else + printf(p, field_width, precision, my_xstrtol(argument)); + } + break; + + case 'o': + case 'u': + case 'x': + case 'X': + if (field_width < 0) { + if (precision < 0) + printf(p, my_xstrtoul(argument)); + else + printf(p, precision, my_xstrtoul(argument)); + } else { + if (precision < 0) + printf(p, field_width, my_xstrtoul(argument)); + else + printf(p, field_width, precision, my_xstrtoul(argument)); + } + break; + + case 'f': + case 'e': + case 'E': + case 'g': + case 'G': + if (field_width < 0) { + if (precision < 0) + printf(p, my_xstrtod(argument)); + else + printf(p, precision, my_xstrtod(argument)); + } else { + if (precision < 0) + printf(p, field_width, my_xstrtod(argument)); + else + printf(p, field_width, precision, my_xstrtod(argument)); + } + break; + + case 'c': + printf(p, *argument); + break; + + case 's': + if (field_width < 0) { + if (precision < 0) + printf(p, argument); + else + printf(p, precision, argument); + } else { + if (precision < 0) + printf(p, field_width, argument); + else + printf(p, field_width, precision, argument); + } + break; + } + + free(p); +} diff --git a/coreutils/pwd.c b/coreutils/pwd.c new file mode 100644 index 000000000..bd36d627e --- /dev/null +++ b/coreutils/pwd.c @@ -0,0 +1,24 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini pwd implementation for busybox + * + * Copyright (C) 1995, 1996 by Bruce Perens . + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include "busybox.h" + +int pwd_main(int argc, char **argv) +{ + char *buf; + + if ((buf = xgetcwd(NULL)) != NULL) { + puts(buf); + fflush_stdout_and_exit(EXIT_SUCCESS); + } + + return EXIT_FAILURE; +} diff --git a/coreutils/realpath.c b/coreutils/realpath.c new file mode 100644 index 000000000..5f3242fb5 --- /dev/null +++ b/coreutils/realpath.c @@ -0,0 +1,48 @@ +/* vi: set sw=4 ts=4: */ + +/* BB_AUDIT SUSv3 N/A -- Apparently a busybox extension. */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Now does proper error checking on output and returns a failure exit code + * if one or more paths cannot be resolved. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include "busybox.h" + +int realpath_main(int argc, char **argv) +{ + int retval = EXIT_SUCCESS; + +#if PATH_MAX > (BUFSIZ+1) + RESERVE_CONFIG_BUFFER(resolved_path, PATH_MAX); +# define resolved_path_MUST_FREE 1 +#else +#define resolved_path bb_common_bufsiz1 +# define resolved_path_MUST_FREE 0 +#endif + + if (--argc == 0) { + bb_show_usage(); + } + + do { + argv++; + if (realpath(*argv, resolved_path) != NULL) { + puts(resolved_path); + } else { + retval = EXIT_FAILURE; + bb_perror_msg("%s", *argv); + } + } while (--argc); + +#if ENABLE_FEATURE_CLEAN_UP && resolved_path_MUST_FREE + RELEASE_CONFIG_BUFFER(resolved_path); +#endif + + fflush_stdout_and_exit(retval); +} diff --git a/coreutils/rm.c b/coreutils/rm.c new file mode 100644 index 000000000..5df7d5f65 --- /dev/null +++ b/coreutils/rm.c @@ -0,0 +1,52 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini rm implementation for busybox + * + * Copyright (C) 2001 Matt Kraai + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/rm.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Size reduction. + */ + +#include +#include "busybox.h" + +int rm_main(int argc, char **argv) +{ + int status = 0; + int flags = 0; + unsigned opt; + + opt_complementary = "f-i:i-f"; + opt = getopt32(argc, argv, "fiRr"); + if(opt & 1) + flags |= FILEUTILS_FORCE; + if(opt & 2) + flags |= FILEUTILS_INTERACTIVE; + if(opt & 12) + flags |= FILEUTILS_RECUR; + + if (*(argv += optind) != NULL) { + do { + const char *base = bb_get_last_path_component(*argv); + + if ((base[0] == '.') && (!base[1] || ((base[1] == '.') && !base[2]))) { + bb_error_msg("cannot remove '.' or '..'"); + } else if (remove_file(*argv, flags) >= 0) { + continue; + } + status = 1; + } while (*++argv); + } else if (!(flags & FILEUTILS_FORCE)) { + bb_show_usage(); + } + + return status; +} diff --git a/coreutils/rmdir.c b/coreutils/rmdir.c new file mode 100644 index 000000000..c61bae795 --- /dev/null +++ b/coreutils/rmdir.c @@ -0,0 +1,60 @@ +/* vi: set sw=4 ts=4: */ +/* + * rmdir implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/rmdir.html */ + +#include +#include +#include +#include "busybox.h" + +int rmdir_main(int argc, char **argv) +{ + int status = EXIT_SUCCESS; + int flags; + int do_dot; + char *path; + + flags = getopt32(argc, argv, "p"); + + argv += optind; + + if (!*argv) { + bb_show_usage(); + } + + do { + path = *argv; + + /* Record if the first char was a '.' so we can use dirname later. */ + do_dot = (*path == '.'); + + do { + if (rmdir(path) < 0) { + bb_perror_msg("'%s'", path); /* Match gnu rmdir msg. */ + status = EXIT_FAILURE; + } else if (flags) { + /* Note: path was not empty or null since rmdir succeeded. */ + path = dirname(path); + /* Path is now just the parent component. Note that dirname + * returns "." if there are no parents. We must distinguish + * this from the case of the original path starting with '.'. + */ + if (do_dot || (*path != '.') || path[1]) { + continue; + } + } + break; + } while (1); + + } while (*++argv); + + return status; +} diff --git a/coreutils/seq.c b/coreutils/seq.c new file mode 100644 index 000000000..f2b4706f2 --- /dev/null +++ b/coreutils/seq.c @@ -0,0 +1,40 @@ +/* vi: set sw=4 ts=4: */ +/* + * seq implementation for busybox + * + * Copyright (C) 2004, Glenn McGrath + * + * Licensed under the GPL v2, see the file LICENSE in this tarball. + */ + +#include +#include +#include "busybox.h" + +int seq_main(int argc, char **argv) +{ + double last, first, increment, i; + + first = increment = 1; + switch (argc) { + case 4: + increment = atof(argv[2]); + case 3: + first = atof(argv[1]); + case 2: + last = atof(argv[argc-1]); + break; + default: + bb_show_usage(); + } + + /* You should note that this is pos-5.0.91 semantics, -- FK. */ + for (i = first; + (increment > 0 && i <= last) || (increment < 0 && i >=last); + i += increment) + { + printf("%g\n", i); + } + + return EXIT_SUCCESS; +} diff --git a/coreutils/sleep.c b/coreutils/sleep.c new file mode 100644 index 000000000..e32e2157d --- /dev/null +++ b/coreutils/sleep.c @@ -0,0 +1,67 @@ +/* vi: set sw=4 ts=4: */ +/* + * sleep implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* BB_AUDIT GNU issues -- fancy version matches except args must be ints. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/sleep.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Rewritten to do proper arg and error checking. + * Also, added a 'fancy' configuration to accept multiple args with + * time suffixes for seconds, minutes, hours, and days. + */ + +#include +#include +#include +#include "busybox.h" + +#ifdef CONFIG_FEATURE_FANCY_SLEEP +static const struct suffix_mult sfx[] = { + { "s", 1 }, + { "m", 60 }, + { "h", 60*60 }, + { "d", 24*60*60 }, + { NULL, 0 } +}; +#endif + +int sleep_main(int argc, char **argv) +{ + unsigned int duration; + +#ifdef CONFIG_FEATURE_FANCY_SLEEP + + if (argc < 2) { + bb_show_usage(); + } + + ++argv; + duration = 0; + do { + duration += xatoul_range_sfx(*argv, 0, UINT_MAX-duration, sfx); + } while (*++argv); + +#else /* CONFIG_FEATURE_FANCY_SLEEP */ + + if (argc != 2) { + bb_show_usage(); + } + + duration = xatou(argv[1]); + +#endif /* CONFIG_FEATURE_FANCY_SLEEP */ + + if (sleep(duration)) { + bb_perror_nomsg_and_die(); + } + + return EXIT_SUCCESS; +} diff --git a/coreutils/sort.c b/coreutils/sort.c new file mode 100644 index 000000000..c23bf9c60 --- /dev/null +++ b/coreutils/sort.c @@ -0,0 +1,357 @@ +/* vi: set sw=4 ts=4: */ +/* + * SuS3 compliant sort implementation for busybox + * + * Copyright (C) 2004 by Rob Landley + * + * MAINTAINER: Rob Landley + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * See SuS3 sort standard at: + * http://www.opengroup.org/onlinepubs/007904975/utilities/sort.html + */ + +#include "busybox.h" + +static int global_flags; + +/* + sort [-m][-o output][-bdfinru][-t char][-k keydef]... [file...] + sort -c [-bdfinru][-t char][-k keydef][file] +*/ + +/* These are sort types */ +#define FLAG_n 1 /* Numeric sort */ +#define FLAG_g 2 /* Sort using strtod() */ +#define FLAG_M 4 /* Sort date */ +/* ucsz apply to root level only, not keys. b at root level implies bb */ +#define FLAG_u 8 /* Unique */ +#define FLAG_c 16 /* Check: no output, exit(!ordered) */ +#define FLAG_s 32 /* Stable sort, no ascii fallback at end */ +#define FLAG_z 64 /* Input is null terminated, not \n */ +/* These can be applied to search keys, the previous four can't */ +#define FLAG_b 128 /* Ignore leading blanks */ +#define FLAG_r 256 /* Reverse */ +#define FLAG_d 512 /* Ignore !(isalnum()|isspace()) */ +#define FLAG_f 1024 /* Force uppercase */ +#define FLAG_i 2048 /* Ignore !isprint() */ +#define FLAG_bb 32768 /* Ignore trailing blanks */ + + +#if ENABLE_FEATURE_SORT_BIG +static char key_separator; + +static struct sort_key { + struct sort_key *next_key; /* linked list */ + unsigned short range[4]; /* start word, start char, end word, end char */ + int flags; +} *key_list; + +static char *get_key(char *str, struct sort_key *key, int flags) +{ + int start = 0, end = 0, len, i, j; + + /* Special case whole string, so we don't have to make a copy */ + if (key->range[0] == 1 && !key->range[1] && !key->range[2] && !key->range[3] + && !(flags & (FLAG_b | FLAG_d | FLAG_f | FLAG_i | FLAG_bb)) + ) { + return str; + } + + /* Find start of key on first pass, end on second pass*/ + len = strlen(str); + for (j = 0; j < 2; j++) { + if (!key->range[2*j]) + end = len; + /* Loop through fields */ + else { + end = 0; + for (i = 1; i < key->range[2*j] + j; i++) { + /* Skip leading blanks or first separator */ + if (str[end]) { + if (!key_separator) + while (isspace(str[end])) end++; + } + /* Skip body of key */ + for (; str[end]; end++) { + if (key_separator) { + if (str[end] == key_separator) + break; + } else { + if (isspace(str[end])) + break; + } + } + } + } + if (!j) start = end; + } + /* Key with explicit separator starts after separator */ + if (key_separator && str[start] == key_separator) + start++; + /* Strip leading whitespace if necessary */ +//XXX: skip_whitespace() + if (flags & FLAG_b) + while (isspace(str[start])) start++; + /* Strip trailing whitespace if necessary */ + if (flags & FLAG_bb) + while (end > start && isspace(str[end-1])) end--; + /* Handle offsets on start and end */ + if (key->range[3]) { + end += key->range[3] - 1; + if (end > len) end = len; + } + if (key->range[1]) { + start += key->range[1] - 1; + if (start > len) start = len; + } + /* Make the copy */ + if (end < start) end = start; + str = xstrndup(str+start, end-start); + /* Handle -d */ + if (flags & FLAG_d) { + for (start = end = 0; str[end]; end++) + if (isspace(str[end]) || isalnum(str[end])) + str[start++] = str[end]; + str[start] = '\0'; + } + /* Handle -i */ + if (flags & FLAG_i) { + for (start = end = 0; str[end]; end++) + if (isprint(str[end])) + str[start++] = str[end]; + str[start] = '\0'; + } + /* Handle -f */ + if (flags & FLAG_f) + for (i = 0; str[i]; i++) + str[i] = toupper(str[i]); + + return str; +} + +static struct sort_key *add_key(void) +{ + struct sort_key **pkey = &key_list; + while (*pkey) + pkey = &((*pkey)->next_key); + return *pkey = xzalloc(sizeof(struct sort_key)); +} + +#define GET_LINE(fp) \ + ((global_flags & FLAG_z) \ + ? bb_get_chunk_from_file(fp, NULL) \ + : xmalloc_getline(fp)) +#else +#define GET_LINE(fp) xmalloc_getline(fp) +#endif + +/* Iterate through keys list and perform comparisons */ +static int compare_keys(const void *xarg, const void *yarg) +{ + int flags = global_flags, retval = 0; + char *x, *y; + +#if ENABLE_FEATURE_SORT_BIG + struct sort_key *key; + + for (key = key_list; !retval && key; key = key->next_key) { + flags = key->flags ? key->flags : global_flags; + /* Chop out and modify key chunks, handling -dfib */ + x = get_key(*(char **)xarg, key, flags); + y = get_key(*(char **)yarg, key, flags); +#else + /* This curly bracket serves no purpose but to match the nesting + level of the for() loop we're not using */ + { + x = *(char **)xarg; + y = *(char **)yarg; +#endif + /* Perform actual comparison */ + switch (flags & 7) { + default: + bb_error_msg_and_die("unknown sort type"); + break; + /* Ascii sort */ + case 0: + retval = strcmp(x, y); + break; +#if ENABLE_FEATURE_SORT_BIG + case FLAG_g: { + char *xx, *yy; + double dx = strtod(x, &xx); + double dy = strtod(y, &yy); + /* not numbers < NaN < -infinity < numbers < +infinity) */ + if (x == xx) + retval = (y == yy ? 0 : -1); + else if (y == yy) + retval = 1; + /* Check for isnan */ + else if (dx != dx) + retval = (dy != dy) ? 0 : -1; + else if (dy != dy) + retval = 1; + /* Check for infinity. Could underflow, but it avoids libm. */ + else if (1.0 / dx == 0.0) { + if (dx < 0) + retval = (1.0 / dy == 0.0 && dy < 0) ? 0 : -1; + else + retval = (1.0 / dy == 0.0 && dy > 0) ? 0 : 1; + } else if (1.0 / dy == 0.0) + retval = (dy < 0) ? 1 : -1; + else + retval = (dx > dy) ? 1 : ((dx < dy) ? -1 : 0); + break; + } + case FLAG_M: { + struct tm thyme; + int dx; + char *xx, *yy; + + xx = strptime(x, "%b", &thyme); + dx = thyme.tm_mon; + yy = strptime(y, "%b", &thyme); + if (!xx) + retval = (!yy) ? 0 : -1; + else if (!yy) + retval = 1; + else + retval = (dx == thyme.tm_mon) ? 0 : dx - thyme.tm_mon; + break; + } + /* Full floating point version of -n */ + case FLAG_n: { + double dx = atof(x); + double dy = atof(y); + retval = (dx > dy) ? 1 : ((dx < dy) ? -1 : 0); + break; + } + } /* switch */ + /* Free key copies. */ + if (x != *(char **)xarg) free(x); + if (y != *(char **)yarg) free(y); + /* if (retval) break; - done by for() anyway */ +#else + /* Integer version of -n for tiny systems */ + case FLAG_n: + retval = atoi(x) - atoi(y); + break; + } /* switch */ +#endif + } + /* Perform fallback sort if necessary */ + if (!retval && !(global_flags & FLAG_s)) + retval = strcmp(*(char **)xarg, *(char **)yarg); + + if (flags & FLAG_r) return -retval; + return retval; +} + +int sort_main(int argc, char **argv) +{ + FILE *fp, *outfile = NULL; + int linecount = 0, i, flag; + char *line, **lines = NULL, *optlist = "ngMucszbrdfimS:T:o:k:t:"; + int c; + + xfunc_error_retval = 2; + /* Parse command line options */ + while ((c = getopt(argc, argv, optlist)) > 0) { + line = strchr(optlist, c); + if (!line) bb_show_usage(); + switch (*line) { +#if ENABLE_FEATURE_SORT_BIG + case 'o': + if (outfile) bb_error_msg_and_die("too many -o"); + outfile = xfopen(optarg, "w"); + break; + case 't': + if (key_separator || optarg[1]) + bb_error_msg_and_die("too many -t"); + key_separator = *optarg; + break; + /* parse sort key */ + case 'k': { + struct sort_key *key = add_key(); + char *temp, *temp2; + + temp = optarg; + for (i = 0; *temp;) { + /* Start of range */ + key->range[2*i] = (unsigned short)strtol(temp, &temp, 10); + if (*temp == '.') + key->range[(2*i)+1] = (unsigned short)strtol(temp+1, &temp, 10); + for (; *temp; temp++) { + if (*temp == ',' && !i++) { + temp++; + break; + } /* no else needed: fall through to syntax error + because comma isn't in optlist */ + temp2 = strchr(optlist, *temp); + flag = (1 << (temp2 - optlist)); + if (!temp2 || (flag > FLAG_M && flag < FLAG_b)) + bb_error_msg_and_die("unknown key option"); + /* b after ',' means strip _trailing_ space */ + if (i && flag == FLAG_b) flag = FLAG_bb; + key->flags |= flag; + } + } + break; + } +#endif + default: + global_flags |= (1 << (line - optlist)); + /* global b strips leading and trailing spaces */ + if (global_flags & FLAG_b) global_flags |= FLAG_bb; + break; + } + } + /* Open input files and read data */ + for (i = argv[optind] ? optind : optind-1; argv[i]; i++) { + fp = stdin; + if (i >= optind && (argv[i][0] != '-' || argv[i][1])) + fp = xfopen(argv[i], "r"); + for (;;) { + line = GET_LINE(fp); + if (!line) break; + if (!(linecount & 63)) + lines = xrealloc(lines, sizeof(char *) * (linecount + 64)); + lines[linecount++] = line; + } + fclose(fp); + } +#if ENABLE_FEATURE_SORT_BIG + /* if no key, perform alphabetic sort */ + if (!key_list) + add_key()->range[0] = 1; + /* handle -c */ + if (global_flags & FLAG_c) { + int j = (global_flags & FLAG_u) ? -1 : 0; + for (i = 1; i < linecount; i++) + if (compare_keys(&lines[i-1], &lines[i]) > j) { + fprintf(stderr, "Check line %d\n", i); + return 1; + } + return 0; + } +#endif + /* Perform the actual sort */ + qsort(lines, linecount, sizeof(char *), compare_keys); + /* handle -u */ + if (global_flags & FLAG_u) { + for (flag = 0, i = 1; i < linecount; i++) { + if (!compare_keys(&lines[flag], &lines[i])) + free(lines[i]); + else + lines[++flag] = lines[i]; + } + if (linecount) linecount = flag+1; + } + /* Print it */ + if (!outfile) outfile = stdout; + for (i = 0; i < linecount; i++) + fprintf(outfile, "%s\n", lines[i]); + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/coreutils/stat.c b/coreutils/stat.c new file mode 100644 index 000000000..31dd6624e --- /dev/null +++ b/coreutils/stat.c @@ -0,0 +1,538 @@ +/* vi: set sw=4 ts=4: */ +/* + * stat -- display file or file system status + * + * Copyright (C) 2001, 2002, 2003, 2004, 2005 Free Software Foundation. + * Copyright (C) 2005 by Erik Andersen + * Copyright (C) 2005 by Mike Frysinger + * + * Written by Michael Meskes + * Taken from coreutils and turned into a busybox applet by Mike Frysinger + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +/* vars to control behavior */ +#define OPT_TERSE 2 +#define OPT_DEREFERENCE 4 +static long flags; + +static char const *file_type(struct stat const *st) +{ + /* See POSIX 1003.1-2001 XCU Table 4-8 lines 17093-17107 + * for some of these formats. + * To keep diagnostics grammatical in English, the + * returned string must start with a consonant. + */ + if (S_ISREG(st->st_mode)) return st->st_size == 0 ? "regular empty file" : "regular file"; + if (S_ISDIR(st->st_mode)) return "directory"; + if (S_ISBLK(st->st_mode)) return "block special file"; + if (S_ISCHR(st->st_mode)) return "character special file"; + if (S_ISFIFO(st->st_mode)) return "fifo"; + if (S_ISLNK(st->st_mode)) return "symbolic link"; + if (S_ISSOCK(st->st_mode)) return "socket"; + if (S_TYPEISMQ(st)) return "message queue"; + if (S_TYPEISSEM(st)) return "semaphore"; + if (S_TYPEISSHM(st)) return "shared memory object"; +#ifdef S_TYPEISTMO + if (S_TYPEISTMO(st)) return "typed memory object"; +#endif + return "weird file"; +} + +static char const *human_time(time_t t) +{ + /* Old + static char *str; + str = ctime(&t); + str[strlen(str)-1] = '\0'; + return str; + */ + /* coreutils 6.3 compat: */ + static char buf[sizeof("YYYY-MM-DD HH:MM:SS.000000000")]; + strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S.000000000", localtime(&t)); + return buf; +} + +/* Return the type of the specified file system. + * Some systems have statfvs.f_basetype[FSTYPSZ]. (AIX, HP-UX, and Solaris) + * Others have statfs.f_fstypename[MFSNAMELEN]. (NetBSD 1.5.2) + * Still others have neither and have to get by with f_type (Linux). + */ +static char const *human_fstype(long f_type) +{ + int i; + static const struct types { + long type; + const char *fs; + } humantypes[] = { + { 0xADFF, "affs" }, + { 0x1Cd1, "devpts" }, + { 0x137D, "ext" }, + { 0xEF51, "ext2" }, + { 0xEF53, "ext2/ext3" }, + { 0x3153464a, "jfs" }, + { 0x58465342, "xfs" }, + { 0xF995E849, "hpfs" }, + { 0x9660, "isofs" }, + { 0x4000, "isofs" }, + { 0x4004, "isofs" }, + { 0x137F, "minix" }, + { 0x138F, "minix (30 char.)" }, + { 0x2468, "minix v2" }, + { 0x2478, "minix v2 (30 char.)" }, + { 0x4d44, "msdos" }, + { 0x4006, "fat" }, + { 0x564c, "novell" }, + { 0x6969, "nfs" }, + { 0x9fa0, "proc" }, + { 0x517B, "smb" }, + { 0x012FF7B4, "xenix" }, + { 0x012FF7B5, "sysv4" }, + { 0x012FF7B6, "sysv2" }, + { 0x012FF7B7, "coh" }, + { 0x00011954, "ufs" }, + { 0x012FD16D, "xia" }, + { 0x5346544e, "ntfs" }, + { 0x1021994, "tmpfs" }, + { 0x52654973, "reiserfs" }, + { 0x28cd3d45, "cramfs" }, + { 0x7275, "romfs" }, + { 0x858458f6, "romfs" }, + { 0x73717368, "squashfs" }, + { 0x62656572, "sysfs" }, + { 0, "UNKNOWN" } + }; + for (i=0; humantypes[i].type; ++i) + if (humantypes[i].type == f_type) + break; + return humantypes[i].fs; +} + +#ifdef CONFIG_FEATURE_STAT_FORMAT +/* print statfs info */ +static void print_statfs(char *pformat, size_t buf_len, char m, + char const *filename, void const *data) +{ + struct statfs const *statfsbuf = data; + + switch (m) { + case 'n': + strncat(pformat, "s", buf_len); + printf(pformat, filename); + break; + case 'i': + strncat(pformat, "Lx", buf_len); + printf(pformat, statfsbuf->f_fsid); + break; + case 'l': + strncat(pformat, "lu", buf_len); + printf(pformat, statfsbuf->f_namelen); + break; + case 't': + strncat(pformat, "lx", buf_len); + printf(pformat, (unsigned long int) (statfsbuf->f_type)); /* no equiv. */ + break; + case 'T': + strncat(pformat, "s", buf_len); + printf(pformat, human_fstype(statfsbuf->f_type)); + break; + case 'b': + strncat(pformat, "jd", buf_len); + printf(pformat, (intmax_t) (statfsbuf->f_blocks)); + break; + case 'f': + strncat(pformat, "jd", buf_len); + printf(pformat, (intmax_t) (statfsbuf->f_bfree)); + break; + case 'a': + strncat(pformat, "jd", buf_len); + printf(pformat, (intmax_t) (statfsbuf->f_bavail)); + break; + case 'S': + case 's': + strncat(pformat, "lu", buf_len); + printf(pformat, (unsigned long int) (statfsbuf->f_bsize)); + break; + case 'c': + strncat(pformat, "jd", buf_len); + printf(pformat, (intmax_t) (statfsbuf->f_files)); + break; + case 'd': + strncat(pformat, "jd", buf_len); + printf(pformat, (intmax_t) (statfsbuf->f_ffree)); + break; + default: + strncat(pformat, "c", buf_len); + printf(pformat, m); + break; + } +} + +/* print stat info */ +static void print_stat(char *pformat, size_t buf_len, char m, + char const *filename, void const *data) +{ +#define TYPE_SIGNED(t) (! ((t) 0 < (t) -1)) + struct stat *statbuf = (struct stat *) data; + struct passwd *pw_ent; + struct group *gw_ent; + + switch (m) { + case 'n': + strncat(pformat, "s", buf_len); + printf(pformat, filename); + break; + case 'N': + strncat(pformat, "s", buf_len); + if (S_ISLNK(statbuf->st_mode)) { + char *linkname = xreadlink(filename); + if (linkname == NULL) { + bb_perror_msg("cannot read symbolic link '%s'", filename); + return; + } + /*printf("\"%s\" -> \"%s\"", filename, linkname); */ + printf(pformat, filename); + printf(" -> "); + printf(pformat, linkname); + } else { + printf(pformat, filename); + } + break; + case 'd': + strncat(pformat, "ju", buf_len); + printf(pformat, (uintmax_t) statbuf->st_dev); + break; + case 'D': + strncat(pformat, "jx", buf_len); + printf(pformat, (uintmax_t) statbuf->st_dev); + break; + case 'i': + strncat(pformat, "ju", buf_len); + printf(pformat, (uintmax_t) statbuf->st_ino); + break; + case 'a': + strncat(pformat, "lo", buf_len); + printf(pformat, (unsigned long int) (statbuf->st_mode & (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO))); + break; + case 'A': + strncat(pformat, "s", buf_len); + printf(pformat, bb_mode_string(statbuf->st_mode)); + break; + case 'f': + strncat(pformat, "lx", buf_len); + printf(pformat, (unsigned long int) statbuf->st_mode); + break; + case 'F': + strncat(pformat, "s", buf_len); + printf(pformat, file_type(statbuf)); + break; + case 'h': + strncat(pformat, "lu", buf_len); + printf(pformat, (unsigned long int) statbuf->st_nlink); + break; + case 'u': + strncat(pformat, "lu", buf_len); + printf(pformat, (unsigned long int) statbuf->st_uid); + break; + case 'U': + strncat(pformat, "s", buf_len); + setpwent(); + pw_ent = getpwuid(statbuf->st_uid); + printf(pformat, (pw_ent != 0L) ? pw_ent->pw_name : "UNKNOWN"); + break; + case 'g': + strncat(pformat, "lu", buf_len); + printf(pformat, (unsigned long int) statbuf->st_gid); + break; + case 'G': + strncat(pformat, "s", buf_len); + setgrent(); + gw_ent = getgrgid(statbuf->st_gid); + printf(pformat, (gw_ent != 0L) ? gw_ent->gr_name : "UNKNOWN"); + break; + case 't': + strncat(pformat, "lx", buf_len); + printf(pformat, (unsigned long int) major(statbuf->st_rdev)); + break; + case 'T': + strncat(pformat, "lx", buf_len); + printf(pformat, (unsigned long int) minor(statbuf->st_rdev)); + break; + case 's': + strncat(pformat, "ju", buf_len); + printf(pformat, (uintmax_t) (statbuf->st_size)); + break; + case 'B': + strncat(pformat, "lu", buf_len); + printf(pformat, (unsigned long int) 512); //ST_NBLOCKSIZE + break; + case 'b': + strncat(pformat, "ju", buf_len); + printf(pformat, (uintmax_t) statbuf->st_blocks); + break; + case 'o': + strncat(pformat, "lu", buf_len); + printf(pformat, (unsigned long int) statbuf->st_blksize); + break; + case 'x': + strncat(pformat, "s", buf_len); + printf(pformat, human_time(statbuf->st_atime)); + break; + case 'X': + strncat(pformat, TYPE_SIGNED(time_t) ? "ld" : "lu", buf_len); + printf(pformat, (unsigned long int) statbuf->st_atime); + break; + case 'y': + strncat(pformat, "s", buf_len); + printf(pformat, human_time(statbuf->st_mtime)); + break; + case 'Y': + strncat(pformat, TYPE_SIGNED(time_t) ? "ld" : "lu", buf_len); + printf(pformat, (unsigned long int) statbuf->st_mtime); + break; + case 'z': + strncat(pformat, "s", buf_len); + printf(pformat, human_time(statbuf->st_ctime)); + break; + case 'Z': + strncat(pformat, TYPE_SIGNED(time_t) ? "ld" : "lu", buf_len); + printf(pformat, (unsigned long int) statbuf->st_ctime); + break; + default: + strncat(pformat, "c", buf_len); + printf(pformat, m); + break; + } +} + +static void print_it(char const *masterformat, char const *filename, + void (*print_func) (char *, size_t, char, char const *, void const *), + void const *data) +{ + char *b; + + /* create a working copy of the format string */ + char *format = xstrdup(masterformat); + + /* Add 2 to accomodate our conversion of the stat '%s' format string + * to the printf '%llu' one. */ + size_t n_alloc = strlen(format) + 2 + 1; + char *dest = xmalloc(n_alloc); + + b = format; + while (b) { + size_t len; + char *p = strchr(b, '%'); + if (!p) { + /* coreutils 6.3 always print at the end */ + /*fputs(b, stdout);*/ + puts(b); + break; + } + *p++ = '\0'; + fputs(b, stdout); + + len = strspn(p, "#-+.I 0123456789"); + dest[0] = '%'; + memcpy(dest + 1, p, len); + dest[1 + len] = 0; + p += len; + + b = p + 1; + switch (*p) { + case '\0': + b = NULL; + /* fall through */ + case '%': + putchar('%'); + break; + default: + print_func(dest, n_alloc, *p, filename, data); + break; + } + } + + free(format); + free(dest); +} +#endif + +/* Stat the file system and print what we find. */ +static int do_statfs(char const *filename, char const *format) +{ + struct statfs statfsbuf; + + if (statfs(filename, &statfsbuf) != 0) { + bb_perror_msg("cannot read file system information for '%s'", filename); + return 0; + } + +#ifdef CONFIG_FEATURE_STAT_FORMAT + if (format == NULL) + format = (flags & OPT_TERSE + ? "%n %i %l %t %s %b %f %a %c %d\n" + : " File: \"%n\"\n" + " ID: %-8i Namelen: %-7l Type: %T\n" + "Block size: %-10s\n" + "Blocks: Total: %-10b Free: %-10f Available: %a\n" + "Inodes: Total: %-10c Free: %d"); + print_it(format, filename, print_statfs, &statfsbuf); +#else + + format = (flags & OPT_TERSE + ? "%s %llx %lu " + : " File: \"%s\"\n" + " ID: %-8Lx Namelen: %-7lu "); + printf(format, + filename, + statfsbuf.f_fsid, + statfsbuf.f_namelen); + + if (flags & OPT_TERSE) + printf("%lx ", (unsigned long int) (statfsbuf.f_type)); + else + printf("Type: %s\n", human_fstype(statfsbuf.f_type)); + + format = (flags & OPT_TERSE + ? "%lu %ld %ld %ld %ld %ld\n" + : "Block size: %-10lu\n" + "Blocks: Total: %-10jd Free: %-10jd Available: %jd\n" + "Inodes: Total: %-10jd Free: %jd\n"); + printf(format, + (unsigned long int) (statfsbuf.f_bsize), + (intmax_t) (statfsbuf.f_blocks), + (intmax_t) (statfsbuf.f_bfree), + (intmax_t) (statfsbuf.f_bavail), + (intmax_t) (statfsbuf.f_files), + (intmax_t) (statfsbuf.f_ffree)); +#endif + + return 1; +} + +/* stat the file and print what we find */ +static int do_stat(char const *filename, char const *format) +{ + struct stat statbuf; + + if ((flags & OPT_DEREFERENCE ? stat : lstat) (filename, &statbuf) != 0) { + bb_perror_msg("cannot stat '%s'", filename); + return 0; + } + +#ifdef CONFIG_FEATURE_STAT_FORMAT + if (format == NULL) { + if (flags & OPT_TERSE) { + format = "%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %o"; + } else { + if (S_ISBLK(statbuf.st_mode) || S_ISCHR(statbuf.st_mode)) { + format = + " File: \"%N\"\n" + " Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n" + "Device: %Dh/%dd\tInode: %-10i Links: %-5h" + " Device type: %t,%T\n" + "Access: (%04a/%10.10A) Uid: (%5u/%8U) Gid: (%5g/%8G)\n" + "Access: %x\n" "Modify: %y\n" "Change: %z\n"; + } else { + format = + " File: \"%N\"\n" + " Size: %-10s\tBlocks: %-10b IO Block: %-6o %F\n" + "Device: %Dh/%dd\tInode: %-10i Links: %h\n" + "Access: (%04a/%10.10A) Uid: (%5u/%8U) Gid: (%5g/%8G)\n" + "Access: %x\n" "Modify: %y\n" "Change: %z\n"; + } + } + } + print_it(format, filename, print_stat, &statbuf); +#else + if (flags & OPT_TERSE) { + printf("%s %ju %ju %lx %lu %lu %jx %ju %lu %lx %lx %lu %lu %lu %lu\n", + filename, + (uintmax_t) (statbuf.st_size), + (uintmax_t) statbuf.st_blocks, + (unsigned long int) statbuf.st_mode, + (unsigned long int) statbuf.st_uid, + (unsigned long int) statbuf.st_gid, + (uintmax_t) statbuf.st_dev, + (uintmax_t) statbuf.st_ino, + (unsigned long int) statbuf.st_nlink, + (unsigned long int) major(statbuf.st_rdev), + (unsigned long int) minor(statbuf.st_rdev), + (unsigned long int) statbuf.st_atime, + (unsigned long int) statbuf.st_mtime, + (unsigned long int) statbuf.st_ctime, + (unsigned long int) statbuf.st_blksize + ); + } else { + char *linkname = NULL; + + struct passwd *pw_ent; + struct group *gw_ent; + setgrent(); + gw_ent = getgrgid(statbuf.st_gid); + setpwent(); + pw_ent = getpwuid(statbuf.st_uid); + + if (S_ISLNK(statbuf.st_mode)) + linkname = xreadlink(filename); + if (linkname) + printf(" File: \"%s\" -> \"%s\"\n", filename, linkname); + else + printf(" File: \"%s\"\n", filename); + + printf(" Size: %-10ju\tBlocks: %-10ju IO Block: %-6lu %s\n" + "Device: %jxh/%jud\tInode: %-10ju Links: %-5lu", + (uintmax_t) (statbuf.st_size), + (uintmax_t) statbuf.st_blocks, + (unsigned long int) statbuf.st_blksize, + file_type(&statbuf), + (uintmax_t) statbuf.st_dev, + (uintmax_t) statbuf.st_dev, + (uintmax_t) statbuf.st_ino, + (unsigned long int) statbuf.st_nlink); + if (S_ISBLK(statbuf.st_mode) || S_ISCHR(statbuf.st_mode)) + printf(" Device type: %lx,%lx\n", + (unsigned long int) major(statbuf.st_rdev), + (unsigned long int) minor(statbuf.st_rdev)); + else + putchar('\n'); + printf("Access: (%04lo/%10.10s) Uid: (%5lu/%8s) Gid: (%5lu/%8s)\n" + "Access: %s\n" "Modify: %s\n" "Change: %s\n", + (unsigned long int) (statbuf.st_mode & (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)), + bb_mode_string(statbuf.st_mode), + (unsigned long int) statbuf.st_uid, + (pw_ent != 0L) ? pw_ent->pw_name : "UNKNOWN", + (unsigned long int) statbuf.st_gid, + (gw_ent != 0L) ? gw_ent->gr_name : "UNKNOWN", + human_time(statbuf.st_atime), + human_time(statbuf.st_mtime), + human_time(statbuf.st_ctime)); + } +#endif + return 1; +} + +int stat_main(int argc, char **argv) +{ + int i; + char *format = NULL; + int ok = 1; + int (*statfunc)(char const *, char const *) = do_stat; + + flags = getopt32(argc, argv, "ftL" + USE_FEATURE_STAT_FORMAT("c:", &format) + ); + + if (flags & 1) /* -f */ + statfunc = do_statfs; + if (argc == optind) /* files */ + bb_show_usage(); + + for (i = optind; i < argc; ++i) + ok &= statfunc(argv[i], format); + + return (ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/coreutils/stty.c b/coreutils/stty.c new file mode 100644 index 000000000..22784a260 --- /dev/null +++ b/coreutils/stty.c @@ -0,0 +1,1302 @@ +/* vi: set sw=4 ts=4: */ +/* stty -- change and print terminal line settings + Copyright (C) 1990-1999 Free Software Foundation, Inc. + + Licensed under the GPL v2 or later, see the file LICENSE in this tarball. +*/ +/* Usage: stty [-ag] [-F device] [setting...] + + Options: + -a Write all current settings to stdout in human-readable form. + -g Write all current settings to stdout in stty-readable form. + -F Open and use the specified device instead of stdin + + If no args are given, write to stdout the baud rate and settings that + have been changed from their defaults. Mode reading and changes + are done on the specified device, or stdin if none was specified. + + David MacKenzie + + Special for busybox ported by Vladimir Oleynik 2001 + + */ + +#include "busybox.h" + +#ifndef _POSIX_VDISABLE +# define _POSIX_VDISABLE ((unsigned char) 0) +#endif + +#define Control(c) ((c) & 0x1f) +/* Canonical values for control characters */ +#ifndef CINTR +# define CINTR Control('c') +#endif +#ifndef CQUIT +# define CQUIT 28 +#endif +#ifndef CERASE +# define CERASE 127 +#endif +#ifndef CKILL +# define CKILL Control('u') +#endif +#ifndef CEOF +# define CEOF Control('d') +#endif +#ifndef CEOL +# define CEOL _POSIX_VDISABLE +#endif +#ifndef CSTART +# define CSTART Control('q') +#endif +#ifndef CSTOP +# define CSTOP Control('s') +#endif +#ifndef CSUSP +# define CSUSP Control('z') +#endif +#if defined(VEOL2) && !defined(CEOL2) +# define CEOL2 _POSIX_VDISABLE +#endif +/* ISC renamed swtch to susp for termios, but we'll accept either name */ +#if defined(VSUSP) && !defined(VSWTCH) +# define VSWTCH VSUSP +# define CSWTCH CSUSP +#endif +#if defined(VSWTCH) && !defined(CSWTCH) +# define CSWTCH _POSIX_VDISABLE +#endif + +/* SunOS 5.3 loses (^Z doesn't work) if 'swtch' is the same as 'susp'. + So the default is to disable 'swtch.' */ +#if defined (__sparc__) && defined (__svr4__) +# undef CSWTCH +# define CSWTCH _POSIX_VDISABLE +#endif + +#if defined(VWERSE) && !defined (VWERASE) /* AIX-3.2.5 */ +# define VWERASE VWERSE +#endif +#if defined(VDSUSP) && !defined (CDSUSP) +# define CDSUSP Control('y') +#endif +#if !defined(VREPRINT) && defined(VRPRNT) /* Irix 4.0.5 */ +# define VREPRINT VRPRNT +#endif +#if defined(VREPRINT) && !defined(CRPRNT) +# define CRPRNT Control('r') +#endif +#if defined(VWERASE) && !defined(CWERASE) +# define CWERASE Control('w') +#endif +#if defined(VLNEXT) && !defined(CLNEXT) +# define CLNEXT Control('v') +#endif +#if defined(VDISCARD) && !defined(VFLUSHO) +# define VFLUSHO VDISCARD +#endif +#if defined(VFLUSH) && !defined(VFLUSHO) /* Ultrix 4.2 */ +# define VFLUSHO VFLUSH +#endif +#if defined(CTLECH) && !defined(ECHOCTL) /* Ultrix 4.3 */ +# define ECHOCTL CTLECH +#endif +#if defined(TCTLECH) && !defined(ECHOCTL) /* Ultrix 4.2 */ +# define ECHOCTL TCTLECH +#endif +#if defined(CRTKIL) && !defined(ECHOKE) /* Ultrix 4.2 and 4.3 */ +# define ECHOKE CRTKIL +#endif +#if defined(VFLUSHO) && !defined(CFLUSHO) +# define CFLUSHO Control('o') +#endif +#if defined(VSTATUS) && !defined(CSTATUS) +# define CSTATUS Control('t') +#endif + +/* Which speeds to set */ +enum speed_setting { + input_speed, output_speed, both_speeds +}; + +/* Which member(s) of 'struct termios' a mode uses */ +enum { + /* Do NOT change the order or values, as mode_type_flag() + * depends on them */ + control, input, output, local, combination +}; + +static const char evenp [] = "evenp"; +static const char raw [] = "raw"; +static const char stty_min [] = "min"; +static const char stty_time [] = "time"; +static const char stty_swtch[] = "swtch"; +static const char stty_eol [] = "eol"; +static const char stty_eof [] = "eof"; +static const char parity [] = "parity"; +static const char stty_oddp [] = "oddp"; +static const char stty_nl [] = "nl"; +static const char stty_ek [] = "ek"; +static const char stty_sane [] = "sane"; +static const char cbreak [] = "cbreak"; +static const char stty_pass8[] = "pass8"; +static const char litout [] = "litout"; +static const char cooked [] = "cooked"; +static const char decctlq [] = "decctlq"; +static const char stty_tabs [] = "tabs"; +static const char stty_lcase[] = "lcase"; +static const char stty_LCASE[] = "LCASE"; +static const char stty_crt [] = "crt"; +static const char stty_dec [] = "dec"; + +/* Flags for 'struct mode_info' */ +#define SANE_SET 1 /* Set in 'sane' mode */ +#define SANE_UNSET 2 /* Unset in 'sane' mode */ +#define REV 4 /* Can be turned off by prepending '-' */ +#define OMIT 8 /* Don't display value */ + +/* Each mode */ +struct mode_info { + const char *name; /* Name given on command line */ + char type; /* Which structure element to change */ + char flags; /* Setting and display options */ + unsigned short mask; /* Other bits to turn off for this mode */ + unsigned long bits; /* Bits to set for this mode */ +}; + +#define MI_ENTRY(N,T,F,B,M) { N, T, F, M, B } + +static const struct mode_info mode_info[] = { + MI_ENTRY("parenb", control, REV, PARENB, 0 ), + MI_ENTRY("parodd", control, REV, PARODD, 0 ), + MI_ENTRY("cs5", control, 0, CS5, CSIZE), + MI_ENTRY("cs6", control, 0, CS6, CSIZE), + MI_ENTRY("cs7", control, 0, CS7, CSIZE), + MI_ENTRY("cs8", control, 0, CS8, CSIZE), + MI_ENTRY("hupcl", control, REV, HUPCL, 0 ), + MI_ENTRY("hup", control, REV | OMIT, HUPCL, 0 ), + MI_ENTRY("cstopb", control, REV, CSTOPB, 0 ), + MI_ENTRY("cread", control, SANE_SET | REV, CREAD, 0 ), + MI_ENTRY("clocal", control, REV, CLOCAL, 0 ), +#ifdef CRTSCTS + MI_ENTRY("crtscts", control, REV, CRTSCTS, 0 ), +#endif + MI_ENTRY("ignbrk", input, SANE_UNSET | REV, IGNBRK, 0 ), + MI_ENTRY("brkint", input, SANE_SET | REV, BRKINT, 0 ), + MI_ENTRY("ignpar", input, REV, IGNPAR, 0 ), + MI_ENTRY("parmrk", input, REV, PARMRK, 0 ), + MI_ENTRY("inpck", input, REV, INPCK, 0 ), + MI_ENTRY("istrip", input, REV, ISTRIP, 0 ), + MI_ENTRY("inlcr", input, SANE_UNSET | REV, INLCR, 0 ), + MI_ENTRY("igncr", input, SANE_UNSET | REV, IGNCR, 0 ), + MI_ENTRY("icrnl", input, SANE_SET | REV, ICRNL, 0 ), + MI_ENTRY("ixon", input, REV, IXON, 0 ), + MI_ENTRY("ixoff", input, SANE_UNSET | REV, IXOFF, 0 ), + MI_ENTRY("tandem", input, REV | OMIT, IXOFF, 0 ), +#ifdef IUCLC + MI_ENTRY("iuclc", input, SANE_UNSET | REV, IUCLC, 0 ), +#endif +#ifdef IXANY + MI_ENTRY("ixany", input, SANE_UNSET | REV, IXANY, 0 ), +#endif +#ifdef IMAXBEL + MI_ENTRY("imaxbel", input, SANE_SET | REV, IMAXBEL, 0 ), +#endif + MI_ENTRY("opost", output, SANE_SET | REV, OPOST, 0 ), +#ifdef OLCUC + MI_ENTRY("olcuc", output, SANE_UNSET | REV, OLCUC, 0 ), +#endif +#ifdef OCRNL + MI_ENTRY("ocrnl", output, SANE_UNSET | REV, OCRNL, 0 ), +#endif +#ifdef ONLCR + MI_ENTRY("onlcr", output, SANE_SET | REV, ONLCR, 0 ), +#endif +#ifdef ONOCR + MI_ENTRY("onocr", output, SANE_UNSET | REV, ONOCR, 0 ), +#endif +#ifdef ONLRET + MI_ENTRY("onlret", output, SANE_UNSET | REV, ONLRET, 0 ), +#endif +#ifdef OFILL + MI_ENTRY("ofill", output, SANE_UNSET | REV, OFILL, 0 ), +#endif +#ifdef OFDEL + MI_ENTRY("ofdel", output, SANE_UNSET | REV, OFDEL, 0 ), +#endif +#ifdef NLDLY + MI_ENTRY("nl1", output, SANE_UNSET, NL1, NLDLY), + MI_ENTRY("nl0", output, SANE_SET, NL0, NLDLY), +#endif +#ifdef CRDLY + MI_ENTRY("cr3", output, SANE_UNSET, CR3, CRDLY), + MI_ENTRY("cr2", output, SANE_UNSET, CR2, CRDLY), + MI_ENTRY("cr1", output, SANE_UNSET, CR1, CRDLY), + MI_ENTRY("cr0", output, SANE_SET, CR0, CRDLY), +#endif + +#ifdef TABDLY + MI_ENTRY("tab3", output, SANE_UNSET, TAB3, TABDLY), + MI_ENTRY("tab2", output, SANE_UNSET, TAB2, TABDLY), + MI_ENTRY("tab1", output, SANE_UNSET, TAB1, TABDLY), + MI_ENTRY("tab0", output, SANE_SET, TAB0, TABDLY), +#else +# ifdef OXTABS + MI_ENTRY("tab3", output, SANE_UNSET, OXTABS, 0 ), +# endif +#endif + +#ifdef BSDLY + MI_ENTRY("bs1", output, SANE_UNSET, BS1, BSDLY), + MI_ENTRY("bs0", output, SANE_SET, BS0, BSDLY), +#endif +#ifdef VTDLY + MI_ENTRY("vt1", output, SANE_UNSET, VT1, VTDLY), + MI_ENTRY("vt0", output, SANE_SET, VT0, VTDLY), +#endif +#ifdef FFDLY + MI_ENTRY("ff1", output, SANE_UNSET, FF1, FFDLY), + MI_ENTRY("ff0", output, SANE_SET, FF0, FFDLY), +#endif + MI_ENTRY("isig", local, SANE_SET | REV, ISIG, 0 ), + MI_ENTRY("icanon", local, SANE_SET | REV, ICANON, 0 ), +#ifdef IEXTEN + MI_ENTRY("iexten", local, SANE_SET | REV, IEXTEN, 0 ), +#endif + MI_ENTRY("echo", local, SANE_SET | REV, ECHO, 0 ), + MI_ENTRY("echoe", local, SANE_SET | REV, ECHOE, 0 ), + MI_ENTRY("crterase", local, REV | OMIT, ECHOE, 0 ), + MI_ENTRY("echok", local, SANE_SET | REV, ECHOK, 0 ), + MI_ENTRY("echonl", local, SANE_UNSET | REV, ECHONL, 0 ), + MI_ENTRY("noflsh", local, SANE_UNSET | REV, NOFLSH, 0 ), +#ifdef XCASE + MI_ENTRY("xcase", local, SANE_UNSET | REV, XCASE, 0 ), +#endif +#ifdef TOSTOP + MI_ENTRY("tostop", local, SANE_UNSET | REV, TOSTOP, 0 ), +#endif +#ifdef ECHOPRT + MI_ENTRY("echoprt", local, SANE_UNSET | REV, ECHOPRT, 0 ), + MI_ENTRY("prterase", local, REV | OMIT, ECHOPRT, 0 ), +#endif +#ifdef ECHOCTL + MI_ENTRY("echoctl", local, SANE_SET | REV, ECHOCTL, 0 ), + MI_ENTRY("ctlecho", local, REV | OMIT, ECHOCTL, 0 ), +#endif +#ifdef ECHOKE + MI_ENTRY("echoke", local, SANE_SET | REV, ECHOKE, 0 ), + MI_ENTRY("crtkill", local, REV | OMIT, ECHOKE, 0 ), +#endif + MI_ENTRY(evenp, combination, REV | OMIT, 0, 0 ), + MI_ENTRY(parity, combination, REV | OMIT, 0, 0 ), + MI_ENTRY(stty_oddp, combination, REV | OMIT, 0, 0 ), + MI_ENTRY(stty_nl, combination, REV | OMIT, 0, 0 ), + MI_ENTRY(stty_ek, combination, OMIT, 0, 0 ), + MI_ENTRY(stty_sane, combination, OMIT, 0, 0 ), + MI_ENTRY(cooked, combination, REV | OMIT, 0, 0 ), + MI_ENTRY(raw, combination, REV | OMIT, 0, 0 ), + MI_ENTRY(stty_pass8, combination, REV | OMIT, 0, 0 ), + MI_ENTRY(litout, combination, REV | OMIT, 0, 0 ), + MI_ENTRY(cbreak, combination, REV | OMIT, 0, 0 ), +#ifdef IXANY + MI_ENTRY(decctlq, combination, REV | OMIT, 0, 0 ), +#endif +#if defined(TABDLY) || defined(OXTABS) + MI_ENTRY(stty_tabs, combination, REV | OMIT, 0, 0 ), +#endif +#if defined(XCASE) && defined(IUCLC) && defined(OLCUC) + MI_ENTRY(stty_lcase, combination, REV | OMIT, 0, 0 ), + MI_ENTRY(stty_LCASE, combination, REV | OMIT, 0, 0 ), +#endif + MI_ENTRY(stty_crt, combination, OMIT, 0, 0 ), + MI_ENTRY(stty_dec, combination, OMIT, 0, 0 ), +}; + +enum { + NUM_mode_info = (sizeof(mode_info) / sizeof(mode_info[0])) +}; + +/* Control character settings */ +struct control_info { + const char *name; /* Name given on command line */ + unsigned char saneval; /* Value to set for 'stty sane' */ + unsigned char offset; /* Offset in c_cc */ +}; + +/* Control characters */ + +static const struct control_info control_info[] = { + {"intr", CINTR, VINTR}, + {"quit", CQUIT, VQUIT}, + {"erase", CERASE, VERASE}, + {"kill", CKILL, VKILL}, + {stty_eof, CEOF, VEOF}, + {stty_eol, CEOL, VEOL}, +#ifdef VEOL2 + {"eol2", CEOL2, VEOL2}, +#endif +#ifdef VSWTCH + {stty_swtch, CSWTCH, VSWTCH}, +#endif + {"start", CSTART, VSTART}, + {"stop", CSTOP, VSTOP}, + {"susp", CSUSP, VSUSP}, +#ifdef VDSUSP + {"dsusp", CDSUSP, VDSUSP}, +#endif +#ifdef VREPRINT + {"rprnt", CRPRNT, VREPRINT}, +#endif +#ifdef VWERASE + {"werase", CWERASE, VWERASE}, +#endif +#ifdef VLNEXT + {"lnext", CLNEXT, VLNEXT}, +#endif +#ifdef VFLUSHO + {"flush", CFLUSHO, VFLUSHO}, +#endif +#ifdef VSTATUS + {"status", CSTATUS, VSTATUS}, +#endif + /* These must be last because of the display routines */ + {stty_min, 1, VMIN}, + {stty_time, 0, VTIME}, +}; + +enum { + NUM_control_info = (sizeof(control_info) / sizeof(control_info[0])) +}; + +/* The width of the screen, for output wrapping */ +static unsigned max_col = 80; /* default */ +/* Current position, to know when to wrap */ +static unsigned current_col; +static const char *device_name = bb_msg_standard_input; + +/* Return a string that is the printable representation of character CH */ +/* Adapted from 'cat' by Torbjorn Granlund */ +static const char *visible(unsigned int ch) +{ + static char buf[10]; + char *bpout = buf; + + if (ch == _POSIX_VDISABLE) + return ""; + + if (ch >= 128) { + ch -= 128; + *bpout++ = 'M'; + *bpout++ = '-'; + } + + if (ch < 32) { + *bpout++ = '^'; + *bpout++ = ch + 64; + } else if (ch < 127) { + *bpout++ = ch; + } else { + *bpout++ = '^'; + *bpout++ = '?'; + } + + *bpout = '\0'; + return buf; +} + +static tcflag_t *mode_type_flag(unsigned type, const struct termios *mode) +{ + static const unsigned char tcflag_offsets[] = { + offsetof(struct termios, c_cflag), /* control */ + offsetof(struct termios, c_iflag), /* input */ + offsetof(struct termios, c_oflag), /* output */ + offsetof(struct termios, c_lflag), /* local */ + }; + + if (type <= local) { + return (tcflag_t*) (((char*)mode) + tcflag_offsets[type]); + } + return NULL; +} + +static speed_t string_to_baud_or_die(const char *arg) +{ + return tty_value_to_baud(xatou(arg)); +} + +static void set_speed_or_die(enum speed_setting type, const char *arg, + struct termios *mode) +{ + speed_t baud; + + baud = string_to_baud_or_die(arg); + + if (type != output_speed) { /* either input or both */ + cfsetispeed(mode, baud); + } + if (type != input_speed) { /* either output or both */ + cfsetospeed(mode, baud); + } +} + +static ATTRIBUTE_NORETURN void perror_on_device_and_die(const char *fmt) +{ + bb_perror_msg_and_die(fmt, device_name); +} + +static void perror_on_device(const char *fmt) +{ + bb_perror_msg(fmt, device_name); +} + +/* No, inline won't be as efficient (gcc 3.4.3) */ +#define streq(a,b) (!strcmp((a),(b))) + +/* Print format string MESSAGE and optional args. + Wrap to next line first if it won't fit. + Print a space first unless MESSAGE will start a new line */ +static void wrapf(const char *message, ...) +{ + char buf[128]; + va_list args; + int buflen; + + va_start(args, message); + vsnprintf(buf, sizeof(buf), message, args); + va_end(args); + buflen = strlen(buf); + if (!buflen) return; + + if (current_col > 0) { + current_col++; + if (buf[0] != '\n') { + if (current_col + buflen >= max_col) { + putchar('\n'); + current_col = 0; + } else + putchar(' '); + } + } + fputs(buf, stdout); + current_col += buflen; + if (buf[buflen-1] == '\n') + current_col = 0; +} + +#ifdef TIOCGWINSZ + +static int get_win_size(int fd, struct winsize *win) +{ + return ioctl(fd, TIOCGWINSZ, (char *) win); +} + +static void set_window_size(int rows, int cols) +{ + struct winsize win; + + if (get_win_size(STDIN_FILENO, &win)) { + if (errno != EINVAL) { + perror_on_device("%s"); + return; + } + memset(&win, 0, sizeof(win)); + } + + if (rows >= 0) + win.ws_row = rows; + if (cols >= 0) + win.ws_col = cols; + +# ifdef TIOCSSIZE + /* Alexander Dupuy wrote: + The following code deals with a bug in the SunOS 4.x (and 3.x?) kernel. + This comment from sys/ttold.h describes Sun's twisted logic - a better + test would have been (ts_lines > 64k || ts_cols > 64k || ts_cols == 0). + At any rate, the problem is gone in Solaris 2.x */ + + if (win.ws_row == 0 || win.ws_col == 0) { + struct ttysize ttysz; + + ttysz.ts_lines = win.ws_row; + ttysz.ts_cols = win.ws_col; + + win.ws_row = win.ws_col = 1; + + if ((ioctl(STDIN_FILENO, TIOCSWINSZ, (char *) &win) != 0) + || (ioctl(STDIN_FILENO, TIOCSSIZE, (char *) &ttysz) != 0)) { + perror_on_device("%s"); + } + return; + } +# endif + + if (ioctl(STDIN_FILENO, TIOCSWINSZ, (char *) &win)) + perror_on_device("%s"); +} + +static void display_window_size(int fancy) +{ + const char *fmt_str = "%s\0%s: no size information for this device"; + struct winsize win; + + if (get_win_size(STDIN_FILENO, &win)) { + if ((errno != EINVAL) || ((fmt_str += 2), !fancy)) { + perror_on_device(fmt_str); + } + } else { + wrapf(fancy ? "rows %d; columns %d;" : "%d %d\n", + win.ws_row, win.ws_col); + } +} + +#else /* !TIOCGWINSZ */ + +static inline void display_window_size(int fancy) {} + +#endif /* !TIOCGWINSZ */ + +static int screen_columns_or_die(void) +{ + const char *s; + +#ifdef TIOCGWINSZ + struct winsize win; + + /* With Solaris 2.[123], this ioctl fails and errno is set to + EINVAL for telnet (but not rlogin) sessions. + On ISC 3.0, it fails for the console and the serial port + (but it works for ptys). + It can also fail on any system when stdout isn't a tty. + In case of any failure, just use the default */ + if (get_win_size(STDOUT_FILENO, &win) == 0 && win.ws_col > 0) + return win.ws_col; +#endif + + s = getenv("COLUMNS"); + if (s) + return xatoi_u(s); + return 80; +} + +static const struct suffix_mult stty_suffixes[] = { + {"b", 512 }, + {"k", 1024}, + {"B", 1024}, + {NULL, 0 } +}; + +static const struct mode_info *find_mode(const char *name) +{ + int i; + for (i = 0; i < NUM_mode_info; ++i) + if (streq(name, mode_info[i].name)) + return &mode_info[i]; + return 0; +} + +static const struct control_info *find_control(const char *name) +{ + int i; + for (i = 0; i < NUM_control_info; ++i) + if (streq(name, control_info[i].name)) + return &control_info[i]; + return 0; +} + +enum { + param_need_arg = 0x80, + param_line = 1 | 0x80, + param_rows = 2 | 0x80, + param_cols = 3 | 0x80, + param_size = 4, + param_speed = 5, + param_ispeed = 6 | 0x80, + param_ospeed = 7 | 0x80, +}; + +static int find_param(const char *name) +{ +#ifdef HAVE_C_LINE + if (streq(name, "line")) return param_line; +#endif +#ifdef TIOCGWINSZ + if (streq(name, "rows")) return param_rows; + if (streq(name, "cols")) return param_cols; + if (streq(name, "columns")) return param_cols; + if (streq(name, "size")) return param_size; +#endif + if (streq(name, "speed")) return param_speed; + if (streq(name, "ispeed")) return param_ispeed; + if (streq(name, "ospeed")) return param_ospeed; + return 0; +} + + +static int recover_mode(const char *arg, struct termios *mode); +static void set_mode(const struct mode_info *info, + int reversed, struct termios *mode); +static void display_all(const struct termios *mode); +static void display_changed(const struct termios *mode); +static void display_recoverable(const struct termios *mode); +static void display_speed(const struct termios *mode, int fancy); +static void sane_mode(struct termios *mode); +static void set_control_char_or_die(const struct control_info *info, + const char *arg, struct termios *mode); + +int stty_main(int argc, char **argv) +{ + struct termios mode; + void (*output_func)(const struct termios *); + const char *file_name = NULL; + int require_set_attr; + int speed_was_set; + int verbose_output; + int recoverable_output; + int noargs; + int k; + + output_func = display_changed; + noargs = 1; + speed_was_set = 0; + require_set_attr = 0; + verbose_output = 0; + recoverable_output = 0; + + /* First pass: only parse/verify command line params */ + k = 0; + while (argv[++k]) { + const struct mode_info *mp; + const struct control_info *cp; + const char *arg = argv[k]; + const char *argnext = argv[k+1]; + int param; + + if (arg[0] == '-') { + int i; + mp = find_mode(arg+1); + if (mp) { + if (!(mp->flags & REV)) + bb_error_msg_and_die("invalid argument '%s'", arg); + noargs = 0; + continue; + } + /* It is an option - parse it */ + i = 0; + while (arg[++i]) { + switch (arg[i]) { + case 'a': + verbose_output = 1; + output_func = display_all; + break; + case 'g': + recoverable_output = 1; + output_func = display_recoverable; + break; + case 'F': + if (file_name) + bb_error_msg_and_die("only one device may be specified"); + file_name = &arg[i+1]; /* "-Fdevice" ? */ + if (!file_name[0]) { /* nope, "-F device" */ + int p = k+1; /* argv[p] is argnext */ + file_name = argnext; + if (!file_name) + bb_error_msg_and_die(bb_msg_requires_arg, "-F"); + /* remove -F param from arg[vc] */ + --argc; + while (argv[p]) { argv[p] = argv[p+1]; ++p; } + } + goto end_option; + default: + bb_error_msg_and_die("invalid argument '%s'", arg); + } + } +end_option: + continue; + } + + mp = find_mode(arg); + if (mp) { + noargs = 0; + continue; + } + + cp = find_control(arg); + if (cp) { + if (!argnext) + bb_error_msg_and_die(bb_msg_requires_arg, arg); + /* called for the side effect of xfunc death only */ + set_control_char_or_die(cp, argnext, &mode); + noargs = 0; + ++k; + continue; + } + + param = find_param(arg); + if (param & param_need_arg) { + if (!argnext) + bb_error_msg_and_die(bb_msg_requires_arg, arg); + ++k; + } + + switch (param) { +#ifdef HAVE_C_LINE + case param_line: +# ifndef TIOCGWINSZ + xatoul_range_sfx(argnext, 1, INT_MAX, stty_suffixes); + break; +# endif /* else fall-through */ +#endif +#ifdef TIOCGWINSZ + case param_rows: + case param_cols: + xatoul_range_sfx(argnext, 1, INT_MAX, stty_suffixes); + break; + case param_size: +#endif + case param_speed: + break; + case param_ispeed: + /* called for the side effect of xfunc death only */ + set_speed_or_die(input_speed, argnext, &mode); + break; + case param_ospeed: + /* called for the side effect of xfunc death only */ + set_speed_or_die(output_speed, argnext, &mode); + break; + default: + if (recover_mode(arg, &mode) == 1) break; + if (string_to_baud_or_die(arg) != (speed_t) -1) break; + bb_error_msg_and_die("invalid argument '%s'", arg); + } + noargs = 0; + } + + /* Specifying both -a and -g is an error */ + if (verbose_output && recoverable_output) + bb_error_msg_and_die("verbose and stty-readable output styles are mutually exclusive"); + /* Specifying -a or -g with non-options is an error */ + if (!noargs && (verbose_output || recoverable_output)) + bb_error_msg_and_die("modes may not be set when specifying an output style"); + + /* Now it is safe to start doing things */ + if (file_name) { + int fd, fdflags; + device_name = file_name; + fd = xopen(device_name, O_RDONLY | O_NONBLOCK); + if (fd != STDIN_FILENO) { + dup2(fd, STDIN_FILENO); + close(fd); + } + fdflags = fcntl(STDIN_FILENO, F_GETFL); + if (fdflags == -1 || fcntl(STDIN_FILENO, F_SETFL, fdflags & ~O_NONBLOCK) < 0) + perror_on_device_and_die("%s: cannot reset non-blocking mode"); + } + + /* Initialize to all zeroes so there is no risk memcmp will report a + spurious difference in an uninitialized portion of the structure */ + memset(&mode, 0, sizeof(mode)); + if (tcgetattr(STDIN_FILENO, &mode)) + perror_on_device_and_die("%s"); + + if (verbose_output || recoverable_output || noargs) { + max_col = screen_columns_or_die(); + output_func(&mode); + return EXIT_SUCCESS; + } + + /* Second pass: perform actions */ + k = 0; + while (argv[++k]) { + const struct mode_info *mp; + const struct control_info *cp; + const char *arg = argv[k]; + const char *argnext = argv[k+1]; + int param; + + if (arg[0] == '-') { + mp = find_mode(arg+1); + if (mp) { + set_mode(mp, 1 /* reversed */, &mode); + } + /* It is an option - already parsed. Skip it */ + continue; + } + + mp = find_mode(arg); + if (mp) { + set_mode(mp, 0 /* non-reversed */, &mode); + continue; + } + + cp = find_control(arg); + if (cp) { + ++k; + set_control_char_or_die(cp, argnext, &mode); + continue; + } + + param = find_param(arg); + if (param & param_need_arg) { + ++k; + } + + switch (param) { +#ifdef HAVE_C_LINE + case param_line: + mode.c_line = xatoul_sfx(argnext, stty_suffixes); + require_set_attr = 1; + break; +#endif +#ifdef TIOCGWINSZ + case param_cols: + set_window_size(-1, xatoul_sfx(argnext, stty_suffixes)); + break; + case param_size: + display_window_size(0); + break; + case param_rows: + set_window_size(xatoul_sfx(argnext, stty_suffixes), -1); + break; +#endif + case param_speed: + display_speed(&mode, 0); + break; + case param_ispeed: + set_speed_or_die(input_speed, argnext, &mode); + speed_was_set = 1; + require_set_attr = 1; + break; + case param_ospeed: + set_speed_or_die(output_speed, argnext, &mode); + speed_was_set = 1; + require_set_attr = 1; + break; + default: + if (recover_mode(arg, &mode) == 1) + require_set_attr = 1; + else /* true: if (string_to_baud_or_die(arg) != (speed_t) -1) */ { + set_speed_or_die(both_speeds, arg, &mode); + speed_was_set = 1; + require_set_attr = 1; + } /* else - impossible (caught in the first pass): + bb_error_msg_and_die("invalid argument '%s'", arg); */ + } + } + + if (require_set_attr) { + struct termios new_mode; + + if (tcsetattr(STDIN_FILENO, TCSADRAIN, &mode)) + perror_on_device_and_die("%s"); + + /* POSIX (according to Zlotnick's book) tcsetattr returns zero if + it performs *any* of the requested operations. This means it + can report 'success' when it has actually failed to perform + some proper subset of the requested operations. To detect + this partial failure, get the current terminal attributes and + compare them to the requested ones */ + + /* Initialize to all zeroes so there is no risk memcmp will report a + spurious difference in an uninitialized portion of the structure */ + memset(&new_mode, 0, sizeof(new_mode)); + if (tcgetattr(STDIN_FILENO, &new_mode)) + perror_on_device_and_die("%s"); + + if (memcmp(&mode, &new_mode, sizeof(mode)) != 0) { +#ifdef CIBAUD + /* SunOS 4.1.3 (at least) has the problem that after this sequence, + tcgetattr (&m1); tcsetattr (&m1); tcgetattr (&m2); + sometimes (m1 != m2). The only difference is in the four bits + of the c_cflag field corresponding to the baud rate. To save + Sun users a little confusion, don't report an error if this + happens. But suppress the error only if we haven't tried to + set the baud rate explicitly -- otherwise we'd never give an + error for a true failure to set the baud rate */ + + new_mode.c_cflag &= (~CIBAUD); + if (speed_was_set || memcmp(&mode, &new_mode, sizeof(mode)) != 0) +#endif + perror_on_device_and_die("%s: cannot perform all requested operations"); + } + } + + return EXIT_SUCCESS; +} + +/* Save set_mode from #ifdef forest plague */ +#ifndef ONLCR +#define ONLCR 0 +#endif +#ifndef OCRNL +#define OCRNL 0 +#endif +#ifndef ONLRET +#define ONLRET 0 +#endif +#ifndef XCASE +#define XCASE 0 +#endif +#ifndef IXANY +#define IXANY 0 +#endif +#ifndef TABDLY +#define TABDLY 0 +#endif +#ifndef OXTABS +#define OXTABS 0 +#endif +#ifndef IUCLC +#define IUCLC 0 +#endif +#ifndef OLCUC +#define OLCUC 0 +#endif +#ifndef ECHOCTL +#define ECHOCTL 0 +#endif +#ifndef ECHOKE +#define ECHOKE 0 +#endif + +static void set_mode(const struct mode_info *info, int reversed, + struct termios *mode) +{ + tcflag_t *bitsp; + + bitsp = mode_type_flag(info->type, mode); + + if (bitsp) { + if (reversed) + *bitsp = *bitsp & ~((unsigned long)info->mask) & ~info->bits; + else + *bitsp = (*bitsp & ~((unsigned long)info->mask)) | info->bits; + return; + } + + /* Combination mode */ + if (info->name == evenp || info->name == parity) { + if (reversed) + mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8; + else + mode->c_cflag = (mode->c_cflag & ~PARODD & ~CSIZE) | PARENB | CS7; + } else if (info->name == stty_oddp) { + if (reversed) + mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8; + else + mode->c_cflag = (mode->c_cflag & ~CSIZE) | CS7 | PARODD | PARENB; + } else if (info->name == stty_nl) { + if (reversed) { + mode->c_iflag = (mode->c_iflag | ICRNL) & ~INLCR & ~IGNCR; + mode->c_oflag = (mode->c_oflag | ONLCR) & ~OCRNL & ~ONLRET; + } else { + mode->c_iflag = mode->c_iflag & ~ICRNL; + if (ONLCR) mode->c_oflag = mode->c_oflag & ~ONLCR; + } + } else if (info->name == stty_ek) { + mode->c_cc[VERASE] = CERASE; + mode->c_cc[VKILL] = CKILL; + } else if (info->name == stty_sane) { + sane_mode(mode); + } + else if (info->name == cbreak) { + if (reversed) + mode->c_lflag |= ICANON; + else + mode->c_lflag &= ~ICANON; + } else if (info->name == stty_pass8) { + if (reversed) { + mode->c_cflag = (mode->c_cflag & ~CSIZE) | CS7 | PARENB; + mode->c_iflag |= ISTRIP; + } else { + mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8; + mode->c_iflag &= ~ISTRIP; + } + } else if (info->name == litout) { + if (reversed) { + mode->c_cflag = (mode->c_cflag & ~CSIZE) | CS7 | PARENB; + mode->c_iflag |= ISTRIP; + mode->c_oflag |= OPOST; + } else { + mode->c_cflag = (mode->c_cflag & ~PARENB & ~CSIZE) | CS8; + mode->c_iflag &= ~ISTRIP; + mode->c_oflag &= ~OPOST; + } + } else if (info->name == raw || info->name == cooked) { + if ((info->name[0] == 'r' && reversed) + || (info->name[0] == 'c' && !reversed)) { + /* Cooked mode */ + mode->c_iflag |= BRKINT | IGNPAR | ISTRIP | ICRNL | IXON; + mode->c_oflag |= OPOST; + mode->c_lflag |= ISIG | ICANON; +#if VMIN == VEOF + mode->c_cc[VEOF] = CEOF; +#endif +#if VTIME == VEOL + mode->c_cc[VEOL] = CEOL; +#endif + } else { + /* Raw mode */ + mode->c_iflag = 0; + mode->c_oflag &= ~OPOST; + mode->c_lflag &= ~(ISIG | ICANON | XCASE); + mode->c_cc[VMIN] = 1; + mode->c_cc[VTIME] = 0; + } + } + else if (IXANY && info->name == decctlq) { + if (reversed) + mode->c_iflag |= IXANY; + else + mode->c_iflag &= ~IXANY; + } + else if (TABDLY && info->name == stty_tabs) { + if (reversed) + mode->c_oflag = (mode->c_oflag & ~TABDLY) | TAB3; + else + mode->c_oflag = (mode->c_oflag & ~TABDLY) | TAB0; + } + else if (OXTABS && info->name == stty_tabs) { + if (reversed) + mode->c_oflag |= OXTABS; + else + mode->c_oflag &= ~OXTABS; + } + else if (XCASE && IUCLC && OLCUC + && (info->name == stty_lcase || info->name == stty_LCASE)) { + if (reversed) { + mode->c_lflag &= ~XCASE; + mode->c_iflag &= ~IUCLC; + mode->c_oflag &= ~OLCUC; + } else { + mode->c_lflag |= XCASE; + mode->c_iflag |= IUCLC; + mode->c_oflag |= OLCUC; + } + } + else if (info->name == stty_crt) { + mode->c_lflag |= ECHOE | ECHOCTL | ECHOKE; + } + else if (info->name == stty_dec) { + mode->c_cc[VINTR] = 3; /* ^C */ + mode->c_cc[VERASE] = 127; /* DEL */ + mode->c_cc[VKILL] = 21; /* ^U */ + mode->c_lflag |= ECHOE | ECHOCTL | ECHOKE; + if (IXANY) mode->c_iflag &= ~IXANY; + } +} + +static void set_control_char_or_die(const struct control_info *info, + const char *arg, struct termios *mode) +{ + unsigned char value; + + if (info->name == stty_min || info->name == stty_time) + value = xatoul_range_sfx(arg, 0, 0xff, stty_suffixes); + else if (arg[0] == '\0' || arg[1] == '\0') + value = arg[0]; + else if (streq(arg, "^-") || streq(arg, "undef")) + value = _POSIX_VDISABLE; + else if (arg[0] == '^') { /* Ignore any trailing junk (^Cjunk) */ + value = arg[1] & 0x1f; /* Non-letters get weird results */ + if (arg[1] == '?') + value = 127; + } else + value = xatoul_range_sfx(arg, 0, 0xff, stty_suffixes); + mode->c_cc[info->offset] = value; +} + +static void display_changed(const struct termios *mode) +{ + int i; + tcflag_t *bitsp; + unsigned long mask; + int prev_type = control; + + display_speed(mode, 1); +#ifdef HAVE_C_LINE + wrapf("line = %d;\n", mode->c_line); +#else + wrapf("\n"); +#endif + + for (i = 0; control_info[i].name != stty_min; ++i) { + if (mode->c_cc[control_info[i].offset] == control_info[i].saneval) + continue; + /* If swtch is the same as susp, don't print both */ +#if VSWTCH == VSUSP + if (control_info[i].name == stty_swtch) + continue; +#endif + /* If eof uses the same slot as min, only print whichever applies */ +#if VEOF == VMIN + if ((mode->c_lflag & ICANON) == 0 + && (control_info[i].name == stty_eof + || control_info[i].name == stty_eol)) continue; +#endif + wrapf("%s = %s;", control_info[i].name, + visible(mode->c_cc[control_info[i].offset])); + } + if ((mode->c_lflag & ICANON) == 0) { + wrapf("min = %d; time = %d;", (int) mode->c_cc[VMIN], + (int) mode->c_cc[VTIME]); + } + if (current_col) wrapf("\n"); + + for (i = 0; i < NUM_mode_info; ++i) { + if (mode_info[i].flags & OMIT) + continue; + if (mode_info[i].type != prev_type) { + if (current_col) wrapf("\n"); + prev_type = mode_info[i].type; + } + + bitsp = mode_type_flag(mode_info[i].type, mode); + mask = mode_info[i].mask ? mode_info[i].mask : mode_info[i].bits; + if ((*bitsp & mask) == mode_info[i].bits) { + if (mode_info[i].flags & SANE_UNSET) { + wrapf("%s", mode_info[i].name); + } + } else if ((mode_info[i].flags & (SANE_SET | REV)) == (SANE_SET | REV)) { + wrapf("-%s", mode_info[i].name); + } + } + if (current_col) wrapf("\n"); +} + +static void display_all(const struct termios *mode) +{ + int i; + tcflag_t *bitsp; + unsigned long mask; + int prev_type = control; + + display_speed(mode, 1); + display_window_size(1); +#ifdef HAVE_C_LINE + wrapf("line = %d;\n", mode->c_line); +#else + wrapf("\n"); +#endif + + for (i = 0; control_info[i].name != stty_min; ++i) { + /* If swtch is the same as susp, don't print both */ +#if VSWTCH == VSUSP + if (control_info[i].name == stty_swtch) + continue; +#endif + /* If eof uses the same slot as min, only print whichever applies */ +#if VEOF == VMIN + if ((mode->c_lflag & ICANON) == 0 + && (control_info[i].name == stty_eof + || control_info[i].name == stty_eol)) continue; +#endif + wrapf("%s = %s;", control_info[i].name, + visible(mode->c_cc[control_info[i].offset])); + } +#if VEOF == VMIN + if ((mode->c_lflag & ICANON) == 0) +#endif + wrapf("min = %d; time = %d;", mode->c_cc[VMIN], mode->c_cc[VTIME]); + if (current_col) wrapf("\n"); + + for (i = 0; i < NUM_mode_info; ++i) { + if (mode_info[i].flags & OMIT) + continue; + if (mode_info[i].type != prev_type) { + wrapf("\n"); + prev_type = mode_info[i].type; + } + + bitsp = mode_type_flag(mode_info[i].type, mode); + mask = mode_info[i].mask ? mode_info[i].mask : mode_info[i].bits; + if ((*bitsp & mask) == mode_info[i].bits) + wrapf("%s", mode_info[i].name); + else if (mode_info[i].flags & REV) + wrapf("-%s", mode_info[i].name); + } + if (current_col) wrapf("\n"); +} + +static void display_speed(const struct termios *mode, int fancy) +{ + //01234567 8 9 + const char *fmt_str = "%lu %lu\n\0ispeed %lu baud; ospeed %lu baud;"; + unsigned long ispeed, ospeed; + + ospeed = ispeed = cfgetispeed(mode); + if (ispeed == 0 || ispeed == (ospeed = cfgetospeed(mode))) { + ispeed = ospeed; /* in case ispeed was 0 */ + //0123 4 5 6 7 8 9 + fmt_str = "%lu\n\0\0\0\0\0speed %lu baud;"; + } + if (fancy) fmt_str += 9; + wrapf(fmt_str, tty_baud_to_value(ispeed), tty_baud_to_value(ospeed)); +} + +static void display_recoverable(const struct termios *mode) +{ + int i; + printf("%lx:%lx:%lx:%lx", + (unsigned long) mode->c_iflag, (unsigned long) mode->c_oflag, + (unsigned long) mode->c_cflag, (unsigned long) mode->c_lflag); + for (i = 0; i < NCCS; ++i) + printf(":%x", (unsigned int) mode->c_cc[i]); + putchar('\n'); +} + +static int recover_mode(const char *arg, struct termios *mode) +{ + int i, n; + unsigned int chr; + unsigned long iflag, oflag, cflag, lflag; + + /* Scan into temporaries since it is too much trouble to figure out + the right format for 'tcflag_t' */ + if (sscanf(arg, "%lx:%lx:%lx:%lx%n", + &iflag, &oflag, &cflag, &lflag, &n) != 4) + return 0; + mode->c_iflag = iflag; + mode->c_oflag = oflag; + mode->c_cflag = cflag; + mode->c_lflag = lflag; + arg += n; + for (i = 0; i < NCCS; ++i) { + if (sscanf(arg, ":%x%n", &chr, &n) != 1) + return 0; + mode->c_cc[i] = chr; + arg += n; + } + + /* Fail if there are too many fields */ + if (*arg != '\0') + return 0; + + return 1; +} + +static void sane_mode(struct termios *mode) +{ + int i; + tcflag_t *bitsp; + + for (i = 0; i < NUM_control_info; ++i) { +#if VMIN == VEOF + if (control_info[i].name == stty_min) + break; +#endif + mode->c_cc[control_info[i].offset] = control_info[i].saneval; + } + + for (i = 0; i < NUM_mode_info; ++i) { + if (mode_info[i].flags & SANE_SET) { + bitsp = mode_type_flag(mode_info[i].type, mode); + *bitsp = (*bitsp & ~((unsigned long)mode_info[i].mask)) + | mode_info[i].bits; + } else if (mode_info[i].flags & SANE_UNSET) { + bitsp = mode_type_flag(mode_info[i].type, mode); + *bitsp = *bitsp & ~((unsigned long)mode_info[i].mask) + & ~mode_info[i].bits; + } + } +} diff --git a/coreutils/sum.c b/coreutils/sum.c new file mode 100644 index 000000000..93f4e22eb --- /dev/null +++ b/coreutils/sum.c @@ -0,0 +1,156 @@ +/* vi: set sw=4 ts=4: */ +/* + * sum -- checksum and count the blocks in a file + * Like BSD sum or SysV sum -r, except like SysV sum if -s option is given. + * + * Copyright (C) 86, 89, 91, 1995-2002, 2004 Free Software Foundation, Inc. + * Copyright (C) 2005 by Erik Andersen + * Copyright (C) 2005 by Mike Frysinger + * + * Written by Kayvan Aghaiepour and David MacKenzie + * Taken from coreutils and turned into a busybox applet by Mike Frysinger + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" + +/* 1 if any of the files read were the standard input */ +static int have_read_stdin; + +/* make a little more readable and avoid using strcmp for just 2 bytes */ +#define IS_STDIN(s) (s[0] == '-' && s[1] == '\0') + +/* Calculate and print the rotated checksum and the size in 1K blocks + of file FILE, or of the standard input if FILE is "-". + If PRINT_NAME is >1, print FILE next to the checksum and size. + The checksum varies depending on sizeof (int). + Return 1 if successful. */ +static int bsd_sum_file(const char *file, int print_name) +{ + FILE *fp; + int checksum = 0; /* The checksum mod 2^16. */ + uintmax_t total_bytes = 0; /* The number of bytes. */ + int ch; /* Each character read. */ + int ret = 0; + + if (IS_STDIN(file)) { + fp = stdin; + have_read_stdin++; + } else { + fp = fopen_or_warn(file, "r"); + if (fp == NULL) + goto out; + } + + while ((ch = getc(fp)) != EOF) { + ++total_bytes; + checksum = (checksum >> 1) + ((checksum & 1) << 15); + checksum += ch; + checksum &= 0xffff; /* Keep it within bounds. */ + } + + if (ferror(fp)) { + bb_perror_msg(file); + fclose_if_not_stdin(fp); + goto out; + } + + if (fclose_if_not_stdin(fp) == EOF) { + bb_perror_msg(file); + goto out; + } + ret++; + printf("%05d %5ju ", checksum, (total_bytes+1023)/1024); + if (print_name > 1) + puts(file); + else + puts(""); +out: + return ret; +} + +/* Calculate and print the checksum and the size in 512-byte blocks + of file FILE, or of the standard input if FILE is "-". + If PRINT_NAME is >0, print FILE next to the checksum and size. + Return 1 if successful. */ +#define MY_BUF_SIZE 8192 +static int sysv_sum_file(const char *file, int print_name) +{ + RESERVE_CONFIG_BUFFER(buf, MY_BUF_SIZE); + int fd; + uintmax_t total_bytes = 0; + + /* The sum of all the input bytes, modulo (UINT_MAX + 1). */ + unsigned int s = 0; + + if (IS_STDIN(file)) { + fd = 0; + have_read_stdin = 1; + } else { + fd = open(file, O_RDONLY); + if (fd == -1) + goto release_and_ret; + } + + while (1) { + size_t bytes_read = safe_read(fd, buf, MY_BUF_SIZE); + + if (bytes_read == 0) + break; + + if (bytes_read == -1) { +release_and_ret: + bb_perror_msg(file); + RELEASE_CONFIG_BUFFER(buf); + if (!IS_STDIN(file)) + close(fd); + return 0; + } + + total_bytes += bytes_read; + while (bytes_read--) + s += buf[bytes_read]; + } + + if (!IS_STDIN(file) && close(fd) == -1) + goto release_and_ret; + else + RELEASE_CONFIG_BUFFER(buf); + + { + int r = (s & 0xffff) + ((s & 0xffffffff) >> 16); + s = (r & 0xffff) + (r >> 16); + + printf("%d %ju ", s, (total_bytes+511)/512); + } + puts(print_name ? file : ""); + + return 1; +} + +int sum_main(int argc, char **argv) +{ + int flags; + int ok; + int (*sum_func)(const char *, int) = bsd_sum_file; + + /* give the bsd func priority over sysv func */ + flags = getopt32(argc, argv, "sr"); + if (flags & 1) + sum_func = sysv_sum_file; + if (flags & 2) + sum_func = bsd_sum_file; + + have_read_stdin = 0; + if ((argc - optind) == 0) + ok = sum_func("-", 0); + else + for (ok = 1; optind < argc; optind++) + ok &= sum_func(argv[optind], 1); + + if (have_read_stdin && fclose(stdin) == EOF) + bb_perror_msg_and_die("-"); + + exit(ok ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/coreutils/sync.c b/coreutils/sync.c new file mode 100644 index 000000000..59f0b504a --- /dev/null +++ b/coreutils/sync.c @@ -0,0 +1,23 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini sync implementation for busybox + * + * Copyright (C) 1995, 1996 by Bruce Perens . + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 N/A -- Matches GNU behavior. */ + +#include +#include +#include "busybox.h" + +int sync_main(int argc, char **argv) +{ + bb_warn_ignoring_args(argc - 1); + + sync(); + + return EXIT_SUCCESS; +} diff --git a/coreutils/tail.c b/coreutils/tail.c new file mode 100644 index 000000000..ed5ea1467 --- /dev/null +++ b/coreutils/tail.c @@ -0,0 +1,326 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini tail implementation for busybox + * + * Copyright (C) 2001 by Matt Kraai + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant (need fancy for -c) */ +/* BB_AUDIT GNU compatible -c, -q, and -v options in 'fancy' configuration. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/tail.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Pretty much rewritten to fix numerous bugs and reduce realloc() calls. + * Bugs fixed (although I may have forgotten one or two... it was pretty bad) + * 1) mixing printf/write without fflush()ing stdout + * 2) no check that any open files are present + * 3) optstring had -q taking an arg + * 4) no error checking on write in some cases, and a warning even then + * 5) q and s interaction bug + * 6) no check for lseek error + * 7) lseek attempted when count==0 even if arg was +0 (from top) + */ + +#include +#include +#include +#include +#include +#include +#include +#include "busybox.h" + +static const struct suffix_mult tail_suffixes[] = { + { "b", 512 }, + { "k", 1024 }, + { "m", 1048576 }, + { NULL, 0 } +}; + +static int status; + +static void tail_xbb_full_write(const char *buf, size_t len) +{ + /* If we get a write error, there is really no sense in continuing. */ + if (full_write(STDOUT_FILENO, buf, len) < 0) + bb_perror_nomsg_and_die(); +} + +static void tail_xprint_header(const char *fmt, const char *filename) +{ +#if defined __GLIBC__ + if (dprintf(STDOUT_FILENO, fmt, filename) < 0) { + bb_perror_nomsg_and_die(); + } +#else + int hdr_len = strlen(fmt) + strlen(filename); + char *hdr = xzalloc(hdr_len); + sprintf(hdr, filename, filename); + tail_xbb_full_write(hdr, hdr_len); +#endif +} + +static ssize_t tail_read(int fd, char *buf, size_t count) +{ + ssize_t r; + off_t current,end; + struct stat sbuf; + + end = current = lseek(fd, 0, SEEK_CUR); + if (!fstat(fd, &sbuf)) + end = sbuf.st_size; + lseek(fd, end < current ? 0 : current, SEEK_SET); + if ((r = safe_read(fd, buf, count)) < 0) { + bb_perror_msg(bb_msg_read_error); + status = EXIT_FAILURE; + } + + return r; +} + +static const char tail_opts[] = + "fn:c:" +#if ENABLE_FEATURE_FANCY_TAIL + "qs:v" +#endif + ; + +static const char header_fmt[] = "\n==> %s <==\n"; + +int tail_main(int argc, char **argv) +{ + long count = 10; + unsigned sleep_period = 1; + int from_top = 0; + int follow = 0; + int header_threshhold = 1; + int count_bytes = 0; + + char *tailbuf; + size_t tailbufsize; + int taillen = 0; + int newline = 0; + + int *fds, nfiles, nread, nwrite, seen, i, opt; + char *s, *buf; + const char *fmt; + +#if !ENABLE_DEBUG_YANK_SUSv2 || ENABLE_FEATURE_FANCY_TAIL + /* Allow legacy syntax of an initial numeric option without -n. */ + if (argc >= 2 && (argv[1][0] == '+' || argv[1][0] == '-') + && isdigit(argv[1][1]) + ) { + optind = 2; + optarg = argv[1]; + goto GET_COUNT; + } +#endif + + while ((opt = getopt(argc, argv, tail_opts)) > 0) { + switch (opt) { + case 'f': + follow = 1; + break; + case 'c': + count_bytes = 1; + /* FALLS THROUGH */ + case 'n': +#if !ENABLE_DEBUG_YANK_SUSv2 || ENABLE_FEATURE_FANCY_TAIL + GET_COUNT: +#endif + count = xatol_sfx(optarg, tail_suffixes); + /* Note: Leading whitespace is an error trapped above. */ + if (*optarg == '+') { + from_top = 1; + } else { + from_top = 0; + } + if (count < 0) { + count = -count; + } + break; +#if ENABLE_FEATURE_FANCY_TAIL + case 'q': + header_threshhold = INT_MAX; + break; + case 's': + sleep_period = xatou(optarg); + break; + case 'v': + header_threshhold = 0; + break; +#endif + default: + bb_show_usage(); + } + } + + /* open all the files */ + fds = (int *)xmalloc(sizeof(int) * (argc - optind + 1)); + + argv += optind; + nfiles = i = 0; + + if ((argc -= optind) == 0) { + struct stat statbuf; + + if (!fstat(STDIN_FILENO, &statbuf) && S_ISFIFO(statbuf.st_mode)) { + follow = 0; + } + /* --argv; */ + *argv = (char *) bb_msg_standard_input; + goto DO_STDIN; + } + + do { + if ((argv[i][0] == '-') && !argv[i][1]) { + DO_STDIN: + fds[nfiles] = STDIN_FILENO; + } else if ((fds[nfiles] = open(argv[i], O_RDONLY)) < 0) { + bb_perror_msg("%s", argv[i]); + status = EXIT_FAILURE; + continue; + } + argv[nfiles] = argv[i]; + ++nfiles; + } while (++i < argc); + + if (!nfiles) { + bb_error_msg_and_die("no files"); + } + + tailbufsize = BUFSIZ; + + /* tail the files */ + if (from_top < count_bytes) { /* Each is 0 or 1, so true iff 0 < 1. */ + /* Hence, !from_top && count_bytes */ + if (tailbufsize < count) { + tailbufsize = count + BUFSIZ; + } + } + + buf = tailbuf = xmalloc(tailbufsize); + + fmt = header_fmt + 1; /* Skip header leading newline on first output. */ + i = 0; + do { + /* Be careful. It would be possible to optimize the count-bytes + * case if the file is seekable. If you do though, remember that + * starting file position may not be the beginning of the file. + * Beware of backing up too far. See example in wc.c. + */ + if ((!(count|from_top)) && (lseek(fds[i], 0, SEEK_END) >= 0)) { + continue; + } + + if (nfiles > header_threshhold) { + tail_xprint_header(fmt, argv[i]); + fmt = header_fmt; + } + + buf = tailbuf; + taillen = 0; + seen = 1; + newline = 0; + + while ((nread = tail_read(fds[i], buf, tailbufsize-taillen)) > 0) { + if (from_top) { + nwrite = nread; + if (seen < count) { + if (count_bytes) { + nwrite -= (count - seen); + seen = count; + } else { + s = buf; + do { + --nwrite; + if ((*s++ == '\n') && (++seen == count)) { + break; + } + } while (nwrite); + } + } + tail_xbb_full_write(buf + nread - nwrite, nwrite); + } else if (count) { + if (count_bytes) { + taillen += nread; + if (taillen > count) { + memmove(tailbuf, tailbuf + taillen - count, count); + taillen = count; + } + } else { + int k = nread; + int nbuf = 0; + + while (k) { + --k; + if (buf[k] == '\n') { + ++nbuf; + } + } + + if (newline + nbuf < count) { + newline += nbuf; + taillen += nread; + + } else { + int extra = 0; + if (buf[nread-1] != '\n') { + extra = 1; + } + + k = newline + nbuf + extra - count; + s = tailbuf; + while (k) { + if (*s == '\n') { + --k; + } + ++s; + } + + taillen += nread - (s - tailbuf); + memmove(tailbuf, s, taillen); + newline = count - extra; + } + if (tailbufsize < taillen + BUFSIZ) { + tailbufsize = taillen + BUFSIZ; + tailbuf = xrealloc(tailbuf, tailbufsize); + } + } + buf = tailbuf + taillen; + } + } + + if (!from_top) { + tail_xbb_full_write(tailbuf, taillen); + } + + taillen = 0; + } while (++i < nfiles); + + buf = xrealloc(tailbuf, BUFSIZ); + + fmt = NULL; + + while (follow) { + sleep(sleep_period); + i = 0; + do { + if (nfiles > header_threshhold) { + fmt = header_fmt; + } + while ((nread = tail_read(fds[i], buf, sizeof(buf))) > 0) { + if (fmt) { + tail_xprint_header(fmt, argv[i]); + fmt = NULL; + } + tail_xbb_full_write(buf, nread); + } + } while (++i < nfiles); + } + + return status; +} diff --git a/coreutils/tee.c b/coreutils/tee.c new file mode 100644 index 000000000..640a231ef --- /dev/null +++ b/coreutils/tee.c @@ -0,0 +1,100 @@ +/* vi: set sw=4 ts=4: */ +/* + * tee implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/tee.html */ + +#include "busybox.h" +#include + +int tee_main(int argc, char **argv) +{ + const char *mode = "w\0a"; + FILE **files; + FILE **fp; + char **names; + char **np; + int flags; + int retval = EXIT_SUCCESS; +#if ENABLE_FEATURE_TEE_USE_BLOCK_IO + ssize_t c; +# define buf bb_common_bufsiz1 +#else + int c; +#endif + flags = getopt32(argc, argv, "ia"); /* 'a' must be 2nd */ + argc -= optind; + argv += optind; + + mode += (flags & 2); /* Since 'a' is the 2nd option... */ + + if (flags & 1) { + signal(SIGINT, SIG_IGN); /* TODO - switch to sigaction. */ + } + /* gnu tee ignores SIGPIPE in case one of the output files is a pipe + * that doesn't consume all its input. Good idea... */ + signal(SIGPIPE, SIG_IGN); /* TODO - switch to sigaction. */ + + /* Allocate an array of FILE *'s, with one extra for a sentinal. */ + fp = files = xzalloc(sizeof(FILE *) * (argc + 2)); + np = names = argv - 1; + + files[0] = stdout; + goto GOT_NEW_FILE; + do { + *fp = fopen_or_warn(*argv, mode); + if (*fp == NULL) { + retval = EXIT_FAILURE; + continue; + } + *np = *argv++; + GOT_NEW_FILE: + setbuf(*fp++, NULL); /* tee must not buffer output. */ + np++; + } while (*argv); + /* names[0] will be filled later */ + +#if ENABLE_FEATURE_TEE_USE_BLOCK_IO + while ((c = safe_read(STDIN_FILENO, buf, BUFSIZ)) > 0) { + fp = files; + do + fwrite(buf, 1, c, *fp++); + while (*fp); + } + if (c < 0) { /* Make sure read errors are signaled. */ + retval = EXIT_FAILURE; + } +#else + setvbuf(stdout, NULL, _IONBF, 0); + while ((c = getchar()) != EOF) { + fp = files; + do + putc(c, *fp++); + while (*fp); + } +#endif + + /* Now we need to check for i/o errors on stdin and the various + * output files. Since we know that the first entry in the output + * file table is stdout, we can save one "if ferror" test by + * setting the first entry to stdin and checking stdout error + * status with fflush_stdout_and_exit()... although fflush()ing + * is unnecessary here. */ + np = names; + fp = files; + names[0] = (char *) bb_msg_standard_input; + files[0] = stdin; + do { /* Now check for input and output errors. */ + /* Checking ferror should be sufficient, but we may want to fclose. + * If we do, remember not to close stdin! */ + die_if_ferror(*fp++, *np++); + } while (*fp); + + fflush_stdout_and_exit(retval); +} diff --git a/coreutils/test.c b/coreutils/test.c new file mode 100644 index 000000000..ebb4f9086 --- /dev/null +++ b/coreutils/test.c @@ -0,0 +1,583 @@ +/* vi: set sw=4 ts=4: */ +/* + * test implementation for busybox + * + * Copyright (c) by a whole pile of folks: + * + * test(1); version 7-like -- author Erik Baalbergen + * modified by Eric Gisin to be used as built-in. + * modified by Arnold Robbins to add SVR3 compatibility + * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket). + * modified by J.T. Conklin for NetBSD. + * modified by Herbert Xu to be used as built-in in ash. + * modified by Erik Andersen to be used + * in busybox. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Original copyright notice states: + * "This program is in the Public Domain." + */ + +#include "busybox.h" +#include +#include +#include +#include +#include + +/* test(1) accepts the following grammar: + oexpr ::= aexpr | aexpr "-o" oexpr ; + aexpr ::= nexpr | nexpr "-a" aexpr ; + nexpr ::= primary | "!" primary + primary ::= unary-operator operand + | operand binary-operator operand + | operand + | "(" oexpr ")" + ; + unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| + "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; + + binary-operator ::= "="|"=="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| + "-nt"|"-ot"|"-ef"; + operand ::= +*/ + +enum token { + EOI, + FILRD, + FILWR, + FILEX, + FILEXIST, + FILREG, + FILDIR, + FILCDEV, + FILBDEV, + FILFIFO, + FILSOCK, + FILSYM, + FILGZ, + FILTT, + FILSUID, + FILSGID, + FILSTCK, + FILNT, + FILOT, + FILEQ, + FILUID, + FILGID, + STREZ, + STRNZ, + STREQ, + STRNE, + STRLT, + STRGT, + INTEQ, + INTNE, + INTGE, + INTGT, + INTLE, + INTLT, + UNOT, + BAND, + BOR, + LPAREN, + RPAREN, + OPERAND +}; + +enum token_types { + UNOP, + BINOP, + BUNOP, + BBINOP, + PAREN +}; + +static const struct t_op { + const char *op_text; + short op_num, op_type; +} ops[] = { + { + "-r", FILRD, UNOP}, { + "-w", FILWR, UNOP}, { + "-x", FILEX, UNOP}, { + "-e", FILEXIST, UNOP}, { + "-f", FILREG, UNOP}, { + "-d", FILDIR, UNOP}, { + "-c", FILCDEV, UNOP}, { + "-b", FILBDEV, UNOP}, { + "-p", FILFIFO, UNOP}, { + "-u", FILSUID, UNOP}, { + "-g", FILSGID, UNOP}, { + "-k", FILSTCK, UNOP}, { + "-s", FILGZ, UNOP}, { + "-t", FILTT, UNOP}, { + "-z", STREZ, UNOP}, { + "-n", STRNZ, UNOP}, { + "-h", FILSYM, UNOP}, /* for backwards compat */ + { + "-O", FILUID, UNOP}, { + "-G", FILGID, UNOP}, { + "-L", FILSYM, UNOP}, { + "-S", FILSOCK, UNOP}, { + "=", STREQ, BINOP}, { + "==", STREQ, BINOP}, { + "!=", STRNE, BINOP}, { + "<", STRLT, BINOP}, { + ">", STRGT, BINOP}, { + "-eq", INTEQ, BINOP}, { + "-ne", INTNE, BINOP}, { + "-ge", INTGE, BINOP}, { + "-gt", INTGT, BINOP}, { + "-le", INTLE, BINOP}, { + "-lt", INTLT, BINOP}, { + "-nt", FILNT, BINOP}, { + "-ot", FILOT, BINOP}, { + "-ef", FILEQ, BINOP}, { + "!", UNOT, BUNOP}, { + "-a", BAND, BBINOP}, { + "-o", BOR, BBINOP}, { + "(", LPAREN, PAREN}, { + ")", RPAREN, PAREN}, { + 0, 0, 0} +}; + +#ifdef CONFIG_FEATURE_TEST_64 +typedef int64_t arith_t; +#else +typedef int arith_t; +#endif + +static char **t_wp; +static struct t_op const *t_wp_op; +static gid_t *group_array; +static int ngroups; + +static enum token t_lex(char *s); +static arith_t oexpr(enum token n); +static arith_t aexpr(enum token n); +static arith_t nexpr(enum token n); +static int binop(void); +static arith_t primary(enum token n); +static int filstat(char *nm, enum token mode); +static arith_t getn(const char *s); +static int newerf(const char *f1, const char *f2); +static int olderf(const char *f1, const char *f2); +static int equalf(const char *f1, const char *f2); +static int test_eaccess(char *path, int mode); +static int is_a_group_member(gid_t gid); +static void initialize_group_array(void); + +static jmp_buf leaving; + +int bb_test(int argc, char **argv) +{ + int res; + + if (strcmp(argv[0], "[") == 0) { + if (strcmp(argv[--argc], "]")) { + bb_error_msg("missing ]"); + return 2; + } + argv[argc] = NULL; + } else if (strcmp(argv[0], "[[") == 0) { + if (strcmp(argv[--argc], "]]")) { + bb_error_msg("missing ]]"); + return 2; + } + argv[argc] = NULL; + } + + res = setjmp(leaving); + if (res) + return res; + + /* resetting ngroups is probably unnecessary. it will + * force a new call to getgroups(), which prevents using + * group data fetched during a previous call. but the + * only way the group data could be stale is if there's + * been an intervening call to setgroups(), and this + * isn't likely in the case of a shell. paranoia + * prevails... + */ + ngroups = 0; + + /* Implement special cases from POSIX.2, section 4.62.4 */ + switch (argc) { + case 1: + return 1; + case 2: + return *argv[1] == '\0'; + case 3: + if (argv[1][0] == '!' && argv[1][1] == '\0') { + return *argv[2] != '\0'; + } + break; + case 4: + if (argv[1][0] != '!' || argv[1][1] != '\0') { + if (t_lex(argv[2]), t_wp_op && t_wp_op->op_type == BINOP) { + t_wp = &argv[1]; + return binop() == 0; + } + } + break; + case 5: + if (argv[1][0] == '!' && argv[1][1] == '\0') { + if (t_lex(argv[3]), t_wp_op && t_wp_op->op_type == BINOP) { + t_wp = &argv[2]; + return binop() != 0; + } + } + break; + } + + t_wp = &argv[1]; + res = !oexpr(t_lex(*t_wp)); + + if (*t_wp != NULL && *++t_wp != NULL) { + bb_error_msg("%s: unknown operand", *t_wp); + return 2; + } + return res; +} + +static void syntax(const char *op, const char *msg) +{ + if (op && *op) { + bb_error_msg("%s: %s", op, msg); + } else { + bb_error_msg("%s", msg); + } + longjmp(leaving, 2); +} + +static arith_t oexpr(enum token n) +{ + arith_t res; + + res = aexpr(n); + if (t_lex(*++t_wp) == BOR) { + return oexpr(t_lex(*++t_wp)) || res; + } + t_wp--; + return res; +} + +static arith_t aexpr(enum token n) +{ + arith_t res; + + res = nexpr(n); + if (t_lex(*++t_wp) == BAND) + return aexpr(t_lex(*++t_wp)) && res; + t_wp--; + return res; +} + +static arith_t nexpr(enum token n) +{ + if (n == UNOT) + return !nexpr(t_lex(*++t_wp)); + return primary(n); +} + +static arith_t primary(enum token n) +{ + arith_t res; + + if (n == EOI) { + syntax(NULL, "argument expected"); + } + if (n == LPAREN) { + res = oexpr(t_lex(*++t_wp)); + if (t_lex(*++t_wp) != RPAREN) + syntax(NULL, "closing paren expected"); + return res; + } + if (t_wp_op && t_wp_op->op_type == UNOP) { + /* unary expression */ + if (*++t_wp == NULL) + syntax(t_wp_op->op_text, "argument expected"); + switch (n) { + case STREZ: + return strlen(*t_wp) == 0; + case STRNZ: + return strlen(*t_wp) != 0; + case FILTT: + return isatty(getn(*t_wp)); + default: + return filstat(*t_wp, n); + } + } + + if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) { + return binop(); + } + + return strlen(*t_wp) > 0; +} + +static int binop(void) +{ + const char *opnd1, *opnd2; + struct t_op const *op; + + opnd1 = *t_wp; + (void) t_lex(*++t_wp); + op = t_wp_op; + + if ((opnd2 = *++t_wp) == (char *) 0) + syntax(op->op_text, "argument expected"); + + switch (op->op_num) { + case STREQ: + return strcmp(opnd1, opnd2) == 0; + case STRNE: + return strcmp(opnd1, opnd2) != 0; + case STRLT: + return strcmp(opnd1, opnd2) < 0; + case STRGT: + return strcmp(opnd1, opnd2) > 0; + case INTEQ: + return getn(opnd1) == getn(opnd2); + case INTNE: + return getn(opnd1) != getn(opnd2); + case INTGE: + return getn(opnd1) >= getn(opnd2); + case INTGT: + return getn(opnd1) > getn(opnd2); + case INTLE: + return getn(opnd1) <= getn(opnd2); + case INTLT: + return getn(opnd1) < getn(opnd2); + case FILNT: + return newerf(opnd1, opnd2); + case FILOT: + return olderf(opnd1, opnd2); + case FILEQ: + return equalf(opnd1, opnd2); + } + /* NOTREACHED */ + return 1; +} + +static int filstat(char *nm, enum token mode) +{ + struct stat s; + unsigned int i; + + if (mode == FILSYM) { +#ifdef S_IFLNK + if (lstat(nm, &s) == 0) { + i = S_IFLNK; + goto filetype; + } +#endif + return 0; + } + + if (stat(nm, &s) != 0) + return 0; + + switch (mode) { + case FILRD: + return test_eaccess(nm, R_OK) == 0; + case FILWR: + return test_eaccess(nm, W_OK) == 0; + case FILEX: + return test_eaccess(nm, X_OK) == 0; + case FILEXIST: + return 1; + case FILREG: + i = S_IFREG; + goto filetype; + case FILDIR: + i = S_IFDIR; + goto filetype; + case FILCDEV: + i = S_IFCHR; + goto filetype; + case FILBDEV: + i = S_IFBLK; + goto filetype; + case FILFIFO: +#ifdef S_IFIFO + i = S_IFIFO; + goto filetype; +#else + return 0; +#endif + case FILSOCK: +#ifdef S_IFSOCK + i = S_IFSOCK; + goto filetype; +#else + return 0; +#endif + case FILSUID: + i = S_ISUID; + goto filebit; + case FILSGID: + i = S_ISGID; + goto filebit; + case FILSTCK: + i = S_ISVTX; + goto filebit; + case FILGZ: + return s.st_size > 0L; + case FILUID: + return s.st_uid == geteuid(); + case FILGID: + return s.st_gid == getegid(); + default: + return 1; + } + + filetype: + return ((s.st_mode & S_IFMT) == i); + + filebit: + return ((s.st_mode & i) != 0); +} + +static enum token t_lex(char *s) +{ + struct t_op const *op = ops; + + if (s == 0) { + t_wp_op = (struct t_op *) 0; + return EOI; + } + while (op->op_text) { + if (strcmp(s, op->op_text) == 0) { + t_wp_op = op; + return op->op_num; + } + op++; + } + t_wp_op = (struct t_op *) 0; + return OPERAND; +} + +/* atoi with error detection */ +static arith_t getn(const char *s) +{ + char *p; +#ifdef CONFIG_FEATURE_TEST_64 + long long r; +#else + long r; +#endif + + errno = 0; +#ifdef CONFIG_FEATURE_TEST_64 + r = strtoll(s, &p, 10); +#else + r = strtol(s, &p, 10); +#endif + + if (errno != 0) + syntax(s, "out of range"); + + if (*(skip_whitespace(p))) + syntax(s, "bad number"); + + return r; +} + +static int newerf(const char *f1, const char *f2) +{ + struct stat b1, b2; + + return (stat(f1, &b1) == 0 && + stat(f2, &b2) == 0 && b1.st_mtime > b2.st_mtime); +} + +static int olderf(const char *f1, const char *f2) +{ + struct stat b1, b2; + + return (stat(f1, &b1) == 0 && + stat(f2, &b2) == 0 && b1.st_mtime < b2.st_mtime); +} + +static int equalf(const char *f1, const char *f2) +{ + struct stat b1, b2; + + return (stat(f1, &b1) == 0 && + stat(f2, &b2) == 0 && + b1.st_dev == b2.st_dev && b1.st_ino == b2.st_ino); +} + +/* Do the same thing access(2) does, but use the effective uid and gid, + and don't make the mistake of telling root that any file is + executable. */ +static int test_eaccess(char *path, int mode) +{ + struct stat st; + unsigned int euid = geteuid(); + + if (stat(path, &st) < 0) + return -1; + + if (euid == 0) { + /* Root can read or write any file. */ + if (mode != X_OK) + return 0; + + /* Root can execute any file that has any one of the execute + bits set. */ + if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) + return 0; + } + + if (st.st_uid == euid) /* owner */ + mode <<= 6; + else if (is_a_group_member(st.st_gid)) + mode <<= 3; + + if (st.st_mode & mode) + return 0; + + return -1; +} + +static void initialize_group_array(void) +{ + ngroups = getgroups(0, NULL); + if (ngroups > 0) { + group_array = xmalloc(ngroups * sizeof(gid_t)); + getgroups(ngroups, group_array); + } +} + +/* Return non-zero if GID is one that we have in our groups list. */ +static int is_a_group_member(gid_t gid) +{ + int i; + + /* Short-circuit if possible, maybe saving a call to getgroups(). */ + if (gid == getgid() || gid == getegid()) + return 1; + + if (ngroups == 0) + initialize_group_array(); + + /* Search through the list looking for GID. */ + for (i = 0; i < ngroups; i++) + if (gid == group_array[i]) + return 1; + + return 0; +} + + +/* applet entry point */ + +int test_main(int argc, char **argv) +{ + exit(bb_test(argc, argv)); +} + diff --git a/coreutils/touch.c b/coreutils/touch.c new file mode 100644 index 000000000..e1af7d0dc --- /dev/null +++ b/coreutils/touch.c @@ -0,0 +1,63 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini touch implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 _NOT_ compliant -- options -a, -m, -r, -t not supported. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/touch.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Previous version called open() and then utime(). While this will be + * be necessary to implement -r and -t, it currently only makes things bigger. + * Also, exiting on a failure was a bug. All args should be processed. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "busybox.h" + +int touch_main(int argc, char **argv) +{ + int fd; + int flags; + int status = EXIT_SUCCESS; + + flags = getopt32(argc, argv, "c"); + + argv += optind; + + if (!*argv) { + bb_show_usage(); + } + + do { + if (utime(*argv, NULL)) { + if (errno == ENOENT) { /* no such file*/ + if (flags & 1) { /* Creation is disabled, so ignore. */ + continue; + } + /* Try to create the file. */ + fd = open(*argv, O_RDWR | O_CREAT, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH + ); + if ((fd >= 0) && !close(fd)) { + continue; + } + } + status = EXIT_FAILURE; + bb_perror_msg("%s", *argv); + } + } while (*++argv); + + return status; +} diff --git a/coreutils/tr.c b/coreutils/tr.c new file mode 100644 index 000000000..237ade047 --- /dev/null +++ b/coreutils/tr.c @@ -0,0 +1,253 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini tr implementation for busybox + * + ** Copyright (c) 1987,1997, Prentice Hall All rights reserved. + * + * The name of Prentice Hall may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * Copyright (c) Michiel Huisjes + * + * This version of tr is adapted from Minix tr and was modified + * by Erik Andersen to be used in busybox. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +// Even with -funsigned-char, gcc still complains about char as an array index. + +#define GCC4_IS_STUPID int + +#define ASCII 0377 + +/* some "globals" shared across this file */ +static char com_fl, del_fl, sq_fl; +/* these last are pointers to static buffers declared in tr_main */ +static char *poutput, *pvector, *pinvec, *poutvec; + +static void convert(void) +{ + int read_chars = 0, in_index = 0, out_index = 0, c, coded, last = -1; + + for (;;) { + // If we're out of input, flush output and read more input. + + if (in_index == read_chars) { + if (out_index) { + if (write(1, (char *) poutput, out_index) != out_index) + bb_error_msg_and_die(bb_msg_write_error); + out_index = 0; + } + + if ((read_chars = read(0, bb_common_bufsiz1, BUFSIZ)) <= 0) { + if (write(1, (char *) poutput, out_index) != out_index) + bb_error_msg(bb_msg_write_error); + exit(0); + } + in_index = 0; + } + c = bb_common_bufsiz1[in_index++]; + coded = pvector[c]; + if (del_fl && pinvec[c]) + continue; + if (sq_fl && last == coded && (pinvec[c] || poutvec[coded])) + continue; + poutput[out_index++] = last = coded; + } + + /* NOTREACHED */ +} + +static void map(char *string1, unsigned int string1_len, + char *string2, unsigned int string2_len) +{ + char last = '0'; + unsigned int i, j; + + for (j = 0, i = 0; i < string1_len; i++) { + if (string2_len <= j) + pvector[(GCC4_IS_STUPID)string1[i]] = last; + else + pvector[(GCC4_IS_STUPID)string1[i]] = last = string2[j++]; + } +} + +/* supported constructs: + * Ranges, e.g., [0-9] ==> 0123456789 + * Escapes, e.g., \a ==> Control-G + * Character classes, e.g. [:upper:] ==> A ... Z + */ +static unsigned int expand(const char *arg, char *buffer) +{ + char *buffer_start = buffer; + int i, ac; + + while (*arg) { + if (*arg == '\\') { + arg++; + *buffer++ = bb_process_escape_sequence(&arg); + } else if (*(arg+1) == '-') { + ac = *(arg+2); + if(ac == 0) { + *buffer++ = *arg++; + continue; + } + i = *arg; + while (i <= ac) + *buffer++ = i++; + arg += 3; /* Skip the assumed a-z */ + } else if (*arg == '[') { + arg++; + i = *arg++; + if (ENABLE_FEATURE_TR_CLASSES && i == ':') { + if (strncmp(arg, "alpha", 5) == 0) { + for (i = 'A'; i <= 'Z'; i++) + *buffer++ = i; + for (i = 'a'; i <= 'z'; i++) + *buffer++ = i; + } + else if (strncmp(arg, "alnum", 5) == 0) { + for (i = '0'; i <= '9'; i++) + *buffer++ = i; + for (i = 'A'; i <= 'Z'; i++) + *buffer++ = i; + for (i = 'a'; i <= 'z'; i++) + *buffer++ = i; + } + else if (strncmp(arg, "digit", 5) == 0) + for (i = '0'; i <= '9'; i++) + *buffer++ = i; + else if (strncmp(arg, "lower", 5) == 0) + for (i = 'a'; i <= 'z'; i++) + *buffer++ = i; + else if (strncmp(arg, "upper", 5) == 0) + for (i = 'A'; i <= 'Z'; i++) + *buffer++ = i; + else if (strncmp(arg, "space", 5) == 0) { + const char s[] = "\t\n\v\f\r "; + strcat((char*)buffer, s); + buffer += sizeof(s) - 1; + } + else if (strncmp(arg, "blank", 5) == 0) { + *buffer++ = '\t'; + *buffer++ = ' '; + } + /* gcc gives a warning if braces aren't used here */ + else if (strncmp(arg, "punct", 5) == 0) { + for (i = 0; i <= ASCII; i++) + if (isprint(i) && (!isalnum(i)) && (!isspace(i))) + *buffer++ = i; + } + else if (strncmp(arg, "cntrl", 5) == 0) { + for (i = 0; i <= ASCII; i++) + if (iscntrl(i)) + *buffer++ = i; + } + else { + *buffer++ = '['; + *buffer++ = ':'; + continue; + } + break; + } + if (ENABLE_FEATURE_TR_EQUIV && i == '=') { + *buffer++ = *arg; + /* skip the closing =] */ + arg += 3; + continue; + } + if (*arg++ != '-') { + *buffer++ = '['; + arg -= 2; + continue; + } + ac = *arg++; + while (i <= ac) + *buffer++ = i++; + arg++; /* Skip the assumed ']' */ + } else + *buffer++ = *arg++; + } + + return (buffer - buffer_start); +} + +static int complement(char *buffer, int buffer_len) +{ + short i, j, ix; + char conv[ASCII + 2]; + + ix = 0; + for (i = 0; i <= ASCII; i++) { + for (j = 0; j < buffer_len; j++) + if (buffer[j] == i) + break; + if (j == buffer_len) + conv[ix++] = i & ASCII; + } + memcpy(buffer, conv, ix); + return ix; +} + +int tr_main(int argc, char **argv) +{ + unsigned char *ptr; + int output_length=0, input_length; + int idx = 1; + int i; + RESERVE_CONFIG_BUFFER(output, BUFSIZ); + RESERVE_CONFIG_BUFFER(vector, ASCII+1); + RESERVE_CONFIG_BUFFER(invec, ASCII+1); + RESERVE_CONFIG_BUFFER(outvec, ASCII+1); + + /* ... but make them available globally */ + poutput = output; + pvector = vector; + pinvec = invec; + poutvec = outvec; + + if (argc > 1 && argv[idx][0] == '-') { + for (ptr = (unsigned char *) &argv[idx][1]; *ptr; ptr++) { + switch (*ptr) { + case 'c': + com_fl = TRUE; + break; + case 'd': + del_fl = TRUE; + break; + case 's': + sq_fl = TRUE; + break; + default: + bb_show_usage(); + } + } + idx++; + } + for (i = 0; i <= ASCII; i++) { + vector[i] = i; + invec[i] = outvec[i] = FALSE; + } + + if (argv[idx] != NULL) { + input_length = expand(argv[idx++], bb_common_bufsiz1); + if (com_fl) + input_length = complement(bb_common_bufsiz1, input_length); + if (argv[idx] != NULL) { + if (*argv[idx] == '\0') + bb_error_msg_and_die("STRING2 cannot be empty"); + output_length = expand(argv[idx], output); + map(bb_common_bufsiz1, input_length, output, output_length); + } + for (i = 0; i < input_length; i++) + invec[(GCC4_IS_STUPID)bb_common_bufsiz1[i]] = TRUE; + for (i = 0; i < output_length; i++) + outvec[(GCC4_IS_STUPID)output[i]] = TRUE; + } + convert(); + return 0; +} diff --git a/coreutils/true.c b/coreutils/true.c new file mode 100644 index 000000000..388e897c0 --- /dev/null +++ b/coreutils/true.c @@ -0,0 +1,19 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini true implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/true.html */ + +#include +#include "busybox.h" + +int true_main(int argc, char **argv) +{ + return EXIT_SUCCESS; +} diff --git a/coreutils/tty.c b/coreutils/tty.c new file mode 100644 index 000000000..b2ab862ec --- /dev/null +++ b/coreutils/tty.c @@ -0,0 +1,45 @@ +/* vi: set sw=4 ts=4: */ +/* + * tty implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/tty.html */ + +#include +#include +#include +#include "busybox.h" + +int tty_main(int argc, char **argv) +{ + const char *s; + int silent; /* Note: No longer relevant in SUSv3. */ + int retval; + + xfunc_error_retval = 2; /* SUSv3 requires > 1 for error. */ + + silent = getopt32(argc, argv, "s"); + + /* gnu tty outputs a warning that it is ignoring all args. */ + bb_warn_ignoring_args(argc - optind); + + retval = 0; + + if ((s = ttyname(0)) == NULL) { + /* According to SUSv3, ttyname can on fail with EBADF or ENOTTY. + * We know the file descriptor is good, so failure means not a tty. */ + s = "not a tty"; + retval = 1; + } + + if (!silent) { + puts(s); + } + + fflush_stdout_and_exit(retval); +} diff --git a/coreutils/uname.c b/coreutils/uname.c new file mode 100644 index 000000000..5a3eafe92 --- /dev/null +++ b/coreutils/uname.c @@ -0,0 +1,107 @@ +/* vi: set sw=4 ts=4: */ +/* uname -- print system information + * Copyright (C) 1989-1999 Free Software Foundation, Inc. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/uname.html */ + +/* Option Example + + -s, --sysname SunOS + -n, --nodename rocky8 + -r, --release 4.0 + -v, --version + -m, --machine sun + -a, --all SunOS rocky8 4.0 sun + + The default behavior is equivalent to `-s'. + + David MacKenzie */ + +/* Busyboxed by Erik Andersen */ + +/* Further size reductions by Glenn McGrath and Manuel Novoa III. */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Now does proper error checking on i/o. Plus some further space savings. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "busybox.h" + +typedef struct { + struct utsname name; + char processor[8]; /* for "unknown" */ +} uname_info_t; + +static const char options[] = "snrvmpa"; +static const unsigned short int utsname_offset[] = { + offsetof(uname_info_t,name.sysname), + offsetof(uname_info_t,name.nodename), + offsetof(uname_info_t,name.release), + offsetof(uname_info_t,name.version), + offsetof(uname_info_t,name.machine), + offsetof(uname_info_t,processor) +}; + +int uname_main(int argc, char **argv) +{ + uname_info_t uname_info; +#if defined(__sparc__) && defined(__linux__) + char *fake_sparc = getenv("FAKE_SPARC"); +#endif + const unsigned short int *delta; + char toprint; + + toprint = getopt32(argc, argv, options); + + if (argc != optind) { + bb_show_usage(); + } + + if (toprint & (1 << 6)) { + toprint = 0x3f; + } + + if (toprint == 0) { + toprint = 1; /* sysname */ + } + + if (uname(&uname_info.name) == -1) { + bb_error_msg_and_die("cannot get system name"); + } + +#if defined(__sparc__) && defined(__linux__) + if ((fake_sparc != NULL) + && ((fake_sparc[0] == 'y') + || (fake_sparc[0] == 'Y'))) { + strcpy(uname_info.name.machine, "sparc"); + } +#endif + + strcpy(uname_info.processor, "unknown"); + + delta = utsname_offset; + do { + if (toprint & 1) { + printf(((char *)(&uname_info)) + *delta); + if (toprint > 1) { + putchar(' '); + } + } + ++delta; + } while (toprint >>= 1); + putchar('\n'); + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/coreutils/uniq.c b/coreutils/uniq.c new file mode 100644 index 000000000..100f2be00 --- /dev/null +++ b/coreutils/uniq.c @@ -0,0 +1,103 @@ +/* vi: set sw=4 ts=4: */ +/* + * uniq implementation for busybox + * + * Copyright (C) 2005 Manuel Novoa III + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +/* BB_AUDIT SUSv3 compliant */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/uniq.html */ + +#include "busybox.h" + +static const char uniq_opts[] = "f:s:" "cdu\0\1\2\4"; + +static FILE *xgetoptfile_uniq_s(char **argv, int read0write2) +{ + const char *n; + + if ((n = *argv) != NULL) { + if ((*n != '-') || n[1]) { + return xfopen(n, "r\0w" + read0write2); + } + } + return (read0write2) ? stdout : stdin; +} + +int uniq_main(int argc, char **argv) +{ + FILE *in, *out; + unsigned long dups, skip_fields, skip_chars, i, uniq_flags; + const char *s0, *e0, *s1, *e1, *input_filename; + int opt; + + uniq_flags = skip_fields = skip_chars = 0; + + while ((opt = getopt(argc, argv, uniq_opts)) > 0) { + if ((opt == 'f') || (opt == 's')) { + unsigned long t = xatoul(optarg); + if (opt == 'f') { + skip_fields = t; + } else { + skip_chars = t; + } + } else if ((s0 = strchr(uniq_opts, opt)) != NULL) { + uniq_flags |= s0[4]; + } else { + bb_show_usage(); + } + } + + input_filename = *(argv += optind); + + in = xgetoptfile_uniq_s(argv, 0); + if (*argv) { + ++argv; + } + out = xgetoptfile_uniq_s(argv, 2); + if (*argv && argv[1]) { + bb_show_usage(); + } + + s1 = e1 = NULL; /* prime the pump */ + + do { + s0 = s1; + e0 = e1; + dups = 0; + + /* gnu uniq ignores newlines */ + while ((s1 = xmalloc_getline(in)) != NULL) { + e1 = s1; + for (i = skip_fields; i; i--) { + e1 = skip_whitespace(e1); + while (*e1 && !isspace(*e1)) { + ++e1; + } + } + for (i = skip_chars; *e1 && i; i--) { + ++e1; + } + + if (!s0 || strcmp(e0, e1)) { + break; + } + + ++dups; /* Note: Testing for overflow seems excessive. */ + } + + if (s0) { + if (!(uniq_flags & (2 << !!dups))) { + fprintf(out, "\0%d " + (uniq_flags & 1), dups + 1); + fprintf(out, "%s\n", s0); + } + free((void *)s0); + } + } while (s1); + + die_if_ferror(in, input_filename); + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/coreutils/usleep.c b/coreutils/usleep.c new file mode 100644 index 000000000..de473a7b2 --- /dev/null +++ b/coreutils/usleep.c @@ -0,0 +1,28 @@ +/* vi: set sw=4 ts=4: */ +/* + * usleep implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 N/A -- Apparently a busybox extension. */ + +#include +#include +#include +#include "busybox.h" + +int usleep_main(int argc, char **argv) +{ + if (argc != 2) { + bb_show_usage(); + } + + if (usleep(xatou(argv[1]))) { + bb_perror_nomsg_and_die(); + } + + return EXIT_SUCCESS; +} diff --git a/coreutils/uudecode.c b/coreutils/uudecode.c new file mode 100644 index 000000000..8b7de74ff --- /dev/null +++ b/coreutils/uudecode.c @@ -0,0 +1,181 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright 2003, Glenn McGrath + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Based on specification from + * http://www.opengroup.org/onlinepubs/007904975/utilities/uuencode.html + * + * Bugs: the spec doesn't mention anything about "`\n`\n" prior to the + * "end" line + */ + + +#include "busybox.h" + +static int read_stduu(FILE *src_stream, FILE *dst_stream) +{ + char *line; + + while ((line = xmalloc_getline(src_stream)) != NULL) { + int length; + char *line_ptr = line; + + if (strcmp(line, "end") == 0) { + return EXIT_SUCCESS; + } + length = ((*line_ptr - 0x20) & 0x3f)* 4 / 3; + + if (length <= 0) { + /* Ignore the "`\n" line, why is it even in the encode file ? */ + continue; + } + if (length > 60) { + bb_error_msg_and_die("line too long"); + } + + line_ptr++; + /* Tolerate an overly long line to accomodate a possible exta '`' */ + if (strlen(line_ptr) < (size_t)length) { + bb_error_msg_and_die("short file"); + } + + while (length > 0) { + /* Merge four 6 bit chars to three 8 bit chars */ + fputc(((line_ptr[0] - 0x20) & 077) << 2 | ((line_ptr[1] - 0x20) & 077) >> 4, dst_stream); + line_ptr++; + length--; + if (length == 0) { + break; + } + + fputc(((line_ptr[0] - 0x20) & 077) << 4 | ((line_ptr[1] - 0x20) & 077) >> 2, dst_stream); + line_ptr++; + length--; + if (length == 0) { + break; + } + + fputc(((line_ptr[0] - 0x20) & 077) << 6 | ((line_ptr[1] - 0x20) & 077), dst_stream); + line_ptr += 2; + length -= 2; + } + free(line); + } + bb_error_msg_and_die("short file"); +} + +static int read_base64(FILE *src_stream, FILE *dst_stream) +{ + static const char base64_table[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n"; + char term_count = 0; + + while (1) { + char translated[4]; + int count = 0; + + while (count < 4) { + char *table_ptr; + int ch; + + /* Get next _valid_ character */ + do { + ch = fgetc(src_stream); + if (ch == EOF) { + bb_error_msg_and_die("short file"); + } + } while ((table_ptr = strchr(base64_table, ch)) == NULL); + + /* Convert encoded charcter to decimal */ + ch = table_ptr - base64_table; + + if (*table_ptr == '=') { + if (term_count == 0) { + translated[count] = 0; + break; + } + term_count++; + } + else if (*table_ptr == '\n') { + /* Check for terminating line */ + if (term_count == 5) { + return EXIT_SUCCESS; + } + term_count = 1; + continue; + } else { + translated[count] = ch; + count++; + term_count = 0; + } + } + + /* Merge 6 bit chars to 8 bit */ + fputc(translated[0] << 2 | translated[1] >> 4, dst_stream); + if (count > 2) { + fputc(translated[1] << 4 | translated[2] >> 2, dst_stream); + } + if (count > 3) { + fputc(translated[2] << 6 | translated[3], dst_stream); + } + } +} + +int uudecode_main(int argc, char **argv) +{ + FILE *src_stream; + char *outname = NULL; + char *line; + + getopt32(argc, argv, "o:", &outname); + + if (optind == argc) { + src_stream = stdin; + } else if (optind + 1 == argc) { + src_stream = xfopen(argv[optind], "r"); + } else { + bb_show_usage(); + } + + /* Search for the start of the encoding */ + while ((line = xmalloc_getline(src_stream)) != NULL) { + int (*decode_fn_ptr)(FILE * src, FILE * dst); + char *line_ptr; + FILE *dst_stream; + int mode; + int ret; + + if (strncmp(line, "begin-base64 ", 13) == 0) { + line_ptr = line + 13; + decode_fn_ptr = read_base64; + } else if (strncmp(line, "begin ", 6) == 0) { + line_ptr = line + 6; + decode_fn_ptr = read_stduu; + } else { + free(line); + continue; + } + + mode = strtoul(line_ptr, NULL, 8); + if (outname == NULL) { + outname = strchr(line_ptr, ' '); + if ((outname == NULL) || (*outname == '\0')) { + break; + } + outname++; + } + if (strcmp(outname, "-") == 0) { + dst_stream = stdout; + } else { + dst_stream = xfopen(outname, "w"); + chmod(outname, mode & (S_IRWXU | S_IRWXG | S_IRWXO)); + } + free(line); + ret = decode_fn_ptr(src_stream, dst_stream); + fclose_if_not_stdin(src_stream); + return ret; + } + bb_error_msg_and_die("no 'begin' line"); +} diff --git a/coreutils/uuencode.c b/coreutils/uuencode.c new file mode 100644 index 000000000..e8f8d541c --- /dev/null +++ b/coreutils/uuencode.c @@ -0,0 +1,75 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 2000 by Glenn McGrath + * + * based on the function base64_encode from http.c in wget v1.6 + * Copyright (C) 1995, 1996, 1997, 1998, 2000 Free Software Foundation, Inc. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + + +#define SRC_BUF_SIZE 45 // This *MUST* be a multiple of 3 +#define DST_BUF_SIZE 4 * ((SRC_BUF_SIZE + 2) / 3) +int uuencode_main(int argc, char **argv) +{ + const size_t src_buf_size = SRC_BUF_SIZE; + const size_t dst_buf_size = DST_BUF_SIZE; + size_t write_size = dst_buf_size; + struct stat stat_buf; + FILE *src_stream = stdin; + const char *tbl; + size_t size; + mode_t mode; + RESERVE_CONFIG_BUFFER(src_buf, SRC_BUF_SIZE + 1); + RESERVE_CONFIG_BUFFER(dst_buf, DST_BUF_SIZE + 1); + + tbl = bb_uuenc_tbl_std; + if (getopt32(argc, argv, "m") & 1) { + tbl = bb_uuenc_tbl_base64; + } + + switch (argc - optind) { + case 2: + src_stream = xfopen(argv[optind], "r"); + xstat(argv[optind], &stat_buf); + mode = stat_buf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); + if (src_stream == stdout) { + puts("NULL"); + } + break; + case 1: + mode = 0666 & ~umask(0666); + break; + default: + bb_show_usage(); + } + + printf("begin%s %o %s", tbl == bb_uuenc_tbl_std ? "" : "-base64", mode, argv[argc - 1]); + + while ((size = fread(src_buf, 1, src_buf_size, src_stream)) > 0) { + if (size != src_buf_size) { + /* write_size is always 60 until the last line */ + write_size = (4 * ((size + 2) / 3)); + /* pad with 0s so we can just encode extra bits */ + memset(&src_buf[size], 0, src_buf_size - size); + } + /* Encode the buffer we just read in */ + bb_uuencode((unsigned char*)src_buf, dst_buf, size, tbl); + + putchar('\n'); + if (tbl == bb_uuenc_tbl_std) { + putchar(tbl[size]); + } + if (fwrite(dst_buf, 1, write_size, stdout) != write_size) { + bb_perror_msg_and_die(bb_msg_write_error); + } + } + printf(tbl == bb_uuenc_tbl_std ? "\n`\nend\n" : "\n====\n"); + + die_if_ferror(src_stream, "source"); /* TODO - Fix this! */ + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/coreutils/watch.c b/coreutils/watch.c new file mode 100644 index 000000000..81856c867 --- /dev/null +++ b/coreutils/watch.c @@ -0,0 +1,79 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini watch implementation for busybox + * + * Copyright (C) 2001 by Michael Habermann + * Copyrigjt (C) Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 N/A */ +/* BB_AUDIT GNU defects -- only option -n is supported. */ + +#include "busybox.h" + +// procps 2.0.18: +// watch [-d] [-n seconds] +// [--differences[=cumulative]] [--interval=seconds] command +// +// procps-3.2.3: +// watch [-dt] [-n seconds] +// [--differences[=cumulative]] [--interval=seconds] [--no-title] command +// +// (procps 3.x and procps 2.x are forks, not newer/older versions of the same) + +int watch_main(int argc, char **argv) +{ + unsigned opt; + unsigned period = 2; + unsigned cmdlen = 1; // 1 for terminal NUL + char *header = NULL; + char *cmd; + char *tmp; + char **p; + + opt_complementary = "-1"; // at least one param please + opt = getopt32(argc, argv, "+dtn:", &tmp); + //if (opt & 0x1) // -d (ignore) + //if (opt & 0x2) // -t + if (opt & 0x4) period = xatou(tmp); + argv += optind; + + p = argv; + while (*p) + cmdlen += strlen(*p++) + 1; + tmp = cmd = xmalloc(cmdlen); + while (*argv) { + tmp += sprintf(tmp, " %s", *argv); + argv++; + } + cmd++; // skip initial space + + while (1) { + printf("\033[H\033[J"); + if (!(opt & 0x2)) { // no -t + int width, len; + char *thyme; + time_t t; + + get_terminal_width_height(STDOUT_FILENO, &width, 0); + header = xrealloc(header, width--); + // '%-*s' pads header with spaces to the full width + snprintf(header, width, "Every %ds: %-*s", period, width, cmd); + time(&t); + thyme = ctime(&t); + len = strlen(thyme); + if (len < width) + strcpy(header + width - len, thyme); + puts(header); + } + fflush(stdout); + // TODO: 'real' watch pipes cmd's output to itself + // and does not allow it to overflow the screen + // (taking into account linewrap!) + system(cmd); + sleep(period); + } + return 0; // gcc thinks we can reach this :) +} diff --git a/coreutils/wc.c b/coreutils/wc.c new file mode 100644 index 000000000..973929ebe --- /dev/null +++ b/coreutils/wc.c @@ -0,0 +1,204 @@ +/* vi: set sw=4 ts=4: */ +/* + * wc implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 _NOT_ compliant -- option -m is not currently supported. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/wc.html */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Rewritten to fix a number of problems and do some size optimizations. + * Problems in the previous busybox implementation (besides bloat) included: + * 1) broken 'wc -c' optimization (read note below) + * 2) broken handling of '-' args + * 3) no checking of ferror on EOF returns + * 4) isprint() wasn't considered when word counting. + * + * TODO: + * + * When locale support is enabled, count multibyte chars in the '-m' case. + * + * NOTES: + * + * The previous busybox wc attempted an optimization using stat for the + * case of counting chars only. I omitted that because it was broken. + * It didn't take into account the possibility of input coming from a + * pipe, or input from a file with file pointer not at the beginning. + * + * To implement such a speed optimization correctly, not only do you + * need the size, but also the file position. Note also that the + * file position may be past the end of file. Consider the example + * (adapted from example in gnu wc.c) + * + * echo hello > /tmp/testfile && + * (dd ibs=1k skip=1 count=0 &> /dev/null ; wc -c) < /tmp/testfile + * + * for which 'wc -c' should output '0'. + */ + +#include "busybox.h" + +#ifdef CONFIG_LOCALE_SUPPORT +#define isspace_given_isprint(c) isspace(c) +#else +#undef isspace +#undef isprint +#define isspace(c) ((((c) == ' ') || (((unsigned int)((c) - 9)) <= (13 - 9)))) +#define isprint(c) (((unsigned int)((c) - 0x20)) <= (0x7e - 0x20)) +#define isspace_given_isprint(c) ((c) == ' ') +#endif + +#if ENABLE_FEATURE_WC_LARGE +#define COUNT_T unsigned long long +#define COUNT_FMT "llu" +#else +#define COUNT_T unsigned +#define COUNT_FMT "u" +#endif + +enum { + WC_LINES = 0, + WC_WORDS = 1, + WC_CHARS = 2, + WC_LENGTH = 3 +}; + +int wc_main(int argc, char **argv) +{ + FILE *fp; + const char *s, *arg; + const char *start_fmt = "%9"COUNT_FMT; + const char *fname_fmt = " %s\n"; + COUNT_T *pcounts; + COUNT_T counts[4]; + COUNT_T totals[4]; + unsigned linepos; + unsigned u; + int num_files = 0; + int c; + char status = EXIT_SUCCESS; + char in_word; + unsigned print_type; + + print_type = getopt32(argc, argv, "lwcL"); + + if (print_type == 0) { + print_type = (1 << WC_LINES) | (1 << WC_WORDS) | (1 << WC_CHARS); + } + + argv += optind; + if (!argv[0]) { + *--argv = (char *) bb_msg_standard_input; + fname_fmt = "\n"; + if (!((print_type-1) & print_type)) /* exactly one option? */ + start_fmt = "%"COUNT_FMT; + } + + memset(totals, 0, sizeof(totals)); + + pcounts = counts; + + while ((arg = *argv++) != 0) { + ++num_files; + fp = fopen_or_warn_stdin(arg); + if (!fp) { + status = EXIT_FAILURE; + continue; + } + + memset(counts, 0, sizeof(counts)); + linepos = 0; + in_word = 0; + + do { + /* Our -w doesn't match GNU wc exactly... oh well */ + + ++counts[WC_CHARS]; + c = getc(fp); + if (isprint(c)) { + ++linepos; + if (!isspace_given_isprint(c)) { + in_word = 1; + continue; + } + } else if (((unsigned int)(c - 9)) <= 4) { + /* \t 9 + * \n 10 + * \v 11 + * \f 12 + * \r 13 + */ + if (c == '\t') { + linepos = (linepos | 7) + 1; + } else { /* '\n', '\r', '\f', or '\v' */ + DO_EOF: + if (linepos > counts[WC_LENGTH]) { + counts[WC_LENGTH] = linepos; + } + if (c == '\n') { + ++counts[WC_LINES]; + } + if (c != '\v') { + linepos = 0; + } + } + } else if (c == EOF) { + if (ferror(fp)) { + bb_perror_msg("%s", arg); + status = EXIT_FAILURE; + } + --counts[WC_CHARS]; + goto DO_EOF; /* Treat an EOF as '\r'. */ + } else { + continue; + } + + counts[WC_WORDS] += in_word; + in_word = 0; + if (c == EOF) { + break; + } + } while (1); + + if (totals[WC_LENGTH] < counts[WC_LENGTH]) { + totals[WC_LENGTH] = counts[WC_LENGTH]; + } + totals[WC_LENGTH] -= counts[WC_LENGTH]; + + fclose_if_not_stdin(fp); + + OUTPUT: + /* coreutils wc tries hard to print pretty columns + * (saves results for all files, find max col len etc...) + * we won't try that hard, it will bloat us too much */ + s = start_fmt; + u = 0; + do { + if (print_type & (1 << u)) { + printf(s, pcounts[u]); + s = " %9"COUNT_FMT; /* Ok... restore the leading space. */ + } + totals[u] += pcounts[u]; + } while (++u < 4); + printf(fname_fmt, arg); + } + + /* If more than one file was processed, we want the totals. To save some + * space, we set the pcounts ptr to the totals array. This has the side + * effect of trashing the totals array after outputting it, but that's + * irrelavent since we no longer need it. */ + if (num_files > 1) { + num_files = 0; /* Make sure we don't get here again. */ + arg = "total"; + pcounts = totals; + --argv; + goto OUTPUT; + } + + fflush_stdout_and_exit(status); +} diff --git a/coreutils/who.c b/coreutils/who.c new file mode 100644 index 000000000..4cd42652b --- /dev/null +++ b/coreutils/who.c @@ -0,0 +1,67 @@ +/* vi: set sw=4 ts=4: */ +/*---------------------------------------------------------------------- + * Mini who is used to display user name, login time, + * idle time and host name. + * + * Author: Da Chen + * + * This is a free document; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation: + * http://www.gnu.org/copyleft/gpl.html + * + * Copyright (c) 2002 AYR Networks, Inc. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + *---------------------------------------------------------------------- + */ + +#include "busybox.h" +#include +#include + +static const char * idle_string (time_t t) +{ + static char str[6]; + + time_t s = time(NULL) - t; + + if (s < 60) + return "."; + if (s < (24 * 60 * 60)) { + sprintf(str, "%02d:%02d", + (int) (s / (60 * 60)), + (int) ((s % (60 * 60)) / 60)); + return str; + } + return "old"; +} + +int who_main(int argc, char **argv) +{ + struct utmp *ut; + struct stat st; + char *name; + + if (argc > 1) { + bb_show_usage(); + } + + setutent(); + printf("USER TTY IDLE TIME HOST\n"); + while ((ut = getutent()) != NULL) { + if (ut->ut_user[0] && ut->ut_type == USER_PROCESS) { + time_t thyme = ut->ut_tv.tv_sec; + + /* ut->ut_line is device name of tty - "/dev/" */ + name = concat_path_file("/dev", ut->ut_line); + printf("%-10s %-8s %-8s %-12.12s %s\n", ut->ut_user, ut->ut_line, + (stat(name, &st)) ? "?" : idle_string(st.st_atime), + ctime(&thyme) + 4, ut->ut_host); + if (ENABLE_FEATURE_CLEAN_UP) free(name); + } + } + if (ENABLE_FEATURE_CLEAN_UP) endutent(); + return 0; +} diff --git a/coreutils/whoami.c b/coreutils/whoami.c new file mode 100644 index 000000000..df714f22c --- /dev/null +++ b/coreutils/whoami.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini whoami implementation for busybox + * + * Copyright (C) 2000 Edward Betts . + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 N/A -- Matches GNU behavior. */ + +#include +#include +#include +#include "busybox.h" + +int whoami_main(int argc, char **argv) +{ + if (argc > 1) + bb_show_usage(); + + puts(bb_getpwuid(NULL, geteuid(), -1)); + /* exits on error */ + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/coreutils/yes.c b/coreutils/yes.c new file mode 100644 index 000000000..894506a89 --- /dev/null +++ b/coreutils/yes.c @@ -0,0 +1,41 @@ +/* vi: set sw=4 ts=4: */ +/* + * yes implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 N/A -- Matches GNU behavior. */ + +/* Mar 16, 2003 Manuel Novoa III (mjn3@codepoet.org) + * + * Size reductions and removed redundant applet name prefix from error messages. + */ + +#include "busybox.h" + +int yes_main(int argc, char **argv) +{ + static const char fmt_str[] = " %s"; + const char *fmt; + char **first_arg; + + *argv = "y"; + if (argc != 1) { + ++argv; + } + + first_arg = argv; + do { + fmt = fmt_str + 1; + do { + printf(fmt, *argv); + fmt = fmt_str; + } while (*++argv); + argv = first_arg; + } while (putchar('\n') != EOF); + + bb_perror_nomsg_and_die(); +} diff --git a/debianutils/Config.in b/debianutils/Config.in new file mode 100644 index 000000000..3d85999ff --- /dev/null +++ b/debianutils/Config.in @@ -0,0 +1,88 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Debian Utilities" + +config MKTEMP + bool "mktemp" + default n + help + mktemp is used to create unique temporary files + +config PIPE_PROGRESS + bool "pipe_progress" + default n + help + Display a dot to indicate pipe activity. + +config READLINK + bool "readlink" + default n + help + This program reads a symbolic link and returns the name + of the file it points to + +config FEATURE_READLINK_FOLLOW + bool "Enable canonicalization by following all symlinks (-f)" + default n + depends on READLINK + help + Enable the readlink option (-f). + +config RUN_PARTS + bool "run-parts" + default n + help + run-parts is a utility designed to run all the scripts in a directory. + + It is useful to set up a directory like cron.daily, where you need to + execute all the scripts in that directory. + + In this implementation of run-parts some features (such as report mode) + are not implemented. + + Unless you know that run-parts is used in some of your scripts + you can safely say N here. + +config FEATURE_RUN_PARTS_LONG_OPTIONS + bool "Enable long options" + default n + depends on RUN_PARTS && GETOPT_LONG + help + Support long options for the run-parts applet. + +config START_STOP_DAEMON + bool "start-stop-daemon" + default y + help + start-stop-daemon is used to control the creation and + termination of system-level processes, usually the ones + started during the startup of the system. + +config FEATURE_START_STOP_DAEMON_FANCY + bool "Support additional arguments" + default y + depends on START_STOP_DAEMON + help + Support additional arguments. + -o|--oknodo ignored since we exit with 0 anyway + -v|--verbose + +config FEATURE_START_STOP_DAEMON_LONG_OPTIONS + bool "Enable long options" + default n + depends on START_STOP_DAEMON && GETOPT_LONG + help + Support long options for the start-stop-daemon applet. + +config WHICH + bool "which" + default n + help + which is used to find programs in your PATH and + print out their pathnames. + +endmenu + diff --git a/debianutils/Kbuild b/debianutils/Kbuild new file mode 100644 index 000000000..99df6a536 --- /dev/null +++ b/debianutils/Kbuild @@ -0,0 +1,13 @@ +# 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_MKTEMP) += mktemp.o +lib-$(CONFIG_PIPE_PROGRESS) += pipe_progress.o +lib-$(CONFIG_READLINK) += readlink.o +lib-$(CONFIG_RUN_PARTS) += run_parts.o +lib-$(CONFIG_START_STOP_DAEMON) += start_stop_daemon.o +lib-$(CONFIG_WHICH) += which.o diff --git a/debianutils/mktemp.c b/debianutils/mktemp.c new file mode 100644 index 000000000..d47d5a0bf --- /dev/null +++ b/debianutils/mktemp.c @@ -0,0 +1,48 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini mktemp implementation for busybox + * + * + * Copyright (C) 2000 by Daniel Jacobowitz + * Written by Daniel Jacobowitz + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include +#include +#include +#include +#include + +int mktemp_main(int argc, char **argv) +{ + unsigned long flags = getopt32(argc, argv, "dqt"); + char *chp; + + if (optind + 1 != argc) + bb_show_usage(); + + chp = argv[optind]; + + if (flags & 4) { + char *dir = getenv("TMPDIR"); + if (dir && *dir != '\0') + chp = concat_path_file(dir, chp); + else + chp = concat_path_file("/tmp/", chp); + } + + if (flags & 1) { + if (mkdtemp(chp) == NULL) + return EXIT_FAILURE; + } else { + if (mkstemp(chp) < 0) + return EXIT_FAILURE; + } + + puts(chp); + + return EXIT_SUCCESS; +} diff --git a/debianutils/pipe_progress.c b/debianutils/pipe_progress.c new file mode 100644 index 000000000..75d26e20f --- /dev/null +++ b/debianutils/pipe_progress.c @@ -0,0 +1,42 @@ +/* vi: set sw=4 ts=4: */ +/* + * Monitor a pipe with a simple progress display. + * + * Copyright (C) 2003 by Rob Landley , Joey Hess + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include +#include +#include +#include + +#define PIPE_PROGRESS_SIZE 4096 + +/* Read a block of data from stdin, write it to stdout. + * Activity is indicated by a '.' to stderr + */ +int pipe_progress_main(int argc, char **argv) +{ + RESERVE_CONFIG_BUFFER(buf, PIPE_PROGRESS_SIZE); + time_t t = time(NULL); + size_t len; + + while ((len = fread(buf, 1, PIPE_PROGRESS_SIZE, stdin)) > 0) { + time_t new_time = time(NULL); + if (new_time != t) { + t = new_time; + fputc('.', stderr); + } + fwrite(buf, len, 1, stdout); + } + + fputc('\n', stderr); + + if (ENABLE_FEATURE_CLEAN_UP) + RELEASE_CONFIG_BUFFER(buf); + + return 0; +} diff --git a/debianutils/readlink.c b/debianutils/readlink.c new file mode 100644 index 000000000..400259080 --- /dev/null +++ b/debianutils/readlink.c @@ -0,0 +1,51 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini readlink implementation for busybox + * + * Copyright (C) 2000,2001 Matt Kraai + * + * Licensed under GPL v2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include +#include +#include +#include + +int readlink_main(int argc, char **argv) +{ + char *buf; + char *fname; + + USE_FEATURE_READLINK_FOLLOW( + unsigned opt; + /* We need exactly one non-option argument. */ + opt_complementary = "=1"; + opt = getopt32(argc, argv, "f"); + fname = argv[optind]; + ) + SKIP_FEATURE_READLINK_FOLLOW( + const unsigned opt = 0; + if (argc != 2) bb_show_usage(); + fname = argv[1]; + ) + + /* compat: coreutils readlink reports errors silently via exit code */ + logmode = LOGMODE_NONE; + + if (opt) { + buf = realpath(fname, bb_common_bufsiz1); + } else { + buf = xreadlink(fname); + } + + if (!buf) + return EXIT_FAILURE; + puts(buf); + + if (ENABLE_FEATURE_CLEAN_UP && buf != bb_common_bufsiz1) + free(buf); + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/debianutils/run_parts.c b/debianutils/run_parts.c new file mode 100644 index 000000000..2b3b18854 --- /dev/null +++ b/debianutils/run_parts.c @@ -0,0 +1,191 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini run-parts implementation for busybox + * + * + * Copyright (C) 2001 by Emanuele Aina + * + * Based on the Debian run-parts program, version 1.15 + * Copyright (C) 1996 Jeff Noxon , + * Copyright (C) 1996-1999 Guy Maor + * + * + * Licensed under GPL v2 or later, see file LICENSE in this tarball for details. + */ + +/* This is my first attempt to write a program in C (well, this is my first + * attempt to write a program! :-) . */ + +/* This piece of code is heavily based on the original version of run-parts, + * taken from debian-utils. I've only removed the long options and a the + * report mode. As the original run-parts support only long options, I've + * broken compatibility because the BusyBox policy doesn't allow them. + * The supported options are: + * -t test. Print the name of the files to be executed, without + * execute them. + * -a ARG argument. Pass ARG as an argument the program executed. It can + * be repeated to pass multiple arguments. + * -u MASK umask. Set the umask of the program executed to MASK. */ + +/* TODO + * done - convert calls to error in perror... and remove error() + * done - convert malloc/realloc to their x... counterparts + * done - remove catch_sigchld + * done - use bb's concat_path_file() + * done - declare run_parts_main() as extern and any other function as static? + */ + +#include "busybox.h" +#include + +static const struct option runparts_long_options[] = { + { "test", 0, NULL, 't' }, + { "umask", 1, NULL, 'u' }, + { "arg", 1, NULL, 'a' }, + { 0, 0, 0, 0 } +}; + +/* valid_name */ +/* True or false? Is this a valid filename (upper/lower alpha, digits, + * underscores, and hyphens only?) + */ +static int valid_name(const struct dirent *d) +{ + const char *c = d->d_name; + + while (*c) { + if (!isalnum(*c) && (*c != '_') && (*c != '-')) { + return 0; + } + ++c; + } + return 1; +} + +/* test mode = 1 is the same as official run_parts + * test_mode = 2 means to fail silently on missing directories + */ +static int run_parts(char **args, const unsigned char test_mode) +{ + struct dirent **namelist = 0; + struct stat st; + char *filename; + char *arg0 = args[0]; + int entries; + int i; + int exitstatus = 0; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) &i; + (void) &exitstatus; +#endif + /* scandir() isn't POSIX, but it makes things easy. */ + entries = scandir(arg0, &namelist, valid_name, alphasort); + + if (entries == -1) { + if (test_mode & 2) { + return 2; + } + bb_perror_msg_and_die("cannot open '%s'", arg0); + } + + for (i = 0; i < entries; i++) { + filename = concat_path_file(arg0, namelist[i]->d_name); + + xstat(filename, &st); + if (S_ISREG(st.st_mode) && !access(filename, X_OK)) { + if (test_mode) { + puts(filename); + } else { + /* exec_errno is common vfork variable */ + volatile int exec_errno = 0; + int result; + int pid; + + if ((pid = vfork()) < 0) { + bb_perror_msg_and_die("failed to fork"); + } else if (!pid) { + args[0] = filename; + execve(filename, args, environ); + exec_errno = errno; + _exit(1); + } + + waitpid(pid, &result, 0); + if (exec_errno) { + errno = exec_errno; + bb_perror_msg("failed to exec %s", filename); + exitstatus = 1; + } + if (WIFEXITED(result) && WEXITSTATUS(result)) { + bb_perror_msg("%s exited with return code %d", filename, WEXITSTATUS(result)); + exitstatus = 1; + } else if (WIFSIGNALED(result)) { + bb_perror_msg("%s exited because of uncaught signal %d", filename, WTERMSIG(result)); + exitstatus = 1; + } + } + } else if (!S_ISDIR(st.st_mode)) { + bb_error_msg("component %s is not an executable plain file", filename); + exitstatus = 1; + } + + free(namelist[i]); + free(filename); + } + free(namelist); + + return exitstatus; +} + + +/* run_parts_main */ +/* Process options */ +int run_parts_main(int argc, char **argv) +{ + char **args = xmalloc(2 * sizeof(char *)); + unsigned char test_mode = 0; + unsigned short argcount = 1; + int opt; + + umask(022); + + while ((opt = getopt_long(argc, argv, "tu:a:", + runparts_long_options, NULL)) > 0) + { + switch (opt) { + /* Enable test mode */ + case 't': + test_mode++; + break; + /* Set the umask of the programs executed */ + case 'u': + /* Check and set the umask of the program executed. As stated in the original + * run-parts, the octal conversion in libc is not foolproof; it will take the + * 8 and 9 digits under some circumstances. We'll just have to live with it. + */ + umask(xstrtoul_range(optarg, 8, 0, 07777)); + break; + /* Pass an argument to the programs */ + case 'a': + /* Add an argument to the commands that we will call. + * Called once for every argument. */ + args = xrealloc(args, (argcount + 2) * (sizeof(char *))); + args[argcount++] = optarg; + break; + default: + bb_show_usage(); + } + } + + /* We require exactly one argument: the directory name */ + if (optind != (argc - 1)) { + bb_show_usage(); + } + + args[0] = argv[optind]; + args[argcount] = 0; + + return run_parts(args, test_mode); +} diff --git a/debianutils/start_stop_daemon.c b/debianutils/start_stop_daemon.c new file mode 100644 index 000000000..f52352995 --- /dev/null +++ b/debianutils/start_stop_daemon.c @@ -0,0 +1,319 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini start-stop-daemon implementation(s) for busybox + * + * Written by Marek Michalkiewicz , + * Adapted for busybox David Kimdon + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include +#include + +static int signal_nr = 15; +static int user_id = -1; +static int quiet; +static char *userspec; +static char *chuid; +static char *cmdname; +static char *execname; +static char *pidfile; + +struct pid_list { + struct pid_list *next; + pid_t pid; +}; + +static struct pid_list *found = NULL; + +static inline void push(pid_t pid) +{ + struct pid_list *p; + + p = xmalloc(sizeof(*p)); + p->next = found; + p->pid = pid; + found = p; +} + +static int pid_is_exec(pid_t pid, const char *name) +{ + char buf[sizeof("/proc//exe") + sizeof(int)*3]; + char *execbuf; + int equal; + + sprintf(buf, "/proc/%d/exe", pid); + execbuf = xstrdup(name); + readlink(buf, execbuf, strlen(name)+1); + + equal = ! strcmp(execbuf, name); + if (ENABLE_FEATURE_CLEAN_UP) + free(execbuf); + return equal; +} + +static int pid_is_user(int pid, int uid) +{ + struct stat sb; + char buf[sizeof("/proc/") + sizeof(int)*3]; + + sprintf(buf, "/proc/%d", pid); + if (stat(buf, &sb) != 0) + return 0; + return (sb.st_uid == uid); +} + +static int pid_is_cmd(pid_t pid, const char *name) +{ + char buf[sizeof("/proc//stat") + sizeof(int)*3]; + FILE *f; + int c; + + sprintf(buf, "/proc/%d/stat", pid); + f = fopen(buf, "r"); + if (!f) + return 0; + while ((c = getc(f)) != EOF && c != '(') + ; + if (c != '(') { + fclose(f); + return 0; + } + /* this hopefully handles command names containing ')' */ + while ((c = getc(f)) != EOF && c == *name) + name++; + fclose(f); + return (c == ')' && *name == '\0'); +} + + +static void check(int pid) +{ + if (execname && !pid_is_exec(pid, execname)) { + return; + } + if (userspec && !pid_is_user(pid, user_id)) { + return; + } + if (cmdname && !pid_is_cmd(pid, cmdname)) { + return; + } + push(pid); +} + + +static void do_pidfile(void) +{ + FILE *f; + pid_t pid; + + f = fopen(pidfile, "r"); + if (f) { + if (fscanf(f, "%d", &pid) == 1) + check(pid); + fclose(f); + } else if (errno != ENOENT) + bb_perror_msg_and_die("open pidfile %s", pidfile); +} + +static void do_procinit(void) +{ + DIR *procdir; + struct dirent *entry; + int foundany, pid; + + if (pidfile) { + do_pidfile(); + return; + } + + procdir = xopendir("/proc"); + + foundany = 0; + while ((entry = readdir(procdir)) != NULL) { + if (sscanf(entry->d_name, "%d", &pid) != 1) + continue; + foundany++; + check(pid); + } + closedir(procdir); + if (!foundany) + bb_error_msg_and_die ("nothing in /proc - not mounted?"); +} + + +static int do_stop(void) +{ + char *what; + struct pid_list *p; + int killed = 0; + + do_procinit(); + + if (cmdname) + what = xstrdup(cmdname); + else if (execname) + what = xstrdup(execname); + else if (pidfile) + what = xasprintf("process in pidfile '%s'", pidfile); + else if (userspec) + what = xasprintf("process(es) owned by '%s'", userspec); + else + bb_error_msg_and_die("internal error, please report"); + + if (!found) { + if (!quiet) + printf("no %s found; none killed\n", what); + if (ENABLE_FEATURE_CLEAN_UP) + free(what); + return -1; + } + for (p = found; p; p = p->next) { + if (kill(p->pid, signal_nr) == 0) { + p->pid = -p->pid; + killed++; + } else { + bb_perror_msg("warning: failed to kill %d", p->pid); + } + } + if (!quiet && killed) { + printf("stopped %s (pid", what); + for (p = found; p; p = p->next) + if(p->pid < 0) + printf(" %d", -p->pid); + puts(")"); + } + if (ENABLE_FEATURE_CLEAN_UP) + free(what); + return killed; +} + +#if ENABLE_FEATURE_START_STOP_DAEMON_LONG_OPTIONS +static const struct option ssd_long_options[] = { + { "stop", 0, NULL, 'K' }, + { "start", 0, NULL, 'S' }, + { "background", 0, NULL, 'b' }, + { "quiet", 0, NULL, 'q' }, + { "make-pidfile", 0, NULL, 'm' }, +#if ENABLE_FEATURE_START_STOP_DAEMON_FANCY + { "oknodo", 0, NULL, 'o' }, + { "verbose", 0, NULL, 'v' }, + { "nicelevel", 1, NULL, 'N' }, +#endif + { "startas", 1, NULL, 'a' }, + { "name", 1, NULL, 'n' }, + { "signal", 1, NULL, 's' }, + { "user", 1, NULL, 'u' }, + { "chuid", 1, NULL, 'c' }, + { "exec", 1, NULL, 'x' }, + { "pidfile", 1, NULL, 'p' }, +#if ENABLE_FEATURE_START_STOP_DAEMON_FANCY + { "retry", 1, NULL, 'R' }, +#endif + { 0, 0, 0, 0 } +}; +#endif + +#define SSD_CTX_STOP 0x1 +#define SSD_CTX_START 0x2 +#define SSD_OPT_BACKGROUND 0x4 +#define SSD_OPT_QUIET 0x8 +#define SSD_OPT_MAKEPID 0x10 +#if ENABLE_FEATURE_START_STOP_DAEMON_FANCY +#define SSD_OPT_OKNODO 0x20 +#define SSD_OPT_VERBOSE 0x40 +#define SSD_OPT_NICELEVEL 0x80 +#endif + +int start_stop_daemon_main(int argc, char **argv) +{ + unsigned opt; + char *signame = NULL; + char *startas = NULL; +#if ENABLE_FEATURE_START_STOP_DAEMON_FANCY +// char *retry_arg = NULL; +// int retries = -1; + char *opt_N; +#endif +#if ENABLE_FEATURE_START_STOP_DAEMON_LONG_OPTIONS + applet_long_options = ssd_long_options; +#endif + + /* Check required one context option was given */ + opt_complementary = "K:S:?:K--S:S--K:m?p:K?xpun:S?xa"; + opt = getopt32(argc, argv, "KSbqm" +// USE_FEATURE_START_STOP_DAEMON_FANCY("ovN:R:") + USE_FEATURE_START_STOP_DAEMON_FANCY("ovN:") + "a:n:s:u:c:x:p:" + USE_FEATURE_START_STOP_DAEMON_FANCY(,&opt_N) +// USE_FEATURE_START_STOP_DAEMON_FANCY(,&retry_arg) + ,&startas, &cmdname, &signame, &userspec, &chuid, &execname, &pidfile); + + quiet = (opt & SSD_OPT_QUIET) + USE_FEATURE_START_STOP_DAEMON_FANCY(&& !(opt & SSD_OPT_VERBOSE)); + + if (signame) { + signal_nr = get_signum(signame); + if (signal_nr < 0) bb_show_usage(); + } + + if (!startas) + startas = execname; + +// USE_FEATURE_START_STOP_DAEMON_FANCY( +// if (retry_arg) +// retries = xatoi_u(retry_arg); +// ) + argc -= optind; + argv += optind; + + if (userspec && sscanf(userspec, "%d", &user_id) != 1) + user_id = bb_xgetpwnam(userspec); + + if (opt & SSD_CTX_STOP) { + int i = do_stop(); + return + USE_FEATURE_START_STOP_DAEMON_FANCY((opt & SSD_OPT_OKNODO) + ? 0 :) !!(i<=0); + } + + do_procinit(); + + if (found) { + if (!quiet) + printf("%s already running\n%d\n", execname, found->pid); + USE_FEATURE_START_STOP_DAEMON_FANCY(return !(opt & SSD_OPT_OKNODO);) + SKIP_FEATURE_START_STOP_DAEMON_FANCY(return EXIT_FAILURE;) + } + *--argv = startas; + if (opt & SSD_OPT_BACKGROUND) { + xdaemon(0, 0); + setsid(); + } + if (opt & SSD_OPT_MAKEPID) { + /* user wants _us_ to make the pidfile */ + FILE *pidf = xfopen(pidfile, "w"); + + pid_t pidt = getpid(); + fprintf(pidf, "%d\n", pidt); + fclose(pidf); + } + if (chuid) { + if (sscanf(chuid, "%d", &user_id) != 1) + user_id = bb_xgetpwnam(chuid); + xsetuid(user_id); + } +#if ENABLE_FEATURE_START_STOP_DAEMON_FANCY + if (opt & SSD_OPT_NICELEVEL) { + /* Set process priority */ + int prio = getpriority(PRIO_PROCESS, 0) + xatoi_range(opt_N, INT_MIN/2, INT_MAX/2); + if (setpriority(PRIO_PROCESS, 0, prio) < 0) { + bb_perror_msg_and_die("setpriority(%d)", prio); + } + } +#endif + execv(startas, argv); + bb_perror_msg_and_die("cannot start %s", startas); +} diff --git a/debianutils/which.c b/debianutils/which.c new file mode 100644 index 000000000..a715a2664 --- /dev/null +++ b/debianutils/which.c @@ -0,0 +1,43 @@ +/* vi: set sw=4 ts=4: */ +/* + * Which implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * Copyright (C) 2006 Gabriel Somlo + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Based on which from debianutils + */ + +#include "busybox.h" + +int which_main(int argc, char **argv) +{ + int status = EXIT_SUCCESS; + char *p; + + if (argc <= 1 || argv[1][0] == '-') { + bb_show_usage(); + } + + while (--argc > 0) { + argv++; + if (strchr(*argv, '/')) { + if (execable_file(*argv)) { + puts(*argv); + continue; + } + } else { + p = find_execable(*argv); + if (p) { + puts(p); + free(p); + continue; + } + } + status = EXIT_FAILURE; + } + + fflush_stdout_and_exit(status); +} diff --git a/docs/autodocifier.pl b/docs/autodocifier.pl new file mode 100755 index 000000000..3aa838eb4 --- /dev/null +++ b/docs/autodocifier.pl @@ -0,0 +1,304 @@ +#!/usr/bin/perl -w + +use strict; +use Getopt::Long; + +# collect lines continued with a '\' into an array +sub continuation { + my $fh = shift; + my @line; + + while (<$fh>) { + my $s = $_; + $s =~ s/\\\s*$//; + #$s =~ s/#.*$//; + push @line, $s; + last unless (/\\\s*$/); + } + return @line; +} + +# regex && eval away unwanted strings from documentation +sub beautify { + my $text = shift; + for (;;) { + my $text2 = $text; + $text =~ s/SKIP_\w+\(.*?"\s*\)//sxg; + $text =~ s/USE_\w+\(\s*?(.*?)"\s*\)/$1"/sxg; + $text =~ s/USAGE_\w+\(\s*?(.*?)"\s*\)/$1"/sxg; + last if ( $text2 eq $text ); + } + $text =~ s/"\s*"//sg; + my @line = split("\n", $text); + $text = join('', + map { + s/^\s*"//; + s/"\s*$//; + s/%/%%/g; + s/\$/\\\$/g; + eval qq[ sprintf(qq{$_}) ] + } @line + ); + return $text; +} + +# generate POD for an applet +sub pod_for_usage { + my $name = shift; + my $usage = shift; + + # Sigh. Fixup the known odd-name applets. + $name =~ s/dpkg_deb/dpkg-deb/g; + $name =~ s/fsck_minix/fsck.minix/g; + $name =~ s/mkfs_minix/mkfs.minix/g; + $name =~ s/run_parts/run-parts/g; + $name =~ s/start_stop_daemon/start-stop-daemon/g; + + # make options bold + my $trivial = $usage->{trivial}; + if (!defined $usage->{trivial}) { + $trivial = ""; + } else { + $trivial =~ s/(?/sxg; + } + my @f0 = + map { $_ !~ /^\s/ && s/(?/g; $_ } + split("\n", (defined $usage->{full} ? $usage->{full} : "")); + + # add "\n" prior to certain lines to make indented + # lines look right + my @f1; + my $len = @f0; + for (my $i = 0; $i < $len; $i++) { + push @f1, $f0[$i]; + if (($i+1) != $len && $f0[$i] !~ /^\s/ && $f0[$i+1] =~ /^\s/) { + next if ($f0[$i] =~ /^$/); + push(@f1, "") unless ($f0[$i+1] =~ /^\s*$/s); + } + } + my $full = join("\n", @f1); + + # prepare notes if they exist + my $notes = (defined $usage->{notes}) + ? "$usage->{notes}\n\n" + : ""; + + # prepare examples if they exist + my $example = (defined $usage->{example}) + ? + "Example:\n\n" . + join ("\n", + map { "\t$_" } + split("\n", $usage->{example})) . "\n\n" + : ""; + + # Pad the name so that the applet name gets a line + # by itself in BusyBox.txt + my $spaces = 10 - length($name); + if ($spaces > 0) { + $name .= " " x $spaces; + } + + return + "=item B<$name>". + "\n\n$name $trivial\n\n". + "$full\n\n" . + "$notes" . + "$example" . + "\n\n" + ; +} + +# the keys are applet names, and +# the values will contain hashrefs of the form: +# +# { +# trivial => "...", +# full => "...", +# notes => "...", +# example => "...", +# } +my %docs; + + +# get command-line options + +my %opt; + +GetOptions( + \%opt, + "help|h", + "pod|p", + "verbose|v", +); + +if (defined $opt{help}) { + print + "$0 [OPTION]... [FILE]...\n", + "\t--help\n", + "\t--pod\n", + "\t--verbose\n", + ; + exit 1; +} + + +# collect documenation into %docs + +foreach (@ARGV) { + open(USAGE, $_) || die("$0: $_: $!"); + my $fh = *USAGE; + my ($applet, $type, @line); + while (<$fh>) { + if (/^#define (\w+)_(\w+)_usage/) { + $applet = $1; + $type = $2; + @line = continuation($fh); + my $doc = $docs{$applet} ||= { }; + my $text = join("\n", @line); + $doc->{$type} = beautify($text); + } + } +} + + +# generate structured documentation + +my $generator = \&pod_for_usage; + +my @names = sort keys %docs; +my $line = "\t[, [[, "; +for (my $i = 0; $i < $#names; $i++) { + if (length ($line.$names[$i]) >= 65) { + print "$line\n\t"; + $line = ""; + } + $line .= "$names[$i], "; +} +print $line . $names[-1]; + +print "\n\n=head1 COMMAND DESCRIPTIONS\n"; +print "\n=over 4\n\n"; + +foreach my $applet (@names) { + print $generator->($applet, $docs{$applet}); +} + +exit 0; + +__END__ + +=head1 NAME + +autodocifier.pl - generate docs for busybox based on usage.h + +=head1 SYNOPSIS + +autodocifier.pl [OPTION]... [FILE]... + +Example: + + ( cat docs/busybox_header.pod; \ + docs/autodocifier.pl usage.h; \ + cat docs/busybox_footer.pod ) > docs/busybox.pod + +=head1 DESCRIPTION + +The purpose of this script is to automagically generate +documentation for busybox using its usage.h as the original source +for content. It used to be that same content has to be duplicated +in 3 places in slightly different formats -- F, +F. This was tedious and error-prone, so it was +decided that F would contain all the text in a +machine-readable form, and scripts could be used to transform this +text into other forms if necessary. + +F is one such script. It is based on a script by +Erik Andersen which was in turn based on a +script by Mark Whitley + +=head1 OPTIONS + +=over 4 + +=item B<--help> + +This displays the help message. + +=item B<--pod> + +Generate POD (this is the default) + +=item B<--verbose> + +Be verbose (not implemented) + +=back + +=head1 FORMAT + +The following is an example of some data this script might parse. + + #define length_trivial_usage \ + "STRING" + #define length_full_usage \ + "Prints out the length of the specified STRING." + #define length_example_usage \ + "$ length Hello\n" \ + "5\n" + +Each entry is a cpp macro that defines a string. The macros are +named systematically in the form: + + $name_$type_usage + +$name is the name of the applet. $type can be "trivial", "full", "notes", +or "example". Every documentation macro must end with "_usage". + +The definition of the types is as follows: + +=over 4 + +=item B + +This should be a brief, one-line description of parameters that +the command expects. This will be displayed when B<-h> is issued to +a command. I + +=item B + +This should contain descriptions of each option. This will also +be displayed along with the trivial help if CONFIG_FEATURE_TRIVIAL_HELP +is disabled. I + +=item B + +This is documentation that is intended to go in the POD or SGML, but +not be printed when a B<-h> is given to a command. To see an example +of notes being used, see init_notes_usage in F. I + +=item B + +This should be an example of how the command is actually used. +This will not be printed when a B<-h> is given to a command -- it +will only be included in the POD or SGML documentation. I + +=back + +=head1 FILES + +F + +=head1 COPYRIGHT + +Copyright (c) 2001 John BEPPU. All rights reserved. This program is +free software; you can redistribute it and/or modify it under the same +terms as Perl itself. + +=head1 AUTHOR + +John BEPPU + +=cut + +# $Id: autodocifier.pl,v 1.26 2004/04/06 15:26:25 andersen Exp $ diff --git a/docs/busybox.net/FAQ.html b/docs/busybox.net/FAQ.html new file mode 100644 index 000000000..c751f7521 --- /dev/null +++ b/docs/busybox.net/FAQ.html @@ -0,0 +1,1108 @@ + + +

Frequently Asked Questions

+ +This is a collection of some of the more frequently asked questions +about BusyBox. Some of the questions even have answers. If you +have additions to this FAQ document, we would love to add them, + +

General questions

+
    +
  1. How can I get started using BusyBox?
  2. +
  3. How do I configure busybox?
  4. +
  5. How do I build a BusyBox-based system?
  6. +
  7. Which Linux kernel versions are supported?
  8. +
  9. Which architectures does BusyBox run on?
  10. +
  11. Which C libraries are supported?
  12. +
  13. Can I include BusyBox as part of the software on my device?
  14. +
  15. Where can I find other small utilities since busybox does not include the features I want?
  16. +
  17. I demand that you to add <favorite feature> right now! How come you don't answer all my questions on the mailing list instantly? I demand that you help me with all of my problems Right Now!
  18. +
  19. I need help with BusyBox! What should I do?
  20. +
  21. I need you to add <favorite feature>! Are the BusyBox developers willing to be paid in order to fix bugs or add in <favorite feature>? Are you willing to provide support contracts?
  22. +
+ +

Troubleshooting

+
    +
  1. I think I found a bug in BusyBox! What should I do?!
  2. +
  3. I'm using an ancient version from the dawn of time and something's broken. Can you backport fixes for free?
  4. +
  5. Busybox init isn't working!
  6. +
  7. I can't configure busybox on my system.
  8. +
  9. Why do I keep getting "sh: can't access tty; job control turned off" errors? Why doesn't Control-C work within my shell?
  10. +
+ +

Programming questions

+
    +
  1. What are the goals of busybox?
  2. +
  3. What is the design of busybox?
  4. +
  5. How is the source code organized?
  6. + +
  7. I want to make busybox even smaller, how do I go about it?
  8. +
  9. Adding an applet to busybox
  10. +
  11. What standards does busybox adhere to?
  12. +
  13. Portability.
  14. +
  15. Tips and tricks.
  16. + +
  17. Who are the BusyBox developers?
  18. + + + +
+ +

General questions

+ +
+

+

How can I get started using BusyBox?

+

If you just want to try out busybox without installing it, download the + tarball, extract it, run "make defconfig", and then run "make". +

+

+ This will create a busybox binary with almost all features enabled. To try + out a busybox applet, type "./busybox [appletname] [options]", for + example "./busybox ls -l" or "./busybox cat LICENSE". Type "./busybox" + to see a command list, and "busybox appletname --help" to see a brief + usage message for a given applet. +

+

+ BusyBox uses the name it was invoked under to determine which applet is + being invoked. (Try "mv busybox ls" and then "./ls -l".) Installing + busybox consists of creating symlinks (or hardlinks) to the busybox + binary for each applet in busybox, and making sure these links are in + the shell's command $PATH. The special applet name "busybox" (or with + any optional suffix, such as "busybox-static") uses the first argument + to determine which applet to run, as shown above. +

+

+ BusyBox also has a feature called the + "standalone shell", where the busybox + shell runs any built-in applets before checking the command path. This + feature is also enabled by "make allyesconfig", and to try it out run + the command line "PATH= ./busybox ash". This will blank your command path + and run busybox as your command shell, so the only commands it can find + (without an explicit path such as /bin/ls) are the built-in busybox ones. + This is another good way to see what's built into busybox. + Note that the standalone shell requires CONFIG_BUSYBOX_EXEC_PATH + to be set appropriately, depending on whether or not /proc/self/exe is + available or not. If you do not have /proc, then point that config option + to the location of your busybox binary, usually /bin/busybox. + (So if you set it to /proc/self/exe, and happen to be able to chroot into + your rootfs, you must mount /proc beforehand.) +

+

+ A typical indication that you set CONFIG_BUSYBOX_EXEC_PATH to proc but + forgot to mount proc is: +

+$ /bin/echo $PATH
+/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/bin/X11
+$ echo $PATH
+/bin/sh: echo: not found
+
+
+

+

How do I configure busybox?

+

Busybox is configured similarly to the linux kernel. Create a default + configuration and then run "make menuconfig" to modify it. The end + result is a .config file that tells the busybox build process what features + to include. So instead of "./configure; make; make install" the equivalent + busybox build would be "make defconfig; make; make install". +

+ +

Busybox configured with all features enabled is a little under a megabyte + dynamically linked on x86. To create a smaller busybox, configure it with + fewer features. Individual busybox applets cost anywhere from a few + hundred bytes to tens of kilobytes. Disable unneeded applets to save, + space, using menuconfig. +

+ +

The most important busybox configurators are:

+ +
    +
  • make defconfig - Create the maximum "sane" configuration. This +enables almost all features, minus things like debugging options and features +that require changes to the rest of the system to work (such as selinux or +devfs device names). Use this if you want to start from a full-featured +busybox and remove features until it's small enough.

  • +
  • make allnoconfig - Disable everything. This creates a tiny version +of busybox that doesn't do anything. Start here if you know exactly what +you want and would like to select only those features.

  • +
  • make menuconfig - Interactively modify a .config file through a +multi-level menu interface. Use this after one of the previous two.

  • +
+ +

Some other configuration options are:

+
    +
  • make oldconfig - Update an old .config file for a newer version +of busybox.

  • +
  • make allyesconfig - Select absolutely everything. This creates +a statically linked version of busybox full of debug code, with dependencies on +selinux, using devfs names... This makes sure everything compiles. Whether +or not the result would do anything useful is an open question.

  • +
  • make allbareconfig - Select all applets but disable all sub-features +within each applet. More build coverage testing.

  • +
  • make randconfig - Create a random configuration for test purposes.

  • +
+ +

Menuconfig modifies your .config file through an interactive menu where you can enable or disable + busybox features, and get help about each feature. + + + +

+ To build a smaller busybox binary, run "make menuconfig" and disable the + features you don't need. (Or run "make allnoconfig" and then use + menuconfig to add just the features you need. Don't forget to recompile + with "make" once you've finished configuring.) +

+
+

+

How do I build a BusyBox-based system?

+

+ BusyBox is a package that replaces a dozen standard packages, but it is + not by itself a complete bootable system. Building an entire Linux + distribution from source is a bit beyond the scope of this FAQ, but it + understandably keeps cropping up on the mailing list, so here are some + pointers. +

+

+ Start by learning how to strip a working system down to the bare essentials + needed to run one or two commands, so you know what it is you actually + need. An excellent practical place to do + this is the Linux + BootDisk Howto, or for a more theoretical approach try + From + PowerUp to Bash Prompt. +

+

+ To learn how to build a working Linux system entirely from source code, + the place to go is the Linux + From Scratch project. They have an entire book of step-by-step + instructions you can + read online + or + download. + Be sure to check out the other sections of their main page, including + Beyond Linux From Scratch, Hardened Linux From Scratch, their Hints + directory, and their LiveCD project. (They also have mailing lists which + are better sources of answers to Linux-system building questions than + the busybox list.) +

+

+ If you want an automated yet customizable system builder which produces + a BusyBox and uClibc based system, try + buildroot, which is + another project by the maintainer of the uClibc (Erik Andersen). + Download the tarball, extract it, unset CC, make. + For more instructions, see the website. +

+ +
+

+

Which Linux kernel versions are supported?

+

+ Full functionality requires Linux 2.4.x or better. (Earlier versions may + still work, but are no longer regularly tested.) A large fraction of the + code should run on just about anything. While the current code is fairly + Linux specific, it should be fairly easy to port the majority of the code + to support, say, FreeBSD or Solaris, or Mac OS X, or even Windows (if you + are into that sort of thing). +

+
+

+

Which architectures does BusyBox run on?

+

+ BusyBox in general will build on any architecture supported by gcc. + Kernel module loading for 2.4 Linux kernels is currently + limited to ARM, CRIS, H8/300, x86, ia64, x86_64, m68k, MIPS, PowerPC, + S390, SH3/4/5, Sparc, v850e, and x86_64 for 2.4.x kernels. +

+

+ With 2.6.x kernels, module loading support should work on all architectures. +

+
+

+

Which C libraries are supported?

+

+ On Linux, BusyBox releases are tested against uClibc (0.9.27 or later) and + glibc (2.2 or later). Both should provide full functionality with busybox, + and if you find a bug we want to hear about it. +

+

+ Linux-libc5 is no longer maintained (and has no known advantages over + uClibc), dietlibc is known to have numerous unfixed bugs, and klibc is + missing too many features to build BusyBox. If you require a small C + library for Linux, the busybox developers recommend uClibc. +

+

+ Some BusyBox applets have been built and run under a combination + of newlib and libgloss (see + this thread). + This is still experimental, but may be supported in a future release. +

+ +
+

+

Can I include BusyBox as part of the software on my device?

+

+ +

+ Yes. As long as you fully comply + with the generous terms of the GPL BusyBox license you can ship BusyBox + as part of the software on your device. +

+ +
+

+

where can i find other small utilities since busybox + does not include the features i want?

+

+ we maintain such a list on this site! +

+ +
+

+

I demand that you to add <favorite feature> right now! How come you don't answer all my questions on the mailing list instantly? I demand that you help me with all of my problems Right Now!

+

+ + You have not paid us a single cent and yet you still have the product of + many years of our work. We are not your slaves! We work on BusyBox + because we find it useful and interesting. If you go off flaming us, we + will ignore you. + + +


+

+

I need help with BusyBox! What should I do?

+

+ + If you find that you need help with BusyBox, you can ask for help on the + BusyBox mailing list at busybox@busybox.net.

+ +

In addition to the mailing list, Erik Andersen (andersee), Manuel Nova + (mjn3), Rob Landley (landley), Mike Frysinger (SpanKY), Bernhard Fischer + (blindvt), and other long-time BusyBox developers are known to hang out + on the uClibc IRC channel: #uclibc on irc.freenode.net. There is a + web archive of + daily logs of the #uclibc IRC channel going back to 2002. +

+ +

+ Please do not send private email to Rob, Erik, Manuel, or the other + BusyBox contributors asking for private help unless you are planning on + paying for consulting services. +

+ +

+ When we answer questions on the BusyBox mailing list, it helps everyone + since people with similar problems in the future will be able to get help + by searching the mailing list archives. Private help is reserved as a paid + service. If you need to use private communication, or if you are serious + about getting timely assistance with BusyBox, you should seriously consider + paying for consulting services. +

+ +
+

+

I need you to add <favorite feature>! Are the BusyBox developers willing to be paid in order to fix bugs or add in <favorite feature>? Are you willing to provide support contracts?

+

+ +

+ Yes we are. The easy way to sponsor a new feature is to post an offer on + the mailing list to see who's interested. You can also email the project's + maintainer and ask them to recommend someone. +

+ +

If you prefer to deal with an organization rather than an individual, Rob + Landley (the current BusyBox maintainer) works for + TimeSys, and Eric Andersen (the previous + busybox maintainer and current uClibc maintainer) owns + CodePoet Consulting. Both + companies offer support contracts and handle new development, and there + are plenty of other companies that do the same. +

+ + + + +

Troubleshooting

+ +
+

+

I think I found a bug in BusyBox! What should I do?

+

+ +

+ If you simply need help with using or configuring BusyBox, please submit a + detailed description of your problem to the BusyBox mailing list at busybox@busybox.net. + Please do not send email to individual developers asking + for private help unless you are planning on paying for consulting services. + When we answer questions on the BusyBox mailing list, it helps everyone, + while private answers help only you... +

+ +

+ Bug reports and new feature patches sometimes get lost when posted to the + mailing list, because the developers of BusyBox are busy people and have + only so much they can keep in their brains at a time. You can post a + polite reminder after 2-3 days without offending anybody. If that doesn't + result in a solution, please use the + BusyBox Bug + and Patch Tracking System to submit a detailed explanation and we'll + get to it as soon as we can. +

+ +

+ Note that bugs entered into the bug system without being mentioned on the + mailing list first may languish there for months before anyone even notices + them. We generally go through the bug system when preparing for new + development releases, to see what fell through the cracks while we were + off writing new features. (It's a fast/unreliable vs slow/reliable thing. + Saves retransits, but the latency sucks.) +

+ +
+

+

I'm using an ancient version from the dawn of time and something's broken. Can you backport fixes for free?

+ +

Variants of this one get asked a lot.

+ +

The purpose of the BusyBox mailing list is to develop and improve BusyBox, +and we're happy to respond to our users' needs. But if you're coming to the +list for free tech support we're going to ask you to upgrade to a current +version before we try to diagnose your problem.

+ +

If you're building BusyBox 0.50 with uClibc 0.9.19 and gcc 0.9.26 there's a +fairly large chance that whatever problem you're seeing has already been fixed. +To get that fix, all you have to do is upgrade to a newer version. If you +don't at least _try_ that, you're wasting our time.

+ +

The volunteers are happy to fix any bugs you point out in the current +versions because doing so helps everybody and makes the project better. We +want to make the current version work for you. But diagnosing, debugging, and +backporting fixes to old versions isn't something we do for free, because it +doesn't help anybody but you. The cost of volunteer tech support is using a +reasonably current version of the project.

+ +

If you don't want to upgrade, you have the complete source code and thus +the ability to fix it yourself, or hire a consultant to do it for you. If you +got your version from a vendor who still supports the older version, they can +help you. But there are limits as to what the volunteers will feel obliged to +do for you.

+ +

As a rule of thumb, volunteers will generally answer polite questions about +a given version for about three years after its release before it's so old +we don't remember the answer off the top of our head. And if you want us to +put any _effort_ into tracking it down, we want you to put in a little effort +of your own by confirming it's still a problem with the current version. It's +also hard for us to fix a problem of yours if we can't reproduce it because +we don't have any systems running an environment that old.

+ +

A consultant will happily set up a special environment just to reproduce +your problem, and you can always ask on the list if any of the developers +have consulting rates.

+ +
+

+

Busybox init isn't working!

+

+ Init is the first program that runs, so it might be that no programs are + working on your new system because of a problem with your cross-compiler, + kernel, console settings, shared libraries, root filesystem... To rule all + that out, first build a statically linked version of the following "hello + world" program with your cross compiler toolchain: +

+
+#include <stdio.h>
+
+int main(int argc, char *argv)
+{
+  printf("Hello world!\n");
+  sleep(999999999);
+}
+
+ +

+ Now try to boot your device with an "init=" argument pointing to your + hello world program. Did you see the hello world message? Until you + do, don't bother messing with busybox init. +

+ +

+ Once you've got it working statically linked, try getting it to work + dynamically linked. Then read the FAQ entry How + do I build a BusyBox-based system?, and the + documentation for BusyBox + init. +

+ +
+

+

I can't configure busybox on my system.

+

+ Configuring Busybox depends on a recent version of sed. Older + distributions (Red Hat 7.2, Debian 3.0) may not come with a + usable version. Luckily BusyBox can use its own sed to configure itself, + although this leads to a bit of a chicken and egg problem. + You can work around this by hand-configuring busybox to build with just + sed, then putting that sed in your path to configure the rest of busybox + with, like so: +

+ +
+  tar xvjf sources/busybox-x.x.x.tar.bz2
+  cd busybox-x.x.x
+  make allnoconfig
+  make include/bb_config.h
+  echo "CONFIG_SED=y" >> .config
+  echo "#undef ENABLE_SED" >> include/bb_config.h
+  echo "#define ENABLE_SED 1" >> include/bb_config.h
+  make
+  mv busybox sed
+  export PATH=`pwd`:"$PATH"
+
+ +

Then you can run "make defconfig" or "make menuconfig" normally.

+ +
+

+

Why do I keep getting "sh: can't access tty; job control turned off" errors? Why doesn't Control-C work within my shell?

+

+ + Job control will be turned off since your shell can not obtain a controlling + terminal. This typically happens when you run your shell on /dev/console. + The kernel will not provide a controlling terminal on the /dev/console + device. Your should run your shell on a normal tty such as tty1 or ttyS0 + and everything will work perfectly. If you REALLY want your shell + to run on /dev/console, then you can hack your kernel (if you are into that + sortof thing) by changing drivers/char/tty_io.c to change the lines where + it sets "noctty = 1;" to instead set it to "0". I recommend you instead + run your shell on a real console... +

+ +

Development

+ +

What are the goals of busybox?

+ +

Busybox aims to be the smallest and simplest correct implementation of the +standard Linux command line tools. First and foremost, this means the +smallest executable size we can manage. We also want to have the simplest +and cleanest implementation we can manage, be standards +compliant, minimize run-time memory usage (heap and stack), run fast, and +take over the world.

+ +

What is the design of busybox?

+ +

Busybox is like a swiss army knife: one thing with many functions. +The busybox executable can act like many different programs depending on +the name used to invoke it. Normal practice is to create a bunch of symlinks +pointing to the busybox binary, each of which triggers a different busybox +function. (See getting started in the +FAQ for more information on usage, and the +busybox documentation for a list of symlink names and what they do.) + +

The "one binary to rule them all" approach is primarily for size reasons: a +single multi-purpose executable is smaller then many small files could be. +This way busybox only has one set of ELF headers, it can easily share code +between different apps even when statically linked, it has better packing +efficiency by avoding gaps between files or compression dictionary resets, +and so on.

+ +

Work is underway on new options such as "make standalone" to build separate +binaries for each applet, and a "libbb.so" to make the busybox common code +available as a shared library. Neither is ready yet at the time of this +writing.

+ + + +

The applet directories

+ +

The directory "applets" contains the busybox startup code (applets.c and +busybox.c), and several subdirectories containing the code for the individual +applets.

+ +

Busybox execution starts with the main() function in applets/busybox.c, +which sets the global variable applet_name to argv[0] and calls +run_applet_by_name() in applets/applets.c. That uses the applets[] array +(defined in include/busybox.h and filled out in include/applets.h) to +transfer control to the appropriate APPLET_main() function (such as +cat_main() or sed_main()). The individual applet takes it from there.

+ +

This is why calling busybox under a different name triggers different +functionality: main() looks up argv[0] in applets[] to get a function pointer +to APPLET_main().

+ +

Busybox applets may also be invoked through the multiplexor applet +"busybox" (see busybox_main() in applets/busybox.c), and through the +standalone shell (grep for STANDALONE_SHELL in applets/shell/*.c). +See getting started in the +FAQ for more information on these alternate usage mechanisms, which are +just different ways to reach the relevant APPLET_main() function.

+ +

The applet subdirectories (archival, console-tools, coreutils, +debianutils, e2fsprogs, editors, findutils, init, loginutils, miscutils, +modutils, networking, procps, shell, sysklogd, and util-linux) correspond +to the configuration sub-menus in menuconfig. Each subdirectory contains the +code to implement the applets in that sub-menu, as well as a Config.in +file defining that configuration sub-menu (with dependencies and help text +for each applet), and the makefile segment (Makefile.in) for that +subdirectory.

+ +

The run-time --help is stored in usage_messages[], which is initialized at +the start of applets/applets.c and gets its help text from usage.h. During the +build this help text is also used to generate the BusyBox documentation (in +html, txt, and man page formats) in the docs directory. See +adding an applet to busybox for more +information.

+ +

libbb

+ +

Most non-setup code shared between busybox applets lives in the libbb +directory. It's a mess that evolved over the years without much auditing +or cleanup. For anybody looking for a great project to break into busybox +development with, documenting libbb would be both incredibly useful and good +experience.

+ +

Common themes in libbb include allocation functions that test +for failure and abort the program with an error message so the caller doesn't +have to test the return value (xmalloc(), xstrdup(), etc), wrapped versions +of open(), close(), read(), and write() that test for their own failures +and/or retry automatically, linked list management functions (llist.c), +command line argument parsing (getopt32.c), and a whole lot more.

+ +
+

+

I want to make busybox even smaller, how do I go about it?

+

+ To conserve bytes it's good to know where they're being used, and the + size of the final executable isn't always a reliable indicator of + the size of the components (since various structures are rounded up, + so a small change may not even be visible by itself, but many small + savings add up). +

+ +

The busybox Makefile builds two versions of busybox, one of which + (busybox_unstripped) has extra information that various analysis tools + can use. (This has nothing to do with CONFIG_DEBUG, leave that off + when trying to optimize for size.) +

+ +

The "make bloatcheck" option uses Matt Mackall's bloat-o-meter + script to compare two versions of busybox (busybox_unstripped vs + busybox_old), and report which symbols changed size and by how much. + To use it, first build a base version with "make baseline". + (This creates busybox_old, which should have the original sizes for + comparison purposes.) Then build the new version with your changes + and run "make bloatcheck" to see the size differences from the old + version. +

+

+ The first line of output has totals: how many symbols were added or + removed, how many symbols grew or shrank, the number of bytes added + and number of bytes removed by these changes, and finally the total + number of bytes difference between the two files. The remaining + lines show each individual symbol, the old and new sizes, and the + increase or decrease in size (which results are sorted by). +

+

+ The "make sizes" option produces raw symbol size information for + busybox_unstripped. This is the output from the "nm --size-sort" + command (see "man nm" for more information), and is the information + bloat-o-meter parses to produce the comparison report above. For + defconfig, this is a good way to find the largest symbols in the tree + (which is a good place to start when trying to shrink the code). To + take a closer look at individual applets, configure busybox with just + one applet (run "make allnoconfig" and then switch on a single applet + with menuconfig), and then use "make sizes" to see the size of that + applet's components. +

+

+ The "showasm" command (in the scripts directory) produces an assembly + dump of a function, providing a closer look at what changed. Try + "scripts/showasm busybox_unstripped" to list available symbols, and + "scripts/showasm busybox_unstripped symbolname" to see the assembly + for a sepecific symbol. +

+
+ + + +

Adding an applet to busybox

+ +

To add a new applet to busybox, first pick a name for the applet and +a corresponding CONFIG_NAME. Then do this:

+ +
    +
  • Figure out where in the busybox source tree your applet best fits, +and put your source code there. Be sure to use APPLET_main() instead +of main(), where APPLET is the name of your applet.
  • + +
  • Add your applet to the relevant Config.in file (which file you add +it to determines where it shows up in "make menuconfig"). This uses +the same general format as the linux kernel's configuration system.
  • + +
  • Add your applet to the relevant Makefile.in file (in the same +directory as the Config.in you chose), using the existing entries as a +template and the same CONFIG symbol as you used for Config.in. (Don't +forget "needlibm" or "needcrypt" if your applet needs libm or +libcrypt.)
  • + +
  • Add your applet to "include/applets.h", using one of the existing +entries as a template. (Note: this is in alphabetical order. Applets +are found via binary search, and if you add an applet out of order it +won't work.)
  • + +
  • Add your applet's runtime help text to "include/usage.h". You need +at least appname_trivial_usage (the minimal help text, always included +in the busybox binary when this applet is enabled) and appname_full_usage +(extra help text included in the busybox binary with +CONFIG_FEATURE_VERBOSE_USAGE is enabled), or it won't compile. +The other two help entry types (appname_example_usage and +appname_notes_usage) are optional. They don't take up space in the binary, +but instead show up in the generated documentation (BusyBox.html, +BusyBox.txt, and the man page BusyBox.1).
  • + +
  • Run menuconfig, switch your applet on, compile, test, and fix the +bugs. Be sure to try both "allyesconfig" and "allnoconfig" (and +"allbareconfig" if relevant).
  • + +
+ +

What standards does busybox adhere to?

+ +

The standard we're paying attention to is the "Shell and Utilities" +portion of the Open +Group Base Standards (also known as the Single Unix Specification version +3 or SUSv3). Note that paying attention isn't necessarily the same thing as +following it.

+ +

SUSv3 doesn't even mention things like init, mount, tar, or losetup, nor +commonly used options like echo's '-e' and '-n', or sed's '-i'. Busybox is +driven by what real users actually need, not the fact the standard believes +we should implement ed or sccs. For size reasons, we're unlikely to include +much internationalization support beyond UTF-8, and on top of all that, our +configuration menu lets developers chop out features to produce smaller but +very non-standard utilities.

+ +

Also, Busybox is aimed primarily at Linux. Unix standards are interesting +because Linux tries to adhere to them, but portability to dozens of platforms +is only interesting in terms of offering a restricted feature set that works +everywhere, not growing dozens of platform-specific extensions. Busybox +should be portable to all hardware platforms Linux supports, and any other +similar operating systems that are easy to do and won't require much +maintenance.

+ +

In practice, standards compliance tends to be a clean-up step once an +applet is otherwise finished. When polishing and testing a busybox applet, +we ensure we have at least the option of full standards compliance, or else +document where we (intentionally) fall short.

+ +

Portability.

+ +

Busybox is a Linux project, but that doesn't mean we don't have to worry +about portability. First of all, there are different hardware platforms, +different C library implementations, different versions of the kernel and +build toolchain... The file "include/platform.h" exists to centralize and +encapsulate various platform-specific things in one place, so most busybox +code doesn't have to care where it's running.

+ +

To start with, Linux runs on dozens of hardware platforms. We try to test +each release on x86, x86-64, arm, power pc, and mips. (Since qemu can handle +all of these, this isn't that hard.) This means we have to care about a number +of portability issues like endianness, word size, and alignment, all of which +belong in platform.h. That header handles conditional #includes and gives +us macros we can use in the rest of our code. At some point in the future +we might grow a platform.c, possibly even a platform subdirectory. As long +as the applets themselves don't have to care.

+ +

On a related note, we made the "default signedness of char varies" problem +go away by feeding the compiler -funsigned-char. This gives us consistent +behavior on all platforms, and defaults to 8-bit clean text processing (which +gets us halfway to UTF-8 support). NOMMU support is less easily separated +(see the tips section later in this document), but we're working on it.

+ +

Another type of portability is build environments: we unapologetically use +a number of gcc and glibc extensions (as does the Linux kernel), but these have +been picked up by packages like uClibc, TCC, and Intel's C Compiler. As for +gcc, we take advantage of newer compiler optimizations to get the smallest +possible size, but we also regression test against an older build environment +using the Red Hat 9 image at "http://busybox.net/downloads/qemu". This has a +2.4 kernel, gcc 3.2, make 3.79.1, and glibc 2.3, and is the oldest +build/deployment environment we still put any effort into maintaining. (If +anyone takes an interest in older kernels you're welcome to submit patches, +but the effort would probably be better spent +trimming +down the 2.6 kernel.) Older gcc versions than that are uninteresting since +we now use c99 features, although +tcc might be worth a +look.

+ +

We also test busybox against the current release of uClibc. Older versions +of uClibc aren't very interesting (they were buggy, and uClibc wasn't really +usable as a general-purpose C library before version 0.9.26 anyway).

+ +

Other unix implementations are mostly uninteresting, since Linux binaries +have become the new standard for portable Unix programs. Specifically, +the ubiquity of Linux was cited as the main reason the Intel Binary +Compatability Standard 2 died, by the standards group organized to name a +successor to ibcs2: the 86open +project. That project disbanded in 1999 with the endorsement of an +existing standard: Linux ELF binaries. Since then, the major players at the +time (such as AIX, Solaris, and +FreeBSD) +have all either grown Linux support or folded.

+ +

The major exceptions are newcomer MacOS X, some embedded environments +(such as newlib+libgloss) which provide a posix environment but not a full +Linux environment, and environments like Cygwin that provide only partial Linux +emulation. Also, some embedded Linux systems run a Linux kernel but amputate +things like the /proc directory to save space.

+ +

Supporting these systems is largely a question of providing a clean subset +of BusyBox's functionality -- whichever applets can easily be made to +work in that environment. Annotating the configuration system to +indicate which applets require which prerequisites (such as procfs) is +also welcome. Other efforts to support these systems (swapping #include +files to build in different environments, adding adapter code to platform.h, +adding more extensive special-case supporting infrastructure such as mount's +legacy mtab support) are handled on a case-by-case basis. Support that can be +cleanly hidden in platform.h is reasonably attractive, and failing that +support that can be cleanly separated into a separate conditionally compiled +file is at least worth a look. Special-case code in the body of an applet is +something we're trying to avoid.

+ +

Programming tips and tricks.

+ +

Various things busybox uses that aren't particularly well documented +elsewhere.

+ +

Encrypted Passwords

+ +

Password fields in /etc/passwd and /etc/shadow are in a special format. +If the first character isn't '$', then it's an old DES style password. If +the first character is '$' then the password is actually three fields +separated by '$' characters:

+
+  $type$salt$encrypted_password
+
+ +

The "type" indicates which encryption algorithm to use: 1 for MD5 and 2 for SHA1.

+ +

The "salt" is a bunch of ramdom characters (generally 8) the encryption +algorithm uses to perturb the password in a known and reproducible way (such +as by appending the random data to the unencrypted password, or combining +them with exclusive or). Salt is randomly generated when setting a password, +and then the same salt value is re-used when checking the password. (Salt is +thus stored unencrypted.)

+ +

The advantage of using salt is that the same cleartext password encrypted +with a different salt value produces a different encrypted value. +If each encrypted password uses a different salt value, an attacker is forced +to do the cryptographic math all over again for each password they want to +check. Without salt, they could simply produce a big dictionary of commonly +used passwords ahead of time, and look up each password in a stolen password +file to see if it's a known value. (Even if there are billions of possible +passwords in the dictionary, checking each one is just a binary search against +a file only a few gigabytes long.) With salt they can't even tell if two +different users share the same password without guessing what that password +is and decrypting it. They also can't precompute the attack dictionary for +a specific password until they know what the salt value is.

+ +

The third field is the encrypted password (plus the salt). For md5 this +is 22 bytes.

+ +

The busybox function to handle all this is pw_encrypt(clear, salt) in +"libbb/pw_encrypt.c". The first argument is the clear text password to be +encrypted, and the second is a string in "$type$salt$password" format, from +which the "type" and "salt" fields will be extracted to produce an encrypted +value. (Only the first two fields are needed, the third $ is equivalent to +the end of the string.) The return value is an encrypted password in +/etc/passwd format, with all three $ separated fields. It's stored in +a static buffer, 128 bytes long.

+ +

So when checking an existing password, if pw_encrypt(text, +old_encrypted_password) returns a string that compares identical to +old_encrypted_password, you've got the right password. When setting a new +password, generate a random 8 character salt string, put it in the right +format with sprintf(buffer, "$%c$%s", type, salt), and feed buffer as the +second argument to pw_encrypt(text,buffer).

+ +

Fork and vfork

+ +

On systems that haven't got a Memory Management Unit, fork() is unreasonably +expensive to implement (and sometimes even impossible), so a less capable +function called vfork() is used instead. (Using vfork() on a system with an +MMU is like pounding a nail with a wrench. Not the best tool for the job, but +it works.)

+ +

Busybox hides the difference between fork() and vfork() in +libbb/bb_fork_exec.c. If you ever want to fork and exec, use bb_fork_exec() +(which returns a pid and takes the same arguments as execve(), although in +this case envp can be NULL) and don't worry about it. This description is +here in case you want to know why that does what it does.

+ +

Implementing fork() depends on having a Memory Management Unit. With an +MMU then you can simply set up a second set of page tables and share the +physical memory via copy-on-write. So a fork() followed quickly by exec() +only copies a few pages of the parent's memory, just the ones it changes +before freeing them.

+ +

With a very primitive MMU (using a base pointer plus length instead of page +tables, which can provide virtual addresses and protect processes from each +other, but no copy on write) you can still implement fork. But it's +unreasonably expensive, because you have to copy all the parent process' +memory into the new process (which could easily be several megabytes per fork). +And you have to do this even though that memory gets freed again as soon as the +exec happens. (This is not just slow and a waste of space but causes memory +usage spikes that can easily cause the system to run out of memory.)

+ +

Without even a primitive MMU, you have no virtual addresses. Every process +can reach out and touch any other process' memory, because all pointers are to +physical addresses with no protection. Even if you copy a process' memory to +new physical addresses, all of its pointers point to the old objects in the +old process. (Searching through the new copy's memory for pointers and +redirect them to the new locations is not an easy problem.)

+ +

So with a primitive or missing MMU, fork() is just not a good idea.

+ +

In theory, vfork() is just a fork() that writeably shares the heap and stack +rather than copying it (so what one process writes the other one sees). In +practice, vfork() has to suspend the parent process until the child does exec, +at which point the parent wakes up and resumes by returning from the call to +vfork(). All modern kernel/libc combinations implement vfork() to put the +parent to sleep until the child does its exec. There's just no other way to +make it work: the parent has to know the child has done its exec() or exit() +before it's safe to return from the function it's in, so it has to block +until that happens. In fact without suspending the parent there's no way to +even store separate copies of the return value (the pid) from the vfork() call +itself: both assignments write into the same memory location.

+ +

One way to understand (and in fact implement) vfork() is this: imagine +the parent does a setjmp and then continues on (pretending to be the child) +until the exec() comes around, then the _exec_ does the actual fork, and the +parent does a longjmp back to the original vfork call and continues on from +there. (It thus becomes obvious why the child can't return, or modify +local variables it doesn't want the parent to see changed when it resumes.) + +

Note a common mistake: the need for vfork doesn't mean you can't have two +processes running at the same time. It means you can't have two processes +sharing the same memory without stomping all over each other. As soon as +the child calls exec(), the parent resumes.

+ +

If the child's attempt to call exec() fails, the child should call _exit() +rather than a normal exit(). This avoids any atexit() code that might confuse +the parent. (The parent should never call _exit(), only a vforked child that +failed to exec.)

+ +

(Now in theory, a nommu system could just copy the _stack_ when it forks +(which presumably is much shorter than the heap), and leave the heap shared. +Even with no MMU at all +In practice, you've just wound up in a multi-threaded situation and you can't +do a malloc() or free() on your heap without freeing the other process' memory +(and if you don't have the proper locking for being threaded, corrupting the +heap if both of you try to do it at the same time and wind up stomping on +each other while traversing the free memory lists). The thing about vfork is +that it's a big red flag warning "there be dragons here" rather than +something subtle and thus even more dangerous.)

+ +

Short reads and writes

+ +

Busybox has special functions, bb_full_read() and bb_full_write(), to +check that all the data we asked for got read or written. Is this a real +world consideration? Try the following:

+ +
while true; do echo hello; sleep 1; done | tee out.txt
+ +

If tee is implemented with bb_full_read(), tee doesn't display output +in real time but blocks until its entire input buffer (generally a couple +kilobytes) is read, then displays it all at once. In that case, we _want_ +the short read, for user interface reasons. (Note that read() should never +return 0 unless it has hit the end of input, and an attempt to write 0 +bytes should be ignored by the OS.)

+ +

As for short writes, play around with two processes piping data to each +other on the command line (cat bigfile | gzip > out.gz) and suspend and +resume a few times (ctrl-z to suspend, "fg" to resume). The writer can +experience short writes, which are especially dangerous because if you don't +notice them you'll discard data. They can also happen when a system is under +load and a fast process is piping to a slower one. (Such as an xterm waiting +on x11 when the scheduler decides X is being a CPU hog with all that +text console scrolling...)

+ +

So will data always be read from the far end of a pipe at the +same chunk sizes it was written in? Nope. Don't rely on that. For one +counterexample, see rfc 896 +for Nagle's algorithm, which waits a fraction of a second or so before +sending out small amounts of data through a TCP/IP connection in case more +data comes in that can be merged into the same packet. (In case you were +wondering why action games that use TCP/IP set TCP_NODELAY to lower the latency +on their their sockets, now you know.)

+ +

Memory used by relocatable code, PIC, and static linking.

+ +

The downside of standard dynamic linking is that it results in self-modifying +code. Although each executable's pages are mmaped() into a process' address +space from the executable file and are thus naturally shared between processes +out of the page cache, the library loader (ld-linux.so.2 or ld-uClibc.so.0) +writes to these pages to supply addresses for relocatable symbols. This +dirties the pages, triggering copy-on-write allocation of new memory for each +processes' dirtied pages.

+ +

One solution to this is Position Independent Code (PIC), a way of linking +a file so all the relocations are grouped together. This dirties fewer +pages (often just a single page) for each process' relocations. The down +side is this results in larger executables, which take up more space on disk +(and a correspondingly larger space in memory). But when many copies of the +same program are running, PIC dynamic linking trades a larger disk footprint +for a smaller memory footprint, by sharing more pages.

+ +

A third solution is static linking. A statically linked program has no +relocations, and thus the entire executable is shared between all running +instances. This tends to have a significantly larger disk footprint, but +on a system with only one or two executables, shared libraries aren't much +of a win anyway.

+ +

You can tell the glibc linker to display debugging information about its +relocations with the environment variable "LD_DEBUG". Try +"LD_DEBUG=help /bin/true" for a list of commands. Learning to interpret +"LD_DEBUG=statistics cat /proc/self/statm" could be interesting.

+ +

For more on this topic, here's Rich Felker:

+
+

Dynamic linking (without fixed load addresses) fundamentally requires +at least one dirty page per dso that uses symbols. Making calls (but +never taking the address explicitly) to functions within the same dso +does not require a dirty page by itself, but will with ELF unless you +use -Bsymbolic or hidden symbols when linking.

+ +

ELF uses significant additional stack space for the kernel to pass all +the ELF data structures to the newly created process image. These are +located above the argument list and environment. This normally adds 1 +dirty page to the process size.

+ +

The ELF dynamic linker has its own data segment, adding one or more +dirty pages. I believe it also performs relocations on itself.

+ +

The ELF dynamic linker makes significant dynamic allocations to manage +the global symbol table and the loaded dso's. This data is never +freed. It will be needed again if libdl is used, so unconditionally +freeing it is not possible, but normal programs do not use libdl. Of +course with glibc all programs use libdl (due to nsswitch) so the +issue was never addressed.

+ +

ELF also has the issue that segments are not page-aligned on disk. +This saves up to 4k on disk, but at the expense of using an additional +dirty page in most cases, due to a large portion of the first data +page being filled with a duplicate copy of the last text page.

+ +

The above is just a partial list of the tiny memory penalties of ELF +dynamic linking, which eventually add up to quite a bit. The smallest +I've been able to get a process down to is 8 dirty pages, and the +above factors seem to mostly account for it (but some were difficult +to measure).

+
+ +

Including kernel headers

+ +

The "linux" or "asm" directories of /usr/include contain Linux kernel +headers, so that the C library can talk directly to the Linux kernel. In +a perfect world, applications shouldn't include these headers directly, but +we don't live in a perfect world.

+ +

For example, Busybox's losetup code wants linux/loop.c because nothing else +#defines the structures to call the kernel's loopback device setup ioctls. +Attempts to cut and paste the information into a local busybox header file +proved incredibly painful, because portions of the loop_info structure vary by +architecture, namely the type __kernel_dev_t has different sizes on alpha, +arm, x86, and so on. Meaning we either #include or +we hardwire #ifdefs to check what platform we're building on and define this +type appropriately for every single hardware architecture supported by +Linux, which is simply unworkable.

+ +

This is aside from the fact that the relevant type defined in +posix_types.h was renamed to __kernel_old_dev_t during the 2.5 series, so +to cut and paste the structure into our header we have to #include + to figure out which name to use. (What we actually do is +check if we're building on 2.6, and if so just use the new 64 bit structure +instead to avoid the rename entirely.) But we still need the version +check, since 2.4 didn't have the 64 bit structure.

+ +

The BusyBox developers spent two years trying to figure +out a clean way to do all this. There isn't one. The losetup in the +util-linux package from kernel.org isn't doing it cleanly either, they just +hide the ugliness by nesting #include files. Their mount/loop.h +#includes "my_dev_t.h", which #includes and + just like we do. There simply is no alternative.

+ +

Just because directly #including kernel headers is sometimes +unavoidable doesn't me we should include them when there's a better +way to do it. However, block copying information out of the kernel headers +is not a better way.

+ +

Who are the BusyBox developers?

+ +

The following login accounts currently exist on busybox.net. (I.E. these +people can commit patches +into subversion for the BusyBox, uClibc, and buildroot projects.)

+ +
+aldot     :Bernhard Fischer
+andersen  :Erik Andersen      - uClibc and BuildRoot maintainer.
+bug1      :Glenn McGrath
+davidm    :David McCullough
+gkajmowi  :Garrett Kajmowicz  - uClibc++ maintainer
+jbglaw    :Jan-Benedict Glaw
+jocke     :Joakim Tjernlund
+landley   :Rob Landley        - BusyBox maintainer
+lethal    :Paul Mundt
+mjn3      :Manuel Novoa III
+osuadmin  :osuadmin
+pgf       :Paul Fox
+pkj       :Peter Kjellerstedt
+prpplague :David Anders
+psm       :Peter S. Mazinger
+russ      :Russ Dill
+sandman   :Robert Griebl
+sjhill    :Steven J. Hill
+solar     :Ned Ludd
+timr      :Tim Riker
+tobiasa   :Tobias Anderberg
+vapier    :Mike Frysinger
+
+ +

The following accounts used to exist on busybox.net, but don't anymore so +I can't ask /etc/passwd for their names. Rob Wentworth +asked Google and recovered the names:

+ +
+aaronl   :Aaron Lehmann
+beppu    :John Beppu
+dwhedon  :David Whedon
+erik     :Erik Andersen
+gfeldman :Gennady Feldman 
+jimg     :Jim Gleason
+kraai    :Matt Kraai
+markw    :Mark Whitley
+miles    :Miles Bader
+proski   :Pavel Roskin
+rjune    :Richard June
+tausq    :Randolph Chung
+vodz     :Vladimir N. Oleynik
+
+ + +
+
+
+ + diff --git a/docs/busybox.net/about.html b/docs/busybox.net/about.html new file mode 100644 index 000000000..5272ee63c --- /dev/null +++ b/docs/busybox.net/about.html @@ -0,0 +1,23 @@ + + +

BusyBox: The Swiss Army Knife of Embedded Linux

+ +

BusyBox combines tiny versions of many common UNIX utilities into a single +small executable. It provides replacements for most of the utilities you +usually find in GNU fileutils, shellutils, etc. The utilities in BusyBox +generally have fewer options than their full-featured GNU cousins; however, +the options that are included provide the expected functionality and behave +very much like their GNU counterparts. BusyBox provides a fairly complete +environment for any small or embedded system.

+ +

BusyBox has been written with size-optimization and limited resources in +mind. It is also extremely modular so you can easily include or exclude +commands (or features) at compile time. This makes it easy to customize +your embedded systems. To create a working system, just add some device +nodes in /dev, a few configuration files in /etc, and a Linux kernel.

+ +

BusyBox is maintained by Rob Landley, +and licensed under the GNU GENERAL PUBLIC LICENSE +version 2 or later.

+ + diff --git a/docs/busybox.net/busybox-growth.ps b/docs/busybox.net/busybox-growth.ps new file mode 100644 index 000000000..2379defa4 --- /dev/null +++ b/docs/busybox.net/busybox-growth.ps @@ -0,0 +1,404 @@ +%!PS-Adobe-2.0 +%%Title: busybox-growth.ps +%%Creator: gnuplot 3.5 (pre 3.6) patchlevel beta 347 +%%CreationDate: Tue Apr 10 14:03:36 2001 +%%DocumentFonts: (atend) +%%BoundingBox: 50 40 554 770 +%%Orientation: Landscape +%%Pages: (atend) +%%EndComments +/gnudict 120 dict def +gnudict begin +/Color true def +/Solid true def +/gnulinewidth 5.000 def +/userlinewidth gnulinewidth def +/vshift -46 def +/dl {10 mul} def +/hpt_ 31.5 def +/vpt_ 31.5 def +/hpt hpt_ def +/vpt vpt_ def +/M {moveto} bind def +/L {lineto} bind def +/R {rmoveto} bind def +/V {rlineto} bind def +/vpt2 vpt 2 mul def +/hpt2 hpt 2 mul def +/Lshow { currentpoint stroke M + 0 vshift R show } def +/Rshow { currentpoint stroke M + dup stringwidth pop neg vshift R show } def +/Cshow { currentpoint stroke M + dup stringwidth pop -2 div vshift R show } def +/UP { dup vpt_ mul /vpt exch def hpt_ mul /hpt exch def + /hpt2 hpt 2 mul def /vpt2 vpt 2 mul def } def +/DL { Color {setrgbcolor Solid {pop []} if 0 setdash } + {pop pop pop Solid {pop []} if 0 setdash} ifelse } def +/BL { stroke gnulinewidth 2 mul setlinewidth } def +/AL { stroke gnulinewidth 2 div setlinewidth } def +/UL { gnulinewidth mul /userlinewidth exch def } def +/PL { stroke userlinewidth setlinewidth } def +/LTb { BL [] 0 0 0 DL } def +/LTa { AL [1 dl 2 dl] 0 setdash 0 0 0 setrgbcolor } def +/LT0 { PL [] 1 0 0 DL } def +/LT1 { PL [4 dl 2 dl] 0 1 0 DL } def +/LT2 { PL [2 dl 3 dl] 0 0 1 DL } def +/LT3 { PL [1 dl 1.5 dl] 1 0 1 DL } def +/LT4 { PL [5 dl 2 dl 1 dl 2 dl] 0 1 1 DL } def +/LT5 { PL [4 dl 3 dl 1 dl 3 dl] 1 1 0 DL } def +/LT6 { PL [2 dl 2 dl 2 dl 4 dl] 0 0 0 DL } def +/LT7 { PL [2 dl 2 dl 2 dl 2 dl 2 dl 4 dl] 1 0.3 0 DL } def +/LT8 { PL [2 dl 2 dl 2 dl 2 dl 2 dl 2 dl 2 dl 4 dl] 0.5 0.5 0.5 DL } def +/Pnt { stroke [] 0 setdash + gsave 1 setlinecap M 0 0 V stroke grestore } def +/Dia { stroke [] 0 setdash 2 copy vpt add M + hpt neg vpt neg V hpt vpt neg V + hpt vpt V hpt neg vpt V closepath stroke + Pnt } def +/Pls { stroke [] 0 setdash vpt sub M 0 vpt2 V + currentpoint stroke M + hpt neg vpt neg R hpt2 0 V stroke + } def +/Box { stroke [] 0 setdash 2 copy exch hpt sub exch vpt add M + 0 vpt2 neg V hpt2 0 V 0 vpt2 V + hpt2 neg 0 V closepath stroke + Pnt } def +/Crs { stroke [] 0 setdash exch hpt sub exch vpt add M + hpt2 vpt2 neg V currentpoint stroke M + hpt2 neg 0 R hpt2 vpt2 V stroke } def +/TriU { stroke [] 0 setdash 2 copy vpt 1.12 mul add M + hpt neg vpt -1.62 mul V + hpt 2 mul 0 V + hpt neg vpt 1.62 mul V closepath stroke + Pnt } def +/Star { 2 copy Pls Crs } def +/BoxF { stroke [] 0 setdash exch hpt sub exch vpt add M + 0 vpt2 neg V hpt2 0 V 0 vpt2 V + hpt2 neg 0 V closepath fill } def +/TriUF { stroke [] 0 setdash vpt 1.12 mul add M + hpt neg vpt -1.62 mul V + hpt 2 mul 0 V + hpt neg vpt 1.62 mul V closepath fill } def +/TriD { stroke [] 0 setdash 2 copy vpt 1.12 mul sub M + hpt neg vpt 1.62 mul V + hpt 2 mul 0 V + hpt neg vpt -1.62 mul V closepath stroke + Pnt } def +/TriDF { stroke [] 0 setdash vpt 1.12 mul sub M + hpt neg vpt 1.62 mul V + hpt 2 mul 0 V + hpt neg vpt -1.62 mul V closepath fill} def +/DiaF { stroke [] 0 setdash vpt add M + hpt neg vpt neg V hpt vpt neg V + hpt vpt V hpt neg vpt V closepath fill } def +/Pent { stroke [] 0 setdash 2 copy gsave + translate 0 hpt M 4 {72 rotate 0 hpt L} repeat + closepath stroke grestore Pnt } def +/PentF { stroke [] 0 setdash gsave + translate 0 hpt M 4 {72 rotate 0 hpt L} repeat + closepath fill grestore } def +/Circle { stroke [] 0 setdash 2 copy + hpt 0 360 arc stroke Pnt } def +/CircleF { stroke [] 0 setdash hpt 0 360 arc fill } def +/C0 { BL [] 0 setdash 2 copy moveto vpt 90 450 arc } bind def +/C1 { BL [] 0 setdash 2 copy moveto + 2 copy vpt 0 90 arc closepath fill + vpt 0 360 arc closepath } bind def +/C2 { BL [] 0 setdash 2 copy moveto + 2 copy vpt 90 180 arc closepath fill + vpt 0 360 arc closepath } bind def +/C3 { BL [] 0 setdash 2 copy moveto + 2 copy vpt 0 180 arc closepath fill + vpt 0 360 arc closepath } bind def +/C4 { BL [] 0 setdash 2 copy moveto + 2 copy vpt 180 270 arc closepath fill + vpt 0 360 arc closepath } bind def +/C5 { BL [] 0 setdash 2 copy moveto + 2 copy vpt 0 90 arc + 2 copy moveto + 2 copy vpt 180 270 arc closepath fill + vpt 0 360 arc } bind def +/C6 { BL [] 0 setdash 2 copy moveto + 2 copy vpt 90 270 arc closepath fill + vpt 0 360 arc closepath } bind def +/C7 { BL [] 0 setdash 2 copy moveto + 2 copy vpt 0 270 arc closepath fill + vpt 0 360 arc closepath } bind def +/C8 { BL [] 0 setdash 2 copy moveto + 2 copy vpt 270 360 arc closepath fill + vpt 0 360 arc closepath } bind def +/C9 { BL [] 0 setdash 2 copy moveto + 2 copy vpt 270 450 arc closepath fill + vpt 0 360 arc closepath } bind def +/C10 { BL [] 0 setdash 2 copy 2 copy moveto vpt 270 360 arc closepath fill + 2 copy moveto + 2 copy vpt 90 180 arc closepath fill + vpt 0 360 arc closepath } bind def +/C11 { BL [] 0 setdash 2 copy moveto + 2 copy vpt 0 180 arc closepath fill + 2 copy moveto + 2 copy vpt 270 360 arc closepath fill + vpt 0 360 arc closepath } bind def +/C12 { BL [] 0 setdash 2 copy moveto + 2 copy vpt 180 360 arc closepath fill + vpt 0 360 arc closepath } bind def +/C13 { BL [] 0 setdash 2 copy moveto + 2 copy vpt 0 90 arc closepath fill + 2 copy moveto + 2 copy vpt 180 360 arc closepath fill + vpt 0 360 arc closepath } bind def +/C14 { BL [] 0 setdash 2 copy moveto + 2 copy vpt 90 360 arc closepath fill + vpt 0 360 arc } bind def +/C15 { BL [] 0 setdash 2 copy vpt 0 360 arc closepath fill + vpt 0 360 arc closepath } bind def +/Rec { newpath 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto + neg 0 rlineto closepath } bind def +/Square { dup Rec } bind def +/Bsquare { vpt sub exch vpt sub exch vpt2 Square } bind def +/S0 { BL [] 0 setdash 2 copy moveto 0 vpt rlineto BL Bsquare } bind def +/S1 { BL [] 0 setdash 2 copy vpt Square fill Bsquare } bind def +/S2 { BL [] 0 setdash 2 copy exch vpt sub exch vpt Square fill Bsquare } bind def +/S3 { BL [] 0 setdash 2 copy exch vpt sub exch vpt2 vpt Rec fill Bsquare } bind def +/S4 { BL [] 0 setdash 2 copy exch vpt sub exch vpt sub vpt Square fill Bsquare } bind def +/S5 { BL [] 0 setdash 2 copy 2 copy vpt Square fill + exch vpt sub exch vpt sub vpt Square fill Bsquare } bind def +/S6 { BL [] 0 setdash 2 copy exch vpt sub exch vpt sub vpt vpt2 Rec fill Bsquare } bind def +/S7 { BL [] 0 setdash 2 copy exch vpt sub exch vpt sub vpt vpt2 Rec fill + 2 copy vpt Square fill + Bsquare } bind def +/S8 { BL [] 0 setdash 2 copy vpt sub vpt Square fill Bsquare } bind def +/S9 { BL [] 0 setdash 2 copy vpt sub vpt vpt2 Rec fill Bsquare } bind def +/S10 { BL [] 0 setdash 2 copy vpt sub vpt Square fill 2 copy exch vpt sub exch vpt Square fill + Bsquare } bind def +/S11 { BL [] 0 setdash 2 copy vpt sub vpt Square fill 2 copy exch vpt sub exch vpt2 vpt Rec fill + Bsquare } bind def +/S12 { BL [] 0 setdash 2 copy exch vpt sub exch vpt sub vpt2 vpt Rec fill Bsquare } bind def +/S13 { BL [] 0 setdash 2 copy exch vpt sub exch vpt sub vpt2 vpt Rec fill + 2 copy vpt Square fill Bsquare } bind def +/S14 { BL [] 0 setdash 2 copy exch vpt sub exch vpt sub vpt2 vpt Rec fill + 2 copy exch vpt sub exch vpt Square fill Bsquare } bind def +/S15 { BL [] 0 setdash 2 copy Bsquare fill Bsquare } bind def +/D0 { gsave translate 45 rotate 0 0 S0 stroke grestore } bind def +/D1 { gsave translate 45 rotate 0 0 S1 stroke grestore } bind def +/D2 { gsave translate 45 rotate 0 0 S2 stroke grestore } bind def +/D3 { gsave translate 45 rotate 0 0 S3 stroke grestore } bind def +/D4 { gsave translate 45 rotate 0 0 S4 stroke grestore } bind def +/D5 { gsave translate 45 rotate 0 0 S5 stroke grestore } bind def +/D6 { gsave translate 45 rotate 0 0 S6 stroke grestore } bind def +/D7 { gsave translate 45 rotate 0 0 S7 stroke grestore } bind def +/D8 { gsave translate 45 rotate 0 0 S8 stroke grestore } bind def +/D9 { gsave translate 45 rotate 0 0 S9 stroke grestore } bind def +/D10 { gsave translate 45 rotate 0 0 S10 stroke grestore } bind def +/D11 { gsave translate 45 rotate 0 0 S11 stroke grestore } bind def +/D12 { gsave translate 45 rotate 0 0 S12 stroke grestore } bind def +/D13 { gsave translate 45 rotate 0 0 S13 stroke grestore } bind def +/D14 { gsave translate 45 rotate 0 0 S14 stroke grestore } bind def +/D15 { gsave translate 45 rotate 0 0 S15 stroke grestore } bind def +/DiaE { stroke [] 0 setdash vpt add M + hpt neg vpt neg V hpt vpt neg V + hpt vpt V hpt neg vpt V closepath stroke } def +/BoxE { stroke [] 0 setdash exch hpt sub exch vpt add M + 0 vpt2 neg V hpt2 0 V 0 vpt2 V + hpt2 neg 0 V closepath stroke } def +/TriUE { stroke [] 0 setdash vpt 1.12 mul add M + hpt neg vpt -1.62 mul V + hpt 2 mul 0 V + hpt neg vpt 1.62 mul V closepath stroke } def +/TriDE { stroke [] 0 setdash vpt 1.12 mul sub M + hpt neg vpt 1.62 mul V + hpt 2 mul 0 V + hpt neg vpt -1.62 mul V closepath stroke } def +/PentE { stroke [] 0 setdash gsave + translate 0 hpt M 4 {72 rotate 0 hpt L} repeat + closepath stroke grestore } def +/CircE { stroke [] 0 setdash + hpt 0 360 arc stroke } def +/Opaque { gsave closepath 1 setgray fill grestore 0 setgray closepath } def +/DiaW { stroke [] 0 setdash vpt add M + hpt neg vpt neg V hpt vpt neg V + hpt vpt V hpt neg vpt V Opaque stroke } def +/BoxW { stroke [] 0 setdash exch hpt sub exch vpt add M + 0 vpt2 neg V hpt2 0 V 0 vpt2 V + hpt2 neg 0 V Opaque stroke } def +/TriUW { stroke [] 0 setdash vpt 1.12 mul add M + hpt neg vpt -1.62 mul V + hpt 2 mul 0 V + hpt neg vpt 1.62 mul V Opaque stroke } def +/TriDW { stroke [] 0 setdash vpt 1.12 mul sub M + hpt neg vpt 1.62 mul V + hpt 2 mul 0 V + hpt neg vpt -1.62 mul V Opaque stroke } def +/PentW { stroke [] 0 setdash gsave + translate 0 hpt M 4 {72 rotate 0 hpt L} repeat + Opaque stroke grestore } def +/CircW { stroke [] 0 setdash + hpt 0 360 arc Opaque stroke } def +/BoxFill { gsave Rec 1 setgray fill grestore } def +end +%%EndProlog +%%Page: 1 1 +gnudict begin +gsave +50 50 translate +0.100 0.100 scale +90 rotate +0 -5040 translate +0 setgray +newpath +(Helvetica) findfont 140 scalefont setfont +1.000 UL +LTb +560 420 M +63 0 V +6409 0 R +-63 0 V +476 420 M +(0) Rshow +560 1056 M +63 0 V +6409 0 R +-63 0 V +-6493 0 R +(100) Rshow +560 1692 M +63 0 V +6409 0 R +-63 0 V +-6493 0 R +(200) Rshow +560 2328 M +63 0 V +6409 0 R +-63 0 V +-6493 0 R +(300) Rshow +560 2964 M +63 0 V +6409 0 R +-63 0 V +-6493 0 R +(400) Rshow +560 3600 M +63 0 V +6409 0 R +-63 0 V +-6493 0 R +(500) Rshow +560 4236 M +63 0 V +6409 0 R +-63 0 V +-6493 0 R +(600) Rshow +560 4872 M +63 0 V +6409 0 R +-63 0 V +-6493 0 R +(700) Rshow +1531 420 M +0 63 V +0 4389 R +0 -63 V +0 -4529 R +(400) Cshow +2825 420 M +0 63 V +0 4389 R +0 -63 V +0 -4529 R +(600) Cshow +4120 420 M +0 63 V +0 4389 R +0 -63 V +0 -4529 R +(800) Cshow +5414 420 M +0 63 V +0 4389 R +0 -63 V +0 -4529 R +(1000) Cshow +6708 420 M +0 63 V +0 4389 R +0 -63 V +0 -4529 R +(1200) Cshow +1.000 UL +LTb +560 420 M +6472 0 V +0 4452 V +-6472 0 V +560 420 L +0 2646 M +currentpoint gsave translate 90 rotate 0 0 M +(tar.gz size \(Kb\)) Cshow +grestore +3796 140 M +(time \(days since Jan 1, 1998\)) Cshow +1.000 UL +LT0 +696 420 M +0 593 V +1255 0 V +0 15 V +214 0 V +0 6 V +958 0 V +0 1 V +-84 0 V +0 37 V +168 0 V +0 262 V +13 0 V +0 56 V +91 0 V +0 33 V +6 0 V +0 1 V +19 0 V +0 11 V +20 0 V +0 13 V +32 0 V +0 104 V +52 0 V +0 27 V +65 0 V +0 15 V +39 0 V +0 126 V +174 0 V +0 103 V +52 0 V +0 49 V +175 0 V +0 56 V +433 0 V +0 661 V +415 0 V +0 857 V +123 0 V +0 -291 V +498 0 V +0 208 V +505 0 V +0 66 V +291 0 V +0 115 V +311 0 V +0 449 V +162 0 V +0 309 V +stroke +grestore +end +showpage +%%Trailer +%%DocumentFonts: Helvetica +%%Pages: 1 diff --git a/docs/busybox.net/copyright.txt b/docs/busybox.net/copyright.txt new file mode 100644 index 000000000..397475632 --- /dev/null +++ b/docs/busybox.net/copyright.txt @@ -0,0 +1,30 @@ + +The code and graphics on this website (and it's mirror sites, if any) are +Copyright (c) 1999-2004 by Erik Andersen. All rights reserved. +Copyright (c) 2005-2006 Rob Landley. + +Documents on this Web site including their graphical elements, design, and +layout are protected by trade dress and other laws and MAY BE COPIED OR +IMITATED IN WHOLE OR IN PART. THIS WEBSITE IS LICENSED FREE OF CHARGE, THERE +IS NO WARRANTY FOR THE WEBSITE TO THE EXTENT PERMITTED BY APPLICABLE LAW. +SHOULD THIS WEBSITE PROVE DEFECTIVE, YOU MAY ASSUME THAT SOMEONE MIGHT GET +AROUND TO SERVICING, REPAIRING OR CORRECTING IT SOMETIME WHEN THEY HAVE NOTHING +BETTER TO DO. REGARDLESS, YOU GET TO KEEP BOTH PIECES. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY +COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THIS +WEBSITE AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR +INABILITY TO USE THIS WEBSITE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR +LOSS OF HAIR, LOSS OF LIFE, LOSS OF MEMORY, LOSS OF YOUR CARKEYS, MISPLACEMENT +OF YOUR PAYCHECK, OR COMMANDER DATA BEING RENDERED UNABLE TO ASSIST THE +STARFLEET OFFICERS ABORD THE STARSHIP ENTERPRISE TO RECALIBRATE THE MAIN +DEFLECTOR ARRAY, LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE +WEBSITE TO OPERATE WITH YOUR WEBBROWSER), EVEN IF SUCH HOLDER OR OTHER PARTY +HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +You have been warned. + +You can contact the webmaster at if you have some sort +of problem with this. + diff --git a/docs/busybox.net/developer.html b/docs/busybox.net/developer.html new file mode 100644 index 000000000..cdb68b78c --- /dev/null +++ b/docs/busybox.net/developer.html @@ -0,0 +1,69 @@ + + +

Morris Dancing

+ +

Subversion commit access requires an account on Morris. The server +behind busybox.net and uclibc.org. If you want to be able to commit things to +Subversion, first contribute some stuff to show you are serious, can handle +some responsibility, and that your patches don't generally need a lot of +cleanup. Then, very nicely ask one of us (Rob +Landley for BusyBox, or Erik +Andersen for uClibc) for an account.

+ +

If you're approved for an account, you'll need to send an email from your +preferred contact email address with the username you'd like to use when +committing changes to SVN, and attach a public ssh key to access your account +with.

+ +

If you don't currently have an ssh version 2 DSA key at least 1024 bits +long (the default), you can generate a key using the +command ssh-keygen -t dsa and hitting enter at the prompts. This +will create the files ~/.ssh/id_dsa and ~/.ssh/id_dsa.pub +You must then send the content of 'id_dsa.pub' to me so I can set up your +account. (The content of 'id_dsa' should of course be kept secret, anyone +who has that can access any account that's installed your public key in +its .ssh/authorized_keys file.)

+ +

Note that if you would prefer to keep your communications with us +private, you can encrypt your email using +Rob's public key or +Erik's public +key.

+ +

Once you are setup with an account, you will need to use your account to +checkout a copy of BusyBox from Subversion:

+ +

svn checkout svn+ssh://username@busybox.net/svn/trunk/busybox

+

or

+

svn checkout svn+ssh://username@uclibc.org/svn/trunk/uclibc

+ +

You must change username to your own username, or omit +it if it's the same as your local username.

+ +

You can then enter the newly checked out project directory, make changes, +check your changes, diff your changes, revert your changes, and and commit your +changes using commands such as:

+ +
+svn diff
+svn status
+svn revert
+EDITOR=vi svn commit
+svn log -v -r PREV:HEAD
+svn help
+
+ +

For additional detail on how to use Subversion, please visit the +the Subversion website. +You might also want to read online or buy a copy of the Subversion Book...

+ +

A morris account also gives you a personal web page +(http://busybox.net/~username comes from ~/public_html on morris), and of +course a shell prompt you can ssh into (as a regular user, root access is +reserved for Erik and Rob). But keep in mind an account on Morris is a +priviledge, not a requirement. Most contributors to busybox and uClibc +haven't got one, and accounts are handed out to make the project maintainers' +lives easier, not because "you deserve it".

+ + diff --git a/docs/busybox.net/download.html b/docs/busybox.net/download.html new file mode 100644 index 000000000..f8746ddab --- /dev/null +++ b/docs/busybox.net/download.html @@ -0,0 +1,29 @@ + + + + +

Download

+ +Source for the latest release can always be +downloaded from http://www.busybox.net/downloads. + +

+You can also obtain Daily Snapshots of +the latest development source tree for those wishing to follow BusyBox development, +but cannot or do not wish to use Subversion (svn). + +

+ + + diff --git a/docs/busybox.net/footer.html b/docs/busybox.net/footer.html new file mode 100644 index 000000000..5f2335a2b --- /dev/null +++ b/docs/busybox.net/footer.html @@ -0,0 +1,47 @@ + + + + + + + +
+ + + + + + + + + + + +
+ + Copyright © 1999-2005 Erik Andersen +
+ Mail all comments, insults, suggestions and bribes to +
+ Denis Vlasenko vda.linux@googlemail.com
+
+
+ This site created with the vi editor + + This site is kindly hosted by OSL +
+ + + + diff --git a/docs/busybox.net/header.html b/docs/busybox.net/header.html new file mode 100644 index 000000000..081f18c7f --- /dev/null +++ b/docs/busybox.net/header.html @@ -0,0 +1,96 @@ + + + + + + BusyBox + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + +
BUSYBOX
+
+ + BusyBox
+
+ About + + Documentation + + Get BusyBox + + Development + +

Links +

+

Developer Pages +

+ + + +
+ diff --git a/docs/busybox.net/images/back.png b/docs/busybox.net/images/back.png new file mode 100644 index 000000000..79923869b Binary files /dev/null and b/docs/busybox.net/images/back.png differ diff --git a/docs/busybox.net/images/busybox.jpeg b/docs/busybox.net/images/busybox.jpeg new file mode 100644 index 000000000..37edc9614 Binary files /dev/null and b/docs/busybox.net/images/busybox.jpeg differ diff --git a/docs/busybox.net/images/busybox.png b/docs/busybox.net/images/busybox.png new file mode 100644 index 000000000..b1eb92f38 Binary files /dev/null and b/docs/busybox.net/images/busybox.png differ diff --git a/docs/busybox.net/images/busybox1.png b/docs/busybox.net/images/busybox1.png new file mode 100644 index 000000000..4d3126a52 Binary files /dev/null and b/docs/busybox.net/images/busybox1.png differ diff --git a/docs/busybox.net/images/busybox2.jpg b/docs/busybox.net/images/busybox2.jpg new file mode 100644 index 000000000..abf8f0610 Binary files /dev/null and b/docs/busybox.net/images/busybox2.jpg differ diff --git a/docs/busybox.net/images/busybox3.jpg b/docs/busybox.net/images/busybox3.jpg new file mode 100644 index 000000000..0fab84cf9 Binary files /dev/null and b/docs/busybox.net/images/busybox3.jpg differ diff --git a/docs/busybox.net/images/dir.png b/docs/busybox.net/images/dir.png new file mode 100644 index 000000000..1d633ce4a Binary files /dev/null and b/docs/busybox.net/images/dir.png differ diff --git a/docs/busybox.net/images/donate.png b/docs/busybox.net/images/donate.png new file mode 100644 index 000000000..b55621bb9 Binary files /dev/null and b/docs/busybox.net/images/donate.png differ diff --git a/docs/busybox.net/images/fm.mini.png b/docs/busybox.net/images/fm.mini.png new file mode 100644 index 000000000..c0883cd34 Binary files /dev/null and b/docs/busybox.net/images/fm.mini.png differ diff --git a/docs/busybox.net/images/gfx_by_gimp.png b/docs/busybox.net/images/gfx_by_gimp.png new file mode 100644 index 000000000..d58314034 Binary files /dev/null and b/docs/busybox.net/images/gfx_by_gimp.png differ diff --git a/docs/busybox.net/images/ltbutton2.png b/docs/busybox.net/images/ltbutton2.png new file mode 100644 index 000000000..9bad9496a Binary files /dev/null and b/docs/busybox.net/images/ltbutton2.png differ diff --git a/docs/busybox.net/images/osuosl.png b/docs/busybox.net/images/osuosl.png new file mode 100644 index 000000000..b00b5007d Binary files /dev/null and b/docs/busybox.net/images/osuosl.png differ diff --git a/docs/busybox.net/images/sdsmall.png b/docs/busybox.net/images/sdsmall.png new file mode 100644 index 000000000..b1024501b Binary files /dev/null and b/docs/busybox.net/images/sdsmall.png differ diff --git a/docs/busybox.net/images/text.png b/docs/busybox.net/images/text.png new file mode 100644 index 000000000..6034f899f Binary files /dev/null and b/docs/busybox.net/images/text.png differ diff --git a/docs/busybox.net/images/valid-html401.png b/docs/busybox.net/images/valid-html401.png new file mode 100644 index 000000000..ec9bc0ce0 Binary files /dev/null and b/docs/busybox.net/images/valid-html401.png differ diff --git a/docs/busybox.net/images/vh40.gif b/docs/busybox.net/images/vh40.gif new file mode 100644 index 000000000..c5e9402e7 Binary files /dev/null and b/docs/busybox.net/images/vh40.gif differ diff --git a/docs/busybox.net/images/written.in.vi.png b/docs/busybox.net/images/written.in.vi.png new file mode 100644 index 000000000..84f59bc15 Binary files /dev/null and b/docs/busybox.net/images/written.in.vi.png differ diff --git a/docs/busybox.net/index.html b/docs/busybox.net/index.html new file mode 100644 index 000000000..1bab6b069 --- /dev/null +++ b/docs/busybox.net/index.html @@ -0,0 +1 @@ + diff --git a/docs/busybox.net/license.html b/docs/busybox.net/license.html new file mode 100644 index 000000000..76358bc65 --- /dev/null +++ b/docs/busybox.net/license.html @@ -0,0 +1,97 @@ + + +

+

BusyBox is licensed under the GNU General Public License, version 2

+ +

BusyBox is licensed under the +GNU General Public License version 2, which is often abbreviated as GPLv2. +(This is the same license the Linux kernel is under, so you may be somewhat +familiar with it by now.)

+ +

A complete copy of the license text is included in the file LICENSE in +the BusyBox source code.

+ +

Anyone thinking of shipping BusyBox as part of a +product should be familiar with the licensing terms under which they are +allowed to use and distribute BusyBox. Read the full test of the GPL (either +through the above link, or in the file LICENSE in the busybox tarball), and +also read the Frequently +Asked Questions about the GPL.

+ +

Basically, if you distribute GPL software the license requires that you also +distribute the source code to that GPL-licensed software. So if you distribute +BusyBox without making the source code to the version you distribute available, +you violate the license terms, and thus infringe on the copyrights of BusyBox. +(This requirement applies whether or not you modified BusyBox; either way the +license terms still apply to you.) Read the license text for the details.

+ +

A note on GPL versions

+ +

Version 2 of the GPL is the only version of the GPL which current versions +of BusyBox may be distributed under. New code added to the tree is licensed +GPL version 2, and the project's license is GPL version 2.

+ +

Older versions of BusyBox (versions 1.2.2 and earlier, up through about svn +16112) included variants of the recommended "GPL version 2 or (at your option) +later versions" boilerplate permission grant. Ancient versions of BusyBox +(before svn 49) did not specify any version at all, and section 9 of GPLv2 +(the most recent version at the time) says those old versions may be +redistributed under any version of GPL (including the obsolete V1). This was +conceptually similar to a dual license, except that the different licenses were +different versions of the GPL.

+ +

However, BusyBox has apparently always contained chunks of code that were +licensed under GPL version 2 only. Examples include applets written by Linus +Torvalds (util-linux/mkfs_minix.c and util_linux/mkswap.c) which stated they +"may be redistributed as per the Linux copyright" (which Linus clarified in the +2.4.0-pre8 release announcement in 2000 was GPLv2 only), and Linux kernel code +copied into libbb/loop.c (after Linus's announcement). There are probably +more, because all we used to check was that the code was GPL, not which +version. (Before the GPLv3 draft proceedings in 2006, it was a purely +theoretical issue that didn't come up much.)

+ +

To summarize: every version of BusyBox may be distributed under the terms of +GPL version 2. New versions (after 1.2.2) may only be distributed under +GPLv2, not under other versions of the GPL. Older versions of BusyBox might +(or might not) be distributable under other versions of the GPL. If you +want to use a GPL version other than 2, you should start with one of the old +versions such as release 1.2.2 or SVN 16112, and do your own homework to +identify and remove any code that can't be licensed under the GPL version you +want to use. New development is all GPLv2.

+ +

License enforcement

+ +

BusyBox's copyrights are enforced by the Software Freedom Law Center +(you can contact them at gpl@busybox.net), which +"accepts primary responsibility for enforcement of US copyrights on the +software... and coordinates international copyright enforcement efforts for +such works as necessary." If you distribute BusyBox in a way that doesn't +comply with the terms of the license BusyBox is distributed under, expect to +hear from these guys. Their entire reason for existing is to do pro-bono +legal work for free/open source software projects. (We used to list people who +violate the BusyBox license in The Hall of Shame, +but these days we find it much more effective to hand them over to the +lawyers.)

+ +

Our enforcement efforts are aimed at bringing people into compliance with +the BusyBox license. Open source software is under a different license from +proprietary software, but if you violate that license you're still a software +pirate and the law gives the vendor (us) some big sticks to play with. We +don't want monetary awards, injunctions, or to generate bad PR for a company, +unless that's the only way to get somebody that repeatedly ignores us to comply +with the license on our code.

+ +

A Good Example

+ +

These days, Linksys is +doing a good job at complying with the GPL, they get to be an +example of how to do things right. Please take a moment and +check out what they do with + +distributing the firmware for their WRT54G Router. +Following their example would be a fine way to ensure that you +have also fulfilled your licensing obligations.

+ + + diff --git a/docs/busybox.net/links.html b/docs/busybox.net/links.html new file mode 100644 index 000000000..9cdbd7c8c --- /dev/null +++ b/docs/busybox.net/links.html @@ -0,0 +1,19 @@ + + +

Related Sites

+ +
uClibc.org +
uClibc++ +
udhcp +
buildroot +
Scratchbox +
OpenEmbedded +
uCdot +
LinuxDevices +
Slashdot +
Freshmeat +
Linux Today +
Linux Weekly News +
Linux HOWTOs + + diff --git a/docs/busybox.net/lists.html b/docs/busybox.net/lists.html new file mode 100644 index 000000000..3a28cc07e --- /dev/null +++ b/docs/busybox.net/lists.html @@ -0,0 +1,46 @@ + + + + + +

Mailing List Information

+BusyBox has a mailing list for discussion and +development. You can subscribe by visiting +this page. +Only subscribers to the BusyBox mailing list are allowed to post +to this list. + +

+There is also a mailing list for active developers +wishing to read the complete diff of each and every change to busybox -- not for the +faint of heart. Active developers can subscribe by visiting +this page. +The Subversion server is the only one permtted to post to this list. And yes, +this list name uses the word 'cvs' even though we don't use that anymore... + +

+ + +

Search the List Archives

+Please search the mailing list archives before asking questions on the mailing +list, since there is a good chance someone else has asked the same question +before. Checking the archives is a great way to avoid annoying everyone on the +list with frequently asked questions... +

+ +

+
+ + + +
+ +
+Google +
+
+
+ + + + diff --git a/docs/busybox.net/news.html b/docs/busybox.net/news.html new file mode 100644 index 000000000..375519925 --- /dev/null +++ b/docs/busybox.net/news.html @@ -0,0 +1,185 @@ + + + +
    +
  • 14 December, 2006 -- BusyBox 1.3.0 (stable) +

    BusyBox 1.3.0.

    + +

    This release has CONFIG_DESKTOP option which enables features + needed for busybox usage on desktop machine. For example, find, chmod + and chown get several less frequently used options, od is significantly + bigger but matches GNU coreutils, etc. Intended to eventually make + busybox a viable alternative for "standard" utilities for slightly + adventurous desktop users. +

    Changes since previous release: +

      +
    • find: taking many more of standard options +
    • ps: POSIX-compliant -o implemented +
    • cp: added -s, -l +
    • grep: added -r, fixed -h +
    • watch: make it exec child like standard one does (was totally + incompatible) +
    • tar: fix limitations which were preventing bbox tar usage + on a big directories: long names and linknames, pax headers + (Linux kernel tarballs have that). Fixed a number of obscure bugs. + Raised max file limit (now 64Gb). Security fixes (/../ attacks). +
    • httpd: added -i (inetd), -f (foreground), support for + directory indexer CGI (example is included), bugfixes. +
    • telnetd: fixed/improved IPv6 support, inetd+standalone support, + other fixes. Useful IPv6 stuff factored out into libbb. +
    • runit/*: new applets adapted from http://smarden.sunsite.dk/runit/ + (these are my personal favorite small-and-beautiful toys) +
    • minor bugfixes to: login, dd, mount, umount, chmod, chown, ln, udhcp, + fdisk, ifconfig, sort, tee, mkswap, wget, insmod. +
    +

    Note that GnuPG key used to sign this release is different. + 1.2.2.1 is also signed post-factum now. Sorry for the mess. +

    +
  • + +
  • 29 October, 2006 -- BusyBox 1.2.2.1 (fix) +

    BusyBox 1.2.2.1.

    + +

    Added compile-time warning that static linking against glibc + produces buggy executables. +

  • + +
  • 24 October, 2006 -- BusyBox 1.2.2 (stable) +

    It's a bit overdue, but + here is + BusyBox 1.2.2.

    + +

    This release has dozens of fixes backported from the ongoing development + branch. There are a couple of bugfixes to sed, two fixes to documentation + generation (BusyBox.html shouldn't have USE() macros in it anymore), fix + umount to report the right errno on failure and to umount block devices by + name with newer kernels, fix mount to handle symlinks properly, make mdev + delete device nodes when called for hotplug remove, fix a segfault + in traceroute, a minor portability fix to md5sum option parsing, a build + fix for httpd with old gccs, an options parsing tweak to hdparm, make test + fail gracefully when getgroups() returns -1, fix a race condition in + modprobe when two instances run at once (hotplug does this), make "tar xf + foo.tar dir/dir" extract all subdirectories, make our getty initialize the + terminal more like mingetty, an selinux build fix, an endianness fix in + ping6, fix for zcip defending addresses, clean up some global variables in + gzip to save memory, fix sulogin -tNNN, a help text tweak, several warning + fixes and build fixes, fixup dnsd a bit, and a partridge in a pear tree.

    + +

    As Linux Weekly News noted, + this is my (Rob's) last release of BusyBox. The new maintainer is Denis + Vlasenko, I'm off to do other things. +

    +
  • + +
  • 29 September, 2006 -- New license email address. +

    The email address gpl@busybox.net is now the recommended way to contact + the Software Freedom Law Center to report BusyBox license violations.

    + +
  • 31 July 2006 -- BusyBox 1.2.1 (stable) +

    Since nobody seems to have objected too loudly over the weekend, I + might as well point you all at + Busybox + 1.2.1, a bugfix-only release with no new features.

    + +

    It has three shell fixes (two to lash: going "var=value" without + saying "export" should now work, plus a missing null pointer check, and + one to ash when redirecting output to a file that fills up.) Fix three + embarassing thinkos in the new dmesg command. Two build tweaks + (dependencies for the compressed usage messages and running make in the + libbb subdirectory). One fix to tar so it can extract git-generated + tarballs (rather than barfing on the pax extensions). And a partridge + in a pear... Ahem.

    + +

    But wait, there's more! A passwd changing fix so an empty + gecos field doesn't trigger a false objection that the new passwd contains + the gecos field. Make all our setuid() and setgid() calls check the return + value in case somebody's using per-process resource limits that prevent + a user from having too many processes (and thus prevent a process from + switching away from root, in which case the process will now _die_ rather + than continue with root privileges). A fix to adduser to make sure that + /etc/group gets updated. And a fix to modprobe to look for modules.conf + in the right place on 2.6 kernels.

    + +
  • 30 June 2006 -- BusyBox 1.2.0 +

    The -devel branch has been stabilized and the result is + Busybox + 1.2.0. Lots of stuff changed, I need to work up a decent changelog + over the weekend.

    + +

    I'm still experimenting with how long is best for the development + cycle, and since we've got some largeish projects queued up I'm going to + try a longer one. Expect 1.3.0 in December. (Expect 1.2.1 any time + we fix enough bugs. :)

    + +

    Update: Here are the first few bug fixes that will go into 1.2.1.

    + +
  • 17 May 2006 -- BusyBox 1.1.3 (stable) +

    BusyBox + 1.1.3 is another bugfix release. It makes passwd use salt, fixes a + memory freeing bug in ls, fixes "build all sources at once" mode, makes + mount -a not abort on the first failure, fixes msh so ctrl-c doesn't kill + background processes, makes patch work with patch hunks that don't have a + timestamp, make less's text search a lot more robust (the old one could + segfault), and fixes readlink -f when built against uClibc.

    + +

    Expect 1.2.0 sometime next month, which won't be a bugfix release.

    + +
  • 10 April 2006 -- BusyBox 1.1.2 (stable) +

    You can now download BusyBox 1.1.2, a bug fix release consisting of 11 patches + backported from the development branch: Some build fixes, several fixes + for mount and nfsmount, a fix for insmod on big endian systems, a fix for + find -xdev, and a fix for comm. Check the file "changelog" in the tarball + for more info.

    + +

    The next new development release (1.2.0) is slated for June. A 1.1.3 + will be released before then if more bug fixes crop up. (The new plan is + to have a 1.x.0 new development release every 3 months, with 1.x.y stable + bugfix only releases based on that as appropriate.)

    + +
  • 27 March 2006 -- Software Freedom Law Center representing BusyBox and uClibc +

    One issue Erik Andersen wanted to resolve when handing off BusyBox + maintainership to Rob Landley was license enforcement. BusyBox and + uClibc's existing license enforcement efforts (pro-bono representation + by Erik's father's law firm, and the + Hall of Shame), haven't + scaled to match the popularity of the projects. So we put our heads + together and did the obvious thing: ask Pamela Jones of + Groklaw for suggestions. She + referred us to the fine folks at softwarefreedom.org.

    + +

    As a result, we're pleased to announce that the + Software Freedom Law Center + has agreed to represent BusyBox and uClibc. We join a number of other + free and open source software projects (such as + X.org, + Wine, and + Plone + in being represented by a fairly cool bunch of lawyers, which is not a + phrase you get to use every day.

    + +
  • 22 March 2006 -- BusyBox 1.1.1 +

    The new maintainer is Rob Landley, and the new release is BusyBox 1.1.1. Expect a "what's new" document in a few days. (Also, Erik and I have have another announcement pending...)

    +

    Update: Rather than put out an endless stream of 1.1.1.x releases, + the various small fixes have been collected together into a + patch, + and new fixes will be appended to that as needed. Expect 1.1.2 around + June.

    +
  • +
  • 11 January 2006 -- 1.1.0 is out +

    The new stable release is + BusyBox + 1.1.0. It has a number of improvements, including several new applets. + (It also has a few rough spots, + but we're trying out a "release early, release often" strategy to see how + that works. Expect 1.1.1 sometime in March.)

    + +
  • Old News

    + Click here to read older news +

    +
  • + + +
+ + + diff --git a/docs/busybox.net/oldnews.html b/docs/busybox.net/oldnews.html new file mode 100644 index 000000000..1017b6975 --- /dev/null +++ b/docs/busybox.net/oldnews.html @@ -0,0 +1,1140 @@ + + + +
    +
  • 31 October 2005 -- 1.1.0-pre1 +

    The development branch of busybox is stable enough for wider testing, so + you can now + download, + the first prerelease of 1.1.0. This prerelease includes a lot of + new + functionality: new applets, new features, and extensive rewrites of + several existing applets. This prerelease should be noticeably more + standards + compliant than earlier versions of busybox, although we're + still working out the bugs.

    + +
  • 16 August 2005 -- 1.01 is out + +

    A new stable release (BusyBox + 1.01) is now available for download, containing over a hundred + small + fixes that have cropped up since the 1.00 release.

    + +
  • 13 January 2005 -- Bug and Patch Tracking

    + + Bug reports sometimes get lost when posted to the mailing list. The + developers of BusyBox are busy people, and have only so much they can keep + in their brains at a time. In my case, I'm lucky if I can remember my own + name, much less a bug report posted last week... To prevent your bug report + from getting lost, if you find a bug in BusyBox, please use the + shiny new Bug and Patch Tracking System + to post all the gory details. + +

    + + The same applies to patches... Regardless of whether your patch + is a bug fix or adds spiffy new features, please post your patch + to the Bug and Patch Tracking System to make certain it is + properly considered. + + +

    +

  • 13 October 2004 -- BusyBox 1.00 released

    + + When you take a careful look at nearly every embedded Linux device or + software distribution shipping today, you will find a copy of BusyBox. + With countless routers, set top boxes, wireless access points, PDAs, and + who knows what else, the future for Linux and BusyBox on embedded devices + is looking very bright. + +

    + + It is therefore with great satisfaction that I declare each and every + device already shipping with BusyBox is now officially out of date. + The highly anticipated release of BusyBox 1.00 has arrived! + +

    + + Over three years in development, BusyBox 1.00 represents a tremendous + improvement over the old 0.60.x stable series. Now featuring a Linux + KernelConf based configuration system (as used by the Linux kernel), + Linux 2.6 kernel support, many many new applets, and the development + work and testing of thousands of people from around the world. + +

    + + If you are already using BusyBox, you are strongly encouraged to upgrade to + BusyBox 1.00. If you are considering developing an embedded Linux device + or software distribution, you may wish to investigate if using BusyBox is + right for your application. If you need help getting started using + BusyBox, if you wish to donate to help cover expenses, or if you find a bug + and need help reporting it, you are invited to visit the BusyBox FAQ. + +

    + + As usual you can download busybox here. + +

    Have Fun! + +

    +

  • Old News

    + Click here to read older news + + +

  • 16 August 2004 -- BusyBox 1.0.0-rc3 released

    + + Here goes release candidate 3... +

    + The changelog has all the details. + And as usual you can download busybox here. + +

    Have Fun! + +

    +

  • 26 July 2004 -- BusyBox 1.0.0-rc2 released

    + + Here goes release candidate 2... +

    + The changelog has all the details. + And as usual you can download busybox here. + +

    Have Fun! + +

    +

  • 20 July 2004 -- BusyBox 1.0.0-rc1 released

    + + Here goes release candidate 1... This fixes all (most?) of the problems + that have turned up since -pre10. In particular, loading and unloading of + kernel modules with 2.6.x kernels should be working much better. +

    + + I really want to get BusyBox 1.0.0 released soon and I see no real + reason why the 1.0.0 release shouldn't happen with things pretty much as + is. BusyBox is in good shape at the moment, and it works nicely for + everything that I'm doing with it. And from the reports I've been getting, + it works nicely for what most everyone else is doing with it as well. + There will eventually be a 1.0.1 anyway, so we might as well get on with + it. No, BusyBox is not perfect. No piece of software ever is. And while + there is still plenty that can be done to improve things, most of that work + is waiting till we can get a solid 1.0.0 release out the door.... +

    + + Please do not bother to send in patches adding cool new features at this + time. Only bug-fix patches will be accepted. If you have submitted a + bug-fixing patch to the busybox mailing list and no one has emailed you + explaining why your patch was rejected, it is safe to say that your patch + has been lost or forgotten. That happens sometimes. Please re-submit your + bug-fixing patch to the BusyBox mailing list, and be sure to put "[PATCH]" + at the beginning of the email subject line! + +

    + The changelog has all the details. + And as usual you can download busybox here. + +

    Have Fun! + +

    + On a less happy note, My 92 year old grandmother (my dad's mom) passed away + yesterday (June 19th). The funeral will be Thursday in a little town about + 2 hours south of my home. I've checked and there is absolutely no way I + could be back in time for the funeral if I attend OLS and give my presentation + as scheduled. +

    + As such, it is with great reluctance and sadness that I have come + to the conclusion I will have to make my appologies and skip OLS + this year. +

    + + +

    +

  • 13 April 2004 -- BusyBox 1.0.0-pre10 released

    + + Ok, I lied. It turns out that -pre9 will not be the final BusyBox + pre-release. With any luck however -pre10 will be, since I really + want to get BusyBox 1.0.0 released very soon. As usual, please do not + bother to send in patches adding cool new features at this time. Only + bug-fix patches will be accepted. It would also be very helpful if + people could continue to review the BusyBox documentation and submit + improvements. + +

    + The changelog has all the details. + And as usual you can download busybox here. + +

    Have Fun! +

    + + +

    +

  • 6 April 2004 -- BusyBox 1.0.0-pre9 released

    + + Here goes the final BusyBox pre-release... This is your last chance for + bug fixes. With luck this will be released as BusyBox 1.0.0 later this + week. Please do not bother to send in patches adding cool new features at + this time. Only bug-fix patches will be accepted. It would also be + very helpful if people could help review the BusyBox documentation + and submit improvements. I've spent a lot of time updating the + documentation to make it better match reality, but I could really use some + assistance in checking that the features supported by the various applets + match the features listed in the documentation. + +

    + I had hoped to get this released a month ago, but + + another release on 1 March 2004 has kept me busy... + +

    + The changelog has all the details. + And as usual you can download busybox here. + +

    Have Fun! +

    + + +

    +

  • 23 February 2004 -- BusyBox 1.0.0-pre8 released

    + + Here goes yet another BusyBox pre-release... Please do not bother to send + in patches supplying new features at this time. Only bug-fix patches will + be accepted. If you have a cool new feature you would like to see + supported, or if you have an amazing new applet you would like to submit, + please wait and submit such things later. We really want to get a release + out we can all be proud of. We are still aiming to finish off the -pre + series in February and move on to the final 1.0.0 release... So if you + spot any bugs, now would be an excellent time to send in a fix to the + busybox mailing list. It would also be very helpful if people could + help review the BusyBox documentation and submit improvements. It would be + especially helpful if people could check that the features supported by the + various applets match the features listed in the documentation. + +

    + + The changelog has all the details. + And as usual you can download busybox here. + +

    Have Fun! +

    + + +

  • 4 February 2004 -- BusyBox 1.0.0-pre7 released

    + + There was a bug in -pre6 that broke argument parsing for a + number of applets, since a variable was not being zeroed out + properly. This release is primarily intended to fix that one + problem. In addition, this release fixes several other + problems, including a rewrite by mjn3 of the code for parsing + the busybox.conf file used for suid handling, some shell updates + from vodz, and a scattering of other small fixes. We are still + aiming to finish off the -pre series in February and move on to + the final 1.0.0 release... If you see any problems, of have + suggestions to make, as always, please feel free to email the + busybox mailing list. + +

    + + The changelog has all + the details. And as usual you can + download busybox here. + +

    Have Fun! +

    + + +

    +

  • 30 January 2004 -- BusyBox 1.0.0-pre6 released

    + + Here goes the next pre-release for the new BusyBox stable + series. This release adds a number of size optimizations, + updates udhcp, fixes up 2.6 modutils support, updates ash + and the shell command line editing, and the usual pile of + bug fixes both large and small. Things appear to be + settling down now, so with a bit of luck and some testing + perhaps we can finish off the -pre series in February and + move on to the final 1.0.0 release... If you see any + problems, of have suggestions to make, as always, please + feel free to email the busybox mailing list. + +

    + + People who rely on the daily BusyBox snapshots + should be aware that snapshots of the old busybox 0.60.x + series are no longer available. Daily snapshots are now + only available for the BusyBox 1.0.0 series and now use + the naming scheme "busybox-<date>.tar.bz2". Please + adjust any build scripts using the old naming scheme accordingly. + +

    + + The changelog has all + the details. And as usual you can + download busybox here. + +

    Have Fun! +

    + + +

    +

  • 23 December 2003 -- BusyBox 1.0.0-pre5 released

    + + Here goes the next pre-release for the new BusyBox stable + series. The most obvious thing in this release is a fix for + a terribly stupid bug in mount that prevented it from working + properly unless you specified the filesystem type. This + release also fixes a few compile problems, updates udhcp, + fixes a silly bug in fdisk, fixes ifup/ifdown to behave like + the Debian version, updates devfsd, updates the 2.6.x + modutils support, add a new 'rx' applet, removes the obsolete + 'loadacm' applet, fixes a few tar bugs, fixes a sed bug, and + a few other odd fixes. + +

    + + If you see any problems, of have suggestions to make, as + always, please feel free to send an email to the busybox + mailing list. + +

    + + The changelog has all + the details. And as usual you can + download busybox here. + +

    Have Fun! +

    + + + +

  • 10 December 2003 -- BusyBox 1.0.0-pre4 released

    + + Here goes the fourth pre-release for the new BusyBox stable + series. This release includes major rework to sed, lots of + rework on tar, a new tiny implementation of bunzip2, a new + devfsd applet, support for 2.6.x kernel modules, updates to + the ash shell, sha1sum and md5sum have been merged into a + common applet, the dpkg applets has been cleaned up, and tons + of random bugs have been fixed. Thanks everyone for all the + testing, bug reports, and patches! Once again, a big + thank-you goes to Glenn McGrath (bug1) for stepping in and + helping get patches merged! + +

    + + And of course, if you are reading this, you might have noticed + the busybox website has been completely reworked. Hopefully + things are now somewhat easier to navigate... If you see any + problems, of have suggestions to make, as always, please feel + free to send an email to the busybox mailing list. + +

    + + The changelog has all + the details. And as usual you can + download busybox here. + +

    Have Fun! + + + +

    +

  • 12 Sept 2003 -- BusyBox 1.0.0-pre3 released

    + + Here goes the third pre-release for the new BusyBox stable + series. The last prerelease has held up quite well under + testing, but a number of problems have turned up as the number + of people using it has increased. Thanks everyone for all + the testing, bug reports, and patches! + +

    + + If you have submitted a patch or a bug report to the busybox + mailing list and no one has emailed you explaining why your + patch was rejected, it is safe to say that your patch has + somehow gotten lost or forgotten. That happens sometimes. + Please re-submit your patch or bug report to the BusyBox + mailing list! + +

    + + The point of the "-preX" versions is to get a larger group of + people and vendors testing, so any problems that turn up can be + fixed prior to the final 1.0.0 release. The main feature + (besides additional testing) that is still still on the TODO + list before the final BusyBox 1.0.0 release is sorting out the + modutils issues. For the new 2.6.x kernels, we already have + patches adding insmod and rmmod support and those need to be + integrated. For 2.4.x kernels, for which busybox only supports + a limited number of architectures, we may want to invest a bit + more work before we cut 1.0.0. Or we may just leave 2.4.x + module loading alone. + +

    + + I had hoped this release would be out a month ago. And of + course, it wasn't since Erik became busy getting a release of + uClibc + out the door. Many thanks to Glenn McGrath (bug1) for + stepping in and helping get a bunch of patches merged! I am + not even going to state a date for releasing BusyBox 1.0.0 + -pre4 (or the final 1.0.0). We're aiming for late September... + But if this release proves as to be exceptionally stable (or + exceptionally unstable!), the next release may be very soon + indeed. + +

    + + The changelog has all + the details. And as usual you can + download busybox here. + +

    Have Fun! + + +

    +

  • 30 July 2003 -- BusyBox 1.0.0-pre2 released

    + + Here goes another pre release for the new BusyBox stable + series. The last prerelease (pre1) was given quite a lot of + testing (thanks everyone!) which has helped turn up a number of + bugs, and these problems have now been fixed. + +

    + + Highlights of -pre2 include updating the 'ash' shell to sync up + with the Debian 'dash' shell, a new 'hdparm' applet was added, + init again supports pivot_root, The 'reboot' 'halt' and + 'poweroff' applets can now be used without using busybox init. + an ifconfig buffer overflow was fixed, losetup now allows + read-write loop devices, uClinux daemon support was added, the + 'watchdog', 'fdisk', and 'kill' applets were rewritten, there were + tons of doc updates, and there were many other bugs fixed. +

    + + If you have submitted a patch and it is not included in this + release and Erik has not emailed you explaining why your patch + was rejected, it is safe to say that he has lost your patch. + That happens sometimes. Please re-submit your patch to the + BusyBox mailing list. +

    + + The point of the "-preX" versions is to get a larger group of + people and vendors testing, so any problems that turn up can be + fixed prior to the final 1.0.0 release. The main feature that + is still still on the TODO list before the final BusyBox 1.0.0 + release is adding module support for the new 2.6.x kernels. If + necessary, a -pre3 BusyBox release will happen on August 6th. + Hopefully (i.e. unless some horrible catastrophic problem + turns up) the final BusyBox 1.0.0 release will be ready by + then... +

    + + The changelog has all + the details. As usual you can download busybox here. + +

    Have Fun! +

    + +

    +

  • 15 July 2003 -- BusyBox 1.0.0-pre1 released

    + + The busybox development series has been under construction for + nearly two years now. Which is just entirely too long... So + it is with great pleasure that I announce the imminent release + of a new stable series. Due to the huge number of changes + since the last stable release (and the usual mindless version + number inflation) I am branding this new stable series verison + 1.0.x... +

    + + The point of "-preX" versions is to get a larger group of + people and vendors testing, so any problems that turn up can be + fixed prior to the magic 1.0.0 release (which should happen + later this month)... I plan to release BusyBox 1.0.0-pre2 next + Monday (July 21st), and, if necessary, -pre3 on July 28th. + Hopefully (i.e. unless some horrible catastrophic problem turns + up) the final BusyBox 1.0.0 release should be ready by the end + of July. +

    + + If you have submitted patches, and they are not in this release + and I have not emailed you explaining why your patch was + rejected, it is safe to say that I have lost your patch. That + happens sometimes. Please do NOT send all your patches, + support questions, etc, directly to Erik. I get hundreds of + emails every day (which is why I end up losing patches + sometimes in the flood)... The busybox mailing list is the + right place to send your patches, support questions, etc. +

    + + I would like to especially thank Vladimir Oleynik (vodz), Glenn + McGrath (bug1), Robert Griebl (sandman), and Manuel Novoa III + (mjn3) for their significant efforts and contributions that + have made this release possible. +

    + + As usual you can download busybox here. + You don't really need to bother with the + changelog, as the changes + vs the stable version are way too extensive to easily enumerate. + But you can take a look if you really want too. + +

    Have Fun! +

    + + + +

    +

  • 26 October 2002 -- BusyBox 0.60.5 released

    + + I am very pleased to announce that the BusyBox 0.60.5 (stable) + is now available for download. This is a bugfix release for + the stable series to address all the problems that have turned + up since the last release. Unfortunately, the previous release + had a few nasty bugs (i.e. init could deadlock, gunzip -c tried + to delete source files, cp -a wouldn't copy symlinks, and init + was not always providing controlling ttys when it should have). + I know I said that the previous release would be the end of the + 0.60.x series. Well, it turns out I'm a liar. But this time I + mean it (just like last time ;-). This will be the last + release for the 0.60.x series -- all further development work + will be done for the development busybox tree. Expect the development + version to have its first real release very very soon now... + +

    + The changelog has all + the details. As usual you can download busybox here. +

    Have Fun! +

    + +

    +

  • 18 September 2002 -- BusyBox 0.60.4 released

    + + I am very pleased to announce that the BusyBox 0.60.4 + (stable) is now available for download. This is primarily + a bugfix release for the stable series to address all + the problems that have turned up since the last + release. This will be the last release for the 0.60.x series. + I mean it this time -- all further development work will be done + on the development busybox tree, which is quite solid now and + should soon be getting its first real release. + +

    + The changelog has all + the details. As usual you can download busybox here. +

    Have Fun! +

    + + +

    +

  • 27 April 2002 -- BusyBox 0.60.3 released

    + + I am very pleased to announce that the BusyBox 0.60.3 (stable) is + now available for download. This is primarily a bugfix release + for the stable series. A number of problems have turned up since + the last release, and this should address most of those problems. + This should be the last release for the 0.60.x series. The + development busybox tree has been progressing nicely, and will + hopefully be ready to become the next stable release. + +

    + The changelog has all + the details. As usual you can download busybox here. +

    Have Fun! +

    + + +

    +

  • 6 March 2002 -- busybox.net now has mirrors!

    + + Busybox.net is now much more available, thanks to + the fine folks at http://i-netinnovations.com/ + who are providing hosting for busybox.net and + uclibc.org. In addition, we now have two mirrors: + http://busybox.linuxmagic.com/ + in Canada and + http://busybox.csservers.de/ + in Germany. I hope this makes things much more + accessible for everyone! + + +

  • +3 January 2002 -- Welcome to busybox.net! + +

    Thanks to the generosity of a number of busybox +users, we have been able to purchase busybox.net +(which is where you are probably reading this). +Right now, busybox.net and uclibc.org are both +living on my home system (at the end of my DSL +line). I apologize for the abrupt move off of +busybox.lineo.com. Unfortunately, I no longer have +the access needed to keep that system updated (for +example, you might notice the daily snapshots there +stopped some time ago).

    + +

    Busybox.net is currently hosted on my home +server, at the end of a DSL line. Unfortunately, +the load on them is quite heavy. To address this, +I'm trying to make arrangements to get busybox.net +co-located directly at an ISP. To assist in the +co-location effort, Mark Whitley +(author of busybox sed, cut, and grep) has donated +his NetWinder computer +for hosting busybox.net and uclibc.org. Once this +system is co-located, the current speed problems +should be completely eliminated. Hopefully, too, +some of you will volunteer to set up some mirror +sites, to help to distribute the load a bit.

    + +

    + Since some people expressed concern over BusyBox +donations, let me assure you that no one is getting +rich here. All BusyBox and uClibc donations will be +spent paying for bandwidth and needed hardware +upgrades. For example, Mark's NetWinder currently +has just 64Meg of memory. As demonstrated when +google spidered the site the other day, 64 Megs in +not enough, so I'm going to be ordering 256Megs of +ram and a larger hard drive for the box today. So +far, donations received have been sufficient to +cover almost all expenses. In the future, we may +have co-location fees to worry about, but for now +we are ok. A HUGE thank-you goes out to +everyone that has contributed!
    + -Erik

    +
  • + +
  • +20 November 2001 -- BusyBox 0.60.2 released + +

    We am very pleased to announce that the BusyBox +0.60.2 (stable) is now released to the world. This +one is primarily a bugfix release for the stable +series, and it should take care of most everyone's +needs till we can get the nice new stuff we have +been working on in CVS ready to release (with the +wonderful new buildsystem). The biggest change in +this release (beyond bugfixes) is the fact that msh +(the minix shell) has been re-worked by Vladimir N. +Oleynik (vodz) and so it no longer crashes when +told to do complex things with backticks.

    + +

    This release has been tested on x86, ARM, and +powerpc using glibc 2.2.4, libc5, and uClibc, so it +should work with just about any Linux system you +throw it at. See the changelog for most +of the details. The last release was +very solid for people, and this one should +be even better.

    + +

    As usual BusyBox 0.60.2 can be downloaded from +http://www.busybox.net/downloads.

    + +

    Have Fun.
    + -Erik

    +
  • + +
  • 18 November 2001 -- Help us buy busybox.net! + + +
    +Click here to help buy busybox.net! +
    + + + + + + +
    +
    + + +I've contacted the current owner of busybox.net and he is willing +to sell the domain name -- for $250. He also owns busybox.org but +will not part with it... I will then need to pay the registry fee +for a couple of years and start paying for bandwidth, so this will +initially cost about $300. I would like to host busybox.net on my +home machine (codepoet.org) so I have full control over the system, +but to do that would require that I increase the level of bandwidth +I am paying for. Did you know that so far this month, there +have been over 1.4 Gigabytes of busybox ftp downloads? I don't +even know how much CVS bandwidth it requires. For the +time being, Lineo has continued to graciously provide this +bandwidth, despite the fact that I no longer work for them. If I +start running this all on my home machine, paying for the needed bandwidth +will start costing some money. +

    + +I was going to pay it all myself, but my wife didn't like that +idea at all (big surprise). It turns out <insert argument +where she wins and I don't> she has better ideas +about what we should spend our money on that don't involve +busybox. She suggested I should ask for contributions on the +mailing list and web page. So... +

    + +I am hoping that if everyone could contribute a bit, we could pick +up the busybox.net domain name and cover the bandwidth costs. I +know that busybox is being used by a lot of companies as well as +individuals -- hopefully people and companies that are willing to +contribute back a bit. So if everyone could please help out, that +would be wonderful! +

    + + +

  • 23 August 2001 -- BusyBox 0.60.1 released +
    + + This is a relatively minor bug fixing release that fixes + up the bugs that have shown up in the stable release in + the last few weeks. Fortunately, nothing too + serious has shown up. This release only fixes bugs -- no + new features, no new applets. So without further ado, + here it is. Come and get it. +

    + The + changelog has all + the details. As usual BusyBox 0.60.1 can be downloaded from + http://busybox.net/downloads. +

    Have Fun! +

    + + +

  • 2 August 2001 -- BusyBox 0.60.0 released +
    + I am very pleased to announce the immediate availability of + BusyBox 0.60.0. I have personally tested this release with libc5, glibc, + and uClibc on + x86, ARM, and powerpc using linux 2.2 and 2.4, and I know a number + of people using it on everything from ia64 to m68k with great success. + Everything seems to be working very nicely now, so getting a nice + stable bug-free(tm) release out seems to be in order. This releases fixes + a memory leak in syslogd, a number of bugs in the ash and msh shells, and + cleans up a number of things. + +

    + + Those wanting an easy way to test the 0.60.0 release with uClibc can + use User-Mode Linux + to give it a try by downloading and compiling + buildroot.tar.gz. + You don't have to be root or reboot your machine to run test this way. + Preconfigured User-Mode Linux kernel source is also on busybox.net. +

    + Another cool thing is the nifty + BusyBox Tutorial contributed by K Computing. This requires + a ShockWave plugin (or standalone viewer), so you may want to grab the + the GPLed shockwave viewer from here + to view the tutorial. +

    + + Finally, In case you didn't notice anything odd about the + version number of this release, let me point out that this release + is not 0.53, because I bumped the version number up a + bit. This reflects the fact that this release is intended to form + a new stable BusyBox release series. If you need to rely on a + stable version of BusyBox, you should plan on using the stable + 0.60.x series. If bugs show up then I will release 0.60.1, then + 0.60.2, etc... This is also intended to deal with the fact that + the BusyBox build system will be getting a major overhaul for the + next release and I don't want that to break products that people + are shipping. To avoid that, the new build system will be + released as part of a new BusyBox development series that will + have some not-yet-decided-on odd version number. Once things + stabilize and the new build system is working for everyone, then + I will release that as a new stable release series. + +

    + The + changelog has all + the details. As usual BusyBox 0.60.0 can be downloaded from + http://busybox.net/downloads. +

    Have Fun! +

    + + +

  • 7 July 2001 -- BusyBox 0.52 released +
    + + I am very pleased to announce the immediate availability of + BusyBox 0.52 (the "new-and-improved rock-solid release"). This + release is the result of many hours of work and has tons + of bugfixes, optimizations, and cleanups. This release adds + several new applets, including several new shells (such as hush, msh, + and ash). + +

    + The + changelog covers + some of the more obvious details, but there are many many things that + are not mentioned, but have been improved in subtle ways. As usual, + BusyBox 0.52 can be downloaded from + http://busybox.net/downloads. +

    Have Fun! +

    + + +

  • 10 April 2001 - Graph of Busybox Growth +
    +The illustrious Larry Doolittle has made a PostScript chart of the growth +of the Busybox tarball size over time. It is available for downloading / +viewing right here. + +

    (Note that while the number of applets in Busybox has increased, you +can still configure Busybox to be as small as you want by selectively +turning off whichever applets you don't need.) +

    + + +

  • 10 April 2001 -- BusyBox 0.51 released +
    + + BusyBox 0.51 (the "rock-solid release") is now out there. This + release adds only 2 new applets: env and vi. The vi applet, + contributed by Sterling Huxley, is very functional, and is only + 22k. This release fixes 3 critical bugs in the 0.50 release. + There were 2 potential segfaults in lash (the busybox shell) in + the 0.50 release which are now fixed. Another critical bug in + 0.50 which is now fixed: syslogd from 0.50 could potentially + deadlock the init process and thereby break your entire system. +

    + + There are a number of improvements in this release as well. For + one thing, the wget applet is greatly improved. Dmitry Zakharov + added FTP support, and Laurence Anderson make wget fully RFC + compliant for HTTP 1.1. The mechanism for including utility + functions in previous releases was clumsy and error prone. Now + all utility functions are part of a new libbb library, which makes + maintaining utility functions much simpler. And BusyBox now + compiles on itanium systems (thanks to the Debian itanium porters + for letting me use their system!). +

    + You can read the + changelog for + complete details. BusyBox 0.51 can be downloaded from + http://busybox.net/downloads. +

    Have Fun! +

    + +

  • Busybox Boot-Floppy Image + +

    Because you asked for it, we have made available a Busybox boot floppy +image. Here's how you use it: + +

      + +
    1. + Download the image + +
    2. dd it onto a floppy like so: dd if=busybox.floppy.img + of=/dev/fd0 ; sync + +
    3. Pop it in a machine and boot up. + +
    + +

    If you want to look at the contents of the initrd image, do this: + +

    +    mount ./busybox.floppy.img /mnt -o loop -t msdos
    +    cp /mnt/initrd.gz /tmp
    +    umount /mnt
    +    gunzip /tmp/initrd.gz
    +    mount /tmp/initrd /mnt -o loop -t minix
    +
    + + +
  • 15 March 2001 -- BusyBox 0.50 released +
    + + This release adds several new applets including ifconfig, route, pivot_root, stty, + and tftp, and also fixes tons of bugs. Tab completion in the + shell is now working very well, and the shell's environment variable + expansion was fixed. Tons of other things were fixed or made + smaller. For a fairly complete overview, see the + changelog. +

    + lash (the busybox shell) is still with us, fixed up a bit so it + now behaves itself quite nicely. It really is quite usable as + long as you don't expect it to provide Bourne shell grammer. + Standard things like pipes, redirects, command line editing, and + environment variable expansion work great. But we have found that + this shell, while very usable, does not provide an extensible + framework for adding in full Bourne shell behavior. So the first order of + business as we begin working on the next BusyBox release will be to merge in the new shell + currently in progress at + Larry Doolittle's website. +

    + + +

  • 27 January 2001 -- BusyBox 0.49 released +
    + + Several new applets, lots of bug fixes, cleanups, and many smaller + things made nicer. Several cleanups and improvements to the shell. + For a list of the most interesting changes + you might want to look at the changelog. +

    + Special thanks go out to Matt Kraai and Larry Doolittle for all their + work on this release, and for keeping on top of things while I've been + out of town. +

    + Special Note
    + + BusyBox 0.49 was supposed to have replaced lash, the BusyBox + shell, with a new shell that understands full Bourne shell/Posix shell grammer. + Well, that simply didn't happen in time for this release. A new + shell that will eventually replace lash is already under + construction. This new shell is being developed by Larry + Doolittle, and could use all of our help. Please see the work in + progress on Larry's website + and help out if you can. This shell will be included in the next + release of BusyBox. +

    + +

  • 13 December 2000 -- BusyBox 0.48 released +
    + + This release fixes lots and lots of bugs. This has had some very + rigorous testing, and looks very, very clean. The usual tar + update of course: tar no longer breaks hardlinks, tar -xzf is + optionally supported, and the LRP folks will be pleased to know + that 'tar -X' and 'tar --exclude' are both now in. Applets are + now looked up using a binary search making lash (the busybox + shell) much faster. For the new debian-installer (for Debian + woody) a .udeb can now be generated. +

    + The curious can get a list of some of the more interesting changes by reading + the changelog. +

    + Many thanks go out to the many many people that have contributed to + this release, especially Matt Kraai, Larry Doolittle, and Kent Robotti. +

    +

  • 26 September 2000 -- BusyBox 0.47 released +
    + + This release fixes lots of bugs (including an ugly bug in 0.46 + syslogd that could fork-bomb your system). Added several new + apps: rdate, wget, getopt, dos2unix, unix2dos, reset, unrpm, + renice, xargs, and expr. syslogd now supports network logging. + There are the usual tar updates. Most apps now use getopt for + more correct option parsing. + See the changelog + for complete details. + + +

  • 11 July 2000 -- BusyBox 0.46 released +
    + + This release fixes several bugs (including a ugly bug in tar, + and fixes for NFSv3 mount support). Added a dumpkmap to allow + people to dump a binary keymaps for use with 'loadkmap', and a + completely reworked 'grep' and 'sed' which should behave better. + BusyBox shell can now also be used as a login shell. + See the changelog + for complete details. + + +

  • 21 June 2000 -- BusyBox 0.45 released +
    + + This release has been slow in coming, but is very solid at this + point. BusyBox now supports libc5 as well as GNU libc. This + release provides the following new apps: cut, tr, insmod, ar, + mktemp, setkeycodes, md5sum, uuencode, uudecode, which, and + telnet. There are bug fixes for just about every app as well (see + the changelog for + details). +

    + Also, some exciting infrastructure news! Busybox now has its own + mailing list, + publically browsable + CVS tree, + anonymous + CVS access, and + for those that are actively contributing there is even + CVS write access. + I think this will be a huge help to the ongoing development of BusyBox. +

    + Also, for the curious, there is no 0.44 release. Somehow 0.44 got announced + a few weeks ago prior to its actually being released. To avoid any confusion + we are just skipping 0.44. +

    + Many thanks go out to the many people that have contributed to this release + of BusyBox (esp. Pavel Roskin)! + + +

  • 19 April 2000 -- syslogd bugfix +
    +Turns out that there was still a bug in busybox syslogd. +For example, with the following test app: +
    +#include <syslog.h>
    +
    +int do_log(char* msg, int delay)
    +{
    +    openlog("testlog", LOG_PID, LOG_DAEMON);
    +    while(1) {
    +	syslog(LOG_ERR, "%s: testing one, two, three\n", msg);
    +	sleep(delay);
    +    }
    +    closelog();
    +    return(0);
    +};
    +
    +int main(void)
    +{
    +    if (fork()==0)
    +	do_log("A", 2);
    +    do_log("B", 3);
    +}
    +
    +it should be logging stuff from both "A" and "B". As released in 0.43 only stuff +from "A" would have been logged. This means that if init tries to log something +while say ppp has the syslog open, init would block (which is bad, bad, bad). +

    +Karl M. Hegbloom has created a fix for the problem. +Thanks Karl! + + +

  • 18 April 2000 -- BusyBox 0.43 released (finally!) +
    +I have finally gotten everything into a state where I feel pretty +good about things. This is definitely the most stable, solid release +so far. A lot of bugs have been fixed, and the following new apps +have been added: sh, basename, dirname, killall, uptime, +freeramdisk, tr, echo, test, and usleep. Tar has been completely +rewritten from scratch. Bss size has also been greatly reduced. +More details are available in the +changelog. +Oh, and as a special bonus, I wrote some fairly comprehensive +documentation, complete with examples and full usage information. + +

    +Many thanks go out to the fine people that have helped by submitting patches +and bug reports; particularly instrumental in helping for this release were +Karl Hegbloom, Pavel Roskin, Friedrich Vedder, Emanuele Caratti, +Bob Tinsley, Nicolas Pitre, Avery Pennarun, Arne Bernin, John Beppu, and Jim Gleason. +There were others so if I somehow forgot to mention you, I'm very sorry. +

    + +You can grab BusyBox 0.43 tarballs here. + +

  • 9 April 2000 -- BusyBox 0.43 pre release +
    +Unfortunately, I have not yet finished all the things I want to +do for BusyBox 0.43, so I am posting this pre-release for people +to poke at. This contains my complete rewrite of tar, which now weighs in at +5k (7k with all options turned on) and works for reading and writing +tarballs (which it does correctly for everything I have been able to throw +at it). Tar also (optionally) supports the "--exclude" option (mainly because +the Linux Router Project folks asked for it). This also has a pre-release +of the micro shell I have been writing. This pre-release should be stable +enough for production use -- it just isn't a release since I have some structural +changes I still want to make. +

    +The pre-release can be found here. +Please let me know ASAP if you find any bugs. + +

  • 28 March 2000 -- Andersen Baby Boy release +
    +I am pleased to announce that on Tuesday March 28th at 5:48pm, weighing in at 7 +lbs. 12 oz, Micah Erik Andersen was born at LDS Hospital here in Salt Lake City. +He was born in the emergency room less then 5 minutes after we arrived -- and +it was such a relief that we even made it to the hospital at all. Despite the +fact that I was driving at an amazingly unlawful speed and honking at everybody +and thinking decidedly unkind thoughts about the people in our way, my wife +(inconsiderate of my feelings and complete lack of medical training) was lying +down in the back seat saying things like "I think I need to start pushing now" +(which she then proceeded to do despite my best encouraging statements to the +contrary). +

    +Anyway, I'm glad to note that despite the much-faster-than-we-were-expecting +labor, both Shaunalei and our new baby boy are doing wonderfully. +

    +So now that I am done with my excuse for the slow release cycle... +Progress on the next release of BusyBox has been slow but steady. I expect +to have a release sometime during the first week of April. This release will +include a number of important changes, including the addition of a shell, a +re-write of tar (to accommodate the Linux Router Project), and syslogd can now +accept multiple concurrent connections, fixing lots of unexpected blocking +problems. + + +

  • 11 February 2000 -- BusyBox 0.42 released +
    + + This is the most solid BusyBox release so far. Many, many + bugs have been fixed. See the + changelog for details. + + Of particular interest, init will now cleanly unmount + filesystems on reboot, cp and mv have been rewritten and + behave much better, and mount and umount no longer leak + loop devices. Many thanks go out to Randolph Chung, + Karl M. Hegbloom, Taketoshi Sano, and Pavel Roskin for + their hard work on this release of BusyBox. Please pound + on it and let me know if you find any bugs. + +

  • 19 January 2000 -- BusyBox 0.41 released +
    + + This release includes bugfixes to cp, mv, logger, true, false, + mkdir, syslogd, and init. New apps include wc, hostid, + logname, tty, whoami, and yes. New features include loop device + support in mount and umount, and better TERM handling by init. + The changelog can be found here. + +

  • 7 January 2000 -- BusyBox 0.40 released +
    + + This release includes bugfixes to init (now includes inittab support), + syslogd, head, logger, du, grep, cp, mv, sed, dmesg, ls, kill, gunzip, and mknod. + New apps include sort, uniq, lsmod, rmmod, fbset, and loadacm. + In particular, this release fixes an important bug in tar which + in some cases produced serious security problems. + As always, the changelog can be found here. + +

  • 11 December 1999 -- BusyBox Website +
    + I have received permission from Bruce Perens (the original author of BusyBox) + to set up this site as the new primary website for BusyBox. This website + will always contain pointers to the latest and greatest, and will also + contain the latest documentation on how to use BusyBox, what it can do, + what arguments its apps support, etc. + +

  • 10 December 1999 -- BusyBox 0.39 released +
    + This release includes fixes to init, reboot, halt, kill, and ls, and contains + the new apps ping, hostname, mkfifo, free, tail, du, tee, and head. A full + changelog can be found here. +

  • 5 December 1999 -- BusyBox 0.38 released +
    + This release includes fixes to tar, cat, ls, dd, rm, umount, find, df, + and make install, and includes new apps syslogd/klogd and logger. + + +
+ + + + diff --git a/docs/busybox.net/products.html b/docs/busybox.net/products.html new file mode 100644 index 000000000..daf8add20 --- /dev/null +++ b/docs/busybox.net/products.html @@ -0,0 +1,170 @@ + + + +

Products/Projects Using BusyBox

+ +Do you use BusyBox? I'd love to know about it and +I'd be happy to link to you. + +

+I know of the following products and/or projects that use BusyBox -- +listed in the order I happen to add them to the web page: + +

+ + + + diff --git a/docs/busybox.net/screenshot.html b/docs/busybox.net/screenshot.html new file mode 100644 index 000000000..4e07fdaba --- /dev/null +++ b/docs/busybox.net/screenshot.html @@ -0,0 +1,62 @@ + + + + + +

Busybox Screenshot!

+ + +Everybody loves to look at screenshots, so here is a live action screenshot of BusyBox. + +
+
+
+$ ./busybox
+BusyBox v1.1.2 (2006.04.11-08:27+0000) multi-call binary
+
+Usage: busybox [function] [arguments]...
+   or: [function] [arguments]...
+
+	BusyBox is a multi-call binary that combines many common Unix
+	utilities into a single executable.  Most people will create a
+	link to busybox for each function they wish to use and BusyBox
+	will act like whatever it was invoked as!
+
+Currently defined functions:
+	[, [[, addgroup, adduser, adjtimex, ar, arping, ash, awk, basename,
+	bbconfig, bunzip2, busybox, bzcat, cal, cat, chattr, chgrp, chmod,
+	chown, chroot, chvt, clear, cmp, comm, cp, cpio, crond, crontab,
+	cut, date, dc, dd, deallocvt, delgroup, deluser, devfsd, df, dirname,
+	dmesg, dnsd, dos2unix, dpkg, dpkg-deb, du, dumpkmap, dumpleases,
+	e2fsck, e2label, echo, egrep, eject, env, ether-wake, expr, fakeidentd,
+	false, fbset, fdflush, fdformat, fdisk, fgrep, find, findfs, fold,
+	free, freeramdisk, fsck, fsck.ext2, fsck.ext3, fsck.minix, ftpget,
+	ftpput, fuser, getopt, getty, grep, gunzip, gzip, halt, hdparm,
+	head, hexdump, hostid, hostname, httpd, hush, hwclock, id, ifconfig,
+	ifdown, ifup, inetd, init, insmod, install, ip, ipaddr, ipcalc,
+	ipcrm, ipcs, iplink, iproute, iptunnel, kill, killall, klogd,
+	lash, last, length, less, linux32, linux64, linuxrc, ln, loadfont,
+	loadkmap, logger, login, logname, logread, losetup, ls, lsattr,
+	lsmod, lzmacat, makedevs, md5sum, mdev, mesg, mkdir, mke2fs, mkfifo,
+	mkfs.ext2, mkfs.ext3, mkfs.minix, mknod, mkswap, mktemp, modprobe,
+	more, mount, mountpoint, msh, mt, mv, nameif, nc, netstat, nice,
+	nohup, nslookup, od, openvt, passwd, patch, pidof, ping, ping6,
+	pipe_progress, pivot_root, poweroff, printenv, printf, ps, pwd,
+	rdate, readlink, readprofile, realpath, reboot, renice, reset,
+	rm, rmdir, rmmod, route, rpm, rpm2cpio, run-parts, runlevel, rx,
+	sed, seq, setarch, setconsole, setkeycodes, setsid, sha1sum, sleep,
+	sort, start-stop-daemon, stat, strings, stty, su, sulogin, sum,
+	swapoff, swapon, switch_root, sync, sysctl, syslogd, tail, tar,
+	tee, telnet, telnetd, test, tftp, time, top, touch, tr, traceroute,
+	true, tty, tune2fs, udhcpc, udhcpd, umount, uname, uncompress,
+	uniq, unix2dos, unlzma, unzip, uptime, usleep, uudecode, uuencode,
+	vconfig, vi, vlock, watch, watchdog, wc, wget, which, who, whoami,
+	xargs, yes, zcat, zcip
+
+$ _
+
+
+ + + diff --git a/docs/busybox.net/shame.html b/docs/busybox.net/shame.html new file mode 100644 index 000000000..d9da44b69 --- /dev/null +++ b/docs/busybox.net/shame.html @@ -0,0 +1,82 @@ + + + +

Hall of Shame!!!

+ +

This page is no longer updated, these days we forward this sort of +thing to the Software Freedom Law +Center instead.

+ +

The following products and/or projects appear to use BusyBox, but do not +appear to release source code as required by the BusyBox license. This is a violation of the law! +The distributors of these products are invited to contact Erik Andersen if they have any confusion +as to what is needed to bring their products into compliance, or if they have +already brought their product into compliance and wish to be removed from the +Hall of Shame. + +

+ +Here are the details of exactly how to comply +with the BusyBox license, so there should be no question as to +exactly what is expected. +Complying with the Busybox license is easy and completely free, so the +companies listed below should be ashamed of themselves. Furthermore, each +product listed here is subject to being legally ordered to cease and desist +distribution for violation of copyright law, and the distributor of each +product is subject to being sued for statutory copyright infringement damages +of up to $150,000 per work plus legal fees. Nobody wants to be sued, and Erik certainly would prefer to spend +his time doing better things than sue people. But he will sue if forced to +do so to maintain compliance. + +

+ +Do everyone a favor and don't break the law -- if you use busybox, comply with +the busybox license by releasing the source code with your product. + +

+ +

+ + + + diff --git a/docs/busybox.net/sponsors.html b/docs/busybox.net/sponsors.html new file mode 100644 index 000000000..ba7920bf2 --- /dev/null +++ b/docs/busybox.net/sponsors.html @@ -0,0 +1,35 @@ + + +

Sponsors

+ +

Please visit our sponsors and thank them for their support! They have +provided money for equipment and bandwidth. Next time you need help with a +project, consider these fine companies!

+ + +
    +
  • OSU OSL
    + OSU OSL kindly provides hosting for BusyBox and uClibc. +
  • + +
  • Penguru Consulting
    + Custom development for embedded Linux systems and multimedia platforms +
  • + +
  • opensource.se
    + Embedded open source consulting in Europe. +
  • + +
  • Codepoet Consulting
    + Custom Linux, embedded Linux, BusyBox, and uClibc development. +
  • + +
  • TimeSys
    + Embedded Linux development, cross-compilers, real-time, KGDB, tsrpm and cygwin. +
  • +
+ +

If you wish to be a sponsor, or if you have already contributed and would +like your name added here, email Rob.

+ + diff --git a/docs/busybox.net/subversion.html b/docs/busybox.net/subversion.html new file mode 100644 index 000000000..7fb620f98 --- /dev/null +++ b/docs/busybox.net/subversion.html @@ -0,0 +1,52 @@ + + +

Accessing Source

+ + + +

Patches

+ +

If you don't want to mess with subversion, you can download +all BusyBox patches or check the +most recent patches. + +

Anonymous Subversion Access

+ +We allow anonymous (read-only) Subversion (svn) access to everyone. To +grab a copy of the latest version of BusyBox using anonymous svn access: + +
+svn co svn://busybox.net/trunk/busybox
+ +

+The current stable branch can be obtained with +

+svn co svn://busybox.net/branches/busybox_1_1_stable
+
+ +

+ +If you are not already familiar with using Subversion, I recommend you visit the Subversion website. You might +also want to read online or buy a copy of the Subversion Book. If you are +already comfortable with using CVS, you may want to skip ahead to the Subversion for CVS Users +part of the Subversion Book. + +

+ +Once you've checked out a copy of the source tree, you can update your source +tree at any time so it is in sync with the latest and greatest by entering your +BusyBox directory and running the command: + +

+svn update
+ +Because you've only been granted anonymous access to the tree, you won't be +able to commit any changes. Changes can be submitted for inclusion by posting +them to the BusyBox mailing list. For those that are actively contributing +Subversion commit access can be made available. + + + diff --git a/docs/busybox.net/tinyutils.html b/docs/busybox.net/tinyutils.html new file mode 100644 index 000000000..9122d6e35 --- /dev/null +++ b/docs/busybox.net/tinyutils.html @@ -0,0 +1,86 @@ + + + +

External Tiny Utilities

+ +This is a list of tiny utilities whose functionality is not provided by +busybox. If you have additional suggestions, please send an e-mail to our +dev mailing list. + +

+ + + + + + + + + + + + + + + + + + + + +
FeatureUtilities
SSHDropbear has both an ssh server and an ssh client that together come in around 100k. It has no external +dependencies (I.E. it does not depend on OpenSSL, using a built-in copy of +LibTomCrypt instead). It's actively maintained, with a quiet but responsive +mailing list.
SMTPssmtp is an extremely simple Mail Transfer Agent.
ntpntpclient is a +tiny ntp client. BusyBox has rdate to set the date from a remote server, but +if you want a daemon to repeatedly adjust the clock over time, try that.
+ +

In a gui environment, you'll probably want a web browser. +Konqueror Embedded requires QT +(or QT Embedded), but not KDE. The Dillo +requires GTK+, but not Gnome. Or you can try the graphical +version of links.

+ +

SCRIPTING LANGUAGES

+

Although busybox has built-in support for shell scripts, plenty of other +small scripting languages are available on the net. A few examples:

+ + + + + + + + + + + + + + + + + + + + + + + + +
microperl A small standalone perl interpreter that can be built from the perl source +s via "make -f Makefile.micro". If you really feel the need for perl on an embe +dded system, this is where to start. +
LuaIf you just want a small embedded scripting language to write new +code in, this Brazilian import is lightweight, fairly popular, and has +a complete book about it online.
rcThe PLAN9 shell. Not compatible with conventional bourne shell syntax, +but fairly lightweight and small.
forthA well known language for fast and small programs, decades old but still +in use for everything from OpenBIOS to computer controlled engine timing.
+ +

For more information, you probably want to look at +buildroot and +TinyGentoo, which +build and use tiny utilities for all sorts of things.

+ + + diff --git a/docs/busybox_footer.pod b/docs/busybox_footer.pod new file mode 100644 index 000000000..b2835f6bc --- /dev/null +++ b/docs/busybox_footer.pod @@ -0,0 +1,258 @@ +=back + +=head1 LIBC NSS + +GNU Libc (glibc) uses the Name Service Switch (NSS) to configure the behavior +of the C library for the local environment, and to configure how it reads +system data, such as passwords and group information. This is implemented +using an /etc/nsswitch.conf configuration file, and using one or more of the +/lib/libnss_* libraries. BusyBox tries to avoid using any libc calls that make +use of NSS. Some applets however, such as login and su, will use libc functions +that require NSS. + +If you enable CONFIG_USE_BB_PWD_GRP, BusyBox will use internal functions to +directly access the /etc/passwd, /etc/group, and /etc/shadow files without +using NSS. This may allow you to run your system without the need for +installing any of the NSS configuration files and libraries. + +When used with glibc, the BusyBox 'networking' applets will similarly require +that you install at least some of the glibc NSS stuff (in particular, +/etc/nsswitch.conf, /lib/libnss_dns*, /lib/libnss_files*, and /lib/libresolv*). + +Shameless Plug: As an alternative, one could use a C library such as uClibc. In +addition to making your system significantly smaller, uClibc does not require the +use of any NSS support files or libraries. + +=head1 MAINTAINER + +Denis Vlasenko + +=head1 AUTHORS + +The following people have contributed code to BusyBox whether they know it or +not. If you have written code included in BusyBox, you should probably be +listed here so you can obtain your bit of eternal glory. If you should be +listed here, or the description of what you have done needs more detail, or is +incorect, please send in an update. + + +=for html
+ +Emanuele Aina + run-parts + +=for html
+ +Erik Andersen + + Tons of new stuff, major rewrite of most of the + core apps, tons of new apps as noted in header files. + Lots of tedious effort writing these boring docs that + nobody is going to actually read. + +=for html
+ +Laurence Anderson + + rpm2cpio, unzip, get_header_cpio, read_gz interface, rpm + +=for html
+ +Jeff Angielski + + ftpput, ftpget + +=for html
+ +Edward Betts + + expr, hostid, logname, whoami + +=for html
+ +John Beppu + + du, nslookup, sort + +=for html
+ +Brian Candler + + tiny-ls(ls) + +=for html
+ +Randolph Chung + + fbset, ping, hostname + +=for html
+ +Dave Cinege + + more(v2), makedevs, dutmp, modularization, auto links file, + various fixes, Linux Router Project maintenance + +=for html
+ +Jordan Crouse + + ipcalc + +=for html
+ +Magnus Damm + + tftp client insmod powerpc support + +=for html
+ +Larry Doolittle + + pristine source directory compilation, lots of patches and fixes. + +=for html
+ +Glenn Engel + + httpd + +=for html
+ +Gennady Feldman + + Sysklogd (single threaded syslogd, IPC Circular buffer support, + logread), various fixes. + +=for html
+ +Karl M. Hegbloom + + cp_mv.c, the test suite, various fixes to utility.c, &c. + +=for html
+ +Daniel Jacobowitz + + mktemp.c + +=for html
+ +Matt Kraai + + documentation, bugfixes, test suite + +=for html
+ +Stephan Linz + + ipcalc, Red Hat equivalence + +=for html
+ +John Lombardo + + tr + +=for html
+ +Glenn McGrath + + Common unarchving code and unarchiving applets, ifupdown, ftpgetput, + nameif, sed, patch, fold, install, uudecode. + Various bugfixes, review and apply numerous patches. + +=for html
+ +Manuel Novoa III + + cat, head, mkfifo, mknod, rmdir, sleep, tee, tty, uniq, usleep, wc, yes, + mesg, vconfig, make_directory, parse_mode, dirname, mode_string, + get_last_path_component, simplify_path, and a number trivial libbb routines + + also bug fixes, partial rewrites, and size optimizations in + ash, basename, cal, cmp, cp, df, du, echo, env, ln, logname, md5sum, mkdir, + mv, realpath, rm, sort, tail, touch, uname, watch, arith, human_readable, + interface, dutmp, ifconfig, route + +=for html
+ +Vladimir Oleynik + + cmdedit; xargs(current), httpd(current); + ports: ash, crond, fdisk, inetd, stty, traceroute, top; + locale, various fixes + and irreconcilable critic of everything not perfect. + +=for html
+ +Bruce Perens + + Original author of BusyBox in 1995, 1996. Some of his code can + still be found hiding here and there... + +=for html
+ +Tim Riker + + bug fixes, member of fan club + +=for html
+ +Kent Robotti + + reset, tons and tons of bug reports and patches. + +=for html
+ +Chip Rosenthal , + + wget - Contributed by permission of Covad Communications + +=for html
+ +Pavel Roskin + + Lots of bugs fixes and patches. + +=for html
+ +Gyepi Sam + + Remote logging feature for syslogd + +=for html
+ +Linus Torvalds + + mkswap, fsck.minix, mkfs.minix + +=for html
+ +Mark Whitley + + grep, sed, cut, xargs(previous), + style-guide, new-applet-HOWTO, bug fixes, etc. + +=for html
+ +Charles P. Wright + + gzip, mini-netcat(nc) + +=for html
+ +Enrique Zanardi + + tarcat (since removed), loadkmap, various fixes, Debian maintenance + +=for html
+ +Tito Ragusa + + devfsd and size optimizations in strings, openvt and deallocvt. + +=cut + +# $Id: busybox_footer.pod,v 1.18 2004/04/25 06:05:14 bug1 Exp $ + diff --git a/docs/busybox_header.pod b/docs/busybox_header.pod new file mode 100644 index 000000000..ab1ebd501 --- /dev/null +++ b/docs/busybox_header.pod @@ -0,0 +1,82 @@ +# vi: set sw=4 ts=4: + +=head1 NAME + +BusyBox - The Swiss Army Knife of Embedded Linux + +=head1 SYNTAX + + BusyBox [arguments...] # or + + [arguments...] # if symlinked + +=head1 DESCRIPTION + +BusyBox combines tiny versions of many common UNIX utilities into a single +small executable. It provides minimalist replacements for most of the utilities +you usually find in GNU coreutils, util-linux, etc. The utilities in BusyBox +generally have fewer options than their full-featured GNU cousins; however, the +options that are included provide the expected functionality and behave very +much like their GNU counterparts. + +BusyBox has been written with size-optimization and limited resources in mind. +It is also extremely modular so you can easily include or exclude commands (or +features) at compile time. This makes it easy to customize your embedded +systems. To create a working system, just add /dev, /etc, and a Linux kernel. +BusyBox provides a fairly complete POSIX environment for any small or embedded +system. + +BusyBox is extremely configurable. This allows you to include only the +components you need, thereby reducing binary size. Run 'make config' or 'make +menuconfig' to select the functionality that you wish to enable. Then run +'make' to compile BusyBox using your configuration. + +After the compile has finished, you should use 'make install' to install +BusyBox. This will install the 'bin/busybox' binary, in the target directory +specified by PREFIX. PREFIX can be set when configuring BusyBox, or you can +specify an alternative location at install time (i.e., with a command line +like 'make PREFIX=/tmp/foo install'). If you enabled any applet installation +scheme (either as symlinks or hardlinks), these will also be installed in +the location pointed to by PREFIX. + +=head1 USAGE + +BusyBox is a multi-call binary. A multi-call binary is an executable program +that performs the same job as more than one utility program. That means there +is just a single BusyBox binary, but that single binary acts like a large +number of utilities. This allows BusyBox to be smaller since all the built-in +utility programs (we call them applets) can share code for many common operations. + +You can also invoke BusyBox by issuing a command as an argument on the +command line. For example, entering + + /bin/busybox ls + +will also cause BusyBox to behave as 'ls'. + +Of course, adding '/bin/busybox' into every command would be painful. So most +people will invoke BusyBox using links to the BusyBox binary. + +For example, entering + + ln -s /bin/busybox ls + ./ls + +will cause BusyBox to behave as 'ls' (if the 'ls' command has been compiled +into BusyBox). Generally speaking, you should never need to make all these +links yourself, as the BusyBox build system will do this for you when you run +the 'make install' command. + +If you invoke BusyBox with no arguments, it will provide you with a list of the +applets that have been compiled into your BusyBox binary. + +=head1 COMMON OPTIONS + +Most BusyBox commands support the B<--help> argument to provide a terse runtime +description of their behavior. If the CONFIG_FEATURE_VERBOSE_USAGE option has +been enabled, more detailed usage information will also be available. + +=head1 COMMANDS + +Currently defined functions include: + diff --git a/docs/contributing.txt b/docs/contributing.txt new file mode 100644 index 000000000..aad43035c --- /dev/null +++ b/docs/contributing.txt @@ -0,0 +1,449 @@ +Contributing To Busybox +======================= + +This document describes what you need to do to contribute to Busybox, where +you can help, guidelines on testing, and how to submit a well-formed patch +that is more likely to be accepted. + +The Busybox home page is at: http://busybox.net/ + + + +Pre-Contribution Checklist +-------------------------- + +So you want to contribute to Busybox, eh? Great, wonderful, glad you want to +help. However, before you dive in, headlong and hotfoot, there are some things +you need to do: + + +Checkout the Latest Code from CVS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This is a necessary first step. Please do not try to work with the last +released version, as there is a good chance that somebody has already fixed +the bug you found. Somebody might have even added the feature you had in mind. +Don't make your work obsolete before you start! + +For information on how to check out Busybox from CVS, please look at the +following links: + + http://busybox.net/cvs_anon.html + http://busybox.net/cvs_howto.html + + +Read the Mailing List +~~~~~~~~~~~~~~~~~~~~~ + +No one is required to read the entire archives of the mailing list, but you +should at least read up on what people have been talking about lately. If +you've recently discovered a problem, chances are somebody else has too. If +you're the first to discover a problem, post a message and let the rest of us +know. + +Archives can be found here: + + http://busybox.net/lists/busybox/ + +If you have a serious interest in Busybox, i.e., you are using it day-to-day or +as part of an embedded project, it would be a good idea to join the mailing +list. + +A web-based sign-up form can be found here: + + http://busybox.net/mailman/listinfo/busybox + + +Coordinate with the Applet Maintainer +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Some (not all) of the applets in Busybox are "owned" by a maintainer who has +put significant effort into it and is probably more familiar with it than +others. To find the maintainer of an applet, look at the top of the .c file +for a name following the word 'Copyright' or 'Written by' or 'Maintainer'. + +Before plunging ahead, it's a good idea to send a message to the mailing list +that says: "Hey, I was thinking about adding the 'transmogrify' feature to the +'foo' applet. Would this be useful? Is anyone else working on it?" You might +want to CC the maintainer (if any) with your question. + + + +Areas Where You Can Help +------------------------ + +Busybox can always use improvement! If you're looking for ways to help, there +are a variety of areas where you could help. + + +What Busybox Doesn't Need +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Before listing the areas where you _can_ help, it's worthwhile to mention the +areas where you shouldn't bother. While Busybox strives to be the "Swiss Army +Knife" of embedded Linux, there are some applets that will not be accepted: + + - Any filesystem manipulation tools: Busybox is filesystem independent and + we do not want to start adding mkfs/fsck tools for every (or any) + filesystem under the sun. (fsck_minix.c and mkfs_minix.c are living on + borrowed time.) There are far too many of these tools out there. Use + the upstream version. Not everything has to be part of Busybox. + + - Any partitioning tools: Partitioning a device is typically done once and + only once, and tools which do this generally do not need to reside on the + target device (esp a flash device). If you need a partitioning tool, grab + one (such as fdisk, sfdisk, or cfdisk from util-linux) and use that, but + don't try to merge it into busybox. These are nasty and complex and we + don't want to maintain them. + + - Any disk, device, or media-specific tools: Use the -utils or -tools package + that was designed for your device; don't try to shoehorn them into Busybox. + + - Any architecture specific tools: Busybox is (or should be) architecture + independent. Do not send us tools that cannot be used across multiple + platforms / arches. + + - Any daemons that are not essential to basic system operation. To date, only + syslogd and klogd meet this requirement. We do not need a web server, an + ftp daemon, a dhcp server, a mail transport agent or a dns resolver. If you + need one of those, you are welcome to ask the folks on the mailing list for + recommendations, but please don't bloat up Busybox with any of these. + + +Bug Reporting +~~~~~~~~~~~~~ + +If you find bugs, please submit a detailed bug report to the busybox mailing +list at busybox@busybox.net. A well-written bug report should include a +transcript of a shell session that demonstrates the bad behavior and enables +anyone else to duplicate the bug on their own machine. The following is such +an example: + + To: busybox@busybox.net + From: diligent@testing.linux.org + Subject: /bin/date doesn't work + + Package: busybox + Version: 1.00 + + When I execute Busybox 'date' it produces unexpected results. + With GNU date I get the following output: + + $ date + Wed Mar 21 14:19:41 MST 2001 + + But when I use BusyBox date I get this instead: + + $ date + llegal instruction + + I am using Debian unstable, kernel version 2.4.19-rmk1 on an Netwinder, + and the latest uClibc from CVS. Thanks for the wonderful program! + + -Diligent + +Note the careful description and use of examples showing not only what BusyBox +does, but also a counter example showing what an equivalent GNU app does. Bug +reports lacking such detail may never be fixed... Thanks for understanding. + + + +Write Documentation +~~~~~~~~~~~~~~~~~~~ + +Chances are, documentation in Busybox is either missing or needs improvement. +Either way, help is welcome. + +Work is being done to automatically generate documentation from sources, +especially from the usage.h file. If you want to correct the documentation, +please make changes to the pre-generation parts, rather than the generated +documentation. [More to come on this later...] + +It is preferred that modifications to documentation be submitted in patch +format (more on this below), but we're a little more lenient when it comes to +docs. You could, for example, just say "after the listing of the mount +options, the following example would be helpful..." + + +Consult Existing Sources +~~~~~~~~~~~~~~~~~~~~~~~~ + +For a quick listing of "needs work" spots in the sources, cd into the Busybox +directory and run the following: + + for i in TODO FIXME XXX; do find -name '*.[ch]'|xargs grep $i; done + +This will show all of the trouble spots or 'questionable' code. Pick a spot, +any spot, these are all invitations for you to contribute. + + +Add a New Applet +~~~~~~~~~~~~~~~~ + +If you want to add a new applet to Busybox, we'd love to see it. However, +before you write any code, please ask beforehand on the mailing list something +like "Do you think applet 'foo' would be useful in Busybox?" or "Would you +guys accept applet 'foo' into Busybox if I were to write it?" If the answer is +"no" by the folks on the mailing list, then you've saved yourself some time. +Conversely, you could get some positive responses from folks who might be +interested in helping you implement it, or can recommend the best approach. +Perhaps most importantly, this is your way of calling "dibs" on something and +avoiding duplication of effort. + +Also, before you write a line of code, please read the 'new-applet-HOWTO.txt' +file in the docs/ directory. + + +Janitorial Work +~~~~~~~~~~~~~~~ + +These are dirty jobs, but somebody's gotta do 'em. + + - Converting applets to use getopt() for option processing. Type 'find -name + '*.c'|grep -L getopt' to get a listing of the applets that currently don't + use getopt. If a .c file processes no options, it should have a line that + reads: /* no options, no getopt */ somewhere in the file. + + - Replace any "naked" calls to malloc, calloc, realloc, str[n]dup, fopen with + the x* equivalents found in libbb/xfuncs.c. + + - Security audits: + http://www.securityfocus.com/popups/forums/secprog/intro.shtml + + - Synthetic code removal: http://www.perl.com/pub/2000/06/commify.html - This + is very Perl-specific, but the advice given in here applies equally well to + C. + + - C library function use audits: Verifying that functions are being used + properly (called with the right args), replacing unsafe library functions + with safer versions, making sure return codes are being checked, etc. + + - Where appropriate, replace preprocessor defined macros and values with + compile-time equivalents. + + - Style guide compliance. See: docs/style-guide.txt + + - Add testcases to tests/testcases. + + - Makefile improvements: + http://www.canb.auug.org.au/~millerp/rmch/recu-make-cons-harm.html + (I think the recursive problems are pretty much taken care of at this point, non?) + + - "Ten Commandments" compliance: (this is a "maybe", certainly not as + important as any of the previous items.) + http://www.lysator.liu.se/c/ten-commandments.html + +Other useful links: + + - the comp.lang.c FAQ: http://web.onetelnet.ch/~twolf/tw/c/index.html#Sources + + + +Submitting Patches To Busybox +----------------------------- + +Here are some guidelines on how to submit a patch to Busybox. + + +Making A Patch +~~~~~~~~~~~~~~ + +If you've got anonymous CVS access set up, making a patch is simple. Just make +sure you're in the busybox/ directory and type 'cvs diff -bwu > mychanges.patch'. +You can send the resulting .patch file to the mailing list with a description +of what it does. (But not before you test it! See the next section for some +guidelines.) It is preferred that patches be sent as attachments, but it is +not required. + +Also, feel free to help test other people's patches and reply to them with +comments. You can apply a patch by saving it into your busybox/ directory and +typing 'patch < mychanges.patch'. Then you can recompile, see if it runs, test +if it works as advertised, and post your findings to the mailing list. + +NOTE: Please do not include extraneous or irrelevant changes in your patches. +Please do not try to "bundle" two patches together into one. Make single, +discreet changes on a per-patch basis. Sometimes you need to make a patch that +touches code in many places, but these kind of patches are rare and should be +coordinated with a maintainer. + + +Testing Guidelines +~~~~~~~~~~~~~~~~~~ + +It's considered good form to test your new feature before you submit a patch +to the mailing list, and especially before you commit a change to CVS. Here +are some guidelines on how to test your changes. + + - Always test Busybox applets against GNU counterparts and make sure the + behavior / output is identical between the two. + + - Try several different permutations and combinations of the features you're + adding (i.e., different combinations of command-line switches) and make sure + they all work; make sure one feature does not interfere with another. + + - Make sure you test compiling against the source both with the feature + turned on and turned off in Config.h and make sure Busybox compiles cleanly + both ways. + + - Run the multibuild.pl script in the tests directory and make sure + everything checks out OK. (Do this from within the busybox/ directory by + typing: 'tests/multibuild.pl'.) + + +Making Sure Your Patch Doesn't Get Lost +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you don't want your patch to be lost or forgotten, send it to the busybox +mailing list with a subject line something like this: + + [PATCH] - Adds "transmogrify" feature to "foo" + +In the body, you should have a pseudo-header that looks like the following: + + Package: busybox + Version: v1.01pre (or whatever the current version is) + Severity: wishlist + +The remainder of the body should read along these lines: + + This patch adds the "transmogrify" feature to the "foo" applet. I have + tested this on [arch] system(s) and it works. I have tested it against the + GNU counterparts and the outputs are identical. I have run the scripts in + the 'tests' directory and nothing breaks. + + + +Improving Your Chances of Patch Acceptance +------------------------------------------ + +Even after you send a brilliant patch to the mailing list, sometimes it can go +unnoticed, un-replied-to, and sometimes (sigh) even lost. This is an +unfortunate fact of life, but there are steps you can take to help your patch +get noticed and convince a maintainer that it should be added: + + +Be Succinct +~~~~~~~~~~~ + +A patch that includes small, isolated, obvious changes is more likely to be +accepted than a patch that touches code in lots of different places or makes +sweeping, dubious changes. + + +Back It Up +~~~~~~~~~~ + +Hard facts on why your patch is better than the existing code will go a long +way toward convincing maintainers that your patch should be included. +Specifically, patches are more likely to be accepted if they are provably more +correct, smaller, faster, simpler, or more maintainable than the existing +code. + +Conversely, any patch that is supported with nothing more than "I think this +would be cool" or "this patch is good because I say it is and I've got a Phd +in Computer Science" will likely be ignored. + + +Follow The Style Guide +~~~~~~~~~~~~~~~~~~~~~~ + +It's considered good form to abide by the established coding style used in a +project; Busybox is no exception. We have gone so far as to delineate the +"elements of Busybox style" in the file docs/style-guide.txt. Please follow +them. + + +Work With Someone Else +~~~~~~~~~~~~~~~~~~~~~~ + +Working on a patch in isolation is less effective than working with someone +else for a variety of reasons. If another Busybox user is interested in what +you're doing, then it's two (or more) voices instead of one that can petition +for inclusion of the patch. You'll also have more people that can test your +changes, or even offer suggestions on better approaches you could take. + +Getting other folks interested follows as a natural course if you've received +responses from queries to applet maintainer or positive responses from folks +on the mailing list. + +We've made strident efforts to put a useful "collaboration" infrastructure in +place in the form of mailing lists, the bug tracking system, and CVS. Please +use these resources. + + +Send Patches to the Bug Tracking System +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This was mentioned above in the "Making Sure Your Patch Doesn't Get Lost" +section, but it is worth mentioning again. A patch sent to the mailing list +might be unnoticed and forgotten. A patch sent to the bug tracking system will +be stored and closely connected to the bug it fixes. + + +Be Polite +~~~~~~~~~ + +The old saying "You'll catch more flies with honey than you will with vinegar" +applies when submitting patches to the mailing list for approval. The way you +present your patch is sometimes just as important as the actual patch itself +(if not more so). Being rude to the maintainers is not an effective way to +convince them that your patch should be included; it will likely have the +opposite effect. + + + +Committing Changes to CVS +------------------------- + +If you submit several patches that demonstrate that you are a skilled and wise +coder, you may be invited to become a committer, thus enabling you to commit +changes directly to CVS. This is nice because you don't have to wait for +someone else to commit your change for you, you can just do it yourself. + +But note that this is a privilege that comes with some responsibilities. You +should test your changes before you commit them. You should also talk to an +applet maintainer before you make any kind of sweeping changes to somebody +else's code. Big changes should still go to the mailing list first. Remember, +being wise, polite, and discreet is more important than being clever. + + +When To Commit +~~~~~~~~~~~~~~ + +Generally, you should feel free to commit a change if: + + - Your changes are small and don't touch many files + - You are fixing a bug + - Somebody has told you that it's okay + - It's obviously the Right Thing + +The more of the above are true, the better it is to just commit a change +directly to CVS. + + +When Not To Commit +~~~~~~~~~~~~~~~~~~ + +Even if you have commit rights, you should probably still post a patch to the +mailing list if: + + - Your changes are broad and touch many different files + - You are adding a feature + - Your changes are speculative or experimental (i.e., trying a new algorithm) + - You are not the maintainer and your changes make the maintainer cringe + +The more of the above are true, the better it is to post a patch to the +mailing list instead of committing. + + + +Final Words +----------- + +If all of this seems complicated, don't panic, it's really not that tough. If +you're having difficulty following some of the steps outlined in this +document don't worry, the folks on the Busybox mailing list are a fairly +good-natured bunch and will work with you to help get your patches into shape +or help you make contributions. + + diff --git a/docs/ipv4_ipv6.txt b/docs/ipv4_ipv6.txt new file mode 100644 index 000000000..92010b59c --- /dev/null +++ b/docs/ipv4_ipv6.txt @@ -0,0 +1,223 @@ +We need better network address conv helpers. +This is what our applets want: + + sockaddr -> hostname +udhcp: hostname -> ipv4 addr +nslookup: hostname -> list of names - done +tftp: host,port -> sockaddr +nc: host,port -> sockaddr +inetd: ? +traceroute: ?, hostname -> ipv4 addr +arping hostname -> ipv4 addr +ping6 hostname -> ipv6 addr +ifconfig hostname -> ipv4 addr (FIXME error check?) +ipcalc ipv4 addr -> hostname +syslogd hostname -> sockaddr +inet_common.c: buggy. hostname -> ipv4 addr +mount hostname -> sockaddr_in + +================== +HOWTO get rid of inet_ntoa/aton: + +foo.sin_addr.s_addr = inet_addr(cp); +- +inet_pton(AF_INET, cp, &foo.sin_addr); + +inet_aton(cp, &foo.sin_addr); +- +inet_pton(AF_INET, cp, &foo.sin_addr); + +ptr = inet_ntoa(foo.sin_addr); +- +char str[INET_ADDRSTRLEN]; +ptr = inet_ntop(AF_INET, &foo.sin_addr, str, sizeof(str)); + +=================== + + struct addrinfo { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + size_t ai_addrlen; + struct sockaddr *ai_addr; + char *ai_canonname; + struct addrinfo *ai_next; + }; + int getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, + struct addrinfo **res); + + void freeaddrinfo(struct addrinfo *res); + + const char *gai_strerror(int errcode); + + The members ai_family, ai_socktype, and ai_protocol have the same meaning + as the corresponding parameters in the socket(2) system call. The getad- + drinfo(3) function returns socket addresses in either IPv4 or IPv6 address + family, (ai_family will be set to either AF_INET or AF_INET6). + + The hints parameter specifies the preferred socket type, or protocol. A + NULL hints specifies that any network address or protocol is acceptable. + If this parameter is not NULL it points to an addrinfo structure whose + ai_family, ai_socktype, and ai_protocol members specify the preferred + socket type. AF_UNSPEC in ai_family specifies any protocol family (either + IPv4 or IPv6, for example). 0 in ai_socktype or ai_protocol specifies + that any socket type or protocol is acceptable as well. The ai_flags mem- + ber specifies additional options, defined below. Multiple flags are spec- + ified by logically OR-ing them together. All the other members in the + hints parameter must contain either 0, or a null pointer. + + The node or service parameter, but not both, may be NULL. node specifies + either a numerical network address (dotted-decimal format for IPv4, hex- + adecimal format for IPv6) or a network hostname, whose network addresses + are looked up and resolved. If hints.ai_flags contains the AI_NUMERICHOST + flag then the node parameter must be a numerical network address. The + AI_NUMERICHOST flag suppresses any potentially lengthy network host + address lookups. + + The getaddrinfo(3) function creates a linked list of addrinfo structures, + one for each network address subject to any restrictions imposed by the + hints parameter. The ai_canonname field of the first of these addrinfo + structures is set to point to the official name of the host, if + hints.ai_flags includes the AI_CANONNAME flag. ai_family, ai_socktype, + and ai_protocol specify the socket creation parameters. A pointer to the + socket address is placed in the ai_addr member, and the length of the + socket address, in bytes, is placed in the ai_addrlen member. + + If node is NULL, the network address in each socket structure is initial- + ized according to the AI_PASSIVE flag, which is set in hints.ai_flags. + The network address in each socket structure will be left unspecified if + AI_PASSIVE flag is set. This is used by server applications, which intend + to accept client connections on any network address. The network address + will be set to the loopback interface address if the AI_PASSIVE flag is + not set. This is used by client applications, which intend to connect to + a server running on the same network host. + + If hints.ai_flags includes the AI_ADDRCONFIG flag, then IPv4 addresses are + returned in the list pointed to by result only if the local system has at + least has at least one IPv4 address configured, and IPv6 addresses are + only returned if the local system has at least one IPv6 address config- + ured. + + If hint.ai_flags specifies the AI_V4MAPPED flag, and hints.ai_family was + specified as AF_INET6, and no matching IPv6 addresses could be found, then + return IPv4-mapped IPv6 addresses in the list pointed to by result. If + both AI_V4MAPPED and AI_ALL are specified in hints.ai_family, then return + both IPv6 and IPv4-mapped IPv6 addresses in the list pointed to by result. + AI_ALL is ignored if AI_V4MAPPED is not also specified. + + service sets the port number in the network address of each socket struc- + ture. If service is NULL the port number will be left uninitialized. If + AI_NUMERICSERV is specified in hints.ai_flags and service is not NULL, + then service must point to a string containing a numeric port number. + This flag is used to inhibit the invocation of a name resolution service + in cases where it is known not to be required. + + +============== + + int getnameinfo(const struct sockaddr *sa, socklen_t salen, + char *host, size_t hostlen, + char *serv, size_t servlen, int flags); + + The getnameinfo(3) function is defined for protocol-independent + address-to-nodename translation. It combines the functionality + of gethostbyaddr(3) and getservbyport(3) and is the inverse of + getaddrinfo(3). The sa argument is a pointer to a generic socket address + structure (of type sockaddr_in or sockaddr_in6) of size salen that + holds the input IP address and port number. The arguments host and + serv are pointers to buffers (of size hostlen and servlen respectively) + to hold the return values. + + The caller can specify that no hostname (or no service name) is required + by providing a NULL host (or serv) argument or a zero hostlen (or servlen) + parameter. However, at least one of hostname or service name must be requested. + + The flags argument modifies the behaviour of getnameinfo(3) as follows: + + NI_NOFQDN + If set, return only the hostname part of the FQDN for local hosts. + + NI_NUMERICHOST + If set, then the numeric form of the hostname is returned. + (When not set, this will still happen in case the node's name + cannot be looked up.) + + NI_NAMEREQD + If set, then a error is returned if the hostname cannot be looked up. + + NI_NUMERICSERV + If set, then the service address is returned in numeric form, + for example by its port number. + + NI_DGRAM + If set, then the service is datagram (UDP) based rather than stream + (TCP) based. This is required for the few ports (512-514) that have different + services for UDP and TCP. + +================= + +Modified IPv6-aware C code: + + struct addrinfo *res, *aip; + struct addrinfo hints; + int sock = -1; + int error; + + /* Get host address. Any type of address will do. */ + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_ALL|AI_ADDRCONFIG; + hints.ai_socktype = SOCK_STREAM; + + error = getaddrinfo(hostname, servicename, &hints, &res); + if (error != 0) { + (void) fprintf(stderr, + "getaddrinfo: %s for host %s service %s\n", + gai_strerror(error), hostname, servicename); + return -1; + } + /* Try all returned addresses until one works */ + for (aip = res; aip != NULL; aip = aip->ai_next) { + /* + * Open socket. The address type depends on what + * getaddrinfo() gave us. + */ + sock = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol); + if (sock == -1) { + perror("socket"); + freeaddrinfo(res); + return -1; + } + + /* Connect to the host. */ + if (connect(sock, aip->ai_addr, aip->ai_addrlen) == -1) { + perror("connect"); + (void) close(sock); + sock = -1; + continue; + } + break; + } + freeaddrinfo(res); + +Note that for new applications, if you write address-family-agnostic data structures, +there is no need for porting. + +However, when it comes to server-side programming in C/C++, there is an additional wrinkle. +Namely, depending on whether your application is written for a dual-stack platform, such +as Solaris or Linux, or a single-stack platform, such as Windows, you would need to +structure the code differently. + +Here's the corresponding server C code for a dual-stack platform: + + int ServSock, csock; + /* struct sockaddr is too small! */ + struct sockaddr_storage addr, from; + ... + ServSock = socket(AF_INET6, SOCK_STREAM, PF_INET6); + bind(ServSock, &addr, sizeof(addr)); + do { + csock = accept(ServSocket, &from, sizeof(from)); + doClientStuff(csock); + } while (!finished); diff --git a/docs/new-applet-HOWTO.txt b/docs/new-applet-HOWTO.txt new file mode 100644 index 000000000..1c2383f35 --- /dev/null +++ b/docs/new-applet-HOWTO.txt @@ -0,0 +1,150 @@ +How to Add a New Applet to BusyBox +================================== + +This document details the steps you must take to add a new applet to BusyBox. + +Credits: +Matt Kraai - initial writeup +Mark Whitley - the remix +Thomas Lundquist - Added stuff for the new directory layout. + +Initial Write +------------- + +First, write your applet. Be sure to include copyright information at the top, +such as who you stole the code from and so forth. Also include the mini-GPL +boilerplate. Be sure to name the main function _main instead of main. +And be sure to put it in .c. Usage does not have to be taken care of by +your applet. +Make sure to #include "busybox.h" as the first include file in your applet so +the bb_config.h and appropriate platform specific files are included properly. + +For a new applet mu, here is the code that would go in mu.c: + +----begin example code------ + +/* vi: set sw=4 ts=4: */ +/* + * Mini mu implementation for busybox + * + * Copyright (C) [YEAR] by [YOUR NAME] + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include + +int mu_main(int argc, char **argv) +{ + int fd; + char mu; + + fd = xopen("/dev/random", O_RDONLY); + + if ((n = safe_read(fd, &mu, 1)) < 1) + bb_perror_msg_and_die("/dev/random"); + + return mu; +} + +----end example code------ + + +Coding Style +------------ + +Before you submit your applet for inclusion in BusyBox, (or better yet, before +you _write_ your applet) please read through the style guide in the docs +directory and make your program compliant. + + +Some Words on libbb +------------------- + +As you are writing your applet, please be aware of the body of pre-existing +useful functions in libbb. Use these instead of reinventing the wheel. + +Additionally, if you have any useful, general-purpose functions in your +applet that could be useful in other applets, consider putting them in libbb. + + +Placement / Directory +--------------------- + +Find the appropriate directory for your new applet. + +Make sure you find the appropriate places in the files, the applets are +sorted alphabetically. + +Add the applet to Makefile.in in the chosen directory: + +obj-$(CONFIG_MU) += mu.o + +Add the applet to Config.in in the chosen directory: + +config CONFIG_MU + bool "MU" + default n + help + Returns an indeterminate value. + + +Usage String(s) +--------------- + +Next, add usage information for you applet to include/usage.h. +This should look like the following: + + #define mu_trivial_usage \ + "-[abcde] FILES" + #define mu_full_usage \ + "Returns an indeterminate value.\n\n" \ + "Options:\n" \ + "\t-a\t\tfirst function\n" \ + "\t-b\t\tsecond function\n" \ + ... + +If your program supports flags, the flags should be mentioned on the first +line (-[abcde]) and a detailed description of each flag should go in the +mu_full_usage section, one flag per line. (Numerous examples of this +currently exist in usage.h.) + + +Header Files +------------ + +Next, add an entry to include/applets.h. Be *sure* to keep the list +in alphabetical order, or else it will break the binary-search lookup +algorithm in busybox.c and the Gods of BusyBox smite you. Yea, verily: + + /* all programs above here are alphabetically "less than" 'mu' */ + #ifdef CONFIG_MU + APPLET("mu", mu_main, _BB_DIR_USR_BIN, mu_usage) + #endif + /* all programs below here are alphabetically "greater than" 'mu' */ + + +Documentation +------------- + +If you're feeling especially nice, you should also document your applet in the +docs directory (but nobody ever does that). + +Adding some text to docs/Configure.help is a nice start. + + +The Grand Announcement +---------------------- + +Then create a diff -urN of the files you added and/or modified. Typically: + /.c + include/usage.c + include/applets.h + /Makefile.in + /config.in +and send it to the mailing list: + busybox@busybox.net + http://busybox.net/mailman/listinfo/busybox + +Sending patches as attachments is preferred, but not required. diff --git a/docs/sigint.htm b/docs/sigint.htm new file mode 100644 index 000000000..6fe76bbef --- /dev/null +++ b/docs/sigint.htm @@ -0,0 +1,627 @@ + + + +Proper handling of SIGINT/SIGQUIT [http://www.cons.org/cracauer/sigint.html] + + +

Proper handling of SIGINT/SIGQUIT

+ +

+ + + + + +
Abstract: +In UNIX terminal sessions, you usually have a key like +C-c (Control-C) to immediately end whatever program you +have running in the foreground. This should work even when the program +you called has called other programs in turn. Everything should be +aborted, giving you your command prompt back, no matter how deep the +call stack is. + +

Basically, it's trivial. But the existence of interactive +applications that use SIGINT and/or SIGQUIT for other purposes than a +complete immediate abort make matters complicated, and - as was to +expect - left us with several ways to solve the problems. Of course, +existing shells and applications follow different ways. + +

This Web pages outlines different ways to solve the problem and +argues that only one of them can do everything right, although it +means that we have to fix some existing software. + + + +

Intended audience: Programmers who implement programs that catch SIGINT/SIGQUIT. +
Programmers who implements shells or shell-like programs that +execute batches of programs. + +

Users who have problems problems getting rid of runaway shell +scripts using Control-C. Or have interactive applications +that don't behave right when sending SIGINT. Examples are emacs'es +that die on Control-g or shellscript statements that sometimes are +executed and sometimes not, apparently not determined by the user's +intention. + + +

Required knowledge: You have to know what it means to catch SIGINT or SIGQUIT and how +processes are waiting for other processes (childs) they spawned. + + +
+ + + +

Basic concepts

+ +What technically happens when you press Control-C is that all programs +running in the foreground in your current terminal (or virtual +terminal) get the signal SIGINT sent. + +

You may change the key that triggers the signal using +stty and running programs may remap the SIGINT-sending +key at any time they like, without your intervention and without +asking you first. + +

The usual reaction of a running program to SIGINT is to exit. +However, not all program do an exit on SIGINT, programs are free to +use the signal for other actions or to ignore it at all. + +

All programs running in the foreground receive the signal. This may +be a nested "stack" of programs: You started a program that started +another and the outer is waiting for the inner to exit. This nesting +may be arbitrarily deep. + +

The innermost program is the one that decides what to do on SIGINT. +It may exit, do something else or do nothing. Still, when the user hit +SIGINT, all the outer programs are awaken, get the signal and may +react on it. + +

What we try to achieve

+ +The problem is with shell scripts (or similar programs that call +several subprograms one after another). + +

Let us consider the most basic script: +

+#! /bin/sh
+program1
+program2
+
+and the usual run looks like this: +
+$ sh myscript
+[output of program1]
+[output of program2]
+$
+
+ +

Let us assume that both programs do nothing special on SIGINT, they +just exit. + +

Now imagine the user hits C-c while a shellscript is executing its +first program. The following programs receive SIGINT: program1 and +also the shell executing the script. program1 exits. + +

But what should the shell do? If we say that it is only the +innermost's programs business to react on SIGINT, the shell will do +nothing special (not exit) and it will continue the execution of the +script and run program2. But this is wrong: The user's intention in +hitting C-c is to abort the whole script, to get his prompt back. If +he hits C-c while the first program is running, he does not want +program2 to be even started. + +

here is what would happen if the shell doesn't do anything: +

+$ sh myscript
+[first half of program1's output]
+C-c   [users presses C-c]
+[second half of program1's output will not be displayed]
+[output of program2 will appear]
+
+ + +

Consider a more annoying example: +

+#! /bin/sh
+# let's assume there are 300 *.dat files
+for file in *.dat ; do
+	dat2ascii $dat
+done
+
+ +If your shell wouldn't end if the user hits C-c, +C-c would just end one dat2ascii run and +the script would continue. Thus, you had to hit C-c up to +300 times to end this script. + +

Alternatives to do so

+ +

There are several ways to handle abortion of shell scripts when +SIGINT is received while a foreground child runs: + +

+ +
  • As just outlined, the shellscript may just continue, ignoring the +fact that the user hit C-c. That way, your shellscript - +including any loops - would continue and you had no chance of aborting +it except using the kill command after finding out the outermost +shell's PID. This "solution" will not be discussed further, as it is +obviously not desirable. + +

  • The shell itself exits immediately when it receives SIGINT. Not +only the program called will exit, but the calling (the +script-executing) shell. The first variant is to exit the shell (and +therefore discontinuing execution of the script) immediately, while +the background program may still be executing (remember that although +the shell is just waiting for the called program to exit, it is woken +up and may act). I will call the way of doing things the "IUE" (for +"immediate unconditional exit") for the rest of this document. + +

  • As a variant of the former, when the shell receives SIGINT +while it is waiting for a child to exit, the shell does not exit +immediately. but it remembers the fact that a SIGINT happened. After +the called program exits and the shell's wait ends, the shell will +exit itself and hence discontinue the script. I will call the way of +doing things the "WUE" (for "wait and unconditional exit") for the +rest of this document. + +

  • There is also a way that the calling shell can tell whether the +called program exited on SIGINT and if it ignored SIGINT (or used it +for other purposes). As in the WUE way, the shell waits for +the child to complete. It figures whether the program was ended on +SIGINT and if so, it discontinue the script. If the program did any +other exit, the script will be continued. I will call the way of doing +things the "WCE" (for "wait and cooperative exit") for the rest of +this document. + +
  • + +

    The problem

    + +On first sight, all three solutions (IUE, WUE and WCE) all seem to do +what we want: If C-c is hit while the first program of the shell +script runs, the script is discontinued. The user gets his prompt back +immediately. So what are the difference between these way of handling +SIGINT? + +

    There are programs that use the signal SIGINT for other purposes +than exiting. They use it as a normal keystroke. The user is expected +to use the key that sends SIGINT during a perfectly normal program +run. As a result, the user sends SIGINT in situations where he/she +does not want the program or the script to end. + +

    The primary example is the emacs editor: C-g does what ESC does in +other applications: It cancels a partially executed or prepared +operation. Technically, emacs remaps the key that sends SIGINT from +C-c to C-g and catches SIGINT. + +

    Remember that the SIGINT is sent to all programs running in the +foreground. If emacs is executing from a shell script, both emacs and +the shell get SIGINT. emacs is the program that decides what to do: +Exit on SIGINT or not. emacs decides not to exit. The problem arises +when the shell draws its own conclusions from receiving SIGINT without +consulting emacs for its opinion. + +

    Consider this script: +

    +#! /bin/sh
    +emacs /tmp/foo
    +cp /tmp/foo /home/user/mail/sent
    +
    + +

    If C-g is used in emacs, both the shell and emacs will received +SIGINT. Emacs will not exit, the user used C-g as a normal editing +keystroke, he/she does not want the script to be aborted on C-g. + +

    The central problem is that the second command (cp) may +unintentionally be killed when the shell draws its own conclusion +about the user's intention. The innermost program is the only one to +judge. + +

    One more example

    + +

    Imagine a mail session using a curses mailer in a tty. You called +your mailer and started to compose a message. Your mailer calls emacs. +C-g is a normal editing key in emacs. Technically it +sends SIGINT (it was C-c, but emacs remapped the key) to +

    +
  • emacs +
  • the shell between your mailer and emacs, the one from your mailers + system("emacs /tmp/bla.44") command +
  • the mailer itself +
  • possibly another shell if your mailer was called by a shell script +or from another application using system(3) +
  • your interactive shell (which ignores it since it is interactive +and hence is not relevant to this discussion) +
  • + +

    If everyone just exits on SIGINT, you will be left with nothing but +your login shell, without asking. + +

    But for sure you don't want to be dropped out of your editor and +out of your mailer back to the commandline, having your edited data +and mailer status deleted. + +

    Understand the difference: While C-g is used an a kind +of abort key in emacs, it isn't the major "abort everything" key. When +you use C-g in emacs, you want to end some internal emacs +command. You don't want your whole emacs and mailer session to end. + +

    So, if the shell exits immediately if the user sends SIGINT (the +second of the four ways shown above), the parent of emacs would die, +leaving emacs without the controlling tty. The user will lose it's +editing session immediately and unrecoverable. If the "main" shell of +the operating system defaults to this behavior, every editor session +that is spawned from a mailer or such will break (because it is +usually executed by system(3), which calls /bin/sh). This was the case +in FreeBSD before I and Bruce Evans changed it in 1998. + +

    If the shell recognized that SIGINT was sent and exits after the +current foreground process exited (the third way of the four), the +editor session will not be disturbed, but things will still not work +right. + +

    A further look at the alternatives

    + +

    Still considering this script to examine the shell's actions in the +IUE, WUE and ICE way of handling SIGINT: +

    +#! /bin/sh
    +emacs /tmp/foo
    +cp /tmp/foo /home/user/mail/sent
    +
    + +

    The IUE ("immediate unconditional exit") way does not work at all: +emacs wants to survive the SIGINT (it's a normal editing key for +emacs), but its parent shell unconditionally thinks "We received +SIGINT. Abort everything. Now.". The shell will exit even before emacs +exits. But this will leave emacs in an unusable state, since the death +of its calling shell will leave it without required resources (file +descriptors). This way does not work at all for shellscripts that call +programs that use SIGINT for other purposes than immediate exit. Even +for programs that exit on SIGINT, but want to do some cleanup between +the signal and the exit, may fail before they complete their cleanup. + +

    It should be noted that this way has one advantage: If a child +blocks SIGINT and does not exit at all, this way will get control back +to the user's terminal. Since such programs should be banned from your +system anyway, I don't think that weighs against the disadvantages. + +

    WUE ("wait and unconditional exit") is a little more clever: If C-g +was used in emacs, the shell will get SIGINT. It will not immediately +exit, but remember the fact that a SIGINT happened. When emacs ends +(maybe a long time after the SIGINT), it will say "Ok, a SIGINT +happened sometime while the child was executing, the user wants the +script to be discontinued". It will then exit. The cp will not be +executed. But that's bad. The "cp" will be executed when the emacs +session ended without the C-g key ever used, but it will not be +executed when the user used C-g at least one time. That is clearly not +desired. Since C-g is a normal editing key in emacs, the user expects +the rest of the script to behave identically no matter what keys he +used. + +

    As a result, the "WUE" way is better than the "IUE" way in that it +does not break SIGINT-using programs completely. The emacs session +will end undisturbed. But it still does not support scripts where +other actions should be performed after a program that use SIGINT for +non-exit purposes. Since the behavior is basically undeterminable for +the user, this can lead to nasty surprises. + +

    The "WCE" way fixes this by "asking" the called program whether it +exited on SIGINT or not. While emacs receives SIGINT, it does not exit +on it and a calling shell waiting for its exit will not be told that +it exited on SIGINT. (Although it receives SIGINT at some point in +time, the system does not enforce that emacs will exit with +"I-exited-on-SIGINT" status. This is under emacs' control, see below). + +

    this still work for the normal script without SIGINT-using +programs:

    +
    +#! /bin/sh
    +program1
    +program2
    +
    + +Unless program1 and program2 mess around with signal handling, the +system will tell the calling shell whether the programs exited +normally or as a result of SIGINT. + +

    The "WCE" way then has an easy way to things right: When one called +program exited with "I-exited-on-SIGINT" status, it will discontinue +the script after this program. If the program ends without this +status, the next command in the script is started. + +

    It is important to understand that a shell in "WCE" modus does not +need to listen to the SIGINT signal at all. Both in the +"emacs-then-cp" script and in the "several-normal-programs" script, it +will be woken up and receive SIGINT when the user hits the +corresponding key. But the shell does not need to react on this event +and it doesn't need to remember the event of any SIGINT, either. +Telling whether the user wants to end a script is done by asking that +program that has to decide, that program that interprets keystrokes +from the user, the innermost program. + +

    So everything is well with WCE?

    + +Well, almost. + +

    The problem with the "WCE" modus is that there are broken programs +that do not properly communicate the required information up to the +calling program. + +

    Unless a program messes with signal handling, the system does this +automatically. + +

    There are programs that want to exit on SIGINT, but they don't let +the system do the automatic exit, because they want to do some +cleanup. To do so, they catch SIGINT, do the cleanup and then exit by +themselves. + +

    And here is where the problem arises: Once they catch the signal, +the system will no longer communicate the "I-exited-on-SIGINT" status +to the calling program automatically. Even if the program exit +immediately in the signal handler of SIGINT. Once it catches the +signal, it has to take care of communicating the signal status +itself. + +

    Some programs don't do this. On SIGINT, they do cleanup and exit +immediatly, but the calling shell isn't told about the non-normal exit +and it will call the next program in the script. + +

    As a result, the user hits SIGINT and while one program exits, the +shellscript continues. To him/her it looks like the shell fails to +obey to his abortion command. + +

    Both IUE or WUE shell would not have this problem, since they +discontinue the script on their own. But as I said, they don't support +programs using SIGINT for non-exiting purposes, no matter whether +these programs properly communicate their signal status to the calling +shell or not. + +

    Since some shell in wide use implement the WUE way (and some even +IUE), there is a considerable number of broken programs out there that +break WCE shells. The programmers just don't recognize it if their +shell isn't WCE. + +

    How to be a proper program

    + +

    (Short note in advance: What you need to achieve is that +WIFSIGNALED(status) is true in the calling program and that +WTERMSIG(status) returns SIGINT.) + +

    If you don't catch SIGINT, the system automatically does the right +thing for you: Your program exits and the calling program gets the +right "I-exited-on-SIGINT" status after waiting for your exit. + +

    But once you catch SIGINT, you have to act. + +

    Decide whether the SIGINT is used for exit/abort purposes and hence +a shellscript calling this program should discontinue. This is +hopefully obvious. If you just need to do some cleanup on SIGINT, but +then exit immediately, the answer is "yes". + +

    If so, you have to tell the calling program about it by exiting +with the "I-exited-on-SIGINT" status. + +

    There is no other way of doing this than to kill yourself with a +SIGINT signal. Do it by resetting the SIGINT handler to SIG_DFL, then +send yourself the signal. + +

    +void sigint_handler(int sig)
    +{
    +	
    +	signal(SIGINT, SIG_DFL);
    +	kill(getpid(), SIGINT);
    +}
    +
    + +Notes: + + + +
  • You cannot "fake" the proper exit status by an exit(3) with a +special numeric value. People often assume this since the manuals for +shells often list some return value for exactly this. But this is just +a convention for your shell script. It does not work from one UNIX API +program to another. + +

    All that happens is that the shell sets the "$?" variable to a +special numeric value for the convenience of your script, because your +script does not have access to the lower-lever UNIX status evaluation +functions. This is just an agreement between your script and the +executing shell, it does not have any meaning in other contexts. + +

  • Do not use kill(0, SIGINT) without consulting the manul for +your OS implementation. I.e. on BSD, this would not send the signal to +the current process, but to all processes in the group. + +

  • POSIX 1003.1 allows all these calls to appear in signal +handlers, so it is portable. + +
  • + +

    In a bourne shell script, you can catch signals using the +trap command. Here, the same as for C programs apply. If +the intention of SIGINT is to end your program, you have to exit in a +way that the calling programs "sees" that you have been killed. If +you don't catch SIGINT, this happend automatically, but of you catch +SIGINT, i.e. to do cleanup work, you have to end the program by +killing yourself, not by calling exit. + +

    Consider this example from FreeBSD's mkdep, which is a +bourne shell script. + +

    +TMP=_mkdep$$
    +trap 'rm -f $TMP ; trap 2 ; kill -2 $$' 1 2 3 13 15
    +
    + +Yes, you have to do it the hard way. It's even more annoying in shell +scripts than in C programs since you can't "pre-delete" temporary +files (which isn't really portable in C, though). + +

    All this applies to programs in all languages, not only C and +bourne shell. Every language implementation that lets you catch SIGINT +should also give you the option to reset the signal and kill yourself. + +

    It is always desireable to exit the right way, even if you don't +expect your usual callers to depend on it, some unusual one will come +along. This proper exit status will be needed for WCE and will not +hurt when the calling shell uses IUE or WUE. + +

    How to be a proper shell

    + +All this applies only for the script-executing case. Most shells will +also have interactive modes where things are different. + + + +
  • Do nothing special when SIGINT appears while you wait for a child. +You don't even have to remember that one happened. + +

  • Wait for child to exit, get the exit status. Do not truncate it +to type char. + +

  • Look at WIFSIGNALED(status) and WTERMSIG(status) to tell +whether the child says "I exited on SIGINT: in my opinion the user +wants the shellscript to be discontinued". + +

  • If the latter applies, discontinue the script. + +

  • Exit. But since a shellscript may in turn be called by a +shellscript, you need to make sure that you properly communicate the +discontinue intention to the calling program. As in any other program +(see above), do + +
    +	signal(SIGINT, SIG_DFL);
    +	kill(getpid(), SIGINT);
    +
    + +
  • + +

    Other remarks

    + +Although this web page talks about SIGINT only, almost the same issues +apply to SIGQUIT, including proper exiting by killing yourself after +catching the signal and proper reaction on the WIFSIGNALED(status) +value. One notable difference for SIGQUIT is that you have to make +sure that not the whole call tree dumps core. + +

    What to fight

    + +Make sure all programs really kill themselves if they react +to SIGINT or SIGQUIT and intend to abort their operation as a result +of this signal. Programs that don't use SIGINT/SIGQUIT as a +termination trigger - but as part of normal operation - don't kill +themselves, but do a normal exit instead. + +

    Make sure people understand why you can't fake an exit-on-signal by +doing exit(...) using any numerical status. + +

    Make sure you use a shell that behaves right. Especially if you +develop programs, since it will help seeing problems. + +

    Concrete examples how to fix programs:

    +
      + +
    • The fix for FreeBSD's +time(1). This fix is the best example, it's quite short and clear and +it fixes a case where someone tried to fake signal exit status by a +numerical value. And the complete program is small. + +

    • Fix for FreeBSD's +truss(1). + +

    • The fix for FreeBSD's +mkdep(1), a shell script. + + +

    • Fix for FreeBSD's make(1), part 1, +part 2. + +
    + +

    Testsuite for shells

    + +I have a collection of shellscripts that test shells for the +behavior. See my download dir to get the newest +"sh-interrupt" files, either as a tarfile or as individual file for +online browsing. This isn't really documented, besides from the +comments the scripts echo. + +

    Appendix 1 - table of implementation choices

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Method signDoes what?Example shells that implement it:What happens when a shellscript called emacs, the user used +C-g and the script has additional commands in it?What happens when a shellscript called emacs, the user did not use +C-c and the script has additional commands in it?What happens if a non-interactive child catches SIGINT?To behave properly, childs must do what?
    IUEThe shell executing a script exits immediately if it receives +SIGINT.4.4BSD ash (ash), NetBSD, FreeBSD prior to 3.0/22.8The editor session is lost and subsequent commands are not +executed.The editor continues as normal and the subsequent commands are +executed. The scripts ends immediately, returning to the caller even before +the current foreground child of the shell exits. It doesn't matter what the child does or how it exits, even if the +child continues to operate, the shell returns.
    WUEIf the shell executing a script received SIGINT while a foreground +process was running, it will exit after that child's exit.pdksh (OpenBSD /bin/sh)The editor continues as normal, but subsequent commands from the +script are not executed.The editor continues as normal and subsequent commands are +executed. The scripts returns to its caller after the current foreground +child exits, no matter how the child exited. It doesn't matter how the child exits (signal status or not), but +if it doesn't return at all, the shell will not return. In no case +will further commands from the script be executed.
    WCEThe shell exits if a child signaled that it was killed on a +signal (either it had the default handler for SIGINT or it killed +itself). bash (Linux /bin/sh), most commercial /bin/sh, FreeBSD /bin/sh +from 3.0/2.2.8.The editor continues as normal and subsequent commands are +executed. The editor continues as normal and subsequent commands are +executed. The scripts returns to its caller after the current foreground +child exits, but only if the child exited with signal status. If +the child did a normal exit (even if it received SIGINT, but catches +it), the script will continue. The child must be implemented right, or the user will not be able +to break shell scripts reliably.
    + +

     +
    ©2005 Martin Cracauer <cracauer @ cons.org> +http://www.cons.org/cracauer/ +
    Last changed: $Date: 2005/02/11 21:44:43 $ + diff --git a/docs/style-guide.txt b/docs/style-guide.txt new file mode 100644 index 000000000..ba0cdbaa4 --- /dev/null +++ b/docs/style-guide.txt @@ -0,0 +1,689 @@ +Busybox Style Guide +=================== + +This document describes the coding style conventions used in Busybox. If you +add a new file to Busybox or are editing an existing file, please format your +code according to this style. If you are the maintainer of a file that does +not follow these guidelines, please -- at your own convenience -- modify the +file(s) you maintain to bring them into conformance with this style guide. +Please note that this is a low priority task. + +To help you format the whitespace of your programs, an ".indent.pro" file is +included in the main Busybox source directory that contains option flags to +format code as per this style guide. This way you can run GNU indent on your +files by typing 'indent myfile.c myfile.h' and it will magically apply all the +right formatting rules to your file. Please _do_not_ run this on all the files +in the directory, just your own. + + + +Declaration Order +----------------- + +Here is the order in which code should be laid out in a file: + + - commented program name and one-line description + - commented author name and email address(es) + - commented GPL boilerplate + - commented longer description / notes for the program (if needed) + - #includes of .h files with angle brackets (<>) around them + - #includes of .h files with quotes ("") around them + - #defines (if any, note the section below titled "Avoid the Preprocessor") + - const and global variables + - function declarations (if necessary) + - function implementations + + + +Whitespace and Formatting +------------------------- + +This is everybody's favorite flame topic so let's get it out of the way right +up front. + + +Tabs vs. Spaces in Line Indentation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The preference in Busybox is to indent lines with tabs. Do not indent lines +with spaces and do not indents lines using a mixture of tabs and spaces. (The +indentation style in the Apache and Postfix source does this sort of thing: +\s\s\s\sif (expr) {\n\tstmt; --ick.) The only exception to this rule is +multi-line comments that use an asterisk at the beginning of each line, i.e.: + + \t/* + \t * This is a block comment. + \t * Note that it has multiple lines + \t * and that the beginning of each line has a tab plus a space + \t * except for the opening '/*' line where the slash + \t * is used instead of a space. + \t */ + +Furthermore, The preference is that tabs be set to display at four spaces +wide, but the beauty of using only tabs (and not spaces) at the beginning of +lines is that you can set your editor to display tabs at *whatever* number of +spaces is desired and the code will still look fine. + + +Operator Spacing +~~~~~~~~~~~~~~~~ + +Put spaces between terms and operators. Example: + + Don't do this: + + for(i=0;i 0) + + +Bracket Spacing +~~~~~~~~~~~~~~~ + +If an opening bracket starts a function, it should be on the +next line with no spacing before it. However, if a bracket follows an opening +control block, it should be on the same line with a single space (not a tab) +between it and the opening control block statement. Examples: + + Don't do this: + + while (!done) + { + + do + { + + Don't do this either: + + while (!done){ + + do{ + + And for heaven's sake, don't do this: + + while (!done) + { + + do + { + + Do this instead: + + while (!done) { + + do { + +Exceptions: + + - if you have long logic statements that need to be wrapped, then uncuddling + the bracket to improve readability is allowed: + + if (some_really_long_checks && some_other_really_long_checks \ + && some_more_really_long_checks) + { + do_foo_now; + +Spacing around Parentheses +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Put a space between C keywords and left parens, but not between function names +and the left paren that starts it's parameter list (whether it is being +declared or called). Examples: + + Don't do this: + + while(foo) { + for(i = 0; i < n; i++) { + + Do this instead: + + while (foo) { + for (i = 0; i < n; i++) { + + But do functions like this: + + static int my_func(int foo, char bar) + ... + baz = my_func(1, 2); + +Also, don't put a space between the left paren and the first term, nor between +the last arg and the right paren. + + Don't do this: + + if ( x < 1 ) + strcmp( thisstr, thatstr ) + + Do this instead: + + if (x < 1) + strcmp(thisstr, thatstr) + + +Cuddled Elses +~~~~~~~~~~~~~ + +Also, please "cuddle" your else statements by putting the else keyword on the +same line after the right bracket that closes an 'if' statement. + + Don't do this: + + if (foo) { + stmt; + } + else { + stmt; + } + + Do this instead: + + if (foo) { + stmt; + } else { + stmt; + } + +The exception to this rule is if you want to include a comment before the else +block. Example: + + if (foo) { + stmts... + } + /* otherwise, we're just kidding ourselves, so re-frob the input */ + else { + other_stmts... + } + + + +Variable and Function Names +--------------------------- + +Use the K&R style with names in all lower-case and underscores occasionally +used to separate words (e.g., "variable_name" and "numchars" are both +acceptable). Using underscores makes variable and function names more readable +because it looks like whitespace; using lower-case is easy on the eyes. + + Frowned upon: + + hitList + TotalChars + szFileName + pf_Nfol_TriState + + Preferred: + + hit_list + total_chars + file_name + sensible_name + +Exceptions: + + - Enums, macros, and constant variables are occasionally written in all + upper-case with words optionally seperatedy by underscores (i.e. FIFOTYPE, + ISBLKDEV()). + + - Nobody is going to get mad at you for using 'pvar' as the name of a + variable that is a pointer to 'var'. + + +Converting to K&R +~~~~~~~~~~~~~~~~~ + +The Busybox codebase is very much a mixture of code gathered from a variety of +sources. This explains why the current codebase contains such a hodge-podge of +different naming styles (Java, Pascal, K&R, just-plain-weird, etc.). The K&R +guideline explained above should therefore be used on new files that are added +to the repository. Furthermore, the maintainer of an existing file that uses +alternate naming conventions should, at his own convenience, convert those +names over to K&R style. Converting variable names is a very low priority +task. + +If you want to do a search-and-replace of a single variable name in different +files, you can do the following in the busybox directory: + + $ perl -pi -e 's/\bOldVar\b/new_var/g' *.[ch] + +If you want to convert all the non-K&R vars in your file all at once, follow +these steps: + + - In the busybox directory type 'examples/mk2knr.pl files-to-convert'. This + does not do the actual conversion, rather, it generates a script called + 'convertme.pl' that shows what will be converted, giving you a chance to + review the changes beforehand. + + - Review the 'convertme.pl' script that gets generated in the busybox + directory and remove / edit any of the substitutions in there. Please + especially check for false positives (strings that should not be + converted). + + - Type './convertme.pl same-files-as-before' to perform the actual + conversion. + + - Compile and see if everything still works. + +Please be aware of changes that have cascading effects into other files. For +example, if you're changing the name of something in, say utility.c, you +should probably run 'examples/mk2knr.pl utility.c' at first, but when you run +the 'convertme.pl' script you should run it on _all_ files like so: +'./convertme.pl *.[ch]'. + + + +Avoid The Preprocessor +---------------------- + +At best, the preprocessor is a necessary evil, helping us account for platform +and architecture differences. Using the preprocessor unnecessarily is just +plain evil. + + +The Folly of #define +~~~~~~~~~~~~~~~~~~~~ + +Use 'const var' for declaring constants. + + Don't do this: + + #define var 80 + + Do this instead, when the variable is in a header file and will be used in + several source files: + + const int var = 80; + + Or do this when the variable is used only in a single source file: + + static const int var = 80; + +Declaring variables as '[static] const' gives variables an actual type and +makes the compiler do type checking for you; the preprocessor does _no_ type +checking whatsoever, making it much more error prone. Declaring variables with +'[static] const' also makes debugging programs much easier since the value of +the variable can be easily queried and displayed. + + +The Folly of Macros +~~~~~~~~~~~~~~~~~~~ + +Use 'static inline' instead of a macro. + + Don't do this: + + #define mini_func(param1, param2) (param1 << param2) + + Do this instead: + + static inline int mini_func(int param1, param2) + { + return (param1 << param2); + } + +Static inline functions are greatly preferred over macros. They provide type +safety, have no length limitations, no formatting limitations, have an actual +return value, and under gcc they are as cheap as macros. Besides, really long +macros with backslashes at the end of each line are ugly as sin. + + +The Folly of #ifdef +~~~~~~~~~~~~~~~~~~~ + +Code cluttered with ifdefs is difficult to read and maintain. Don't do it. +Instead, put your ifdefs at the top of your .c file (or in a header), and +conditionally define 'static inline' functions, (or *maybe* macros), which are +used in the code. + + Don't do this: + + ret = my_func(bar, baz); + if (!ret) + return -1; + #ifdef CONFIG_FEATURE_FUNKY + maybe_do_funky_stuff(bar, baz); + #endif + + Do this instead: + + (in .h header file) + + #ifdef CONFIG_FEATURE_FUNKY + static inline void maybe_do_funky_stuff (int bar, int baz) + { + /* lotsa code in here */ + } + #else + static inline void maybe_do_funky_stuff (int bar, int baz) {} + #endif + + (in the .c source file) + + ret = my_func(bar, baz); + if (!ret) + return -1; + maybe_do_funky_stuff(bar, baz); + +The great thing about this approach is that the compiler will optimize away +the "no-op" case (the empty function) when the feature is turned off. + +Note also the use of the word 'maybe' in the function name to indicate +conditional execution. + + + +Notes on Strings +---------------- + +Strings in C can get a little thorny. Here's some guidelines for dealing with +strings in Busybox. (There is surely more that could be added to this +section.) + + +String Files +~~~~~~~~~~~~ + +Put all help/usage messages in usage.c. Put other strings in messages.c. +Putting these strings into their own file is a calculated decision designed to +confine spelling errors to a single place and aid internationalization +efforts, if needed. (Side Note: we might want to use a single file - maybe +called 'strings.c' - instead of two, food for thought). + + +Testing String Equivalence +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There's a right way and a wrong way to test for sting equivalence with +strcmp(): + + The wrong way: + + if (!strcmp(string, "foo")) { + ... + + The right way: + + if (strcmp(string, "foo") == 0){ + ... + +The use of the "equals" (==) operator in the latter example makes it much more +obvious that you are testing for equivalence. The former example with the +"not" (!) operator makes it look like you are testing for an error. In a more +perfect world, we would have a streq() function in the string library, but +that ain't the world we're living in. + + +Avoid Dangerous String Functions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Unfortunately, the way C handles strings makes them prone to overruns when +certain library functions are (mis)used. The following table offers a summary +of some of the more notorious troublemakers: + +function overflows preferred +---------------------------------------- +strcpy dest string strncpy +strcat dest string strncat +gets string it gets fgets +getwd buf string getcwd +[v]sprintf str buffer [v]snprintf +realpath path buffer use with pathconf +[vf]scanf its arguments just avoid it + + +The above is by no means a complete list. Be careful out there. + + + +Avoid Big Static Buffers +------------------------ + +First, some background to put this discussion in context: Static buffers look +like this in code: + + /* in a .c file outside any functions */ + static char buffer[BUFSIZ]; /* happily used by any function in this file, + but ick! big! */ + +The problem with these is that any time any busybox app is run, you pay a +memory penalty for this buffer, even if the applet that uses said buffer is +not run. This can be fixed, thusly: + + static char *buffer; + ... + other_func() + { + strcpy(buffer, lotsa_chars); /* happily uses global *buffer */ + ... + foo_main() + { + buffer = xmalloc(sizeof(char)*BUFSIZ); + ... + +However, this approach trades bss segment for text segment. Rather than +mallocing the buffers (and thus growing the text size), buffers can be +declared on the stack in the *_main() function and made available globally by +assigning them to a global pointer thusly: + + static char *pbuffer; + ... + other_func() + { + strcpy(pbuffer, lotsa_chars); /* happily uses global *pbuffer */ + ... + foo_main() + { + char *buffer[BUFSIZ]; /* declared locally, on stack */ + pbuffer = buffer; /* but available globally */ + ... + +This last approach has some advantages (low code size, space not used until +it's needed), but can be a problem in some low resource machines that have +very limited stack space (e.g., uCLinux). + +A macro is declared in busybox.h that implements compile-time selection +between xmalloc() and stack creation, so you can code the line in question as + + RESERVE_CONFIG_BUFFER(buffer, BUFSIZ); + +and the right thing will happen, based on your configuration. + + + +Miscellaneous Coding Guidelines +------------------------------- + +The following are important items that don't fit into any of the above +sections. + + +Model Busybox Applets After GNU Counterparts +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When in doubt about the proper behavior of a Busybox program (output, +formatting, options, etc.), model it after the equivalent GNU program. +Doesn't matter how that program behaves on some other flavor of *NIX; doesn't +matter what the POSIX standard says or doesn't say, just model Busybox +programs after their GNU counterparts and it will make life easier on (nearly) +everyone. + +The only time we deviate from emulating the GNU behavior is when: + + - We are deliberately not supporting a feature (such as a command line + switch) + - Emulating the GNU behavior is prohibitively expensive (lots more code + would be required, lots more memory would be used, etc.) + - The difference is minor or cosmetic + +A note on the 'cosmetic' case: Output differences might be considered +cosmetic, but if the output is significant enough to break other scripts that +use the output, it should really be fixed. + + +Scope +~~~~~ + +If a const variable is used only in a single source file, put it in the source +file and not in a header file. Likewise, if a const variable is used in only +one function, do not make it global to the file. Instead, declare it inside +the function body. Bottom line: Make a conscious effort to limit declarations +to the smallest scope possible. + +Inside applet files, all functions should be declared static so as to keep the +global name space clean. The only exception to this rule is the "applet_main" +function which must be declared extern. + +If you write a function that performs a task that could be useful outside the +immediate file, turn it into a general-purpose function with no ties to any +applet and put it in the utility.c file instead. + + +Brackets Are Your Friends +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Please use brackets on all if and else statements, even if it is only one +line. Example: + + Don't do this: + + if (foo) + stmt1; + stmt2 + stmt3; + + Do this instead: + + if (foo) { + stmt1; + } + stmt2 + stmt3; + +The "bracketless" approach is error prone because someday you might add a line +like this: + + if (foo) + stmt1; + new_line(); + stmt2 + stmt3; + +And the resulting behavior of your program would totally bewilder you. (Don't +laugh, it happens to us all.) Remember folks, this is C, not Python. + + +Function Declarations +~~~~~~~~~~~~~~~~~~~~~ + +Do not use old-style function declarations that declare variable types between +the parameter list and opening bracket. Example: + + Don't do this: + + int foo(parm1, parm2) + char parm1; + float parm2; + { + .... + + Do this instead: + + int foo(char parm1, float parm2) + { + .... + +The only time you would ever need to use the old declaration syntax is to +support ancient, antediluvian compilers. To our good fortune, we have access +to more modern compilers and the old declaration syntax is neither necessary +nor desired. + + +Emphasizing Logical Blocks +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Organization and readability are improved by putting extra newlines around +blocks of code that perform a single task. These are typically blocks that +begin with a C keyword, but not always. + +Furthermore, you should put a single comment (not necessarily one line, just +one comment) before the block, rather than commenting each and every line. +There is an optimal amount of commenting that a program can have; you can +comment too much as well as too little. + +A picture is really worth a thousand words here, the following example +illustrates how to emphasize logical blocks: + + while (line = get_line_from_file(fp)) { + + /* eat the newline, if any */ + chomp(line); + + /* ignore blank lines */ + if (strlen(file_to_act_on) == 0) { + continue; + } + + /* if the search string is in this line, print it, + * unless we were told to be quiet */ + if (strstr(line, search) && !be_quiet) { + puts(line); + } + + /* clean up */ + free(line); + } + + +Processing Options with getopt +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If your applet needs to process command-line switches, please use getopt() to +do so. Numerous examples can be seen in many of the existing applets, but +basically it boils down to two things: at the top of the .c file, have this +line in the midst of your #includes: + + #include + +And a code block similar to the following near the top of your applet_main() +routine: + + while ((opt = getopt(argc, argv, "abc")) > 0) { + switch (opt) { + case 'a': + do_a_opt = 1; + break; + case 'b': + do_b_opt = 1; + break; + case 'c': + do_c_opt = 1; + break; + default: + show_usage(); /* in utility.c */ + } + } + +If your applet takes no options (such as 'init'), there should be a line +somewhere in the file reads: + + /* no options, no getopt */ + +That way, when people go grepping to see which applets need to be converted to +use getopt, they won't get false positives. + +Additional Note: Do not use the getopt_long library function and do not try to +hand-roll your own long option parsing. Busybox applets should only support +short options. Explanations and examples of the short options should be +documented in usage.h. diff --git a/docs/tar_pax.txt b/docs/tar_pax.txt new file mode 100644 index 000000000..8a3f1e755 --- /dev/null +++ b/docs/tar_pax.txt @@ -0,0 +1,239 @@ +'pax headers' is POSIX 2003 (iirc) addition designed to fix +tar format limitations - older tar format has fixed fields +for everything (filename, uid, filesize etc) which can overflow. + +pax Header Block + +The pax header block shall be identical to the ustar header block +described in ustar Interchange Format, except that two additional +typeflag values are defined: + +x + Represents extended header records for the following file in +the archive (which shall have its own ustar header block). + +g + Represents global extended header records for the following +files in the archive. Each value shall affect all subsequent files +that do not override that value in their own extended header +record and until another global extended header record is reached +that provides another value for the same field. The typeflag g +global headers should not be used with interchange media that +could suffer partial data loss in transporting the archive. + +For both of these types, the size field shall be the size of the +extended header records in octets. The other fields in the header +block are not meaningful to this version of the pax utility. +However, if this archive is read by a pax utility conforming to +the ISO POSIX-2:1993 standard, the header block fields are used to +create a regular file that contains the extended header records as +data. Therefore, header block field values should be selected to +provide reasonable file access to this regular file. + +A further difference from the ustar header block is that data +blocks for files of typeflag 1 (the digit one) (hard link) may be +included, which means that the size field may be greater than +zero. + +pax Extended Header + +An extended header shall consist of one or more records, each +constructed as follows: + +"%d %s=%s\n", , , + +The field shall be the decimal length of the extended +header record in octets, including length string itself and the +trailing . + +[skip] + +atime + The file access time for the following file(s), equivalent to +the value of the st_atime member of the stat structure for a file, +as described by the stat() function. The access time shall be +restored if the process has the appropriate privilege required to +do so. The format of the shall be as described in pax +Extended Header File Times. + +charset + The name of the character set used to encode the data in the +following file(s). + + The encoding is included in an extended header for information +only; when pax is used as described in IEEE Std 1003.1-2001, it +shall not translate the file data into any other encoding. The +BINARY entry indicates unencoded binary data. + + When used in write or copy mode, it is implementation-defined +whether pax includes a charset extended header record for a file. + +comment + A series of characters used as a comment. All characters in +the field shall be ignored by pax. + +gid + The group ID of the group that owns the file, expressed as a +decimal number using digits from the ISO/IEC 646:1991 standard. +This record shall override the gid field in the following header +block(s). When used in write or copy mode, pax shall include a gid +extended header record for each file whose group ID is greater +than 2097151 (octal 7777777). + +gname + The group of the file(s), formatted as a group name in the +group database. This record shall override the gid and gname +fields in the following header block(s), and any gid extended +header record. When used in read, copy, or list mode, pax shall +translate the name from the UTF-8 encoding in the header record to +the character set appropriate for the group database on the +receiving system. If any of the UTF-8 characters cannot be +translated, and if the -o invalid= UTF-8 option is not specified, +the results are implementation-defined. When used in write or copy +mode, pax shall include a gname extended header record for each +file whose group name cannot be represented entirely with the +letters and digits of the portable character set. + +linkpath + The pathname of a link being created to another file, of any +type, previously archived. This record shall override the linkname +field in the following ustar header block(s). The following ustar +header block shall determine the type of link created. If typeflag +of the following header block is 1, it shall be a hard link. If +typeflag is 2, it shall be a symbolic link and the linkpath value +shall be the contents of the symbolic link. The pax utility shall +translate the name of the link (contents of the symbolic link) +from the UTF-8 encoding to the character set appropriate for the +local file system. When used in write or copy mode, pax shall +include a linkpath extended header record for each link whose +pathname cannot be represented entirely with the members of the +portable character set other than NUL. + +mtime + The file modification time of the following file(s), +equivalent to the value of the st_mtime member of the stat +structure for a file, as described in the stat() function. This +record shall override the mtime field in the following header +block(s). The modification time shall be restored if the process +has the appropriate privilege required to do so. The format of the + shall be as described in pax Extended Header File Times. + +path + The pathname of the following file(s). This record shall +override the name and prefix fields in the following header +block(s). The pax utility shall translate the pathname of the file +from the UTF-8 encoding to the character set appropriate for the +local file system. + + When used in write or copy mode, pax shall include a path +extended header record for each file whose pathname cannot be +represented entirely with the members of the portable character +set other than NUL. + +realtime.any + The keywords prefixed by "realtime." are reserved for future +standardization. + +security.any + The keywords prefixed by "security." are reserved for future +standardization. + +size + The size of the file in octets, expressed as a decimal number +using digits from the ISO/IEC 646:1991 standard. This record shall +override the size field in the following header block(s). When +used in write or copy mode, pax shall include a size extended +header record for each file with a size value greater than +8589934591 (octal 77777777777). + +uid + The user ID of the file owner, expressed as a decimal number +using digits from the ISO/IEC 646:1991 standard. This record shall +override the uid field in the following header block(s). When used +in write or copy mode, pax shall include a uid extended header +record for each file whose owner ID is greater than 2097151 (octal +7777777). + +uname + The owner of the following file(s), formatted as a user name +in the user database. This record shall override the uid and uname +fields in the following header block(s), and any uid extended +header record. When used in read, copy, or list mode, pax shall +translate the name from the UTF-8 encoding in the header record to +the character set appropriate for the user database on the +receiving system. If any of the UTF-8 characters cannot be +translated, and if the -o invalid= UTF-8 option is not specified, +the results are implementation-defined. When used in write or copy +mode, pax shall include a uname extended header record for each +file whose user name cannot be represented entirely with the +letters and digits of the portable character set. + +If the field is zero length, it shall delete any header +block field, previously entered extended header value, or global +extended header value of the same name. + +If a keyword in an extended header record (or in a -o +option-argument) overrides or deletes a corresponding field in the +ustar header block, pax shall ignore the contents of that header +block field. + +Unlike the ustar header block fields, NULs shall not delimit +s; all characters within the field shall be +considered data for the field. None of the length limitations of +the ustar header block fields in ustar Header Block shall apply to +the extended header records. + +pax Extended Header File Times + +Time records shall be formatted as a decimal representation of the +time in seconds since the Epoch. If a period ( '.' ) decimal point +character is present, the digits to the right of the point shall +represent the units of a subsecond timing granularity. In read or +copy mode, the pax utility shall truncate the time of a file to +the greatest value that is not greater than the input header +file time. In write or copy mode, the pax utility shall output a +time exactly if it can be represented exactly as a decimal number, +and otherwise shall generate only enough digits so that the same +time shall be recovered if the file is extracted on a system whose +underlying implementation supports the same time granularity. + +Example from Linux kernel archive tarball: + +00000000 70 61 78 5f 67 6c 6f 62 61 6c 5f 68 65 61 64 65 |pax_global_heade| +00000010 72 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |r...............| +00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000060 00 00 00 00 30 30 30 30 36 36 36 00 30 30 30 30 |....0000666.0000| +00000070 30 30 30 00 30 30 30 30 30 30 30 00 30 30 30 30 |000.0000000.0000| +00000080 30 30 30 30 30 36 34 00 30 30 30 30 30 30 30 30 |0000064.00000000| +00000090 30 30 30 00 30 30 31 34 30 35 33 00 67 00 00 00 |000.0014053.g...| +000000a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000000b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000000c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000000d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000000e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000000f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000100 00 75 73 74 61 72 00 30 30 67 69 74 00 00 00 00 |.ustar.00git....| +00000110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000120 00 00 00 00 00 00 00 00 00 67 69 74 00 00 00 00 |.........git....| +00000130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000140 00 00 00 00 00 00 00 00 00 30 30 30 30 30 30 30 |.........0000000| +00000150 00 30 30 30 30 30 30 30 00 00 00 00 00 00 00 00 |.0000000........| +00000160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000180 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000001a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000001b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000001c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000001d0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000001e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +00000200 35 32 20 63 6f 6d 6d 65 6e 74 3d 62 31 30 35 30 |52 comment=b1050| +00000210 32 62 32 32 61 31 32 30 39 64 36 62 34 37 36 33 |2b22a1209d6b4763| +00000220 39 64 38 38 62 38 31 32 62 32 31 66 62 35 39 34 |9d88b812b21fb594| +00000230 39 65 34 0a 00 00 00 00 00 00 00 00 00 00 00 00 |9e4.............| +00000240 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +... diff --git a/e2fsprogs/Config.in b/e2fsprogs/Config.in new file mode 100644 index 000000000..0062b2fe3 --- /dev/null +++ b/e2fsprogs/Config.in @@ -0,0 +1,67 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Linux Ext2 FS Progs" + +config CHATTR + bool "chattr" + default n + help + chattr changes the file attributes on a second extended file system. + +config E2FSCK + bool "e2fsck" + default n + help + e2fsck is used to check Linux second extended file systems (ext2fs). + e2fsck also supports ext2 filesystems countaining a journal (ext3). + The normal compat symlinks 'fsck.ext2' and 'fsck.ext3' are also + provided. + +config FSCK + bool "fsck" + default n + help + fsck is used to check and optionally repair one or more filesystems. + In actuality, fsck is simply a front-end for the various file system + checkers (fsck.fstype) available under Linux. + +config LSATTR + bool "lsattr" + default n + help + lsattr lists the file attributes on a second extended file system. + +config MKE2FS + bool "mke2fs" + default n + help + mke2fs is used to create an ext2/ext3 filesystem. The normal compat + symlinks 'mkfs.ext2' and 'mkfs.ext3' are also provided. + +config TUNE2FS + bool "tune2fs" + default n + help + tune2fs allows the system administrator to adjust various tunable + filesystem parameters on Linux ext2/ext3 filesystems. + +config E2LABEL + bool "e2label" + default n + depends on TUNE2FS + help + e2label will display or change the filesystem label on the ext2 + filesystem located on device. + +config FINDFS + bool "findfs" + default n + depends on TUNE2FS + help + findfs will search the disks in the system looking for a filesystem + which has a label matching label or a UUID equal to uuid. + +endmenu diff --git a/e2fsprogs/Kbuild b/e2fsprogs/Kbuild new file mode 100644 index 000000000..b05bb92e1 --- /dev/null +++ b/e2fsprogs/Kbuild @@ -0,0 +1,16 @@ +# 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_CHATTR) += chattr.o +lib-$(CONFIG_E2FSCK) += e2fsck.o util.o +lib-$(CONFIG_FSCK) += fsck.o util.o +lib-$(CONFIG_LSATTR) += lsattr.o +lib-$(CONFIG_MKE2FS) += mke2fs.o util.o +lib-$(CONFIG_TUNE2FS) += tune2fs.o util.o + +CFLAGS += -include $(srctree)/e2fsprogs/e2fsbb.h diff --git a/e2fsprogs/README b/e2fsprogs/README new file mode 100644 index 000000000..fac090193 --- /dev/null +++ b/e2fsprogs/README @@ -0,0 +1,3 @@ +This is a pretty straight rip from the e2fsprogs pkg. + +See README's in subdirs for specific info. diff --git a/e2fsprogs/blkid/Kbuild b/e2fsprogs/blkid/Kbuild new file mode 100644 index 000000000..ddcfdfd9a --- /dev/null +++ b/e2fsprogs/blkid/Kbuild @@ -0,0 +1,23 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +NEEDED-$(CONFIG_E2FSCK) = y +NEEDED-$(CONFIG_FSCK) = y +NEEDED-$(CONFIG_MKE2FS) = y +NEEDED-$(CONFIG_TUNE2FS) = y + +lib-y:= +lib-$(NEEDED-y) += cache.o dev.o devname.o devno.o blkid_getsize.o \ + probe.o read.o resolve.o save.o tag.o list.o + +CFLAGS_dev.o := -include $(srctree)/include/busybox.h +CFLAGS_devname.o := -include $(srctree)/include/busybox.h +CFLAGS_devno.o := -include $(srctree)/include/busybox.h +CFLAGS_blkid_getsize.o := -include $(srctree)/include/busybox.h +CFLAGS_probe.o := -include $(srctree)/include/busybox.h +CFLAGS_save.o := -include $(srctree)/include/busybox.h +CFLAGS_tag.o := -include $(srctree)/include/busybox.h +CFLAGS_list.o := -include $(srctree)/include/busybox.h diff --git a/e2fsprogs/blkid/blkid.h b/e2fsprogs/blkid/blkid.h new file mode 100644 index 000000000..4fa9f6fdf --- /dev/null +++ b/e2fsprogs/blkid/blkid.h @@ -0,0 +1,105 @@ +/* vi: set sw=4 ts=4: */ +/* + * blkid.h - Interface for libblkid, a library to identify block devices + * + * Copyright (C) 2001 Andreas Dilger + * Copyright (C) 2003 Theodore Ts'o + * + * %Begin-Header% + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + * %End-Header% + */ + +#ifndef _BLKID_BLKID_H +#define _BLKID_BLKID_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define BLKID_VERSION "1.0.0" +#define BLKID_DATE "12-Feb-2003" + +typedef struct blkid_struct_dev *blkid_dev; +typedef struct blkid_struct_cache *blkid_cache; +typedef __s64 blkid_loff_t; + +typedef struct blkid_struct_tag_iterate *blkid_tag_iterate; +typedef struct blkid_struct_dev_iterate *blkid_dev_iterate; + +/* + * Flags for blkid_get_dev + * + * BLKID_DEV_CREATE Create an empty device structure if not found + * in the cache. + * BLKID_DEV_VERIFY Make sure the device structure corresponds + * with reality. + * BLKID_DEV_FIND Just look up a device entry, and return NULL + * if it is not found. + * BLKID_DEV_NORMAL Get a valid device structure, either from the + * cache or by probing the device. + */ +#define BLKID_DEV_FIND 0x0000 +#define BLKID_DEV_CREATE 0x0001 +#define BLKID_DEV_VERIFY 0x0002 +#define BLKID_DEV_NORMAL (BLKID_DEV_CREATE | BLKID_DEV_VERIFY) + +/* cache.c */ +extern void blkid_put_cache(blkid_cache cache); +extern int blkid_get_cache(blkid_cache *cache, const char *filename); + +/* dev.c */ +extern const char *blkid_dev_devname(blkid_dev dev); + +extern blkid_dev_iterate blkid_dev_iterate_begin(blkid_cache cache); +extern int blkid_dev_set_search(blkid_dev_iterate iter, + char *search_type, char *search_value); +extern int blkid_dev_next(blkid_dev_iterate iterate, blkid_dev *dev); +extern void blkid_dev_iterate_end(blkid_dev_iterate iterate); + +/* devno.c */ +extern char *blkid_devno_to_devname(dev_t devno); + +/* devname.c */ +extern int blkid_probe_all(blkid_cache cache); +extern int blkid_probe_all_new(blkid_cache cache); +extern blkid_dev blkid_get_dev(blkid_cache cache, const char *devname, + int flags); + +/* getsize.c */ +extern blkid_loff_t blkid_get_dev_size(int fd); + +/* probe.c */ +int blkid_known_fstype(const char *fstype); +extern blkid_dev blkid_verify(blkid_cache cache, blkid_dev dev); + +/* read.c */ + +/* resolve.c */ +extern char *blkid_get_tag_value(blkid_cache cache, const char *tagname, + const char *devname); +extern char *blkid_get_devname(blkid_cache cache, const char *token, + const char *value); + +/* tag.c */ +extern blkid_tag_iterate blkid_tag_iterate_begin(blkid_dev dev); +extern int blkid_tag_next(blkid_tag_iterate iterate, + const char **type, const char **value); +extern void blkid_tag_iterate_end(blkid_tag_iterate iterate); +extern int blkid_dev_has_tag(blkid_dev dev, const char *type, + const char *value); +extern blkid_dev blkid_find_dev_with_tag(blkid_cache cache, + const char *type, + const char *value); +extern int blkid_parse_tag_string(const char *token, char **ret_type, + char **ret_val); + +#ifdef __cplusplus +} +#endif + +#endif /* _BLKID_BLKID_H */ diff --git a/e2fsprogs/blkid/blkidP.h b/e2fsprogs/blkid/blkidP.h new file mode 100644 index 000000000..c7cb8abe9 --- /dev/null +++ b/e2fsprogs/blkid/blkidP.h @@ -0,0 +1,187 @@ +/* vi: set sw=4 ts=4: */ +/* + * blkidP.h - Internal interfaces for libblkid + * + * Copyright (C) 2001 Andreas Dilger + * Copyright (C) 2003 Theodore Ts'o + * + * %Begin-Header% + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + * %End-Header% + */ + +#ifndef _BLKID_BLKIDP_H +#define _BLKID_BLKIDP_H + +#include +#include + +#include "blkid.h" +#include "list.h" + +#ifdef __GNUC__ +#define __BLKID_ATTR(x) __attribute__(x) +#else +#define __BLKID_ATTR(x) +#endif + + +/* + * This describes the attributes of a specific device. + * We can traverse all of the tags by bid_tags (linking to the tag bit_names). + * The bid_label and bid_uuid fields are shortcuts to the LABEL and UUID tag + * values, if they exist. + */ +struct blkid_struct_dev +{ + struct list_head bid_devs; /* All devices in the cache */ + struct list_head bid_tags; /* All tags for this device */ + blkid_cache bid_cache; /* Dev belongs to this cache */ + char *bid_name; /* Device inode pathname */ + char *bid_type; /* Preferred device TYPE */ + int bid_pri; /* Device priority */ + dev_t bid_devno; /* Device major/minor number */ + time_t bid_time; /* Last update time of device */ + unsigned int bid_flags; /* Device status bitflags */ + char *bid_label; /* Shortcut to device LABEL */ + char *bid_uuid; /* Shortcut to binary UUID */ +}; + +#define BLKID_BID_FL_VERIFIED 0x0001 /* Device data validated from disk */ +#define BLKID_BID_FL_INVALID 0x0004 /* Device is invalid */ + +/* + * Each tag defines a NAME=value pair for a particular device. The tags + * are linked via bit_names for a single device, so that traversing the + * names list will get you a list of all tags associated with a device. + * They are also linked via bit_values for all devices, so one can easily + * search all tags with a given NAME for a specific value. + */ +struct blkid_struct_tag +{ + struct list_head bit_tags; /* All tags for this device */ + struct list_head bit_names; /* All tags with given NAME */ + char *bit_name; /* NAME of tag (shared) */ + char *bit_val; /* value of tag */ + blkid_dev bit_dev; /* pointer to device */ +}; +typedef struct blkid_struct_tag *blkid_tag; + +/* + * Minimum number of seconds between device probes, even when reading + * from the cache. This is to avoid re-probing all devices which were + * just probed by another program that does not share the cache. + */ +#define BLKID_PROBE_MIN 2 + +/* + * Time in seconds an entry remains verified in the in-memory cache + * before being reverified (in case of long-running processes that + * keep a cache in memory and continue to use it for a long time). + */ +#define BLKID_PROBE_INTERVAL 200 + +/* This describes an entire blkid cache file and probed devices. + * We can traverse all of the found devices via bic_list. + * We can traverse all of the tag types by bic_tags, which hold empty tags + * for each tag type. Those tags can be used as list_heads for iterating + * through all devices with a specific tag type (e.g. LABEL). + */ +struct blkid_struct_cache +{ + struct list_head bic_devs; /* List head of all devices */ + struct list_head bic_tags; /* List head of all tag types */ + time_t bic_time; /* Last probe time */ + time_t bic_ftime; /* Mod time of the cachefile */ + unsigned int bic_flags; /* Status flags of the cache */ + char *bic_filename; /* filename of cache */ +}; + +#define BLKID_BIC_FL_PROBED 0x0002 /* We probed /proc/partition devices */ +#define BLKID_BIC_FL_CHANGED 0x0004 /* Cache has changed from disk */ + +extern char *blkid_strdup(const char *s); +extern char *blkid_strndup(const char *s, const int length); + +#define BLKID_CACHE_FILE "/etc/blkid.tab" +extern const char *blkid_devdirs[]; + +#define BLKID_ERR_IO 5 +#define BLKID_ERR_PROC 9 +#define BLKID_ERR_MEM 12 +#define BLKID_ERR_CACHE 14 +#define BLKID_ERR_DEV 19 +#define BLKID_ERR_PARAM 22 +#define BLKID_ERR_BIG 27 + +/* + * Priority settings for different types of devices + */ +#define BLKID_PRI_EVMS 30 +#define BLKID_PRI_LVM 20 +#define BLKID_PRI_MD 10 + +#if defined(TEST_PROGRAM) && !defined(CONFIG_BLKID_DEBUG) +#define CONFIG_BLKID_DEBUG +#endif + +#define DEBUG_CACHE 0x0001 +#define DEBUG_DUMP 0x0002 +#define DEBUG_DEV 0x0004 +#define DEBUG_DEVNAME 0x0008 +#define DEBUG_DEVNO 0x0010 +#define DEBUG_PROBE 0x0020 +#define DEBUG_READ 0x0040 +#define DEBUG_RESOLVE 0x0080 +#define DEBUG_SAVE 0x0100 +#define DEBUG_TAG 0x0200 +#define DEBUG_INIT 0x8000 +#define DEBUG_ALL 0xFFFF + +#ifdef CONFIG_BLKID_DEBUG +#include +extern int blkid_debug_mask; +#define DBG(m,x) if ((m) & blkid_debug_mask) x; +#else +#define DBG(m,x) +#endif + +#ifdef CONFIG_BLKID_DEBUG +extern void blkid_debug_dump_dev(blkid_dev dev); +extern void blkid_debug_dump_tag(blkid_tag tag); +#endif + +/* lseek.c */ +/* extern blkid_loff_t blkid_llseek(int fd, blkid_loff_t offset, int whence); */ +#ifdef CONFIG_LFS +# define blkid_llseek lseek64 +#else +# define blkid_llseek lseek +#endif + +/* read.c */ +extern void blkid_read_cache(blkid_cache cache); + +/* save.c */ +extern int blkid_flush_cache(blkid_cache cache); + +/* + * Functions to create and find a specific tag type: tag.c + */ +extern void blkid_free_tag(blkid_tag tag); +extern blkid_tag blkid_find_tag_dev(blkid_dev dev, const char *type); +extern int blkid_set_tag(blkid_dev dev, const char *name, + const char *value, const int vlength); + +/* + * Functions to create and find a specific tag type: dev.c + */ +extern blkid_dev blkid_new_dev(void); +extern void blkid_free_dev(blkid_dev dev); + +#ifdef __cplusplus +} +#endif + +#endif /* _BLKID_BLKIDP_H */ diff --git a/e2fsprogs/blkid/blkid_getsize.c b/e2fsprogs/blkid/blkid_getsize.c new file mode 100644 index 000000000..941efa42c --- /dev/null +++ b/e2fsprogs/blkid/blkid_getsize.c @@ -0,0 +1,179 @@ +/* vi: set sw=4 ts=4: */ +/* + * getsize.c --- get the size of a partition. + * + * Copyright (C) 1995, 1995 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + * %End-Header% + */ + +/* include this before sys/queues.h! */ +#include "blkidP.h" + +#include +#include +#ifdef HAVE_ERRNO_H +#include +#endif +#include +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#ifdef HAVE_LINUX_FD_H +#include +#endif +#ifdef HAVE_SYS_DISKLABEL_H +#include +#include +#endif +#ifdef HAVE_SYS_DISK_H +#ifdef HAVE_SYS_QUEUE_H +#include /* for LIST_HEAD */ +#endif +#include +#endif +#ifdef __linux__ +#include +#endif + +#if defined(__linux__) && defined(_IO) && !defined(BLKGETSIZE) +#define BLKGETSIZE _IO(0x12,96) /* return device size */ +#endif + +#if defined(__linux__) && defined(_IOR) && !defined(BLKGETSIZE64) +#define BLKGETSIZE64 _IOR(0x12,114,size_t) /* return device size in bytes (u64 *arg) */ +#endif + +#ifdef APPLE_DARWIN +#define BLKGETSIZE DKIOCGETBLOCKCOUNT32 +#endif /* APPLE_DARWIN */ + +static int valid_offset(int fd, blkid_loff_t offset) +{ + char ch; + + if (blkid_llseek(fd, offset, 0) < 0) + return 0; + if (read(fd, &ch, 1) < 1) + return 0; + return 1; +} + +/* + * Returns the number of blocks in a partition + */ +blkid_loff_t blkid_get_dev_size(int fd) +{ + int valid_blkgetsize64 = 1; +#ifdef __linux__ + struct utsname ut; +#endif + unsigned long long size64; + unsigned long size; + blkid_loff_t high, low; +#ifdef FDGETPRM + struct floppy_struct this_floppy; +#endif +#ifdef HAVE_SYS_DISKLABEL_H + int part = -1; + struct disklabel lab; + struct partition *pp; + char ch; + struct stat st; +#endif /* HAVE_SYS_DISKLABEL_H */ + +#ifdef DKIOCGETBLOCKCOUNT /* For Apple Darwin */ + if (ioctl(fd, DKIOCGETBLOCKCOUNT, &size64) >= 0) { + if ((sizeof(blkid_loff_t) < sizeof(unsigned long long)) + && (size64 << 9 > 0xFFFFFFFF)) + return 0; /* EFBIG */ + return (blkid_loff_t) size64 << 9; + } +#endif + +#ifdef BLKGETSIZE64 +#ifdef __linux__ + if ((uname(&ut) == 0) && + ((ut.release[0] == '2') && (ut.release[1] == '.') && + (ut.release[2] < '6') && (ut.release[3] == '.'))) + valid_blkgetsize64 = 0; +#endif + if (valid_blkgetsize64 && + ioctl(fd, BLKGETSIZE64, &size64) >= 0) { + if ((sizeof(blkid_loff_t) < sizeof(unsigned long long)) + && ((size64) > 0xFFFFFFFF)) + return 0; /* EFBIG */ + return size64; + } +#endif + +#ifdef BLKGETSIZE + if (ioctl(fd, BLKGETSIZE, &size) >= 0) + return (blkid_loff_t)size << 9; +#endif + +#ifdef FDGETPRM + if (ioctl(fd, FDGETPRM, &this_floppy) >= 0) + return (blkid_loff_t)this_floppy.size << 9; +#endif +#ifdef HAVE_SYS_DISKLABEL_H +#if 0 + /* + * This should work in theory but I haven't tested it. Anyone + * on a BSD system want to test this for me? In the meantime, + * binary search mechanism should work just fine. + */ + if ((fstat(fd, &st) >= 0) && S_ISBLK(st.st_mode)) + part = st.st_rdev & 7; + if (part >= 0 && (ioctl(fd, DIOCGDINFO, (char *)&lab) >= 0)) { + pp = &lab.d_partitions[part]; + if (pp->p_size) + return pp->p_size << 9; + } +#endif +#endif /* HAVE_SYS_DISKLABEL_H */ + + /* + * OK, we couldn't figure it out by using a specialized ioctl, + * which is generally the best way. So do binary search to + * find the size of the partition. + */ + low = 0; + for (high = 1024; valid_offset(fd, high); high *= 2) + low = high; + while (low < high - 1) + { + const blkid_loff_t mid = (low + high) / 2; + + if (valid_offset(fd, mid)) + low = mid; + else + high = mid; + } + return low + 1; +} + +#ifdef TEST_PROGRAM +int main(int argc, char **argv) +{ + blkid_loff_t bytes; + int fd; + + if (argc < 2) { + fprintf(stderr, "Usage: %s device\n" + "Determine the size of a device\n", argv[0]); + return 1; + } + + if ((fd = open(argv[1], O_RDONLY)) < 0) + perror(argv[0]); + + bytes = blkid_get_dev_size(fd); + printf("Device %s has %lld 1k blocks.\n", argv[1], bytes >> 10); + + return 0; +} +#endif diff --git a/e2fsprogs/blkid/cache.c b/e2fsprogs/blkid/cache.c new file mode 100644 index 000000000..9bae6fb67 --- /dev/null +++ b/e2fsprogs/blkid/cache.c @@ -0,0 +1,126 @@ +/* vi: set sw=4 ts=4: */ +/* + * cache.c - allocation/initialization/free routines for cache + * + * Copyright (C) 2001 Andreas Dilger + * Copyright (C) 2003 Theodore Ts'o + * + * %Begin-Header% + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + * %End-Header% + */ + +#include +#include +#include +#include "blkidP.h" + +int blkid_debug_mask = 0; + +int blkid_get_cache(blkid_cache *ret_cache, const char *filename) +{ + blkid_cache cache; + +#ifdef CONFIG_BLKID_DEBUG + if (!(blkid_debug_mask & DEBUG_INIT)) { + char *dstr = getenv("BLKID_DEBUG"); + + if (dstr) + blkid_debug_mask = strtoul(dstr, 0, 0); + blkid_debug_mask |= DEBUG_INIT; + } +#endif + + DBG(DEBUG_CACHE, printf("creating blkid cache (using %s)\n", + filename ? filename : "default cache")); + + if (!(cache = (blkid_cache) calloc(1, sizeof(struct blkid_struct_cache)))) + return -BLKID_ERR_MEM; + + INIT_LIST_HEAD(&cache->bic_devs); + INIT_LIST_HEAD(&cache->bic_tags); + + if (filename && !strlen(filename)) + filename = 0; + if (!filename && (getuid() == geteuid())) + filename = getenv("BLKID_FILE"); + if (!filename) + filename = BLKID_CACHE_FILE; + cache->bic_filename = blkid_strdup(filename); + + blkid_read_cache(cache); + + *ret_cache = cache; + return 0; +} + +void blkid_put_cache(blkid_cache cache) +{ + if (!cache) + return; + + (void) blkid_flush_cache(cache); + + DBG(DEBUG_CACHE, printf("freeing cache struct\n")); + + /* DBG(DEBUG_CACHE, blkid_debug_dump_cache(cache)); */ + + while (!list_empty(&cache->bic_devs)) { + blkid_dev dev = list_entry(cache->bic_devs.next, + struct blkid_struct_dev, + bid_devs); + blkid_free_dev(dev); + } + + while (!list_empty(&cache->bic_tags)) { + blkid_tag tag = list_entry(cache->bic_tags.next, + struct blkid_struct_tag, + bit_tags); + + while (!list_empty(&tag->bit_names)) { + blkid_tag bad = list_entry(tag->bit_names.next, + struct blkid_struct_tag, + bit_names); + + DBG(DEBUG_CACHE, printf("warning: unfreed tag %s=%s\n", + bad->bit_name, bad->bit_val)); + blkid_free_tag(bad); + } + blkid_free_tag(tag); + } + free(cache->bic_filename); + + free(cache); +} + +#ifdef TEST_PROGRAM +int main(int argc, char** argv) +{ + blkid_cache cache = NULL; + int ret; + + blkid_debug_mask = DEBUG_ALL; + if ((argc > 2)) { + fprintf(stderr, "Usage: %s [filename]\n", argv[0]); + exit(1); + } + + if ((ret = blkid_get_cache(&cache, argv[1])) < 0) { + fprintf(stderr, "error %d parsing cache file %s\n", ret, + argv[1] ? argv[1] : BLKID_CACHE_FILE); + exit(1); + } + if ((ret = blkid_get_cache(&cache, bb_dev_null)) != 0) { + fprintf(stderr, "%s: error creating cache (%d)\n", + argv[0], ret); + exit(1); + } + if ((ret = blkid_probe_all(cache) < 0)) + fprintf(stderr, "error probing devices\n"); + + blkid_put_cache(cache); + + return ret; +} +#endif diff --git a/e2fsprogs/blkid/dev.c b/e2fsprogs/blkid/dev.c new file mode 100644 index 000000000..eddbd02b7 --- /dev/null +++ b/e2fsprogs/blkid/dev.c @@ -0,0 +1,214 @@ +/* vi: set sw=4 ts=4: */ +/* + * dev.c - allocation/initialization/free routines for dev + * + * Copyright (C) 2001 Andreas Dilger + * Copyright (C) 2003 Theodore Ts'o + * + * %Begin-Header% + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + * %End-Header% + */ + +#include +#include + +#include "blkidP.h" + +blkid_dev blkid_new_dev(void) +{ + blkid_dev dev; + + if (!(dev = (blkid_dev) calloc(1, sizeof(struct blkid_struct_dev)))) + return NULL; + + INIT_LIST_HEAD(&dev->bid_devs); + INIT_LIST_HEAD(&dev->bid_tags); + + return dev; +} + +void blkid_free_dev(blkid_dev dev) +{ + if (!dev) + return; + + DBG(DEBUG_DEV, + printf(" freeing dev %s (%s)\n", dev->bid_name, dev->bid_type)); + DBG(DEBUG_DEV, blkid_debug_dump_dev(dev)); + + list_del(&dev->bid_devs); + while (!list_empty(&dev->bid_tags)) { + blkid_tag tag = list_entry(dev->bid_tags.next, + struct blkid_struct_tag, + bit_tags); + blkid_free_tag(tag); + } + if (dev->bid_name) + free(dev->bid_name); + free(dev); +} + +/* + * Given a blkid device, return its name + */ +const char *blkid_dev_devname(blkid_dev dev) +{ + return dev->bid_name; +} + +#ifdef CONFIG_BLKID_DEBUG +void blkid_debug_dump_dev(blkid_dev dev) +{ + struct list_head *p; + + if (!dev) { + printf(" dev: NULL\n"); + return; + } + + printf(" dev: name = %s\n", dev->bid_name); + printf(" dev: DEVNO=\"0x%0llx\"\n", dev->bid_devno); + printf(" dev: TIME=\"%lu\"\n", dev->bid_time); + printf(" dev: PRI=\"%d\"\n", dev->bid_pri); + printf(" dev: flags = 0x%08X\n", dev->bid_flags); + + list_for_each(p, &dev->bid_tags) { + blkid_tag tag = list_entry(p, struct blkid_struct_tag, bit_tags); + if (tag) + printf(" tag: %s=\"%s\"\n", tag->bit_name, + tag->bit_val); + else + printf(" tag: NULL\n"); + } + puts(""); +} +#endif + +/* + * dev iteration routines for the public libblkid interface. + * + * These routines do not expose the list.h implementation, which are a + * contamination of the namespace, and which force us to reveal far, far + * too much of our internal implemenation. I'm not convinced I want + * to keep list.h in the long term, anyway. It's fine for kernel + * programming, but performance is not the #1 priority for this + * library, and I really don't like the tradeoff of type-safety for + * performance for this application. [tytso:20030125.2007EST] + */ + +/* + * This series of functions iterate over all devices in a blkid cache + */ +#define DEV_ITERATE_MAGIC 0x01a5284c + +struct blkid_struct_dev_iterate { + int magic; + blkid_cache cache; + struct list_head *p; +}; + +blkid_dev_iterate blkid_dev_iterate_begin(blkid_cache cache) +{ + blkid_dev_iterate iter; + + iter = xmalloc(sizeof(struct blkid_struct_dev_iterate)); + iter->magic = DEV_ITERATE_MAGIC; + iter->cache = cache; + iter->p = cache->bic_devs.next; + return iter; +} + +/* + * Return 0 on success, -1 on error + */ +extern int blkid_dev_next(blkid_dev_iterate iter, + blkid_dev *dev) +{ + *dev = 0; + if (!iter || iter->magic != DEV_ITERATE_MAGIC || + iter->p == &iter->cache->bic_devs) + return -1; + *dev = list_entry(iter->p, struct blkid_struct_dev, bid_devs); + iter->p = iter->p->next; + return 0; +} + +void blkid_dev_iterate_end(blkid_dev_iterate iter) +{ + if (!iter || iter->magic != DEV_ITERATE_MAGIC) + return; + iter->magic = 0; + free(iter); +} + +#ifdef TEST_PROGRAM +#ifdef HAVE_GETOPT_H +#include +#else +extern char *optarg; +extern int optind; +#endif + +void usage(char *prog) +{ + fprintf(stderr, "Usage: %s [-f blkid_file] [-m debug_mask]\n", prog); + fprintf(stderr, "\tList all devices and exit\n", prog); + exit(1); +} + +int main(int argc, char **argv) +{ + blkid_dev_iterate iter; + blkid_cache cache = NULL; + blkid_dev dev; + int c, ret; + char *tmp; + char *file = NULL; + char *search_type = NULL; + char *search_value = NULL; + + while ((c = getopt (argc, argv, "m:f:")) != EOF) + switch (c) { + case 'f': + file = optarg; + break; + case 'm': + blkid_debug_mask = strtoul (optarg, &tmp, 0); + if (*tmp) { + fprintf(stderr, "Invalid debug mask: %d\n", + optarg); + exit(1); + } + break; + case '?': + usage(argv[0]); + } + if (argc >= optind+2) { + search_type = argv[optind]; + search_value = argv[optind+1]; + optind += 2; + } + if (argc != optind) + usage(argv[0]); + + if ((ret = blkid_get_cache(&cache, file)) != 0) { + fprintf(stderr, "%s: error creating cache (%d)\n", + argv[0], ret); + exit(1); + } + + iter = blkid_dev_iterate_begin(cache); + if (search_type) + blkid_dev_set_search(iter, search_type, search_value); + while (blkid_dev_next(iter, &dev) == 0) { + printf("Device: %s\n", blkid_dev_devname(dev)); + } + blkid_dev_iterate_end(iter); + + + blkid_put_cache(cache); + return 0; +} +#endif diff --git a/e2fsprogs/blkid/devname.c b/e2fsprogs/blkid/devname.c new file mode 100644 index 000000000..d69000be7 --- /dev/null +++ b/e2fsprogs/blkid/devname.c @@ -0,0 +1,368 @@ +/* vi: set sw=4 ts=4: */ +/* + * devname.c - get a dev by its device inode name + * + * Copyright (C) Andries Brouwer + * Copyright (C) 1999, 2000, 2001, 2002, 2003 Theodore Ts'o + * Copyright (C) 2001 Andreas Dilger + * + * %Begin-Header% + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + * %End-Header% + */ + +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include +#include +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#include +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_SYS_MKDEV_H +#include +#endif +#include + +#include "blkidP.h" + +/* + * Find a dev struct in the cache by device name, if available. + * + * If there is no entry with the specified device name, and the create + * flag is set, then create an empty device entry. + */ +blkid_dev blkid_get_dev(blkid_cache cache, const char *devname, int flags) +{ + blkid_dev dev = NULL, tmp; + struct list_head *p; + + if (!cache || !devname) + return NULL; + + list_for_each(p, &cache->bic_devs) { + tmp = list_entry(p, struct blkid_struct_dev, bid_devs); + if (strcmp(tmp->bid_name, devname)) + continue; + + DBG(DEBUG_DEVNAME, + printf("found devname %s in cache\n", tmp->bid_name)); + dev = tmp; + break; + } + + if (!dev && (flags & BLKID_DEV_CREATE)) { + dev = blkid_new_dev(); + if (!dev) + return NULL; + dev->bid_name = blkid_strdup(devname); + dev->bid_cache = cache; + list_add_tail(&dev->bid_devs, &cache->bic_devs); + cache->bic_flags |= BLKID_BIC_FL_CHANGED; + } + + if (flags & BLKID_DEV_VERIFY) + dev = blkid_verify(cache, dev); + return dev; +} + +/* + * Probe a single block device to add to the device cache. + */ +static void probe_one(blkid_cache cache, const char *ptname, + dev_t devno, int pri) +{ + blkid_dev dev = NULL; + struct list_head *p; + const char **dir; + char *devname = NULL; + + /* See if we already have this device number in the cache. */ + list_for_each(p, &cache->bic_devs) { + blkid_dev tmp = list_entry(p, struct blkid_struct_dev, + bid_devs); + if (tmp->bid_devno == devno) { + dev = blkid_verify(cache, tmp); + break; + } + } + if (dev && dev->bid_devno == devno) + goto set_pri; + + /* + * Take a quick look at /dev/ptname for the device number. We check + * all of the likely device directories. If we don't find it, or if + * the stat information doesn't check out, use blkid_devno_to_devname() + * to find it via an exhaustive search for the device major/minor. + */ + for (dir = blkid_devdirs; *dir; dir++) { + struct stat st; + char device[256]; + + sprintf(device, "%s/%s", *dir, ptname); + if ((dev = blkid_get_dev(cache, device, BLKID_DEV_FIND)) && + dev->bid_devno == devno) + goto set_pri; + + if (stat(device, &st) == 0 && S_ISBLK(st.st_mode) && + st.st_rdev == devno) { + devname = blkid_strdup(device); + break; + } + } + if (!devname) { + devname = blkid_devno_to_devname(devno); + if (!devname) + return; + } + dev = blkid_get_dev(cache, devname, BLKID_DEV_NORMAL); + free(devname); + +set_pri: + if (!pri && !strncmp(ptname, "md", 2)) + pri = BLKID_PRI_MD; + if (dev) + dev->bid_pri = pri; + return; +} + +#define PROC_PARTITIONS "/proc/partitions" +#define VG_DIR "/proc/lvm/VGs" + +/* + * This function initializes the UUID cache with devices from the LVM + * proc hierarchy. We currently depend on the names of the LVM + * hierarchy giving us the device structure in /dev. (XXX is this a + * safe thing to do?) + */ +#ifdef VG_DIR +#include +static dev_t lvm_get_devno(const char *lvm_device) +{ + FILE *lvf; + char buf[1024]; + int ma, mi; + dev_t ret = 0; + + DBG(DEBUG_DEVNAME, printf("opening %s\n", lvm_device)); + if ((lvf = fopen(lvm_device, "r")) == NULL) { + DBG(DEBUG_DEVNAME, printf("%s: (%d) %s\n", lvm_device, errno, + strerror(errno))); + return 0; + } + + while (fgets(buf, sizeof(buf), lvf)) { + if (sscanf(buf, "device: %d:%d", &ma, &mi) == 2) { + ret = makedev(ma, mi); + break; + } + } + fclose(lvf); + + return ret; +} + +static void lvm_probe_all(blkid_cache cache) +{ + DIR *vg_list; + struct dirent *vg_iter; + int vg_len = strlen(VG_DIR); + dev_t dev; + + if ((vg_list = opendir(VG_DIR)) == NULL) + return; + + DBG(DEBUG_DEVNAME, printf("probing LVM devices under %s\n", VG_DIR)); + + while ((vg_iter = readdir(vg_list)) != NULL) { + DIR *lv_list; + char *vdirname; + char *vg_name; + struct dirent *lv_iter; + + vg_name = vg_iter->d_name; + if (!strcmp(vg_name, ".") || !strcmp(vg_name, "..")) + continue; + vdirname = xmalloc(vg_len + strlen(vg_name) + 8); + sprintf(vdirname, "%s/%s/LVs", VG_DIR, vg_name); + + lv_list = opendir(vdirname); + free(vdirname); + if (lv_list == NULL) + continue; + + while ((lv_iter = readdir(lv_list)) != NULL) { + char *lv_name, *lvm_device; + + lv_name = lv_iter->d_name; + if (!strcmp(lv_name, ".") || !strcmp(lv_name, "..")) + continue; + + lvm_device = xmalloc(vg_len + strlen(vg_name) + + strlen(lv_name) + 8); + sprintf(lvm_device, "%s/%s/LVs/%s", VG_DIR, vg_name, + lv_name); + dev = lvm_get_devno(lvm_device); + sprintf(lvm_device, "%s/%s", vg_name, lv_name); + DBG(DEBUG_DEVNAME, printf("LVM dev %s: devno 0x%04X\n", + lvm_device, + (unsigned int) dev)); + probe_one(cache, lvm_device, dev, BLKID_PRI_LVM); + free(lvm_device); + } + closedir(lv_list); + } + closedir(vg_list); +} +#endif + +#define PROC_EVMS_VOLUMES "/proc/evms/volumes" + +static int +evms_probe_all(blkid_cache cache) +{ + char line[100]; + int ma, mi, sz, num = 0; + FILE *procpt; + char device[110]; + + procpt = fopen(PROC_EVMS_VOLUMES, "r"); + if (!procpt) + return 0; + while (fgets(line, sizeof(line), procpt)) { + if (sscanf (line, " %d %d %d %*s %*s %[^\n ]", + &ma, &mi, &sz, device) != 4) + continue; + + DBG(DEBUG_DEVNAME, printf("Checking partition %s (%d, %d)\n", + device, ma, mi)); + + probe_one(cache, device, makedev(ma, mi), BLKID_PRI_EVMS); + num++; + } + fclose(procpt); + return num; +} + +/* + * Read the device data for all available block devices in the system. + */ +int blkid_probe_all(blkid_cache cache) +{ + FILE *proc; + char line[1024]; + char ptname0[128], ptname1[128], *ptname = 0; + char *ptnames[2]; + dev_t devs[2]; + int ma, mi; + unsigned long long sz; + int lens[2] = { 0, 0 }; + int which = 0, last = 0; + + ptnames[0] = ptname0; + ptnames[1] = ptname1; + + if (!cache) + return -BLKID_ERR_PARAM; + + if (cache->bic_flags & BLKID_BIC_FL_PROBED && + time(0) - cache->bic_time < BLKID_PROBE_INTERVAL) + return 0; + + blkid_read_cache(cache); + evms_probe_all(cache); +#ifdef VG_DIR + lvm_probe_all(cache); +#endif + + proc = fopen(PROC_PARTITIONS, "r"); + if (!proc) + return -BLKID_ERR_PROC; + + while (fgets(line, sizeof(line), proc)) { + last = which; + which ^= 1; + ptname = ptnames[which]; + + if (sscanf(line, " %d %d %llu %128[^\n ]", + &ma, &mi, &sz, ptname) != 4) + continue; + devs[which] = makedev(ma, mi); + + DBG(DEBUG_DEVNAME, printf("read partition name %s\n", ptname)); + + /* Skip whole disk devs unless they have no partitions + * If we don't have a partition on this dev, also + * check previous dev to see if it didn't have a partn. + * heuristic: partition name ends in a digit. + * + * Skip extended partitions. + * heuristic: size is 1 + * + * FIXME: skip /dev/{ida,cciss,rd} whole-disk devs + */ + + lens[which] = strlen(ptname); + if (isdigit(ptname[lens[which] - 1])) { + DBG(DEBUG_DEVNAME, + printf("partition dev %s, devno 0x%04X\n", + ptname, (unsigned int) devs[which])); + + if (sz > 1) + probe_one(cache, ptname, devs[which], 0); + lens[which] = 0; + lens[last] = 0; + } else if (lens[last] && strncmp(ptnames[last], ptname, + lens[last])) { + DBG(DEBUG_DEVNAME, + printf("whole dev %s, devno 0x%04X\n", + ptnames[last], (unsigned int) devs[last])); + probe_one(cache, ptnames[last], devs[last], 0); + lens[last] = 0; + } + } + + /* Handle the last device if it wasn't partitioned */ + if (lens[which]) + probe_one(cache, ptname, devs[which], 0); + + fclose(proc); + + cache->bic_time = time(0); + cache->bic_flags |= BLKID_BIC_FL_PROBED; + blkid_flush_cache(cache); + return 0; +} + +#ifdef TEST_PROGRAM +int main(int argc, char **argv) +{ + blkid_cache cache = NULL; + int ret; + + blkid_debug_mask = DEBUG_ALL; + if (argc != 1) { + fprintf(stderr, "Usage: %s\n" + "Probe all devices and exit\n", argv[0]); + exit(1); + } + if ((ret = blkid_get_cache(&cache, bb_dev_null)) != 0) { + fprintf(stderr, "%s: error creating cache (%d)\n", + argv[0], ret); + exit(1); + } + if (blkid_probe_all(cache) < 0) + printf("%s: error probing devices\n", argv[0]); + + blkid_put_cache(cache); + return 0; +} +#endif diff --git a/e2fsprogs/blkid/devno.c b/e2fsprogs/blkid/devno.c new file mode 100644 index 000000000..13e7f1a87 --- /dev/null +++ b/e2fsprogs/blkid/devno.c @@ -0,0 +1,223 @@ +/* vi: set sw=4 ts=4: */ +/* + * devno.c - find a particular device by its device number (major/minor) + * + * Copyright (C) 2000, 2001, 2003 Theodore Ts'o + * Copyright (C) 2001 Andreas Dilger + * + * %Begin-Header% + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + * %End-Header% + */ + +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#include +#include +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_SYS_MKDEV_H +#include +#endif + +#include "blkidP.h" + +struct dir_list { + char *name; + struct dir_list *next; +}; + +char *blkid_strndup(const char *s, int length) +{ + char *ret; + + if (!s) + return NULL; + + if (!length) + length = strlen(s); + + ret = xmalloc(length + 1); + strncpy(ret, s, length); + ret[length] = '\0'; + return ret; +} + +char *blkid_strdup(const char *s) +{ + return blkid_strndup(s, 0); +} + +/* + * This function adds an entry to the directory list + */ +static void add_to_dirlist(const char *name, struct dir_list **list) +{ + struct dir_list *dp; + + dp = xmalloc(sizeof(struct dir_list)); + dp->name = blkid_strdup(name); + dp->next = *list; + *list = dp; +} + +/* + * This function frees a directory list + */ +static void free_dirlist(struct dir_list **list) +{ + struct dir_list *dp, *next; + + for (dp = *list; dp; dp = next) { + next = dp->next; + free(dp->name); + free(dp); + } + *list = NULL; +} + +static void scan_dir(char *dir_name, dev_t devno, struct dir_list **list, + char **devname) +{ + DIR *dir; + struct dirent *dp; + char path[1024]; + int dirlen; + struct stat st; + + if ((dir = opendir(dir_name)) == NULL) + return; + dirlen = strlen(dir_name) + 2; + while ((dp = readdir(dir)) != 0) { + if (dirlen + strlen(dp->d_name) >= sizeof(path)) + continue; + + if (dp->d_name[0] == '.' && + ((dp->d_name[1] == 0) || + ((dp->d_name[1] == '.') && (dp->d_name[2] == 0)))) + continue; + + sprintf(path, "%s/%s", dir_name, dp->d_name); + if (stat(path, &st) < 0) + continue; + + if (S_ISDIR(st.st_mode)) + add_to_dirlist(path, list); + else if (S_ISBLK(st.st_mode) && st.st_rdev == devno) { + *devname = blkid_strdup(path); + DBG(DEBUG_DEVNO, + printf("found 0x%llx at %s (%p)\n", devno, + path, *devname)); + break; + } + } + closedir(dir); + return; +} + +/* Directories where we will try to search for device numbers */ +const char *blkid_devdirs[] = { "/devices", "/devfs", "/dev", NULL }; + +/* + * This function finds the pathname to a block device with a given + * device number. It returns a pointer to allocated memory to the + * pathname on success, and NULL on failure. + */ +char *blkid_devno_to_devname(dev_t devno) +{ + struct dir_list *list = NULL, *new_list = NULL; + char *devname = NULL; + const char **dir; + + /* + * Add the starting directories to search in reverse order of + * importance, since we are using a stack... + */ + for (dir = blkid_devdirs; *dir; dir++) + add_to_dirlist(*dir, &list); + + while (list) { + struct dir_list *current = list; + + list = list->next; + DBG(DEBUG_DEVNO, printf("directory %s\n", current->name)); + scan_dir(current->name, devno, &new_list, &devname); + free(current->name); + free(current); + if (devname) + break; + /* + * If we're done checking at this level, descend to + * the next level of subdirectories. (breadth-first) + */ + if (list == NULL) { + list = new_list; + new_list = NULL; + } + } + free_dirlist(&list); + free_dirlist(&new_list); + + if (!devname) { + DBG(DEBUG_DEVNO, + printf("blkid: cannot find devno 0x%04lx\n", + (unsigned long) devno)); + } else { + DBG(DEBUG_DEVNO, + printf("found devno 0x%04llx as %s\n", devno, devname)); + } + + + return devname; +} + +#ifdef TEST_PROGRAM +int main(int argc, char** argv) +{ + char *devname, *tmp; + int major, minor; + dev_t devno; + const char *errmsg = "Cannot parse %s: %s\n"; + + blkid_debug_mask = DEBUG_ALL; + if ((argc != 2) && (argc != 3)) { + fprintf(stderr, "Usage:\t%s device_number\n\t%s major minor\n" + "Resolve a device number to a device name\n", + argv[0], argv[0]); + exit(1); + } + if (argc == 2) { + devno = strtoul(argv[1], &tmp, 0); + if (*tmp) { + fprintf(stderr, errmsg, "device number", argv[1]); + exit(1); + } + } else { + major = strtoul(argv[1], &tmp, 0); + if (*tmp) { + fprintf(stderr, errmsg, "major number", argv[1]); + exit(1); + } + minor = strtoul(argv[2], &tmp, 0); + if (*tmp) { + fprintf(stderr, errmsg, "minor number", argv[2]); + exit(1); + } + devno = makedev(major, minor); + } + printf("Looking for device 0x%04Lx\n", devno); + devname = blkid_devno_to_devname(devno); + free(devname); + return 0; +} +#endif diff --git a/e2fsprogs/blkid/list.c b/e2fsprogs/blkid/list.c new file mode 100644 index 000000000..04d61a19b --- /dev/null +++ b/e2fsprogs/blkid/list.c @@ -0,0 +1,110 @@ +/* vi: set sw=4 ts=4: */ + +#include "list.h" + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +void __list_add(struct list_head * add, + struct list_head * prev, + struct list_head * next) +{ + next->prev = add; + add->next = next; + add->prev = prev; + prev->next = add; +} + +/* + * list_add - add a new entry + * @add: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +void list_add(struct list_head *add, struct list_head *head) +{ + __list_add(add, head, head->next); +} + +/* + * list_add_tail - add a new entry + * @add: new entry to be added + * @head: list head to add it before + * + * Insert a new entry before the specified head. + * This is useful for implementing queues. + */ +void list_add_tail(struct list_head *add, struct list_head *head) +{ + __list_add(add, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +void __list_del(struct list_head * prev, struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +/* + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * + * list_empty() on @entry does not return true after this, @entry is + * in an undefined state. + */ +void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); +} + +/* + * list_del_init - deletes entry from list and reinitialize it. + * @entry: the element to delete from the list. + */ +void list_del_init(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + INIT_LIST_HEAD(entry); +} + +/* + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +int list_empty(struct list_head *head) +{ + return head->next == head; +} + +/* + * list_splice - join two lists + * @list: the new list to add. + * @head: the place to add it in the first list. + */ +void list_splice(struct list_head *list, struct list_head *head) +{ + struct list_head *first = list->next; + + if (first != list) { + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; + } +} diff --git a/e2fsprogs/blkid/list.h b/e2fsprogs/blkid/list.h new file mode 100644 index 000000000..8b06d853b --- /dev/null +++ b/e2fsprogs/blkid/list.h @@ -0,0 +1,73 @@ +/* vi: set sw=4 ts=4: */ +#if !defined(_BLKID_LIST_H) && !defined(LIST_HEAD) +#define _BLKID_LIST_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +void __list_add(struct list_head * add, struct list_head * prev, struct list_head * next); +void list_add(struct list_head *add, struct list_head *head); +void list_add_tail(struct list_head *add, struct list_head *head); +void __list_del(struct list_head * prev, struct list_head * next); +void list_del(struct list_head *entry); +void list_del_init(struct list_head *entry); +int list_empty(struct list_head *head); +void list_splice(struct list_head *list, struct list_head *head); + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) + +/** + * list_for_each - iterate over elements in a list + * @pos: the &struct list_head to use as a loop counter. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +/** + * list_for_each_safe - iterate over elements in a list, but don't dereference + * pos after the body is done (in case it is freed) + * @pos: the &struct list_head to use as a loop counter. + * @pnext: the &struct list_head to use as a pointer to the next item. + * @head: the head for your list (not included in iteration). + */ +#define list_for_each_safe(pos, pnext, head) \ + for (pos = (head)->next, pnext = pos->next; pos != (head); \ + pos = pnext, pnext = pos->next) + +#ifdef __cplusplus +} +#endif + +#endif /* _BLKID_LIST_H */ diff --git a/e2fsprogs/blkid/probe.c b/e2fsprogs/blkid/probe.c new file mode 100644 index 000000000..ea9a619ee --- /dev/null +++ b/e2fsprogs/blkid/probe.c @@ -0,0 +1,721 @@ +/* vi: set sw=4 ts=4: */ +/* + * probe.c - identify a block device by its contents, and return a dev + * struct with the details + * + * Copyright (C) 1999 by Andries Brouwer + * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o + * Copyright (C) 2001 by Andreas Dilger + * + * %Begin-Header% + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + * %End-Header% + */ + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_SYS_MKDEV_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#include "blkidP.h" +#include "../uuid/uuid.h" +#include "probe.h" + +/* + * This is a special case code to check for an MDRAID device. We do + * this special since it requires checking for a superblock at the end + * of the device. + */ +static int check_mdraid(int fd, unsigned char *ret_uuid) +{ + struct mdp_superblock_s *md; + blkid_loff_t offset; + char buf[4096]; + + if (fd < 0) + return -BLKID_ERR_PARAM; + + offset = (blkid_get_dev_size(fd) & ~((blkid_loff_t)65535)) - 65536; + + if (blkid_llseek(fd, offset, 0) < 0 || + read(fd, buf, 4096) != 4096) + return -BLKID_ERR_IO; + + /* Check for magic number */ + if (memcmp("\251+N\374", buf, 4)) + return -BLKID_ERR_PARAM; + + if (!ret_uuid) + return 0; + *ret_uuid = 0; + + /* The MD UUID is not contiguous in the superblock, make it so */ + md = (struct mdp_superblock_s *)buf; + if (md->set_uuid0 || md->set_uuid1 || md->set_uuid2 || md->set_uuid3) { + memcpy(ret_uuid, &md->set_uuid0, 4); + memcpy(ret_uuid, &md->set_uuid1, 12); + } + return 0; +} + +static void set_uuid(blkid_dev dev, uuid_t uuid) +{ + char str[37]; + + if (!uuid_is_null(uuid)) { + uuid_unparse(uuid, str); + blkid_set_tag(dev, "UUID", str, sizeof(str)); + } +} + +static void get_ext2_info(blkid_dev dev, unsigned char *buf) +{ + struct ext2_super_block *es = (struct ext2_super_block *) buf; + const char *label = 0; + + DBG(DEBUG_PROBE, printf("ext2_sb.compat = %08X:%08X:%08X\n", + blkid_le32(es->s_feature_compat), + blkid_le32(es->s_feature_incompat), + blkid_le32(es->s_feature_ro_compat))); + + if (strlen(es->s_volume_name)) + label = es->s_volume_name; + blkid_set_tag(dev, "LABEL", label, sizeof(es->s_volume_name)); + + set_uuid(dev, es->s_uuid); +} + +static int probe_ext3(int fd __BLKID_ATTR((unused)), + blkid_cache cache __BLKID_ATTR((unused)), + blkid_dev dev, + const struct blkid_magic *id __BLKID_ATTR((unused)), + unsigned char *buf) +{ + struct ext2_super_block *es; + + es = (struct ext2_super_block *)buf; + + /* Distinguish between jbd and ext2/3 fs */ + if (blkid_le32(es->s_feature_incompat) & + EXT3_FEATURE_INCOMPAT_JOURNAL_DEV) + return -BLKID_ERR_PARAM; + + /* Distinguish between ext3 and ext2 */ + if (!(blkid_le32(es->s_feature_compat) & + EXT3_FEATURE_COMPAT_HAS_JOURNAL)) + return -BLKID_ERR_PARAM; + + get_ext2_info(dev, buf); + + blkid_set_tag(dev, "SEC_TYPE", "ext2", sizeof("ext2")); + + return 0; +} + +static int probe_ext2(int fd __BLKID_ATTR((unused)), + blkid_cache cache __BLKID_ATTR((unused)), + blkid_dev dev, + const struct blkid_magic *id __BLKID_ATTR((unused)), + unsigned char *buf) +{ + struct ext2_super_block *es; + + es = (struct ext2_super_block *)buf; + + /* Distinguish between jbd and ext2/3 fs */ + if (blkid_le32(es->s_feature_incompat) & + EXT3_FEATURE_INCOMPAT_JOURNAL_DEV) + return -BLKID_ERR_PARAM; + + get_ext2_info(dev, buf); + + return 0; +} + +static int probe_jbd(int fd __BLKID_ATTR((unused)), + blkid_cache cache __BLKID_ATTR((unused)), + blkid_dev dev, + const struct blkid_magic *id __BLKID_ATTR((unused)), + unsigned char *buf) +{ + struct ext2_super_block *es = (struct ext2_super_block *) buf; + + if (!(blkid_le32(es->s_feature_incompat) & + EXT3_FEATURE_INCOMPAT_JOURNAL_DEV)) + return -BLKID_ERR_PARAM; + + get_ext2_info(dev, buf); + + return 0; +} + +static int probe_vfat(int fd __BLKID_ATTR((unused)), + blkid_cache cache __BLKID_ATTR((unused)), + blkid_dev dev, + const struct blkid_magic *id __BLKID_ATTR((unused)), + unsigned char *buf) +{ + struct vfat_super_block *vs; + char serno[10]; + const char *label = 0; + int label_len = 0; + + vs = (struct vfat_super_block *)buf; + + if (strncmp(vs->vs_label, "NO NAME", 7)) { + char *end = vs->vs_label + sizeof(vs->vs_label) - 1; + + while (*end == ' ' && end >= vs->vs_label) + --end; + if (end >= vs->vs_label) { + label = vs->vs_label; + label_len = end - vs->vs_label + 1; + } + } + + /* We can't just print them as %04X, because they are unaligned */ + sprintf(serno, "%02X%02X-%02X%02X", vs->vs_serno[3], vs->vs_serno[2], + vs->vs_serno[1], vs->vs_serno[0]); + blkid_set_tag(dev, "LABEL", label, label_len); + blkid_set_tag(dev, "UUID", serno, sizeof(serno)); + + return 0; +} + +static int probe_msdos(int fd __BLKID_ATTR((unused)), + blkid_cache cache __BLKID_ATTR((unused)), + blkid_dev dev, + const struct blkid_magic *id __BLKID_ATTR((unused)), + unsigned char *buf) +{ + struct msdos_super_block *ms = (struct msdos_super_block *) buf; + char serno[10]; + const char *label = 0; + int label_len = 0; + + if (strncmp(ms->ms_label, "NO NAME", 7)) { + char *end = ms->ms_label + sizeof(ms->ms_label) - 1; + + while (*end == ' ' && end >= ms->ms_label) + --end; + if (end >= ms->ms_label) { + label = ms->ms_label; + label_len = end - ms->ms_label + 1; + } + } + + /* We can't just print them as %04X, because they are unaligned */ + sprintf(serno, "%02X%02X-%02X%02X", ms->ms_serno[3], ms->ms_serno[2], + ms->ms_serno[1], ms->ms_serno[0]); + blkid_set_tag(dev, "UUID", serno, 0); + blkid_set_tag(dev, "LABEL", label, label_len); + blkid_set_tag(dev, "SEC_TYPE", "msdos", sizeof("msdos")); + + return 0; +} + +static int probe_xfs(int fd __BLKID_ATTR((unused)), + blkid_cache cache __BLKID_ATTR((unused)), + blkid_dev dev, + const struct blkid_magic *id __BLKID_ATTR((unused)), + unsigned char *buf) +{ + struct xfs_super_block *xs; + const char *label = 0; + + xs = (struct xfs_super_block *)buf; + + if (strlen(xs->xs_fname)) + label = xs->xs_fname; + blkid_set_tag(dev, "LABEL", label, sizeof(xs->xs_fname)); + set_uuid(dev, xs->xs_uuid); + return 0; +} + +static int probe_reiserfs(int fd __BLKID_ATTR((unused)), + blkid_cache cache __BLKID_ATTR((unused)), + blkid_dev dev, + const struct blkid_magic *id, unsigned char *buf) +{ + struct reiserfs_super_block *rs = (struct reiserfs_super_block *) buf; + unsigned int blocksize; + const char *label = 0; + + blocksize = blkid_le16(rs->rs_blocksize); + + /* If the superblock is inside the journal, we have the wrong one */ + if (id->bim_kboff/(blocksize>>10) > blkid_le32(rs->rs_journal_block)) + return -BLKID_ERR_BIG; + + /* LABEL/UUID are only valid for later versions of Reiserfs v3.6. */ + if (!strcmp(id->bim_magic, "ReIsEr2Fs") || + !strcmp(id->bim_magic, "ReIsEr3Fs")) { + if (strlen(rs->rs_label)) + label = rs->rs_label; + set_uuid(dev, rs->rs_uuid); + } + blkid_set_tag(dev, "LABEL", label, sizeof(rs->rs_label)); + + return 0; +} + +static int probe_jfs(int fd __BLKID_ATTR((unused)), + blkid_cache cache __BLKID_ATTR((unused)), + blkid_dev dev, + const struct blkid_magic *id __BLKID_ATTR((unused)), + unsigned char *buf) +{ + struct jfs_super_block *js; + const char *label = 0; + + js = (struct jfs_super_block *)buf; + + if (strlen((char *) js->js_label)) + label = (char *) js->js_label; + blkid_set_tag(dev, "LABEL", label, sizeof(js->js_label)); + set_uuid(dev, js->js_uuid); + return 0; +} + +static int probe_romfs(int fd __BLKID_ATTR((unused)), + blkid_cache cache __BLKID_ATTR((unused)), + blkid_dev dev, + const struct blkid_magic *id __BLKID_ATTR((unused)), + unsigned char *buf) +{ + struct romfs_super_block *ros; + const char *label = 0; + + ros = (struct romfs_super_block *)buf; + + if (strlen((char *) ros->ros_volume)) + label = (char *) ros->ros_volume; + blkid_set_tag(dev, "LABEL", label, 0); + return 0; +} + +static int probe_cramfs(int fd __BLKID_ATTR((unused)), + blkid_cache cache __BLKID_ATTR((unused)), + blkid_dev dev, + const struct blkid_magic *id __BLKID_ATTR((unused)), + unsigned char *buf) +{ + struct cramfs_super_block *csb; + const char *label = 0; + + csb = (struct cramfs_super_block *)buf; + + if (strlen((char *) csb->name)) + label = (char *) csb->name; + blkid_set_tag(dev, "LABEL", label, 0); + return 0; +} + +static int probe_swap0(int fd __BLKID_ATTR((unused)), + blkid_cache cache __BLKID_ATTR((unused)), + blkid_dev dev, + const struct blkid_magic *id __BLKID_ATTR((unused)), + unsigned char *buf __BLKID_ATTR((unused))) +{ + blkid_set_tag(dev, "UUID", 0, 0); + blkid_set_tag(dev, "LABEL", 0, 0); + return 0; +} + +static int probe_swap1(int fd, + blkid_cache cache __BLKID_ATTR((unused)), + blkid_dev dev, + const struct blkid_magic *id __BLKID_ATTR((unused)), + unsigned char *buf __BLKID_ATTR((unused))) +{ + struct swap_id_block *sws; + + probe_swap0(fd, cache, dev, id, buf); + /* + * Version 1 swap headers are always located at offset of 1024 + * bytes, although the swap signature itself is located at the + * end of the page (which may vary depending on hardware + * pagesize). + */ + if (lseek(fd, 1024, SEEK_SET) < 0) return 1; + sws = (struct swap_id_block *)xmalloc(1024); + if (read(fd, sws, 1024) != 1024) { + free(sws); + return 1; + } + + /* arbitrary sanity check.. is there any garbage down there? */ + if (sws->sws_pad[32] == 0 && sws->sws_pad[33] == 0) { + if (sws->sws_volume[0]) + blkid_set_tag(dev, "LABEL", (const char*)sws->sws_volume, + sizeof(sws->sws_volume)); + if (sws->sws_uuid[0]) + set_uuid(dev, sws->sws_uuid); + } + free(sws); + + return 0; +} + +static const char +* const udf_magic[] = { "BEA01", "BOOT2", "CD001", "CDW02", "NSR02", + "NSR03", "TEA01", 0 }; + +static int probe_udf(int fd, blkid_cache cache __BLKID_ATTR((unused)), + blkid_dev dev __BLKID_ATTR((unused)), + const struct blkid_magic *id __BLKID_ATTR((unused)), + unsigned char *buf __BLKID_ATTR((unused))) +{ + int j, bs; + struct iso_volume_descriptor isosb; + const char * const * m; + + /* determine the block size by scanning in 2K increments + (block sizes larger than 2K will be null padded) */ + for (bs = 1; bs < 16; bs++) { + lseek(fd, bs*2048+32768, SEEK_SET); + if (read(fd, (char *)&isosb, sizeof(isosb)) != sizeof(isosb)) + return 1; + if (isosb.id[0]) + break; + } + + /* Scan up to another 64 blocks looking for additional VSD's */ + for (j = 1; j < 64; j++) { + if (j > 1) { + lseek(fd, j*bs*2048+32768, SEEK_SET); + if (read(fd, (char *)&isosb, sizeof(isosb)) + != sizeof(isosb)) + return 1; + } + /* If we find NSR0x then call it udf: + NSR01 for UDF 1.00 + NSR02 for UDF 1.50 + NSR03 for UDF 2.00 */ + if (!strncmp(isosb.id, "NSR0", 4)) + return 0; + for (m = udf_magic; *m; m++) + if (!strncmp(*m, isosb.id, 5)) + break; + if (*m == 0) + return 1; + } + return 1; +} + +static int probe_ocfs(int fd __BLKID_ATTR((unused)), + blkid_cache cache __BLKID_ATTR((unused)), + blkid_dev dev, + const struct blkid_magic *id __BLKID_ATTR((unused)), + unsigned char *buf) +{ + struct ocfs_volume_header ovh; + struct ocfs_volume_label ovl; + __u32 major; + + memcpy(&ovh, buf, sizeof(ovh)); + memcpy(&ovl, buf+512, sizeof(ovl)); + + major = ocfsmajor(ovh); + if (major == 1) + blkid_set_tag(dev,"SEC_TYPE","ocfs1",sizeof("ocfs1")); + else if (major >= 9) + blkid_set_tag(dev,"SEC_TYPE","ntocfs",sizeof("ntocfs")); + + blkid_set_tag(dev, "LABEL", (const char*)ovl.label, ocfslabellen(ovl)); + blkid_set_tag(dev, "MOUNT", (const char*)ovh.mount, ocfsmountlen(ovh)); + set_uuid(dev, ovl.vol_id); + return 0; +} + +static int probe_ocfs2(int fd __BLKID_ATTR((unused)), + blkid_cache cache __BLKID_ATTR((unused)), + blkid_dev dev, + const struct blkid_magic *id __BLKID_ATTR((unused)), + unsigned char *buf) +{ + struct ocfs2_super_block *osb; + + osb = (struct ocfs2_super_block *)buf; + + blkid_set_tag(dev, "LABEL", (const char*)osb->s_label, sizeof(osb->s_label)); + set_uuid(dev, osb->s_uuid); + return 0; +} + +static int probe_oracleasm(int fd __BLKID_ATTR((unused)), + blkid_cache cache __BLKID_ATTR((unused)), + blkid_dev dev, + const struct blkid_magic *id __BLKID_ATTR((unused)), + unsigned char *buf) +{ + struct oracle_asm_disk_label *dl; + + dl = (struct oracle_asm_disk_label *)buf; + + blkid_set_tag(dev, "LABEL", dl->dl_id, sizeof(dl->dl_id)); + return 0; +} + +/* + * BLKID_BLK_OFFS is at least as large as the highest bim_kboff defined + * in the type_array table below + bim_kbalign. + * + * When probing for a lot of magics, we handle everything in 1kB buffers so + * that we don't have to worry about reading each combination of block sizes. + */ +#define BLKID_BLK_OFFS 64 /* currently reiserfs */ + +/* + * Various filesystem magics that we can check for. Note that kboff and + * sboff are in kilobytes and bytes respectively. All magics are in + * byte strings so we don't worry about endian issues. + */ +static const struct blkid_magic type_array[] = { +/* type kboff sboff len magic probe */ + { "oracleasm", 0, 32, 8, "ORCLDISK", probe_oracleasm }, + { "ntfs", 0, 3, 8, "NTFS ", 0 }, + { "jbd", 1, 0x38, 2, "\123\357", probe_jbd }, + { "ext3", 1, 0x38, 2, "\123\357", probe_ext3 }, + { "ext2", 1, 0x38, 2, "\123\357", probe_ext2 }, + { "reiserfs", 8, 0x34, 8, "ReIsErFs", probe_reiserfs }, + { "reiserfs", 64, 0x34, 9, "ReIsEr2Fs", probe_reiserfs }, + { "reiserfs", 64, 0x34, 9, "ReIsEr3Fs", probe_reiserfs }, + { "reiserfs", 64, 0x34, 8, "ReIsErFs", probe_reiserfs }, + { "reiserfs", 8, 20, 8, "ReIsErFs", probe_reiserfs }, + { "vfat", 0, 0x52, 5, "MSWIN", probe_vfat }, + { "vfat", 0, 0x52, 8, "FAT32 ", probe_vfat }, + { "vfat", 0, 0x36, 5, "MSDOS", probe_msdos }, + { "vfat", 0, 0x36, 8, "FAT16 ", probe_msdos }, + { "vfat", 0, 0x36, 8, "FAT12 ", probe_msdos }, + { "minix", 1, 0x10, 2, "\177\023", 0 }, + { "minix", 1, 0x10, 2, "\217\023", 0 }, + { "minix", 1, 0x10, 2, "\150\044", 0 }, + { "minix", 1, 0x10, 2, "\170\044", 0 }, + { "vxfs", 1, 0, 4, "\365\374\001\245", 0 }, + { "xfs", 0, 0, 4, "XFSB", probe_xfs }, + { "romfs", 0, 0, 8, "-rom1fs-", probe_romfs }, + { "bfs", 0, 0, 4, "\316\372\173\033", 0 }, + { "cramfs", 0, 0, 4, "E=\315\050", probe_cramfs }, + { "qnx4", 0, 4, 6, "QNX4FS", 0 }, + { "udf", 32, 1, 5, "BEA01", probe_udf }, + { "udf", 32, 1, 5, "BOOT2", probe_udf }, + { "udf", 32, 1, 5, "CD001", probe_udf }, + { "udf", 32, 1, 5, "CDW02", probe_udf }, + { "udf", 32, 1, 5, "NSR02", probe_udf }, + { "udf", 32, 1, 5, "NSR03", probe_udf }, + { "udf", 32, 1, 5, "TEA01", probe_udf }, + { "iso9660", 32, 1, 5, "CD001", 0 }, + { "iso9660", 32, 9, 5, "CDROM", 0 }, + { "jfs", 32, 0, 4, "JFS1", probe_jfs }, + { "hfs", 1, 0, 2, "BD", 0 }, + { "ufs", 8, 0x55c, 4, "T\031\001\000", 0 }, + { "hpfs", 8, 0, 4, "I\350\225\371", 0 }, + { "sysv", 0, 0x3f8, 4, "\020~\030\375", 0 }, + { "swap", 0, 0xff6, 10, "SWAP-SPACE", probe_swap0 }, + { "swap", 0, 0xff6, 10, "SWAPSPACE2", probe_swap1 }, + { "swap", 0, 0x1ff6, 10, "SWAP-SPACE", probe_swap0 }, + { "swap", 0, 0x1ff6, 10, "SWAPSPACE2", probe_swap1 }, + { "swap", 0, 0x3ff6, 10, "SWAP-SPACE", probe_swap0 }, + { "swap", 0, 0x3ff6, 10, "SWAPSPACE2", probe_swap1 }, + { "swap", 0, 0x7ff6, 10, "SWAP-SPACE", probe_swap0 }, + { "swap", 0, 0x7ff6, 10, "SWAPSPACE2", probe_swap1 }, + { "swap", 0, 0xfff6, 10, "SWAP-SPACE", probe_swap0 }, + { "swap", 0, 0xfff6, 10, "SWAPSPACE2", probe_swap1 }, + { "ocfs", 0, 8, 9, "OracleCFS", probe_ocfs }, + { "ocfs2", 1, 0, 6, "OCFSV2", probe_ocfs2 }, + { "ocfs2", 2, 0, 6, "OCFSV2", probe_ocfs2 }, + { "ocfs2", 4, 0, 6, "OCFSV2", probe_ocfs2 }, + { "ocfs2", 8, 0, 6, "OCFSV2", probe_ocfs2 }, + { NULL, 0, 0, 0, NULL, NULL } +}; + +/* + * Verify that the data in dev is consistent with what is on the actual + * block device (using the devname field only). Normally this will be + * called when finding items in the cache, but for long running processes + * is also desirable to revalidate an item before use. + * + * If we are unable to revalidate the data, we return the old data and + * do not set the BLKID_BID_FL_VERIFIED flag on it. + */ +blkid_dev blkid_verify(blkid_cache cache, blkid_dev dev) +{ + const struct blkid_magic *id; + unsigned char *bufs[BLKID_BLK_OFFS + 1], *buf; + const char *type; + struct stat st; + time_t diff, now; + int fd, idx; + + if (!dev) + return NULL; + + now = time(0); + diff = now - dev->bid_time; + + if ((now < dev->bid_time) || + (diff < BLKID_PROBE_MIN) || + (dev->bid_flags & BLKID_BID_FL_VERIFIED && + diff < BLKID_PROBE_INTERVAL)) + return dev; + + DBG(DEBUG_PROBE, + printf("need to revalidate %s (time since last check %lu)\n", + dev->bid_name, diff)); + + if (((fd = open(dev->bid_name, O_RDONLY)) < 0) || + (fstat(fd, &st) < 0)) { + if (errno == ENXIO || errno == ENODEV || errno == ENOENT) { + blkid_free_dev(dev); + return NULL; + } + /* We don't have read permission, just return cache data. */ + DBG(DEBUG_PROBE, + printf("returning unverified data for %s\n", + dev->bid_name)); + return dev; + } + + memset(bufs, 0, sizeof(bufs)); + + /* + * Iterate over the type array. If we already know the type, + * then try that first. If it doesn't work, then blow away + * the type information, and try again. + * + */ +try_again: + type = 0; + if (!dev->bid_type || !strcmp(dev->bid_type, "mdraid")) { + uuid_t uuid; + + if (check_mdraid(fd, uuid) == 0) { + set_uuid(dev, uuid); + type = "mdraid"; + goto found_type; + } + } + for (id = type_array; id->bim_type; id++) { + if (dev->bid_type && + strcmp(id->bim_type, dev->bid_type)) + continue; + + idx = id->bim_kboff + (id->bim_sboff >> 10); + if (idx > BLKID_BLK_OFFS || idx < 0) + continue; + buf = bufs[idx]; + if (!buf) { + if (lseek(fd, idx << 10, SEEK_SET) < 0) + continue; + + buf = (unsigned char *)xmalloc(1024); + + if (read(fd, buf, 1024) != 1024) { + free(buf); + continue; + } + bufs[idx] = buf; + } + + if (memcmp(id->bim_magic, buf + (id->bim_sboff&0x3ff), + id->bim_len)) + continue; + + if ((id->bim_probe == NULL) || + (id->bim_probe(fd, cache, dev, id, buf) == 0)) { + type = id->bim_type; + goto found_type; + } + } + + if (!id->bim_type && dev->bid_type) { + /* + * Zap the device filesystem type and try again + */ + blkid_set_tag(dev, "TYPE", 0, 0); + blkid_set_tag(dev, "SEC_TYPE", 0, 0); + blkid_set_tag(dev, "LABEL", 0, 0); + blkid_set_tag(dev, "UUID", 0, 0); + goto try_again; + } + + if (!dev->bid_type) { + blkid_free_dev(dev); + return NULL; + } + +found_type: + if (dev && type) { + dev->bid_devno = st.st_rdev; + dev->bid_time = time(0); + dev->bid_flags |= BLKID_BID_FL_VERIFIED; + cache->bic_flags |= BLKID_BIC_FL_CHANGED; + + blkid_set_tag(dev, "TYPE", type, 0); + + DBG(DEBUG_PROBE, printf("%s: devno 0x%04llx, type %s\n", + dev->bid_name, st.st_rdev, type)); + } + + close(fd); + + return dev; +} + +int blkid_known_fstype(const char *fstype) +{ + const struct blkid_magic *id; + + for (id = type_array; id->bim_type; id++) { + if (strcmp(fstype, id->bim_type) == 0) + return 1; + } + return 0; +} + +#ifdef TEST_PROGRAM +int main(int argc, char **argv) +{ + blkid_dev dev; + blkid_cache cache; + int ret; + + blkid_debug_mask = DEBUG_ALL; + if (argc != 2) { + fprintf(stderr, "Usage: %s device\n" + "Probe a single device to determine type\n", argv[0]); + exit(1); + } + if ((ret = blkid_get_cache(&cache, bb_dev_null)) != 0) { + fprintf(stderr, "%s: error creating cache (%d)\n", + argv[0], ret); + exit(1); + } + dev = blkid_get_dev(cache, argv[1], BLKID_DEV_NORMAL); + if (!dev) { + printf("%s: %s has an unsupported type\n", argv[0], argv[1]); + return 1; + } + printf("%s is type %s\n", argv[1], dev->bid_type ? + dev->bid_type : "(null)"); + if (dev->bid_label) + printf("\tlabel is '%s'\n", dev->bid_label); + if (dev->bid_uuid) + printf("\tuuid is %s\n", dev->bid_uuid); + + blkid_free_dev(dev); + return 0; +} +#endif diff --git a/e2fsprogs/blkid/probe.h b/e2fsprogs/blkid/probe.h new file mode 100644 index 000000000..78f796419 --- /dev/null +++ b/e2fsprogs/blkid/probe.h @@ -0,0 +1,375 @@ +/* vi: set sw=4 ts=4: */ +/* + * probe.h - constants and on-disk structures for extracting device data + * + * Copyright (C) 1999 by Andries Brouwer + * Copyright (C) 1999, 2000, 2003 by Theodore Ts'o + * Copyright (C) 2001 by Andreas Dilger + * + * %Begin-Header% + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + * %End-Header% + */ + +#ifndef _BLKID_PROBE_H +#define _BLKID_PROBE_H + +#include + +struct blkid_magic; + +typedef int (*blkid_probe_t)(int fd, blkid_cache cache, blkid_dev dev, + const struct blkid_magic *id, unsigned char *buf); + +struct blkid_magic { + const char *bim_type; /* type name for this magic */ + long bim_kboff; /* kilobyte offset of superblock */ + unsigned bim_sboff; /* byte offset within superblock */ + unsigned bim_len; /* length of magic */ + const char *bim_magic; /* magic string */ + blkid_probe_t bim_probe; /* probe function */ +}; + +/* + * Structures for each of the content types we want to extract information + * from. We do not necessarily need the magic field here, because we have + * already identified the content type before we get this far. It may still + * be useful if there are probe functions which handle multiple content types. + */ +struct ext2_super_block { + __u32 s_inodes_count; + __u32 s_blocks_count; + __u32 s_r_blocks_count; + __u32 s_free_blocks_count; + __u32 s_free_inodes_count; + __u32 s_first_data_block; + __u32 s_log_block_size; + __u32 s_dummy3[7]; + unsigned char s_magic[2]; + __u16 s_state; + __u32 s_dummy5[8]; + __u32 s_feature_compat; + __u32 s_feature_incompat; + __u32 s_feature_ro_compat; + unsigned char s_uuid[16]; + char s_volume_name[16]; +}; +#define EXT3_FEATURE_COMPAT_HAS_JOURNAL 0x00000004 +#define EXT3_FEATURE_INCOMPAT_RECOVER 0x00000004 +#define EXT3_FEATURE_INCOMPAT_JOURNAL_DEV 0x00000008 + +struct xfs_super_block { + unsigned char xs_magic[4]; + __u32 xs_blocksize; + __u64 xs_dblocks; + __u64 xs_rblocks; + __u32 xs_dummy1[2]; + unsigned char xs_uuid[16]; + __u32 xs_dummy2[15]; + char xs_fname[12]; + __u32 xs_dummy3[2]; + __u64 xs_icount; + __u64 xs_ifree; + __u64 xs_fdblocks; +}; + +struct reiserfs_super_block { + __u32 rs_blocks_count; + __u32 rs_free_blocks; + __u32 rs_root_block; + __u32 rs_journal_block; + __u32 rs_journal_dev; + __u32 rs_orig_journal_size; + __u32 rs_dummy2[5]; + __u16 rs_blocksize; + __u16 rs_dummy3[3]; + unsigned char rs_magic[12]; + __u32 rs_dummy4[5]; + unsigned char rs_uuid[16]; + char rs_label[16]; +}; + +struct jfs_super_block { + unsigned char js_magic[4]; + __u32 js_version; + __u64 js_size; + __u32 js_bsize; + __u32 js_dummy1; + __u32 js_pbsize; + __u32 js_dummy2[27]; + unsigned char js_uuid[16]; + unsigned char js_label[16]; + unsigned char js_loguuid[16]; +}; + +struct romfs_super_block { + unsigned char ros_magic[8]; + __u32 ros_dummy1[2]; + unsigned char ros_volume[16]; +}; + +struct cramfs_super_block { + __u8 magic[4]; + __u32 size; + __u32 flags; + __u32 future; + __u8 signature[16]; + struct cramfs_info { + __u32 crc; + __u32 edition; + __u32 blocks; + __u32 files; + } info; + __u8 name[16]; +}; + +struct swap_id_block { +/* unsigned char sws_boot[1024]; */ + __u32 sws_version; + __u32 sws_lastpage; + __u32 sws_nrbad; + unsigned char sws_uuid[16]; + char sws_volume[16]; + unsigned char sws_pad[117]; + __u32 sws_badpg; +}; + +/* Yucky misaligned values */ +struct vfat_super_block { +/* 00*/ unsigned char vs_ignored[3]; +/* 03*/ unsigned char vs_sysid[8]; +/* 0b*/ unsigned char vs_sector_size[2]; +/* 0d*/ __u8 vs_cluster_size; +/* 0e*/ __u16 vs_reserved; +/* 10*/ __u8 vs_fats; +/* 11*/ unsigned char vs_dir_entries[2]; +/* 13*/ unsigned char vs_sectors[2]; +/* 15*/ unsigned char vs_media; +/* 16*/ __u16 vs_fat_length; +/* 18*/ __u16 vs_secs_track; +/* 1a*/ __u16 vs_heads; +/* 1c*/ __u32 vs_hidden; +/* 20*/ __u32 vs_total_sect; +/* 24*/ __u32 vs_fat32_length; +/* 28*/ __u16 vs_flags; +/* 2a*/ __u8 vs_version[2]; +/* 2c*/ __u32 vs_root_cluster; +/* 30*/ __u16 vs_insfo_sector; +/* 32*/ __u16 vs_backup_boot; +/* 34*/ __u16 vs_reserved2[6]; +/* 40*/ unsigned char vs_unknown[3]; +/* 43*/ unsigned char vs_serno[4]; +/* 47*/ char vs_label[11]; +/* 52*/ unsigned char vs_magic[8]; +/* 5a*/ unsigned char vs_dummy2[164]; +/*1fe*/ unsigned char vs_pmagic[2]; +}; + +/* Yucky misaligned values */ +struct msdos_super_block { +/* 00*/ unsigned char ms_ignored[3]; +/* 03*/ unsigned char ms_sysid[8]; +/* 0b*/ unsigned char ms_sector_size[2]; +/* 0d*/ __u8 ms_cluster_size; +/* 0e*/ __u16 ms_reserved; +/* 10*/ __u8 ms_fats; +/* 11*/ unsigned char ms_dir_entries[2]; +/* 13*/ unsigned char ms_sectors[2]; +/* 15*/ unsigned char ms_media; +/* 16*/ __u16 ms_fat_length; +/* 18*/ __u16 ms_secs_track; +/* 1a*/ __u16 ms_heads; +/* 1c*/ __u32 ms_hidden; +/* 20*/ __u32 ms_total_sect; +/* 24*/ unsigned char ms_unknown[3]; +/* 27*/ unsigned char ms_serno[4]; +/* 2b*/ char ms_label[11]; +/* 36*/ unsigned char ms_magic[8]; +/* 3d*/ unsigned char ms_dummy2[192]; +/*1fe*/ unsigned char ms_pmagic[2]; +}; + +struct minix_super_block { + __u16 ms_ninodes; + __u16 ms_nzones; + __u16 ms_imap_blocks; + __u16 ms_zmap_blocks; + __u16 ms_firstdatazone; + __u16 ms_log_zone_size; + __u32 ms_max_size; + unsigned char ms_magic[2]; + __u16 ms_state; + __u32 ms_zones; +}; + +struct mdp_superblock_s { + __u32 md_magic; + __u32 major_version; + __u32 minor_version; + __u32 patch_version; + __u32 gvalid_words; + __u32 set_uuid0; + __u32 ctime; + __u32 level; + __u32 size; + __u32 nr_disks; + __u32 raid_disks; + __u32 md_minor; + __u32 not_persistent; + __u32 set_uuid1; + __u32 set_uuid2; + __u32 set_uuid3; +}; + +struct hfs_super_block { + char h_magic[2]; + char h_dummy[18]; + __u32 h_blksize; +}; + +struct ocfs_volume_header { + unsigned char minor_version[4]; + unsigned char major_version[4]; + unsigned char signature[128]; + char mount[128]; + unsigned char mount_len[2]; +}; + +struct ocfs_volume_label { + unsigned char disk_lock[48]; + char label[64]; + unsigned char label_len[2]; + unsigned char vol_id[16]; + unsigned char vol_id_len[2]; +}; + +#define ocfsmajor(o) ((__u32)o.major_version[0] \ + + (((__u32) o.major_version[1]) << 8) \ + + (((__u32) o.major_version[2]) << 16) \ + + (((__u32) o.major_version[3]) << 24)) +#define ocfslabellen(o) ((__u32)o.label_len[0] + (((__u32) o.label_len[1]) << 8)) +#define ocfsmountlen(o) ((__u32)o.mount_len[0] + (((__u32) o.mount_len[1])<<8)) + +#define OCFS_MAGIC "OracleCFS" + +struct ocfs2_super_block { + unsigned char signature[8]; + unsigned char s_dummy1[184]; + unsigned char s_dummy2[80]; + char s_label[64]; + unsigned char s_uuid[16]; +}; + +#define OCFS2_MIN_BLOCKSIZE 512 +#define OCFS2_MAX_BLOCKSIZE 4096 + +#define OCFS2_SUPER_BLOCK_BLKNO 2 + +#define OCFS2_SUPER_BLOCK_SIGNATURE "OCFSV2" + +struct oracle_asm_disk_label { + char dummy[32]; + char dl_tag[8]; + char dl_id[24]; +}; + +#define ORACLE_ASM_DISK_LABEL_MARKED "ORCLDISK" +#define ORACLE_ASM_DISK_LABEL_OFFSET 32 + +#define ISODCL(from, to) (to - from + 1) +struct iso_volume_descriptor { + char type[ISODCL(1,1)]; /* 711 */ + char id[ISODCL(2,6)]; + char version[ISODCL(7,7)]; + char data[ISODCL(8,2048)]; +}; + +/* + * Byte swap functions + */ +#ifdef __GNUC__ +#define _INLINE_ static __inline__ +#else /* For Watcom C */ +#define _INLINE_ static inline +#endif + +static __u16 blkid_swab16(__u16 val); +static __u32 blkid_swab32(__u32 val); +static __u64 blkid_swab64(__u64 val); + +#if ((defined __GNUC__) && \ + (defined(__i386__) || defined(__i486__) || defined(__i586__))) + +#define _BLKID_HAVE_ASM_BITOPS_ + +_INLINE_ __u32 blkid_swab32(__u32 val) +{ +#ifdef EXT2FS_REQUIRE_486 + __asm__("bswap %0" : "=r" (val) : "0" (val)); +#else + __asm__("xchgb %b0,%h0\n\t" /* swap lower bytes */ + "rorl $16,%0\n\t" /* swap words */ + "xchgb %b0,%h0" /* swap higher bytes */ + :"=q" (val) + : "0" (val)); +#endif + return val; +} + +_INLINE_ __u16 blkid_swab16(__u16 val) +{ + __asm__("xchgb %b0,%h0" /* swap bytes */ \ + : "=q" (val) \ + : "0" (val)); \ + return val; +} + +_INLINE_ __u64 blkid_swab64(__u64 val) +{ + return blkid_swab32(val >> 32) | + ( ((__u64)blkid_swab32((__u32)val)) << 32 ); +} +#endif + +#if !defined(_BLKID_HAVE_ASM_BITOPS_) + +_INLINE_ __u16 blkid_swab16(__u16 val) +{ + return (val >> 8) | (val << 8); +} + +_INLINE_ __u32 blkid_swab32(__u32 val) +{ + return (val>>24) | ((val>>8) & 0xFF00) | + ((val<<8) & 0xFF0000) | (val<<24); +} + +_INLINE_ __u64 blkid_swab64(__u64 val) +{ + return blkid_swab32(val >> 32) | + ( ((__u64)blkid_swab32((__u32)val)) << 32 ); +} +#endif + + + +#if __BYTE_ORDER == __BIG_ENDIAN +#define blkid_le16(x) blkid_swab16(x) +#define blkid_le32(x) blkid_swab32(x) +#define blkid_le64(x) blkid_swab64(x) +#define blkid_be16(x) (x) +#define blkid_be32(x) (x) +#define blkid_be64(x) (x) +#else +#define blkid_le16(x) (x) +#define blkid_le32(x) (x) +#define blkid_le64(x) (x) +#define blkid_be16(x) blkid_swab16(x) +#define blkid_be32(x) blkid_swab32(x) +#define blkid_be64(x) blkid_swab64(x) +#endif + +#undef _INLINE_ + +#endif /* _BLKID_PROBE_H */ diff --git a/e2fsprogs/blkid/read.c b/e2fsprogs/blkid/read.c new file mode 100644 index 000000000..bb02a2e21 --- /dev/null +++ b/e2fsprogs/blkid/read.c @@ -0,0 +1,462 @@ +/* vi: set sw=4 ts=4: */ +/* + * read.c - read the blkid cache from disk, to avoid scanning all devices + * + * Copyright (C) 2001, 2003 Theodore Y. Ts'o + * Copyright (C) 2001 Andreas Dilger + * + * %Begin-Header% + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + * %End-Header% + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "blkidP.h" +#include "../uuid/uuid.h" + +#ifdef HAVE_STRTOULL +#define __USE_ISOC9X +#define STRTOULL strtoull /* defined in stdlib.h if you try hard enough */ +#else +/* FIXME: need to support real strtoull here */ +#define STRTOULL strtoul +#endif + +#include + +#ifdef TEST_PROGRAM +#define blkid_debug_dump_dev(dev) (debug_dump_dev(dev)) +static void debug_dump_dev(blkid_dev dev); +#endif + +/* + * File format: + * + * ...]>device_name + * + * The following tags are required for each entry: + * unique (within this file) ID number of this device + * (ascii time_t) time this entry was last read from disk + * (detected) type of filesystem/data for this partition + * + * The following tags may be present, depending on the device contents + * (user supplied) label (volume name, etc) + * (generated) universally unique identifier (serial no) + */ + +static char *skip_over_blank(char *cp) +{ + while (*cp && isspace(*cp)) + cp++; + return cp; +} + +static char *skip_over_word(char *cp) +{ + char ch; + + while ((ch = *cp)) { + /* If we see a backslash, skip the next character */ + if (ch == '\\') { + cp++; + if (*cp == '\0') + break; + cp++; + continue; + } + if (isspace(ch) || ch == '<' || ch == '>') + break; + cp++; + } + return cp; +} + +static char *strip_line(char *line) +{ + char *p; + + line = skip_over_blank(line); + + p = line + strlen(line) - 1; + + while (*line) { + if (isspace(*p)) + *p-- = '\0'; + else + break; + } + + return line; +} + +/* + * Start parsing a new line from the cache. + * + * line starts with " continue parsing line + * line starts with " skip line + * line starts with other, return -BLKID_ERR_CACHE -> error + */ +static int parse_start(char **cp) +{ + char *p; + + p = strip_line(*cp); + + /* Skip comment or blank lines. We can't just NUL the first '#' char, + * in case it is inside quotes, or escaped. + */ + if (*p == '\0' || *p == '#') + return 0; + + if (!strncmp(p, "", 9)) { + DBG(DEBUG_READ, printf("found device trailer %9s\n", *cp)); + *cp += 9; + return 0; + } + + return -BLKID_ERR_CACHE; +} + +/* + * Allocate a new device struct with device name filled in. Will handle + * finding the device on lines of the form: + * devname + * devnamebar + */ +static int parse_dev(blkid_cache cache, blkid_dev *dev, char **cp) +{ + char *start, *tmp, *end, *name; + int ret; + + if ((ret = parse_start(cp)) <= 0) + return ret; + + start = tmp = strchr(*cp, '>'); + if (!start) { + DBG(DEBUG_READ, + printf("blkid: short line parsing dev: %s\n", *cp)); + return -BLKID_ERR_CACHE; + } + start = skip_over_blank(start + 1); + end = skip_over_word(start); + + DBG(DEBUG_READ, printf("device should be %*s\n", end - start, start)); + + if (**cp == '>') + *cp = end; + else + (*cp)++; + + *tmp = '\0'; + + if (!(tmp = strrchr(end, '<')) || parse_end(&tmp) < 0) { + DBG(DEBUG_READ, + printf("blkid: missing ending: %s\n", end)); + } else if (tmp) + *tmp = '\0'; + + if (end - start <= 1) { + DBG(DEBUG_READ, printf("blkid: empty device name: %s\n", *cp)); + return -BLKID_ERR_CACHE; + } + + name = blkid_strndup(start, end-start); + if (name == NULL) + return -BLKID_ERR_MEM; + + DBG(DEBUG_READ, printf("found dev %s\n", name)); + + if (!(*dev = blkid_get_dev(cache, name, BLKID_DEV_CREATE))) + return -BLKID_ERR_MEM; + + free(name); + return 1; +} + +/* + * Extract a tag of the form NAME="value" from the line. + */ +static int parse_token(char **name, char **value, char **cp) +{ + char *end; + + if (!name || !value || !cp) + return -BLKID_ERR_PARAM; + + if (!(*value = strchr(*cp, '='))) + return 0; + + **value = '\0'; + *name = strip_line(*cp); + *value = skip_over_blank(*value + 1); + + if (**value == '"') { + end = strchr(*value + 1, '"'); + if (!end) { + DBG(DEBUG_READ, + printf("unbalanced quotes at: %s\n", *value)); + *cp = *value; + return -BLKID_ERR_CACHE; + } + (*value)++; + *end = '\0'; + end++; + } else { + end = skip_over_word(*value); + if (*end) { + *end = '\0'; + end++; + } + } + *cp = end; + + return 1; +} + +/* + * Extract a tag of the form value from the line. + */ +/* +static int parse_xml(char **name, char **value, char **cp) +{ + char *end; + + if (!name || !value || !cp) + return -BLKID_ERR_PARAM; + + *name = strip_line(*cp); + + if ((*name)[0] != '<' || (*name)[1] == '/') + return 0; + + FIXME: finish this. +} +*/ + +/* + * Extract a tag from the line. + * + * Return 1 if a valid tag was found. + * Return 0 if no tag found. + * Return -ve error code. + */ +static int parse_tag(blkid_cache cache, blkid_dev dev, char **cp) +{ + char *name; + char *value; + int ret; + + if (!cache || !dev) + return -BLKID_ERR_PARAM; + + if ((ret = parse_token(&name, &value, cp)) <= 0 /* && + (ret = parse_xml(&name, &value, cp)) <= 0 */) + return ret; + + /* Some tags are stored directly in the device struct */ + if (!strcmp(name, "DEVNO")) + dev->bid_devno = STRTOULL(value, 0, 0); + else if (!strcmp(name, "PRI")) + dev->bid_pri = strtol(value, 0, 0); + else if (!strcmp(name, "TIME")) + /* FIXME: need to parse a long long eventually */ + dev->bid_time = strtol(value, 0, 0); + else + ret = blkid_set_tag(dev, name, value, strlen(value)); + + DBG(DEBUG_READ, printf(" tag: %s=\"%s\"\n", name, value)); + + return ret < 0 ? ret : 1; +} + +/* + * Parse a single line of data, and return a newly allocated dev struct. + * Add the new device to the cache struct, if one was read. + * + * Lines are of the form /dev/foo + * + * Returns -ve value on error. + * Returns 0 otherwise. + * If a valid device was read, *dev_p is non-NULL, otherwise it is NULL + * (e.g. comment lines, unknown XML content, etc). + */ +static int blkid_parse_line(blkid_cache cache, blkid_dev *dev_p, char *cp) +{ + blkid_dev dev; + int ret; + + if (!cache || !dev_p) + return -BLKID_ERR_PARAM; + + *dev_p = NULL; + + DBG(DEBUG_READ, printf("line: %s\n", cp)); + + if ((ret = parse_dev(cache, dev_p, &cp)) <= 0) + return ret; + + dev = *dev_p; + + while ((ret = parse_tag(cache, dev, &cp)) > 0) { + ; + } + + if (dev->bid_type == NULL) { + DBG(DEBUG_READ, + printf("blkid: device %s has no TYPE\n",dev->bid_name)); + blkid_free_dev(dev); + } + + DBG(DEBUG_READ, blkid_debug_dump_dev(dev)); + + return ret; +} + +/* + * Parse the specified filename, and return the data in the supplied or + * a newly allocated cache struct. If the file doesn't exist, return a + * new empty cache struct. + */ +void blkid_read_cache(blkid_cache cache) +{ + FILE *file; + char buf[4096]; + int fd, lineno = 0; + struct stat st; + + if (!cache) + return; + + /* + * If the file doesn't exist, then we just return an empty + * struct so that the cache can be populated. + */ + if ((fd = open(cache->bic_filename, O_RDONLY)) < 0) + return; + if (fstat(fd, &st) < 0) + goto errout; + if ((st.st_mtime == cache->bic_ftime) || + (cache->bic_flags & BLKID_BIC_FL_CHANGED)) { + DBG(DEBUG_CACHE, printf("skipping re-read of %s\n", + cache->bic_filename)); + goto errout; + } + + DBG(DEBUG_CACHE, printf("reading cache file %s\n", + cache->bic_filename)); + + file = fdopen(fd, "r"); + if (!file) + goto errout; + + while (fgets(buf, sizeof(buf), file)) { + blkid_dev dev; + unsigned int end; + + lineno++; + if (buf[0] == 0) + continue; + end = strlen(buf) - 1; + /* Continue reading next line if it ends with a backslash */ + while (buf[end] == '\\' && end < sizeof(buf) - 2 && + fgets(buf + end, sizeof(buf) - end, file)) { + end = strlen(buf) - 1; + lineno++; + } + + if (blkid_parse_line(cache, &dev, buf) < 0) { + DBG(DEBUG_READ, + printf("blkid: bad format on line %d\n", lineno)); + continue; + } + } + fclose(file); + + /* + * Initially we do not need to write out the cache file. + */ + cache->bic_flags &= ~BLKID_BIC_FL_CHANGED; + cache->bic_ftime = st.st_mtime; + + return; +errout: + close(fd); + return; +} + +#ifdef TEST_PROGRAM +static void debug_dump_dev(blkid_dev dev) +{ + struct list_head *p; + + if (!dev) { + printf(" dev: NULL\n"); + return; + } + + printf(" dev: name = %s\n", dev->bid_name); + printf(" dev: DEVNO=\"0x%0llx\"\n", dev->bid_devno); + printf(" dev: TIME=\"%lu\"\n", dev->bid_time); + printf(" dev: PRI=\"%d\"\n", dev->bid_pri); + printf(" dev: flags = 0x%08X\n", dev->bid_flags); + + list_for_each(p, &dev->bid_tags) { + blkid_tag tag = list_entry(p, struct blkid_struct_tag, bit_tags); + if (tag) + printf(" tag: %s=\"%s\"\n", tag->bit_name, + tag->bit_val); + else + printf(" tag: NULL\n"); + } + puts(""); +} + +int main(int argc, char**argv) +{ + blkid_cache cache = NULL; + int ret; + + blkid_debug_mask = DEBUG_ALL; + if (argc > 2) { + fprintf(stderr, "Usage: %s [filename]\n" + "Test parsing of the cache (filename)\n", argv[0]); + exit(1); + } + if ((ret = blkid_get_cache(&cache, argv[1])) < 0) + fprintf(stderr, "error %d reading cache file %s\n", ret, + argv[1] ? argv[1] : BLKID_CACHE_FILE); + + blkid_put_cache(cache); + + return ret; +} +#endif diff --git a/e2fsprogs/blkid/resolve.c b/e2fsprogs/blkid/resolve.c new file mode 100644 index 000000000..7942de2cd --- /dev/null +++ b/e2fsprogs/blkid/resolve.c @@ -0,0 +1,139 @@ +/* vi: set sw=4 ts=4: */ +/* + * resolve.c - resolve names and tags into specific devices + * + * Copyright (C) 2001, 2003 Theodore Ts'o. + * Copyright (C) 2001 Andreas Dilger + * + * %Begin-Header% + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + * %End-Header% + */ + +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include +#include +#include +#include +#include "blkidP.h" +#include "probe.h" + +/* + * Find a tagname (e.g. LABEL or UUID) on a specific device. + */ +char *blkid_get_tag_value(blkid_cache cache, const char *tagname, + const char *devname) +{ + blkid_tag found; + blkid_dev dev; + blkid_cache c = cache; + char *ret = NULL; + + DBG(DEBUG_RESOLVE, printf("looking for %s on %s\n", tagname, devname)); + + if (!devname) + return NULL; + + if (!cache) { + if (blkid_get_cache(&c, NULL) < 0) + return NULL; + } + + if ((dev = blkid_get_dev(c, devname, BLKID_DEV_NORMAL)) && + (found = blkid_find_tag_dev(dev, tagname))) + ret = blkid_strdup(found->bit_val); + + if (!cache) + blkid_put_cache(c); + + return ret; +} + +/* + * Locate a device name from a token (NAME=value string), or (name, value) + * pair. In the case of a token, value is ignored. If the "token" is not + * of the form "NAME=value" and there is no value given, then it is assumed + * to be the actual devname and a copy is returned. + */ +char *blkid_get_devname(blkid_cache cache, const char *token, + const char *value) +{ + blkid_dev dev; + blkid_cache c = cache; + char *t = 0, *v = 0; + char *ret = NULL; + + if (!token) + return NULL; + + if (!cache) { + if (blkid_get_cache(&c, NULL) < 0) + return NULL; + } + + DBG(DEBUG_RESOLVE, + printf("looking for %s%s%s %s\n", token, value ? "=" : "", + value ? value : "", cache ? "in cache" : "from disk")); + + if (!value) { + if (!strchr(token, '=')) + return blkid_strdup(token); + blkid_parse_tag_string(token, &t, &v); + if (!t || !v) + goto errout; + token = t; + value = v; + } + + dev = blkid_find_dev_with_tag(c, token, value); + if (!dev) + goto errout; + + ret = blkid_strdup(blkid_dev_devname(dev)); + +errout: + free(t); + free(v); + if (!cache) { + blkid_put_cache(c); + } + return ret; +} + +#ifdef TEST_PROGRAM +int main(int argc, char **argv) +{ + char *value; + blkid_cache cache; + + blkid_debug_mask = DEBUG_ALL; + if (argc != 2 && argc != 3) { + fprintf(stderr, "Usage:\t%s tagname=value\n" + "\t%s tagname devname\n" + "Find which device holds a given token or\n" + "Find what the value of a tag is in a device\n", + argv[0], argv[0]); + exit(1); + } + if (blkid_get_cache(&cache, bb_dev_null) < 0) { + fprintf(stderr, "cannot get blkid cache\n"); + exit(1); + } + + if (argv[2]) { + value = blkid_get_tag_value(cache, argv[1], argv[2]); + printf("%s has tag %s=%s\n", argv[2], argv[1], + value ? value : ""); + } else { + value = blkid_get_devname(cache, argv[1], NULL); + printf("%s has tag %s\n", value ? value : "", argv[1]); + } + blkid_put_cache(cache); + return value ? 0 : 1; +} +#endif diff --git a/e2fsprogs/blkid/save.c b/e2fsprogs/blkid/save.c new file mode 100644 index 000000000..cdbaabcb1 --- /dev/null +++ b/e2fsprogs/blkid/save.c @@ -0,0 +1,189 @@ +/* vi: set sw=4 ts=4: */ +/* + * save.c - write the cache struct to disk + * + * Copyright (C) 2001 by Andreas Dilger + * Copyright (C) 2003 Theodore Ts'o + * + * %Begin-Header% + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + * %End-Header% + */ + +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_SYS_MKDEV_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#include "blkidP.h" + +static int save_dev(blkid_dev dev, FILE *file) +{ + struct list_head *p; + + if (!dev || dev->bid_name[0] != '/') + return 0; + + DBG(DEBUG_SAVE, + printf("device %s, type %s\n", dev->bid_name, dev->bid_type)); + + fprintf(file, + "bid_devno, dev->bid_time); + if (dev->bid_pri) + fprintf(file, " PRI=\"%d\"", dev->bid_pri); + list_for_each(p, &dev->bid_tags) { + blkid_tag tag = list_entry(p, struct blkid_struct_tag, bit_tags); + fprintf(file, " %s=\"%s\"", tag->bit_name,tag->bit_val); + } + fprintf(file, ">%s\n", dev->bid_name); + + return 0; +} + +/* + * Write out the cache struct to the cache file on disk. + */ +int blkid_flush_cache(blkid_cache cache) +{ + struct list_head *p; + char *tmp = NULL; + const char *opened = NULL; + const char *filename; + FILE *file = NULL; + int fd, ret = 0; + struct stat st; + + if (!cache) + return -BLKID_ERR_PARAM; + + if (list_empty(&cache->bic_devs) || + !(cache->bic_flags & BLKID_BIC_FL_CHANGED)) { + DBG(DEBUG_SAVE, printf("skipping cache file write\n")); + return 0; + } + + filename = cache->bic_filename ? cache->bic_filename: BLKID_CACHE_FILE; + + /* If we can't write to the cache file, then don't even try */ + if (((ret = stat(filename, &st)) < 0 && errno != ENOENT) || + (ret == 0 && access(filename, W_OK) < 0)) { + DBG(DEBUG_SAVE, + printf("can't write to cache file %s\n", filename)); + return 0; + } + + /* + * Try and create a temporary file in the same directory so + * that in case of error we don't overwrite the cache file. + * If the cache file doesn't yet exist, it isn't a regular + * file (e.g. /dev/null or a socket), or we couldn't create + * a temporary file then we open it directly. + */ + if (ret == 0 && S_ISREG(st.st_mode)) { + tmp = xmalloc(strlen(filename) + 8); + sprintf(tmp, "%s-XXXXXX", filename); + fd = mkstemp(tmp); + if (fd >= 0) { + file = fdopen(fd, "w"); + opened = tmp; + } + fchmod(fd, 0644); + } + + if (!file) { + file = fopen(filename, "w"); + opened = filename; + } + + DBG(DEBUG_SAVE, + printf("writing cache file %s (really %s)\n", + filename, opened)); + + if (!file) { + ret = errno; + goto errout; + } + + list_for_each(p, &cache->bic_devs) { + blkid_dev dev = list_entry(p, struct blkid_struct_dev, bid_devs); + if (!dev->bid_type) + continue; + if ((ret = save_dev(dev, file)) < 0) + break; + } + + if (ret >= 0) { + cache->bic_flags &= ~BLKID_BIC_FL_CHANGED; + ret = 1; + } + + fclose(file); + if (opened != filename) { + if (ret < 0) { + unlink(opened); + DBG(DEBUG_SAVE, + printf("unlinked temp cache %s\n", opened)); + } else { + char *backup; + + backup = xmalloc(strlen(filename) + 5); + sprintf(backup, "%s.old", filename); + unlink(backup); + link(filename, backup); + free(backup); + rename(opened, filename); + DBG(DEBUG_SAVE, + printf("moved temp cache %s\n", opened)); + } + } + +errout: + free(tmp); + return ret; +} + +#ifdef TEST_PROGRAM +int main(int argc, char **argv) +{ + blkid_cache cache = NULL; + int ret; + + blkid_debug_mask = DEBUG_ALL; + if (argc > 2) { + fprintf(stderr, "Usage: %s [filename]\n" + "Test loading/saving a cache (filename)\n", argv[0]); + exit(1); + } + + if ((ret = blkid_get_cache(&cache, bb_dev_null)) != 0) { + fprintf(stderr, "%s: error creating cache (%d)\n", + argv[0], ret); + exit(1); + } + if ((ret = blkid_probe_all(cache)) < 0) { + fprintf(stderr, "error (%d) probing devices\n", ret); + exit(1); + } + cache->bic_filename = blkid_strdup(argv[1]); + + if ((ret = blkid_flush_cache(cache)) < 0) { + fprintf(stderr, "error (%d) saving cache\n", ret); + exit(1); + } + + blkid_put_cache(cache); + + return ret; +} +#endif diff --git a/e2fsprogs/blkid/tag.c b/e2fsprogs/blkid/tag.c new file mode 100644 index 000000000..9e862a50a --- /dev/null +++ b/e2fsprogs/blkid/tag.c @@ -0,0 +1,432 @@ +/* vi: set sw=4 ts=4: */ +/* + * tag.c - allocation/initialization/free routines for tag structs + * + * Copyright (C) 2001 Andreas Dilger + * Copyright (C) 2003 Theodore Ts'o + * + * %Begin-Header% + * This file may be redistributed under the terms of the + * GNU Lesser General Public License. + * %End-Header% + */ + +#include +#include +#include + +#include "blkidP.h" + +static blkid_tag blkid_new_tag(void) +{ + blkid_tag tag; + + if (!(tag = (blkid_tag) calloc(1, sizeof(struct blkid_struct_tag)))) + return NULL; + + INIT_LIST_HEAD(&tag->bit_tags); + INIT_LIST_HEAD(&tag->bit_names); + + return tag; +} + +#ifdef CONFIG_BLKID_DEBUG +void blkid_debug_dump_tag(blkid_tag tag) +{ + if (!tag) { + printf(" tag: NULL\n"); + return; + } + + printf(" tag: %s=\"%s\"\n", tag->bit_name, tag->bit_val); +} +#endif + +void blkid_free_tag(blkid_tag tag) +{ + if (!tag) + return; + + DBG(DEBUG_TAG, printf(" freeing tag %s=%s\n", tag->bit_name, + tag->bit_val ? tag->bit_val : "(NULL)")); + DBG(DEBUG_TAG, blkid_debug_dump_tag(tag)); + + list_del(&tag->bit_tags); /* list of tags for this device */ + list_del(&tag->bit_names); /* list of tags with this type */ + + free(tag->bit_name); + free(tag->bit_val); + free(tag); +} + +/* + * Find the desired tag on a device. If value is NULL, then the + * first such tag is returned, otherwise return only exact tag if found. + */ +blkid_tag blkid_find_tag_dev(blkid_dev dev, const char *type) +{ + struct list_head *p; + + if (!dev || !type) + return NULL; + + list_for_each(p, &dev->bid_tags) { + blkid_tag tmp = list_entry(p, struct blkid_struct_tag, + bit_tags); + + if (!strcmp(tmp->bit_name, type)) + return tmp; + } + return NULL; +} + +/* + * Find the desired tag type in the cache. + * We return the head tag for this tag type. + */ +static blkid_tag blkid_find_head_cache(blkid_cache cache, const char *type) +{ + blkid_tag head = NULL, tmp; + struct list_head *p; + + if (!cache || !type) + return NULL; + + list_for_each(p, &cache->bic_tags) { + tmp = list_entry(p, struct blkid_struct_tag, bit_tags); + if (!strcmp(tmp->bit_name, type)) { + DBG(DEBUG_TAG, + printf(" found cache tag head %s\n", type)); + head = tmp; + break; + } + } + return head; +} + +/* + * Set a tag on an existing device. + * + * If value is NULL, then delete the tagsfrom the device. + */ +int blkid_set_tag(blkid_dev dev, const char *name, + const char *value, const int vlength) +{ + blkid_tag t = 0, head = 0; + char *val = 0; + + if (!dev || !name) + return -BLKID_ERR_PARAM; + + if (!(val = blkid_strndup(value, vlength)) && value) + return -BLKID_ERR_MEM; + t = blkid_find_tag_dev(dev, name); + if (!value) { + blkid_free_tag(t); + } else if (t) { + if (!strcmp(t->bit_val, val)) { + /* Same thing, exit */ + free(val); + return 0; + } + free(t->bit_val); + t->bit_val = val; + } else { + /* Existing tag not present, add to device */ + if (!(t = blkid_new_tag())) + goto errout; + t->bit_name = blkid_strdup(name); + t->bit_val = val; + t->bit_dev = dev; + + list_add_tail(&t->bit_tags, &dev->bid_tags); + + if (dev->bid_cache) { + head = blkid_find_head_cache(dev->bid_cache, + t->bit_name); + if (!head) { + head = blkid_new_tag(); + if (!head) + goto errout; + + DBG(DEBUG_TAG, + printf(" creating new cache tag head %s\n", name)); + head->bit_name = blkid_strdup(name); + if (!head->bit_name) + goto errout; + list_add_tail(&head->bit_tags, + &dev->bid_cache->bic_tags); + } + list_add_tail(&t->bit_names, &head->bit_names); + } + } + + /* Link common tags directly to the device struct */ + if (!strcmp(name, "TYPE")) + dev->bid_type = val; + else if (!strcmp(name, "LABEL")) + dev->bid_label = val; + else if (!strcmp(name, "UUID")) + dev->bid_uuid = val; + + if (dev->bid_cache) + dev->bid_cache->bic_flags |= BLKID_BIC_FL_CHANGED; + return 0; + +errout: + blkid_free_tag(t); + if (!t) + free(val); + blkid_free_tag(head); + return -BLKID_ERR_MEM; +} + + +/* + * Parse a "NAME=value" string. This is slightly different than + * parse_token, because that will end an unquoted value at a space, while + * this will assume that an unquoted value is the rest of the token (e.g. + * if we are passed an already quoted string from the command-line we don't + * have to both quote and escape quote so that the quotes make it to + * us). + * + * Returns 0 on success, and -1 on failure. + */ +int blkid_parse_tag_string(const char *token, char **ret_type, char **ret_val) +{ + char *name, *value, *cp; + + DBG(DEBUG_TAG, printf("trying to parse '%s' as a tag\n", token)); + + if (!token || !(cp = strchr(token, '='))) + return -1; + + name = blkid_strdup(token); + if (!name) + return -1; + value = name + (cp - token); + *value++ = '\0'; + if (*value == '"' || *value == '\'') { + char c = *value++; + if (!(cp = strrchr(value, c))) + goto errout; /* missing closing quote */ + *cp = '\0'; + } + value = blkid_strdup(value); + if (!value) + goto errout; + + *ret_type = name; + *ret_val = value; + + return 0; + +errout: + free(name); + return -1; +} + +/* + * Tag iteration routines for the public libblkid interface. + * + * These routines do not expose the list.h implementation, which are a + * contamination of the namespace, and which force us to reveal far, far + * too much of our internal implemenation. I'm not convinced I want + * to keep list.h in the long term, anyway. It's fine for kernel + * programming, but performance is not the #1 priority for this + * library, and I really don't like the tradeoff of type-safety for + * performance for this application. [tytso:20030125.2007EST] + */ + +/* + * This series of functions iterate over all tags in a device + */ +#define TAG_ITERATE_MAGIC 0x01a5284c + +struct blkid_struct_tag_iterate { + int magic; + blkid_dev dev; + struct list_head *p; +}; + +blkid_tag_iterate blkid_tag_iterate_begin(blkid_dev dev) +{ + blkid_tag_iterate iter; + + iter = xmalloc(sizeof(struct blkid_struct_tag_iterate)); + iter->magic = TAG_ITERATE_MAGIC; + iter->dev = dev; + iter->p = dev->bid_tags.next; + return iter; +} + +/* + * Return 0 on success, -1 on error + */ +extern int blkid_tag_next(blkid_tag_iterate iter, + const char **type, const char **value) +{ + blkid_tag tag; + + *type = 0; + *value = 0; + if (!iter || iter->magic != TAG_ITERATE_MAGIC || + iter->p == &iter->dev->bid_tags) + return -1; + tag = list_entry(iter->p, struct blkid_struct_tag, bit_tags); + *type = tag->bit_name; + *value = tag->bit_val; + iter->p = iter->p->next; + return 0; +} + +void blkid_tag_iterate_end(blkid_tag_iterate iter) +{ + if (!iter || iter->magic != TAG_ITERATE_MAGIC) + return; + iter->magic = 0; + free(iter); +} + +/* + * This function returns a device which matches a particular + * type/value pair. If there is more than one device that matches the + * search specification, it returns the one with the highest priority + * value. This allows us to give preference to EVMS or LVM devices. + * + * XXX there should also be an interface which uses an iterator so we + * can get all of the devices which match a type/value search parameter. + */ +extern blkid_dev blkid_find_dev_with_tag(blkid_cache cache, + const char *type, + const char *value) +{ + blkid_tag head; + blkid_dev dev; + int pri; + struct list_head *p; + + if (!cache || !type || !value) + return NULL; + + blkid_read_cache(cache); + + DBG(DEBUG_TAG, printf("looking for %s=%s in cache\n", type, value)); + +try_again: + pri = -1; + dev = 0; + head = blkid_find_head_cache(cache, type); + + if (head) { + list_for_each(p, &head->bit_names) { + blkid_tag tmp = list_entry(p, struct blkid_struct_tag, + bit_names); + + if (!strcmp(tmp->bit_val, value) && + tmp->bit_dev->bid_pri > pri) { + dev = tmp->bit_dev; + pri = dev->bid_pri; + } + } + } + if (dev && !(dev->bid_flags & BLKID_BID_FL_VERIFIED)) { + dev = blkid_verify(cache, dev); + if (dev && (dev->bid_flags & BLKID_BID_FL_VERIFIED)) + goto try_again; + } + + if (!dev && !(cache->bic_flags & BLKID_BIC_FL_PROBED)) { + if (blkid_probe_all(cache) < 0) + return NULL; + goto try_again; + } + return dev; +} + +#ifdef TEST_PROGRAM +#ifdef HAVE_GETOPT_H +#include +#else +extern char *optarg; +extern int optind; +#endif + +void usage(char *prog) +{ + fprintf(stderr, "Usage: %s [-f blkid_file] [-m debug_mask] device " + "[type value]\n", + prog); + fprintf(stderr, "\tList all tags for a device and exit\n", prog); + exit(1); +} + +int main(int argc, char **argv) +{ + blkid_tag_iterate iter; + blkid_cache cache = NULL; + blkid_dev dev; + int c, ret, found; + int flags = BLKID_DEV_FIND; + char *tmp; + char *file = NULL; + char *devname = NULL; + char *search_type = NULL; + char *search_value = NULL; + const char *type, *value; + + while ((c = getopt (argc, argv, "m:f:")) != EOF) + switch (c) { + case 'f': + file = optarg; + break; + case 'm': + blkid_debug_mask = strtoul (optarg, &tmp, 0); + if (*tmp) { + fprintf(stderr, "Invalid debug mask: %d\n", + optarg); + exit(1); + } + break; + case '?': + usage(argv[0]); + } + if (argc > optind) + devname = argv[optind++]; + if (argc > optind) + search_type = argv[optind++]; + if (argc > optind) + search_value = argv[optind++]; + if (!devname || (argc != optind)) + usage(argv[0]); + + if ((ret = blkid_get_cache(&cache, file)) != 0) { + fprintf(stderr, "%s: error creating cache (%d)\n", + argv[0], ret); + exit(1); + } + + dev = blkid_get_dev(cache, devname, flags); + if (!dev) { + fprintf(stderr, "%s: cannot find device in blkid cache\n"); + exit(1); + } + if (search_type) { + found = blkid_dev_has_tag(dev, search_type, search_value); + printf("Device %s: (%s, %s) %s\n", blkid_dev_devname(dev), + search_type, search_value ? search_value : "NULL", + found ? "FOUND" : "NOT FOUND"); + return !found; + } + printf("Device %s...\n", blkid_dev_devname(dev)); + + iter = blkid_tag_iterate_begin(dev); + while (blkid_tag_next(iter, &type, &value) == 0) { + printf("\tTag %s has value %s\n", type, value); + } + blkid_tag_iterate_end(iter); + + blkid_put_cache(cache); + return 0; +} +#endif diff --git a/e2fsprogs/chattr.c b/e2fsprogs/chattr.c new file mode 100644 index 000000000..4c341627e --- /dev/null +++ b/e2fsprogs/chattr.c @@ -0,0 +1,218 @@ +/* vi: set sw=4 ts=4: */ +/* + * chattr.c - Change file attributes on an ext2 file system + * + * Copyright (C) 1993, 1994 Remy Card + * Laboratoire MASI, Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * This file can be redistributed under the terms of the GNU General + * Public License + */ + +/* + * History: + * 93/10/30 - Creation + * 93/11/13 - Replace stat() calls by lstat() to avoid loops + * 94/02/27 - Integrated in Ted's distribution + * 98/12/29 - Ignore symlinks when working recursively (G M Sipe) + * 98/12/29 - Display version info only when -V specified (G M Sipe) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ext2fs/ext2_fs.h" + +#ifdef __GNUC__ +# define EXT2FS_ATTR(x) __attribute__(x) +#else +# define EXT2FS_ATTR(x) +#endif + +#include "e2fsbb.h" +#include "e2p/e2p.h" + +#define OPT_ADD 1 +#define OPT_REM 2 +#define OPT_SET 4 +#define OPT_SET_VER 8 +static int flags; +static int recursive; + +static unsigned long version; + +static unsigned long af; +static unsigned long rf; +static unsigned long sf; + +struct flags_char { + unsigned long flag; + char optchar; +}; + +static const struct flags_char flags_array[] = { + { EXT2_NOATIME_FL, 'A' }, + { EXT2_SYNC_FL, 'S' }, + { EXT2_DIRSYNC_FL, 'D' }, + { EXT2_APPEND_FL, 'a' }, + { EXT2_COMPR_FL, 'c' }, + { EXT2_NODUMP_FL, 'd' }, + { EXT2_IMMUTABLE_FL, 'i' }, + { EXT3_JOURNAL_DATA_FL, 'j' }, + { EXT2_SECRM_FL, 's' }, + { EXT2_UNRM_FL, 'u' }, + { EXT2_NOTAIL_FL, 't' }, + { EXT2_TOPDIR_FL, 'T' }, + { 0, 0 } +}; + +static unsigned long get_flag(char c) +{ + const struct flags_char *fp; + for (fp = flags_array; fp->flag; fp++) + if (fp->optchar == c) + return fp->flag; + bb_show_usage(); + return 0; +} + +static int decode_arg(char *arg) +{ + unsigned long *fl; + char opt = *arg++; + + if (opt == '-') { + flags |= OPT_REM; + fl = &rf; + } else if (opt == '+') { + flags |= OPT_ADD; + fl = ⁡ + } else if (opt == '=') { + flags |= OPT_SET; + fl = &sf; + } else + return EOF; + + for (; *arg ; ++arg) + (*fl) |= get_flag(*arg); + + return 1; +} + +static int chattr_dir_proc(const char *, struct dirent *, void *); + +static void change_attributes(const char * name) +{ + unsigned long fsflags; + struct stat st; + + if (lstat(name, &st) == -1) { + bb_error_msg("stat %s failed", name); + return; + } + if (S_ISLNK(st.st_mode) && recursive) + return; + + /* Don't try to open device files, fifos etc. We probably + * ought to display an error if the file was explicitly given + * on the command line (whether or not recursive was + * requested). */ + if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode) && !S_ISDIR(st.st_mode)) + return; + + if (flags & OPT_SET_VER) + if (fsetversion(name, version) == -1) + bb_error_msg("setting version on %s", name); + + if (flags & OPT_SET) { + fsflags = sf; + } else { + if (fgetflags(name, &fsflags) == -1) { + bb_error_msg("reading flags on %s", name); + goto skip_setflags; + } + if (flags & OPT_REM) + fsflags &= ~rf; + if (flags & OPT_ADD) + fsflags |= af; + if (!S_ISDIR(st.st_mode)) + fsflags &= ~EXT2_DIRSYNC_FL; + } + if (fsetflags(name, fsflags) == -1) + bb_error_msg("setting flags on %s", name); + +skip_setflags: + if (S_ISDIR(st.st_mode) && recursive) + iterate_on_dir(name, chattr_dir_proc, NULL); +} + +static int chattr_dir_proc(const char *dir_name, struct dirent *de, + void *private EXT2FS_ATTR((unused))) +{ + /*if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) {*/ + if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || \ + (de->d_name[1] == '.' && de->d_name[2] == '\0'))) { + char *path = concat_subpath_file(dir_name, de->d_name); + if (path) { + change_attributes(path); + free(path); + } + } + return 0; +} + +int chattr_main(int argc, char **argv) +{ + int i; + char *arg; + + /* parse the args */ + for (i = 1; i < argc; ++i) { + arg = argv[i]; + + /* take care of -R and -v */ + if (arg[0] == '-') { + if (arg[1] == 'R' && arg[2] == '\0') { + recursive = 1; + continue; + } else if (arg[1] == 'v' && arg[2] == '\0') { + char *tmp; + ++i; + if (i >= argc) + bb_show_usage(); + version = strtol(argv[i], &tmp, 0); + if (*tmp) + bb_error_msg_and_die("bad version '%s'", arg); + flags |= OPT_SET_VER; + continue; + } + } + + if (decode_arg(arg) == EOF) + break; + } + + /* run sanity checks on all the arguments given us */ + if (i >= argc) + bb_show_usage(); + if ((flags & OPT_SET) && ((flags & OPT_ADD) || (flags & OPT_REM))) + bb_error_msg_and_die("= is incompatible with - and +"); + if ((rf & af) != 0) + bb_error_msg_and_die("Can't set and unset a flag"); + if (!flags) + bb_error_msg_and_die("Must use '-v', =, - or +"); + + /* now run chattr on all the files passed to us */ + while (i < argc) + change_attributes(argv[i++]); + + return EXIT_SUCCESS; +} diff --git a/e2fsprogs/e2fsbb.h b/e2fsprogs/e2fsbb.h new file mode 100644 index 000000000..78e7cbd04 --- /dev/null +++ b/e2fsprogs/e2fsbb.h @@ -0,0 +1,43 @@ +/* vi: set sw=4 ts=4: */ +/* + * File: e2fsbb.h + * + * Redefine a bunch of e2fsprogs stuff to use busybox routines + * instead. This makes upgrade between e2fsprogs versions easy. + */ + +#ifndef __E2FSBB_H__ +#define __E2FSBB_H__ 1 + +#include "libbb.h" + +/* version we've last synced against */ +#define E2FSPROGS_VERSION "1.38" +#define E2FSPROGS_DATE "30-Jun-2005" + +typedef long errcode_t; +#define ERRCODE_RANGE 8 +#define error_message(code) strerror((int) (code & ((1< + * Free Software License: + * All rights are reserved by the author, with the following exceptions: + * Permission is granted to freely reproduce and distribute this software, + * possibly in exchange for a fee, provided that this copyright notice appears + * intact. Permission is also granted to adapt this software to produce + * derivative works, as long as the modified versions carry this copyright + * notice and additional notices stating that the work has been modified. + * This source code may be translated into executable form and incorporated + * into proprietary software; there is no requirement for such software to + * contain a copyright notice related to this source. + * + * linux/fs/recovery and linux/fs/revoke + * Written by Stephen C. Tweedie , 1999 + * + * Copyright 1999-2000 Red Hat Software --- All Rights Reserved + * + * Journal recovery routines for the generic filesystem journaling code; + * part of the ext2fs journaling system. + * + * Licensed under GPLv2 or later, see file License in this tarball for details. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 /* get strnlen() */ +#endif + +#include "e2fsck.h" /*Put all of our defines here to clean things up*/ + +#define _(x) x +#define N_(x) x + +/* + * Procedure declarations + */ + +static void e2fsck_pass1_dupblocks(e2fsck_t ctx, char *block_buf); + +/* pass1.c */ +static void e2fsck_use_inode_shortcuts(e2fsck_t ctx, int bool); + +/* pass2.c */ +static int e2fsck_process_bad_inode(e2fsck_t ctx, ext2_ino_t dir, + ext2_ino_t ino, char *buf); + +/* pass3.c */ +static int e2fsck_reconnect_file(e2fsck_t ctx, ext2_ino_t inode); +static errcode_t e2fsck_expand_directory(e2fsck_t ctx, ext2_ino_t dir, + int num, int gauranteed_size); +static ext2_ino_t e2fsck_get_lost_and_found(e2fsck_t ctx, int fix); +static errcode_t e2fsck_adjust_inode_count(e2fsck_t ctx, ext2_ino_t ino, + int adj); + +/* rehash.c */ +static void e2fsck_rehash_directories(e2fsck_t ctx); + +/* util.c */ +static void *e2fsck_allocate_memory(e2fsck_t ctx, unsigned int size, + const char *description); +static int ask(e2fsck_t ctx, const char * string, int def); +static void e2fsck_read_bitmaps(e2fsck_t ctx); +static void preenhalt(e2fsck_t ctx); +static void e2fsck_read_inode(e2fsck_t ctx, unsigned long ino, + struct ext2_inode * inode, const char * proc); +static void e2fsck_write_inode(e2fsck_t ctx, unsigned long ino, + struct ext2_inode * inode, const char * proc); +static blk_t get_backup_sb(e2fsck_t ctx, ext2_filsys fs, + const char *name, io_manager manager); + +/* unix.c */ +static void e2fsck_clear_progbar(e2fsck_t ctx); +static int e2fsck_simple_progress(e2fsck_t ctx, const char *label, + float percent, unsigned int dpynum); + + +/* + * problem.h --- e2fsck problem error codes + */ + +typedef __u32 problem_t; + +struct problem_context { + errcode_t errcode; + ext2_ino_t ino, ino2, dir; + struct ext2_inode *inode; + struct ext2_dir_entry *dirent; + blk_t blk, blk2; + e2_blkcnt_t blkcount; + int group; + __u64 num; + const char *str; +}; + + +/* + * Function declarations + */ +static int fix_problem(e2fsck_t ctx, problem_t code, struct problem_context *pctx); +static int end_problem_latch(e2fsck_t ctx, int mask); +static int set_latch_flags(int mask, int setflags, int clearflags); +static void clear_problem_context(struct problem_context *ctx); + +/* + * Dictionary Abstract Data Type + * Copyright (C) 1997 Kaz Kylheku + * + * dict.h v 1.22.2.6 2000/11/13 01:36:44 kaz + * kazlib_1_20 + */ + +#ifndef DICT_H +#define DICT_H + +/* + * Blurb for inclusion into C++ translation units + */ + +typedef unsigned long dictcount_t; +#define DICTCOUNT_T_MAX ULONG_MAX + +/* + * The dictionary is implemented as a red-black tree + */ + +typedef enum { dnode_red, dnode_black } dnode_color_t; + +typedef struct dnode_t { + struct dnode_t *dict_left; + struct dnode_t *dict_right; + struct dnode_t *dict_parent; + dnode_color_t dict_color; + const void *dict_key; + void *dict_data; +} dnode_t; + +typedef int (*dict_comp_t)(const void *, const void *); +typedef void (*dnode_free_t)(dnode_t *); + +typedef struct dict_t { + dnode_t dict_nilnode; + dictcount_t dict_nodecount; + dictcount_t dict_maxcount; + dict_comp_t dict_compare; + dnode_free_t dict_freenode; + int dict_dupes; +} dict_t; + +typedef void (*dnode_process_t)(dict_t *, dnode_t *, void *); + +typedef struct dict_load_t { + dict_t *dict_dictptr; + dnode_t dict_nilnode; +} dict_load_t; + +#define dict_count(D) ((D)->dict_nodecount) +#define dnode_get(N) ((N)->dict_data) +#define dnode_getkey(N) ((N)->dict_key) + +#endif + +/* + * Compatibility header file for e2fsck which should be included + * instead of linux/jfs.h + * + * Copyright (C) 2000 Stephen C. Tweedie + */ + +/* + * Pull in the definition of the e2fsck context structure + */ + +struct buffer_head { + char b_data[8192]; + e2fsck_t b_ctx; + io_channel b_io; + int b_size; + blk_t b_blocknr; + int b_dirty; + int b_uptodate; + int b_err; +}; + + +#define K_DEV_FS 1 +#define K_DEV_JOURNAL 2 + +#define lock_buffer(bh) do {} while(0) +#define unlock_buffer(bh) do {} while(0) +#define buffer_req(bh) 1 +#define do_readahead(journal, start) do {} while(0) + +static e2fsck_t e2fsck_global_ctx; /* Try your very best not to use this! */ + +typedef struct { + int object_length; +} kmem_cache_t; + +#define kmem_cache_alloc(cache,flags) malloc((cache)->object_length) + +/* + * We use the standard libext2fs portability tricks for inline + * functions. + */ + +static kmem_cache_t * do_cache_create(int len) +{ + kmem_cache_t *new_cache; + + new_cache = malloc(sizeof(*new_cache)); + if (new_cache) + new_cache->object_length = len; + return new_cache; +} + +static void do_cache_destroy(kmem_cache_t *cache) +{ + free(cache); +} + + +/* + * Dictionary Abstract Data Type + */ + + +/* + * These macros provide short convenient names for structure members, + * which are embellished with dict_ prefixes so that they are + * properly confined to the documented namespace. It's legal for a + * program which uses dict to define, for instance, a macro called ``parent''. + * Such a macro would interfere with the dnode_t struct definition. + * In general, highly portable and reusable C modules which expose their + * structures need to confine structure member names to well-defined spaces. + * The resulting identifiers aren't necessarily convenient to use, nor + * readable, in the implementation, however! + */ + +#define left dict_left +#define right dict_right +#define parent dict_parent +#define color dict_color +#define key dict_key +#define data dict_data + +#define nilnode dict_nilnode +#define maxcount dict_maxcount +#define compare dict_compare +#define dupes dict_dupes + +#define dict_root(D) ((D)->nilnode.left) +#define dict_nil(D) (&(D)->nilnode) + +static void dnode_free(dnode_t *node); + +/* + * Perform a ``left rotation'' adjustment on the tree. The given node P and + * its right child C are rearranged so that the P instead becomes the left + * child of C. The left subtree of C is inherited as the new right subtree + * for P. The ordering of the keys within the tree is thus preserved. + */ + +static void rotate_left(dnode_t *upper) +{ + dnode_t *lower, *lowleft, *upparent; + + lower = upper->right; + upper->right = lowleft = lower->left; + lowleft->parent = upper; + + lower->parent = upparent = upper->parent; + + /* don't need to check for root node here because root->parent is + the sentinel nil node, and root->parent->left points back to root */ + + if (upper == upparent->left) { + upparent->left = lower; + } else { + assert (upper == upparent->right); + upparent->right = lower; + } + + lower->left = upper; + upper->parent = lower; +} + +/* + * This operation is the ``mirror'' image of rotate_left. It is + * the same procedure, but with left and right interchanged. + */ + +static void rotate_right(dnode_t *upper) +{ + dnode_t *lower, *lowright, *upparent; + + lower = upper->left; + upper->left = lowright = lower->right; + lowright->parent = upper; + + lower->parent = upparent = upper->parent; + + if (upper == upparent->right) { + upparent->right = lower; + } else { + assert (upper == upparent->left); + upparent->left = lower; + } + + lower->right = upper; + upper->parent = lower; +} + +/* + * Do a postorder traversal of the tree rooted at the specified + * node and free everything under it. Used by dict_free(). + */ + +static void free_nodes(dict_t *dict, dnode_t *node, dnode_t *nil) +{ + if (node == nil) + return; + free_nodes(dict, node->left, nil); + free_nodes(dict, node->right, nil); + dict->dict_freenode(node); +} + +/* + * Verify that the tree contains the given node. This is done by + * traversing all of the nodes and comparing their pointers to the + * given pointer. Returns 1 if the node is found, otherwise + * returns zero. It is intended for debugging purposes. + */ + +static int verify_dict_has_node(dnode_t *nil, dnode_t *root, dnode_t *node) +{ + if (root != nil) { + return root == node + || verify_dict_has_node(nil, root->left, node) + || verify_dict_has_node(nil, root->right, node); + } + return 0; +} + + +/* + * Select a different set of node allocator routines. + */ + +static void dict_set_allocator(dict_t *dict, dnode_free_t fr) +{ + assert (dict_count(dict) == 0); + dict->dict_freenode = fr; +} + +/* + * Free all the nodes in the dictionary by using the dictionary's + * installed free routine. The dictionary is emptied. + */ + +static void dict_free_nodes(dict_t *dict) +{ + dnode_t *nil = dict_nil(dict), *root = dict_root(dict); + free_nodes(dict, root, nil); + dict->dict_nodecount = 0; + dict->nilnode.left = &dict->nilnode; + dict->nilnode.right = &dict->nilnode; +} + +/* + * Initialize a user-supplied dictionary object. + */ + +static dict_t *dict_init(dict_t *dict, dictcount_t maxcount, dict_comp_t comp) +{ + dict->compare = comp; + dict->dict_freenode = dnode_free; + dict->dict_nodecount = 0; + dict->maxcount = maxcount; + dict->nilnode.left = &dict->nilnode; + dict->nilnode.right = &dict->nilnode; + dict->nilnode.parent = &dict->nilnode; + dict->nilnode.color = dnode_black; + dict->dupes = 0; + return dict; +} + +/* + * Locate a node in the dictionary having the given key. + * If the node is not found, a null a pointer is returned (rather than + * a pointer that dictionary's nil sentinel node), otherwise a pointer to the + * located node is returned. + */ + +static dnode_t *dict_lookup(dict_t *dict, const void *key) +{ + dnode_t *root = dict_root(dict); + dnode_t *nil = dict_nil(dict); + dnode_t *saved; + int result; + + /* simple binary search adapted for trees that contain duplicate keys */ + + while (root != nil) { + result = dict->compare(key, root->key); + if (result < 0) + root = root->left; + else if (result > 0) + root = root->right; + else { + if (!dict->dupes) { /* no duplicates, return match */ + return root; + } else { /* could be dupes, find leftmost one */ + do { + saved = root; + root = root->left; + while (root != nil && dict->compare(key, root->key)) + root = root->right; + } while (root != nil); + return saved; + } + } + } + + return NULL; +} + +/* + * Insert a node into the dictionary. The node should have been + * initialized with a data field. All other fields are ignored. + * The behavior is undefined if the user attempts to insert into + * a dictionary that is already full (for which the dict_isfull() + * function returns true). + */ + +static void dict_insert(dict_t *dict, dnode_t *node, const void *key) +{ + dnode_t *where = dict_root(dict), *nil = dict_nil(dict); + dnode_t *parent = nil, *uncle, *grandpa; + int result = -1; + + node->key = key; + + /* basic binary tree insert */ + + while (where != nil) { + parent = where; + result = dict->compare(key, where->key); + /* trap attempts at duplicate key insertion unless it's explicitly allowed */ + assert (dict->dupes || result != 0); + if (result < 0) + where = where->left; + else + where = where->right; + } + + assert (where == nil); + + if (result < 0) + parent->left = node; + else + parent->right = node; + + node->parent = parent; + node->left = nil; + node->right = nil; + + dict->dict_nodecount++; + + /* red black adjustments */ + + node->color = dnode_red; + + while (parent->color == dnode_red) { + grandpa = parent->parent; + if (parent == grandpa->left) { + uncle = grandpa->right; + if (uncle->color == dnode_red) { /* red parent, red uncle */ + parent->color = dnode_black; + uncle->color = dnode_black; + grandpa->color = dnode_red; + node = grandpa; + parent = grandpa->parent; + } else { /* red parent, black uncle */ + if (node == parent->right) { + rotate_left(parent); + parent = node; + assert (grandpa == parent->parent); + /* rotation between parent and child preserves grandpa */ + } + parent->color = dnode_black; + grandpa->color = dnode_red; + rotate_right(grandpa); + break; + } + } else { /* symmetric cases: parent == parent->parent->right */ + uncle = grandpa->left; + if (uncle->color == dnode_red) { + parent->color = dnode_black; + uncle->color = dnode_black; + grandpa->color = dnode_red; + node = grandpa; + parent = grandpa->parent; + } else { + if (node == parent->left) { + rotate_right(parent); + parent = node; + assert (grandpa == parent->parent); + } + parent->color = dnode_black; + grandpa->color = dnode_red; + rotate_left(grandpa); + break; + } + } + } + + dict_root(dict)->color = dnode_black; + +} + +/* + * Allocate a node using the dictionary's allocator routine, give it + * the data item. + */ + +static dnode_t *dnode_init(dnode_t *dnode, void *data) +{ + dnode->data = data; + dnode->parent = NULL; + dnode->left = NULL; + dnode->right = NULL; + return dnode; +} + +static int dict_alloc_insert(dict_t *dict, const void *key, void *data) +{ + dnode_t *node = malloc(sizeof(dnode_t)); + + if (node) { + dnode_init(node, data); + dict_insert(dict, node, key); + return 1; + } + return 0; +} + +/* + * Return the node with the lowest (leftmost) key. If the dictionary is empty + * (that is, dict_isempty(dict) returns 1) a null pointer is returned. + */ + +static dnode_t *dict_first(dict_t *dict) +{ + dnode_t *nil = dict_nil(dict), *root = dict_root(dict), *left; + + if (root != nil) + while ((left = root->left) != nil) + root = left; + + return (root == nil) ? NULL : root; +} + +/* + * Return the given node's successor node---the node which has the + * next key in the the left to right ordering. If the node has + * no successor, a null pointer is returned rather than a pointer to + * the nil node. + */ + +static dnode_t *dict_next(dict_t *dict, dnode_t *curr) +{ + dnode_t *nil = dict_nil(dict), *parent, *left; + + if (curr->right != nil) { + curr = curr->right; + while ((left = curr->left) != nil) + curr = left; + return curr; + } + + parent = curr->parent; + + while (parent != nil && curr == parent->right) { + curr = parent; + parent = curr->parent; + } + + return (parent == nil) ? NULL : parent; +} + + +static void dnode_free(dnode_t *node) +{ + free(node); +} + + +#undef left +#undef right +#undef parent +#undef color +#undef key +#undef data + +#undef nilnode +#undef maxcount +#undef compare +#undef dupes + + +/* + * dirinfo.c --- maintains the directory information table for e2fsck. + */ + +/* + * This subroutine is called during pass1 to create a directory info + * entry. During pass1, the passed-in parent is 0; it will get filled + * in during pass2. + */ +static void e2fsck_add_dir_info(e2fsck_t ctx, ext2_ino_t ino, ext2_ino_t parent) +{ + struct dir_info *dir; + int i, j; + ext2_ino_t num_dirs; + errcode_t retval; + unsigned long old_size; + + if (!ctx->dir_info) { + ctx->dir_info_count = 0; + retval = ext2fs_get_num_dirs(ctx->fs, &num_dirs); + if (retval) + num_dirs = 1024; /* Guess */ + ctx->dir_info_size = num_dirs + 10; + ctx->dir_info = (struct dir_info *) + e2fsck_allocate_memory(ctx, ctx->dir_info_size + * sizeof (struct dir_info), + "directory map"); + } + + if (ctx->dir_info_count >= ctx->dir_info_size) { + old_size = ctx->dir_info_size * sizeof(struct dir_info); + ctx->dir_info_size += 10; + retval = ext2fs_resize_mem(old_size, ctx->dir_info_size * + sizeof(struct dir_info), + &ctx->dir_info); + if (retval) { + ctx->dir_info_size -= 10; + return; + } + } + + /* + * Normally, add_dir_info is called with each inode in + * sequential order; but once in a while (like when pass 3 + * needs to recreate the root directory or lost+found + * directory) it is called out of order. In those cases, we + * need to move the dir_info entries down to make room, since + * the dir_info array needs to be sorted by inode number for + * get_dir_info()'s sake. + */ + if (ctx->dir_info_count && + ctx->dir_info[ctx->dir_info_count-1].ino >= ino) { + for (i = ctx->dir_info_count-1; i > 0; i--) + if (ctx->dir_info[i-1].ino < ino) + break; + dir = &ctx->dir_info[i]; + if (dir->ino != ino) + for (j = ctx->dir_info_count++; j > i; j--) + ctx->dir_info[j] = ctx->dir_info[j-1]; + } else + dir = &ctx->dir_info[ctx->dir_info_count++]; + + dir->ino = ino; + dir->dotdot = parent; + dir->parent = parent; +} + +/* + * get_dir_info() --- given an inode number, try to find the directory + * information entry for it. + */ +static struct dir_info *e2fsck_get_dir_info(e2fsck_t ctx, ext2_ino_t ino) +{ + int low, high, mid; + + low = 0; + high = ctx->dir_info_count-1; + if (!ctx->dir_info) + return 0; + if (ino == ctx->dir_info[low].ino) + return &ctx->dir_info[low]; + if (ino == ctx->dir_info[high].ino) + return &ctx->dir_info[high]; + + while (low < high) { + mid = (low+high)/2; + if (mid == low || mid == high) + break; + if (ino == ctx->dir_info[mid].ino) + return &ctx->dir_info[mid]; + if (ino < ctx->dir_info[mid].ino) + high = mid; + else + low = mid; + } + return 0; +} + +/* + * Free the dir_info structure when it isn't needed any more. + */ +static void e2fsck_free_dir_info(e2fsck_t ctx) +{ + ext2fs_free_mem(&ctx->dir_info); + ctx->dir_info_size = 0; + ctx->dir_info_count = 0; +} + +/* + * Return the count of number of directories in the dir_info structure + */ +static int e2fsck_get_num_dirinfo(e2fsck_t ctx) +{ + return ctx->dir_info_count; +} + +/* + * A simple interator function + */ +static struct dir_info *e2fsck_dir_info_iter(e2fsck_t ctx, int *control) +{ + if (*control >= ctx->dir_info_count) + return 0; + + return ctx->dir_info + (*control)++; +} + +/* + * dirinfo.c --- maintains the directory information table for e2fsck. + * + */ + +#ifdef ENABLE_HTREE + +/* + * This subroutine is called during pass1 to create a directory info + * entry. During pass1, the passed-in parent is 0; it will get filled + * in during pass2. + */ +static void e2fsck_add_dx_dir(e2fsck_t ctx, ext2_ino_t ino, int num_blocks) +{ + struct dx_dir_info *dir; + int i, j; + errcode_t retval; + unsigned long old_size; + + if (!ctx->dx_dir_info) { + ctx->dx_dir_info_count = 0; + ctx->dx_dir_info_size = 100; /* Guess */ + ctx->dx_dir_info = (struct dx_dir_info *) + e2fsck_allocate_memory(ctx, ctx->dx_dir_info_size + * sizeof (struct dx_dir_info), + "directory map"); + } + + if (ctx->dx_dir_info_count >= ctx->dx_dir_info_size) { + old_size = ctx->dx_dir_info_size * sizeof(struct dx_dir_info); + ctx->dx_dir_info_size += 10; + retval = ext2fs_resize_mem(old_size, ctx->dx_dir_info_size * + sizeof(struct dx_dir_info), + &ctx->dx_dir_info); + if (retval) { + ctx->dx_dir_info_size -= 10; + return; + } + } + + /* + * Normally, add_dx_dir_info is called with each inode in + * sequential order; but once in a while (like when pass 3 + * needs to recreate the root directory or lost+found + * directory) it is called out of order. In those cases, we + * need to move the dx_dir_info entries down to make room, since + * the dx_dir_info array needs to be sorted by inode number for + * get_dx_dir_info()'s sake. + */ + if (ctx->dx_dir_info_count && + ctx->dx_dir_info[ctx->dx_dir_info_count-1].ino >= ino) { + for (i = ctx->dx_dir_info_count-1; i > 0; i--) + if (ctx->dx_dir_info[i-1].ino < ino) + break; + dir = &ctx->dx_dir_info[i]; + if (dir->ino != ino) + for (j = ctx->dx_dir_info_count++; j > i; j--) + ctx->dx_dir_info[j] = ctx->dx_dir_info[j-1]; + } else + dir = &ctx->dx_dir_info[ctx->dx_dir_info_count++]; + + dir->ino = ino; + dir->numblocks = num_blocks; + dir->hashversion = 0; + dir->dx_block = e2fsck_allocate_memory(ctx, num_blocks + * sizeof (struct dx_dirblock_info), + "dx_block info array"); + +} + +/* + * get_dx_dir_info() --- given an inode number, try to find the directory + * information entry for it. + */ +static struct dx_dir_info *e2fsck_get_dx_dir_info(e2fsck_t ctx, ext2_ino_t ino) +{ + int low, high, mid; + + low = 0; + high = ctx->dx_dir_info_count-1; + if (!ctx->dx_dir_info) + return 0; + if (ino == ctx->dx_dir_info[low].ino) + return &ctx->dx_dir_info[low]; + if (ino == ctx->dx_dir_info[high].ino) + return &ctx->dx_dir_info[high]; + + while (low < high) { + mid = (low+high)/2; + if (mid == low || mid == high) + break; + if (ino == ctx->dx_dir_info[mid].ino) + return &ctx->dx_dir_info[mid]; + if (ino < ctx->dx_dir_info[mid].ino) + high = mid; + else + low = mid; + } + return 0; +} + +/* + * Free the dx_dir_info structure when it isn't needed any more. + */ +static void e2fsck_free_dx_dir_info(e2fsck_t ctx) +{ + int i; + struct dx_dir_info *dir; + + if (ctx->dx_dir_info) { + dir = ctx->dx_dir_info; + for (i=0; i < ctx->dx_dir_info_count; i++) { + ext2fs_free_mem(&dir->dx_block); + } + ext2fs_free_mem(&ctx->dx_dir_info); + } + ctx->dx_dir_info_size = 0; + ctx->dx_dir_info_count = 0; +} + +/* + * A simple interator function + */ +static struct dx_dir_info *e2fsck_dx_dir_info_iter(e2fsck_t ctx, int *control) +{ + if (*control >= ctx->dx_dir_info_count) + return 0; + + return ctx->dx_dir_info + (*control)++; +} + +#endif /* ENABLE_HTREE */ +/* + * e2fsck.c - a consistency checker for the new extended file system. + * + */ + +/* + * This function allocates an e2fsck context + */ +static errcode_t e2fsck_allocate_context(e2fsck_t *ret) +{ + e2fsck_t context; + errcode_t retval; + + retval = ext2fs_get_mem(sizeof(struct e2fsck_struct), &context); + if (retval) + return retval; + + memset(context, 0, sizeof(struct e2fsck_struct)); + + context->process_inode_size = 256; + context->ext_attr_ver = 2; + + *ret = context; + return 0; +} + +struct ea_refcount_el { + blk_t ea_blk; + int ea_count; +}; + +struct ea_refcount { + blk_t count; + blk_t size; + blk_t cursor; + struct ea_refcount_el *list; +}; + +static void ea_refcount_free(ext2_refcount_t refcount) +{ + if (!refcount) + return; + + ext2fs_free_mem(&refcount->list); + ext2fs_free_mem(&refcount); +} + +/* + * This function resets an e2fsck context; it is called when e2fsck + * needs to be restarted. + */ +static errcode_t e2fsck_reset_context(e2fsck_t ctx) +{ + ctx->flags = 0; + ctx->lost_and_found = 0; + ctx->bad_lost_and_found = 0; + ext2fs_free_inode_bitmap(ctx->inode_used_map); + ctx->inode_used_map = 0; + ext2fs_free_inode_bitmap(ctx->inode_dir_map); + ctx->inode_dir_map = 0; + ext2fs_free_inode_bitmap(ctx->inode_reg_map); + ctx->inode_reg_map = 0; + ext2fs_free_block_bitmap(ctx->block_found_map); + ctx->block_found_map = 0; + ext2fs_free_icount(ctx->inode_link_info); + ctx->inode_link_info = 0; + if (ctx->journal_io) { + if (ctx->fs && ctx->fs->io != ctx->journal_io) + io_channel_close(ctx->journal_io); + ctx->journal_io = 0; + } + if (ctx->fs) { + ext2fs_free_dblist(ctx->fs->dblist); + ctx->fs->dblist = 0; + } + e2fsck_free_dir_info(ctx); +#ifdef ENABLE_HTREE + e2fsck_free_dx_dir_info(ctx); +#endif + ea_refcount_free(ctx->refcount); + ctx->refcount = 0; + ea_refcount_free(ctx->refcount_extra); + ctx->refcount_extra = 0; + ext2fs_free_block_bitmap(ctx->block_dup_map); + ctx->block_dup_map = 0; + ext2fs_free_block_bitmap(ctx->block_ea_map); + ctx->block_ea_map = 0; + ext2fs_free_inode_bitmap(ctx->inode_bad_map); + ctx->inode_bad_map = 0; + ext2fs_free_inode_bitmap(ctx->inode_imagic_map); + ctx->inode_imagic_map = 0; + ext2fs_u32_list_free(ctx->dirs_to_hash); + ctx->dirs_to_hash = 0; + + /* + * Clear the array of invalid meta-data flags + */ + ext2fs_free_mem(&ctx->invalid_inode_bitmap_flag); + ext2fs_free_mem(&ctx->invalid_block_bitmap_flag); + ext2fs_free_mem(&ctx->invalid_inode_table_flag); + + /* Clear statistic counters */ + ctx->fs_directory_count = 0; + ctx->fs_regular_count = 0; + ctx->fs_blockdev_count = 0; + ctx->fs_chardev_count = 0; + ctx->fs_links_count = 0; + ctx->fs_symlinks_count = 0; + ctx->fs_fast_symlinks_count = 0; + ctx->fs_fifo_count = 0; + ctx->fs_total_count = 0; + ctx->fs_sockets_count = 0; + ctx->fs_ind_count = 0; + ctx->fs_dind_count = 0; + ctx->fs_tind_count = 0; + ctx->fs_fragmented = 0; + ctx->large_files = 0; + + /* Reset the superblock to the user's requested value */ + ctx->superblock = ctx->use_superblock; + + return 0; +} + +static void e2fsck_free_context(e2fsck_t ctx) +{ + if (!ctx) + return; + + e2fsck_reset_context(ctx); + if (ctx->blkid) + blkid_put_cache(ctx->blkid); + + ext2fs_free_mem(&ctx); +} + +/* + * ea_refcount.c + */ + +/* + * The strategy we use for keeping track of EA refcounts is as + * follows. We keep a sorted array of first EA blocks and its + * reference counts. Once the refcount has dropped to zero, it is + * removed from the array to save memory space. Once the EA block is + * checked, its bit is set in the block_ea_map bitmap. + */ + + +static errcode_t ea_refcount_create(int size, ext2_refcount_t *ret) +{ + ext2_refcount_t refcount; + errcode_t retval; + size_t bytes; + + retval = ext2fs_get_mem(sizeof(struct ea_refcount), &refcount); + if (retval) + return retval; + memset(refcount, 0, sizeof(struct ea_refcount)); + + if (!size) + size = 500; + refcount->size = size; + bytes = (size_t) (size * sizeof(struct ea_refcount_el)); +#ifdef DEBUG + printf("Refcount allocated %d entries, %d bytes.\n", + refcount->size, bytes); +#endif + retval = ext2fs_get_mem(bytes, &refcount->list); + if (retval) + goto errout; + memset(refcount->list, 0, bytes); + + refcount->count = 0; + refcount->cursor = 0; + + *ret = refcount; + return 0; + +errout: + ea_refcount_free(refcount); + return retval; +} + +/* + * collapse_refcount() --- go through the refcount array, and get rid + * of any count == zero entries + */ +static void refcount_collapse(ext2_refcount_t refcount) +{ + unsigned int i, j; + struct ea_refcount_el *list; + + list = refcount->list; + for (i = 0, j = 0; i < refcount->count; i++) { + if (list[i].ea_count) { + if (i != j) + list[j] = list[i]; + j++; + } + } +#if defined(DEBUG) || defined(TEST_PROGRAM) + printf("Refcount_collapse: size was %d, now %d\n", + refcount->count, j); +#endif + refcount->count = j; +} + + +/* + * insert_refcount_el() --- Insert a new entry into the sorted list at a + * specified position. + */ +static struct ea_refcount_el *insert_refcount_el(ext2_refcount_t refcount, + blk_t blk, int pos) +{ + struct ea_refcount_el *el; + errcode_t retval; + blk_t new_size = 0; + int num; + + if (refcount->count >= refcount->size) { + new_size = refcount->size + 100; +#ifdef DEBUG + printf("Reallocating refcount %d entries...\n", new_size); +#endif + retval = ext2fs_resize_mem((size_t) refcount->size * + sizeof(struct ea_refcount_el), + (size_t) new_size * + sizeof(struct ea_refcount_el), + &refcount->list); + if (retval) + return 0; + refcount->size = new_size; + } + num = (int) refcount->count - pos; + if (num < 0) + return 0; /* should never happen */ + if (num) { + memmove(&refcount->list[pos+1], &refcount->list[pos], + sizeof(struct ea_refcount_el) * num); + } + refcount->count++; + el = &refcount->list[pos]; + el->ea_count = 0; + el->ea_blk = blk; + return el; +} + + +/* + * get_refcount_el() --- given an block number, try to find refcount + * information in the sorted list. If the create flag is set, + * and we can't find an entry, create one in the sorted list. + */ +static struct ea_refcount_el *get_refcount_el(ext2_refcount_t refcount, + blk_t blk, int create) +{ + float range; + int low, high, mid; + blk_t lowval, highval; + + if (!refcount || !refcount->list) + return 0; +retry: + low = 0; + high = (int) refcount->count-1; + if (create && ((refcount->count == 0) || + (blk > refcount->list[high].ea_blk))) { + if (refcount->count >= refcount->size) + refcount_collapse(refcount); + + return insert_refcount_el(refcount, blk, + (unsigned) refcount->count); + } + if (refcount->count == 0) + return 0; + + if (refcount->cursor >= refcount->count) + refcount->cursor = 0; + if (blk == refcount->list[refcount->cursor].ea_blk) + return &refcount->list[refcount->cursor++]; +#ifdef DEBUG + printf("Non-cursor get_refcount_el: %u\n", blk); +#endif + while (low <= high) { + if (low == high) + mid = low; + else { + /* Interpolate for efficiency */ + lowval = refcount->list[low].ea_blk; + highval = refcount->list[high].ea_blk; + + if (blk < lowval) + range = 0; + else if (blk > highval) + range = 1; + else + range = ((float) (blk - lowval)) / + (highval - lowval); + mid = low + ((int) (range * (high-low))); + } + + if (blk == refcount->list[mid].ea_blk) { + refcount->cursor = mid+1; + return &refcount->list[mid]; + } + if (blk < refcount->list[mid].ea_blk) + high = mid-1; + else + low = mid+1; + } + /* + * If we need to create a new entry, it should be right at + * low (where high will be left at low-1). + */ + if (create) { + if (refcount->count >= refcount->size) { + refcount_collapse(refcount); + if (refcount->count < refcount->size) + goto retry; + } + return insert_refcount_el(refcount, blk, low); + } + return 0; +} + +static errcode_t +ea_refcount_increment(ext2_refcount_t refcount, blk_t blk, int *ret) +{ + struct ea_refcount_el *el; + + el = get_refcount_el(refcount, blk, 1); + if (!el) + return EXT2_ET_NO_MEMORY; + el->ea_count++; + + if (ret) + *ret = el->ea_count; + return 0; +} + +static errcode_t +ea_refcount_decrement(ext2_refcount_t refcount, blk_t blk, int *ret) +{ + struct ea_refcount_el *el; + + el = get_refcount_el(refcount, blk, 0); + if (!el || el->ea_count == 0) + return EXT2_ET_INVALID_ARGUMENT; + + el->ea_count--; + + if (ret) + *ret = el->ea_count; + return 0; +} + +static errcode_t +ea_refcount_store(ext2_refcount_t refcount, blk_t blk, int count) +{ + struct ea_refcount_el *el; + + /* + * Get the refcount element + */ + el = get_refcount_el(refcount, blk, count ? 1 : 0); + if (!el) + return count ? EXT2_ET_NO_MEMORY : 0; + el->ea_count = count; + return 0; +} + +static inline void ea_refcount_intr_begin(ext2_refcount_t refcount) +{ + refcount->cursor = 0; +} + + +static blk_t ea_refcount_intr_next(ext2_refcount_t refcount, int *ret) +{ + struct ea_refcount_el *list; + + while (1) { + if (refcount->cursor >= refcount->count) + return 0; + list = refcount->list; + if (list[refcount->cursor].ea_count) { + if (ret) + *ret = list[refcount->cursor].ea_count; + return list[refcount->cursor++].ea_blk; + } + refcount->cursor++; + } +} + + +/* + * ehandler.c --- handle bad block errors which come up during the + * course of an e2fsck session. + */ + + +static const char *operation; + +static errcode_t +e2fsck_handle_read_error(io_channel channel, unsigned long block, int count, + void *data, size_t size FSCK_ATTR((unused)), + int actual FSCK_ATTR((unused)), errcode_t error) +{ + int i; + char *p; + ext2_filsys fs = (ext2_filsys) channel->app_data; + e2fsck_t ctx; + + ctx = (e2fsck_t) fs->priv_data; + + /* + * If more than one block was read, try reading each block + * separately. We could use the actual bytes read to figure + * out where to start, but we don't bother. + */ + if (count > 1) { + p = (char *) data; + for (i=0; i < count; i++, p += channel->block_size, block++) { + error = io_channel_read_blk(channel, block, + 1, p); + if (error) + return error; + } + return 0; + } + if (operation) + printf(_("Error reading block %lu (%s) while %s. "), block, + error_message(error), operation); + else + printf(_("Error reading block %lu (%s). "), block, + error_message(error)); + preenhalt(ctx); + if (ask(ctx, _("Ignore error"), 1)) { + if (ask(ctx, _("Force rewrite"), 1)) + io_channel_write_blk(channel, block, 1, data); + return 0; + } + + return error; +} + +static errcode_t +e2fsck_handle_write_error(io_channel channel, unsigned long block, int count, + const void *data, size_t size FSCK_ATTR((unused)), + int actual FSCK_ATTR((unused)), errcode_t error) +{ + int i; + const char *p; + ext2_filsys fs = (ext2_filsys) channel->app_data; + e2fsck_t ctx; + + ctx = (e2fsck_t) fs->priv_data; + + /* + * If more than one block was written, try writing each block + * separately. We could use the actual bytes read to figure + * out where to start, but we don't bother. + */ + if (count > 1) { + p = (const char *) data; + for (i=0; i < count; i++, p += channel->block_size, block++) { + error = io_channel_write_blk(channel, block, + 1, p); + if (error) + return error; + } + return 0; + } + + if (operation) + printf(_("Error writing block %lu (%s) while %s. "), block, + error_message(error), operation); + else + printf(_("Error writing block %lu (%s). "), block, + error_message(error)); + preenhalt(ctx); + if (ask(ctx, _("Ignore error"), 1)) + return 0; + + return error; +} + +static const char *ehandler_operation(const char *op) +{ + const char *ret = operation; + + operation = op; + return ret; +} + +static void ehandler_init(io_channel channel) +{ + channel->read_error = e2fsck_handle_read_error; + channel->write_error = e2fsck_handle_write_error; +} + +/* + * journal.c --- code for handling the "ext3" journal + * + * Copyright (C) 2000 Andreas Dilger + * Copyright (C) 2000 Theodore Ts'o + * + * Parts of the code are based on fs/jfs/journal.c by Stephen C. Tweedie + * Copyright (C) 1999 Red Hat Software + * + * This file may be redistributed under the terms of the + * GNU General Public License version 2 or at your discretion + * any later version. + */ + +/* + * Define USE_INODE_IO to use the inode_io.c / fileio.c codepaths. + * This creates a larger static binary, and a smaller binary using + * shared libraries. It's also probably slightly less CPU-efficient, + * which is why it's not on by default. But, it's a good way of + * testing the functions in inode_io.c and fileio.c. + */ +#undef USE_INODE_IO + +/* Kernel compatibility functions for handling the journal. These allow us + * to use the recovery.c file virtually unchanged from the kernel, so we + * don't have to do much to keep kernel and user recovery in sync. + */ +static int journal_bmap(journal_t *journal, blk_t block, unsigned long *phys) +{ +#ifdef USE_INODE_IO + *phys = block; + return 0; +#else + struct inode *inode = journal->j_inode; + errcode_t retval; + blk_t pblk; + + if (!inode) { + *phys = block; + return 0; + } + + retval= ext2fs_bmap(inode->i_ctx->fs, inode->i_ino, + &inode->i_ext2, NULL, 0, block, &pblk); + *phys = pblk; + return retval; +#endif +} + +static struct buffer_head *getblk(kdev_t kdev, blk_t blocknr, int blocksize) +{ + struct buffer_head *bh; + + bh = e2fsck_allocate_memory(kdev->k_ctx, sizeof(*bh), "block buffer"); + if (!bh) + return NULL; + + bh->b_ctx = kdev->k_ctx; + if (kdev->k_dev == K_DEV_FS) + bh->b_io = kdev->k_ctx->fs->io; + else + bh->b_io = kdev->k_ctx->journal_io; + bh->b_size = blocksize; + bh->b_blocknr = blocknr; + + return bh; +} + +static void sync_blockdev(kdev_t kdev) +{ + io_channel io; + + if (kdev->k_dev == K_DEV_FS) + io = kdev->k_ctx->fs->io; + else + io = kdev->k_ctx->journal_io; + + io_channel_flush(io); +} + +static void ll_rw_block(int rw, int nr, struct buffer_head *bhp[]) +{ + int retval; + struct buffer_head *bh; + + for (; nr > 0; --nr) { + bh = *bhp++; + if (rw == READ && !bh->b_uptodate) { + retval = io_channel_read_blk(bh->b_io, + bh->b_blocknr, + 1, bh->b_data); + if (retval) { + bb_error_msg("while reading block %lu", + (unsigned long) bh->b_blocknr); + bh->b_err = retval; + continue; + } + bh->b_uptodate = 1; + } else if (rw == WRITE && bh->b_dirty) { + retval = io_channel_write_blk(bh->b_io, + bh->b_blocknr, + 1, bh->b_data); + if (retval) { + bb_error_msg("while writing block %lu", + (unsigned long) bh->b_blocknr); + bh->b_err = retval; + continue; + } + bh->b_dirty = 0; + bh->b_uptodate = 1; + } + } +} + +static void mark_buffer_dirty(struct buffer_head *bh) +{ + bh->b_dirty = 1; +} + +static inline void mark_buffer_clean(struct buffer_head * bh) +{ + bh->b_dirty = 0; +} + +static void brelse(struct buffer_head *bh) +{ + if (bh->b_dirty) + ll_rw_block(WRITE, 1, &bh); + ext2fs_free_mem(&bh); +} + +static int buffer_uptodate(struct buffer_head *bh) +{ + return bh->b_uptodate; +} + +static inline void mark_buffer_uptodate(struct buffer_head *bh, int val) +{ + bh->b_uptodate = val; +} + +static void wait_on_buffer(struct buffer_head *bh) +{ + if (!bh->b_uptodate) + ll_rw_block(READ, 1, &bh); +} + + +static void e2fsck_clear_recover(e2fsck_t ctx, int error) +{ + ctx->fs->super->s_feature_incompat &= ~EXT3_FEATURE_INCOMPAT_RECOVER; + + /* if we had an error doing journal recovery, we need a full fsck */ + if (error) + ctx->fs->super->s_state &= ~EXT2_VALID_FS; + ext2fs_mark_super_dirty(ctx->fs); +} + +static errcode_t e2fsck_get_journal(e2fsck_t ctx, journal_t **ret_journal) +{ + struct ext2_super_block *sb = ctx->fs->super; + struct ext2_super_block jsuper; + struct problem_context pctx; + struct buffer_head *bh; + struct inode *j_inode = NULL; + struct kdev_s *dev_fs = NULL, *dev_journal; + const char *journal_name = 0; + journal_t *journal = NULL; + errcode_t retval = 0; + io_manager io_ptr = 0; + unsigned long start = 0; + blk_t blk; + int ext_journal = 0; + int tried_backup_jnl = 0; + int i; + + clear_problem_context(&pctx); + + journal = e2fsck_allocate_memory(ctx, sizeof(journal_t), "journal"); + if (!journal) { + return EXT2_ET_NO_MEMORY; + } + + dev_fs = e2fsck_allocate_memory(ctx, 2*sizeof(struct kdev_s), "kdev"); + if (!dev_fs) { + retval = EXT2_ET_NO_MEMORY; + goto errout; + } + dev_journal = dev_fs+1; + + dev_fs->k_ctx = dev_journal->k_ctx = ctx; + dev_fs->k_dev = K_DEV_FS; + dev_journal->k_dev = K_DEV_JOURNAL; + + journal->j_dev = dev_journal; + journal->j_fs_dev = dev_fs; + journal->j_inode = NULL; + journal->j_blocksize = ctx->fs->blocksize; + + if (uuid_is_null(sb->s_journal_uuid)) { + if (!sb->s_journal_inum) + return EXT2_ET_BAD_INODE_NUM; + j_inode = e2fsck_allocate_memory(ctx, sizeof(*j_inode), + "journal inode"); + if (!j_inode) { + retval = EXT2_ET_NO_MEMORY; + goto errout; + } + + j_inode->i_ctx = ctx; + j_inode->i_ino = sb->s_journal_inum; + + if ((retval = ext2fs_read_inode(ctx->fs, + sb->s_journal_inum, + &j_inode->i_ext2))) { + try_backup_journal: + if (sb->s_jnl_backup_type != EXT3_JNL_BACKUP_BLOCKS || + tried_backup_jnl) + goto errout; + memset(&j_inode->i_ext2, 0, sizeof(struct ext2_inode)); + memcpy(&j_inode->i_ext2.i_block[0], sb->s_jnl_blocks, + EXT2_N_BLOCKS*4); + j_inode->i_ext2.i_size = sb->s_jnl_blocks[16]; + j_inode->i_ext2.i_links_count = 1; + j_inode->i_ext2.i_mode = LINUX_S_IFREG | 0600; + tried_backup_jnl++; + } + if (!j_inode->i_ext2.i_links_count || + !LINUX_S_ISREG(j_inode->i_ext2.i_mode)) { + retval = EXT2_ET_NO_JOURNAL; + goto try_backup_journal; + } + if (j_inode->i_ext2.i_size / journal->j_blocksize < + JFS_MIN_JOURNAL_BLOCKS) { + retval = EXT2_ET_JOURNAL_TOO_SMALL; + goto try_backup_journal; + } + for (i=0; i < EXT2_N_BLOCKS; i++) { + blk = j_inode->i_ext2.i_block[i]; + if (!blk) { + if (i < EXT2_NDIR_BLOCKS) { + retval = EXT2_ET_JOURNAL_TOO_SMALL; + goto try_backup_journal; + } + continue; + } + if (blk < sb->s_first_data_block || + blk >= sb->s_blocks_count) { + retval = EXT2_ET_BAD_BLOCK_NUM; + goto try_backup_journal; + } + } + journal->j_maxlen = j_inode->i_ext2.i_size / journal->j_blocksize; + +#ifdef USE_INODE_IO + retval = ext2fs_inode_io_intern2(ctx->fs, sb->s_journal_inum, + &j_inode->i_ext2, + &journal_name); + if (retval) + goto errout; + + io_ptr = inode_io_manager; +#else + journal->j_inode = j_inode; + ctx->journal_io = ctx->fs->io; + if ((retval = journal_bmap(journal, 0, &start)) != 0) + goto errout; +#endif + } else { + ext_journal = 1; + if (!ctx->journal_name) { + char uuid[37]; + + uuid_unparse(sb->s_journal_uuid, uuid); + ctx->journal_name = blkid_get_devname(ctx->blkid, + "UUID", uuid); + if (!ctx->journal_name) + ctx->journal_name = blkid_devno_to_devname(sb->s_journal_dev); + } + journal_name = ctx->journal_name; + + if (!journal_name) { + fix_problem(ctx, PR_0_CANT_FIND_JOURNAL, &pctx); + return EXT2_ET_LOAD_EXT_JOURNAL; + } + + io_ptr = unix_io_manager; + } + +#ifndef USE_INODE_IO + if (ext_journal) +#endif + retval = io_ptr->open(journal_name, IO_FLAG_RW, + &ctx->journal_io); + if (retval) + goto errout; + + io_channel_set_blksize(ctx->journal_io, ctx->fs->blocksize); + + if (ext_journal) { + if (ctx->fs->blocksize == 1024) + start = 1; + bh = getblk(dev_journal, start, ctx->fs->blocksize); + if (!bh) { + retval = EXT2_ET_NO_MEMORY; + goto errout; + } + ll_rw_block(READ, 1, &bh); + if ((retval = bh->b_err) != 0) + goto errout; + memcpy(&jsuper, start ? bh->b_data : bh->b_data + 1024, + sizeof(jsuper)); + brelse(bh); +#if BB_BIG_ENDIAN + if (jsuper.s_magic == ext2fs_swab16(EXT2_SUPER_MAGIC)) + ext2fs_swap_super(&jsuper); +#endif + if (jsuper.s_magic != EXT2_SUPER_MAGIC || + !(jsuper.s_feature_incompat & EXT3_FEATURE_INCOMPAT_JOURNAL_DEV)) { + fix_problem(ctx, PR_0_EXT_JOURNAL_BAD_SUPER, &pctx); + retval = EXT2_ET_LOAD_EXT_JOURNAL; + goto errout; + } + /* Make sure the journal UUID is correct */ + if (memcmp(jsuper.s_uuid, ctx->fs->super->s_journal_uuid, + sizeof(jsuper.s_uuid))) { + fix_problem(ctx, PR_0_JOURNAL_BAD_UUID, &pctx); + retval = EXT2_ET_LOAD_EXT_JOURNAL; + goto errout; + } + + journal->j_maxlen = jsuper.s_blocks_count; + start++; + } + + if (!(bh = getblk(dev_journal, start, journal->j_blocksize))) { + retval = EXT2_ET_NO_MEMORY; + goto errout; + } + + journal->j_sb_buffer = bh; + journal->j_superblock = (journal_superblock_t *)bh->b_data; + +#ifdef USE_INODE_IO + ext2fs_free_mem(&j_inode); +#endif + + *ret_journal = journal; + return 0; + +errout: + ext2fs_free_mem(&dev_fs); + ext2fs_free_mem(&j_inode); + ext2fs_free_mem(&journal); + return retval; + +} + +static errcode_t e2fsck_journal_fix_bad_inode(e2fsck_t ctx, + struct problem_context *pctx) +{ + struct ext2_super_block *sb = ctx->fs->super; + int recover = ctx->fs->super->s_feature_incompat & + EXT3_FEATURE_INCOMPAT_RECOVER; + int has_journal = ctx->fs->super->s_feature_compat & + EXT3_FEATURE_COMPAT_HAS_JOURNAL; + + if (has_journal || sb->s_journal_inum) { + /* The journal inode is bogus, remove and force full fsck */ + pctx->ino = sb->s_journal_inum; + if (fix_problem(ctx, PR_0_JOURNAL_BAD_INODE, pctx)) { + if (has_journal && sb->s_journal_inum) + printf("*** ext3 journal has been deleted - " + "filesystem is now ext2 only ***\n\n"); + sb->s_feature_compat &= ~EXT3_FEATURE_COMPAT_HAS_JOURNAL; + sb->s_journal_inum = 0; + ctx->flags |= E2F_FLAG_JOURNAL_INODE; /* FIXME: todo */ + e2fsck_clear_recover(ctx, 1); + return 0; + } + return EXT2_ET_BAD_INODE_NUM; + } else if (recover) { + if (fix_problem(ctx, PR_0_JOURNAL_RECOVER_SET, pctx)) { + e2fsck_clear_recover(ctx, 1); + return 0; + } + return EXT2_ET_UNSUPP_FEATURE; + } + return 0; +} + +#define V1_SB_SIZE 0x0024 +static void clear_v2_journal_fields(journal_t *journal) +{ + e2fsck_t ctx = journal->j_dev->k_ctx; + struct problem_context pctx; + + clear_problem_context(&pctx); + + if (!fix_problem(ctx, PR_0_CLEAR_V2_JOURNAL, &pctx)) + return; + + memset(((char *) journal->j_superblock) + V1_SB_SIZE, 0, + ctx->fs->blocksize-V1_SB_SIZE); + mark_buffer_dirty(journal->j_sb_buffer); +} + + +static errcode_t e2fsck_journal_load(journal_t *journal) +{ + e2fsck_t ctx = journal->j_dev->k_ctx; + journal_superblock_t *jsb; + struct buffer_head *jbh = journal->j_sb_buffer; + struct problem_context pctx; + + clear_problem_context(&pctx); + + ll_rw_block(READ, 1, &jbh); + if (jbh->b_err) { + bb_error_msg(_("reading journal superblock")); + return jbh->b_err; + } + + jsb = journal->j_superblock; + /* If we don't even have JFS_MAGIC, we probably have a wrong inode */ + if (jsb->s_header.h_magic != htonl(JFS_MAGIC_NUMBER)) + return e2fsck_journal_fix_bad_inode(ctx, &pctx); + + switch (ntohl(jsb->s_header.h_blocktype)) { + case JFS_SUPERBLOCK_V1: + journal->j_format_version = 1; + if (jsb->s_feature_compat || + jsb->s_feature_incompat || + jsb->s_feature_ro_compat || + jsb->s_nr_users) + clear_v2_journal_fields(journal); + break; + + case JFS_SUPERBLOCK_V2: + journal->j_format_version = 2; + if (ntohl(jsb->s_nr_users) > 1 && + uuid_is_null(ctx->fs->super->s_journal_uuid)) + clear_v2_journal_fields(journal); + if (ntohl(jsb->s_nr_users) > 1) { + fix_problem(ctx, PR_0_JOURNAL_UNSUPP_MULTIFS, &pctx); + return EXT2_ET_JOURNAL_UNSUPP_VERSION; + } + break; + + /* + * These should never appear in a journal super block, so if + * they do, the journal is badly corrupted. + */ + case JFS_DESCRIPTOR_BLOCK: + case JFS_COMMIT_BLOCK: + case JFS_REVOKE_BLOCK: + return EXT2_ET_CORRUPT_SUPERBLOCK; + + /* If we don't understand the superblock major type, but there + * is a magic number, then it is likely to be a new format we + * just don't understand, so leave it alone. */ + default: + return EXT2_ET_JOURNAL_UNSUPP_VERSION; + } + + if (JFS_HAS_INCOMPAT_FEATURE(journal, ~JFS_KNOWN_INCOMPAT_FEATURES)) + return EXT2_ET_UNSUPP_FEATURE; + + if (JFS_HAS_RO_COMPAT_FEATURE(journal, ~JFS_KNOWN_ROCOMPAT_FEATURES)) + return EXT2_ET_RO_UNSUPP_FEATURE; + + /* We have now checked whether we know enough about the journal + * format to be able to proceed safely, so any other checks that + * fail we should attempt to recover from. */ + if (jsb->s_blocksize != htonl(journal->j_blocksize)) { + bb_error_msg(_("%s: no valid journal superblock found"), + ctx->device_name); + return EXT2_ET_CORRUPT_SUPERBLOCK; + } + + if (ntohl(jsb->s_maxlen) < journal->j_maxlen) + journal->j_maxlen = ntohl(jsb->s_maxlen); + else if (ntohl(jsb->s_maxlen) > journal->j_maxlen) { + bb_error_msg(_("%s: journal too short"), + ctx->device_name); + return EXT2_ET_CORRUPT_SUPERBLOCK; + } + + journal->j_tail_sequence = ntohl(jsb->s_sequence); + journal->j_transaction_sequence = journal->j_tail_sequence; + journal->j_tail = ntohl(jsb->s_start); + journal->j_first = ntohl(jsb->s_first); + journal->j_last = ntohl(jsb->s_maxlen); + + return 0; +} + +static void e2fsck_journal_reset_super(e2fsck_t ctx, journal_superblock_t *jsb, + journal_t *journal) +{ + char *p; + union { + uuid_t uuid; + __u32 val[4]; + } u; + __u32 new_seq = 0; + int i; + + /* Leave a valid existing V1 superblock signature alone. + * Anything unrecognisable we overwrite with a new V2 + * signature. */ + + if (jsb->s_header.h_magic != htonl(JFS_MAGIC_NUMBER) || + jsb->s_header.h_blocktype != htonl(JFS_SUPERBLOCK_V1)) { + jsb->s_header.h_magic = htonl(JFS_MAGIC_NUMBER); + jsb->s_header.h_blocktype = htonl(JFS_SUPERBLOCK_V2); + } + + /* Zero out everything else beyond the superblock header */ + + p = ((char *) jsb) + sizeof(journal_header_t); + memset (p, 0, ctx->fs->blocksize-sizeof(journal_header_t)); + + jsb->s_blocksize = htonl(ctx->fs->blocksize); + jsb->s_maxlen = htonl(journal->j_maxlen); + jsb->s_first = htonl(1); + + /* Initialize the journal sequence number so that there is "no" + * chance we will find old "valid" transactions in the journal. + * This avoids the need to zero the whole journal (slow to do, + * and risky when we are just recovering the filesystem). + */ + uuid_generate(u.uuid); + for (i = 0; i < 4; i ++) + new_seq ^= u.val[i]; + jsb->s_sequence = htonl(new_seq); + + mark_buffer_dirty(journal->j_sb_buffer); + ll_rw_block(WRITE, 1, &journal->j_sb_buffer); +} + +static errcode_t e2fsck_journal_fix_corrupt_super(e2fsck_t ctx, + journal_t *journal, + struct problem_context *pctx) +{ + struct ext2_super_block *sb = ctx->fs->super; + int recover = ctx->fs->super->s_feature_incompat & + EXT3_FEATURE_INCOMPAT_RECOVER; + + if (sb->s_feature_compat & EXT3_FEATURE_COMPAT_HAS_JOURNAL) { + if (fix_problem(ctx, PR_0_JOURNAL_BAD_SUPER, pctx)) { + e2fsck_journal_reset_super(ctx, journal->j_superblock, + journal); + journal->j_transaction_sequence = 1; + e2fsck_clear_recover(ctx, recover); + return 0; + } + return EXT2_ET_CORRUPT_SUPERBLOCK; + } else if (e2fsck_journal_fix_bad_inode(ctx, pctx)) + return EXT2_ET_CORRUPT_SUPERBLOCK; + + return 0; +} + +static void e2fsck_journal_release(e2fsck_t ctx, journal_t *journal, + int reset, int drop) +{ + journal_superblock_t *jsb; + + if (drop) + mark_buffer_clean(journal->j_sb_buffer); + else if (!(ctx->options & E2F_OPT_READONLY)) { + jsb = journal->j_superblock; + jsb->s_sequence = htonl(journal->j_transaction_sequence); + if (reset) + jsb->s_start = 0; /* this marks the journal as empty */ + mark_buffer_dirty(journal->j_sb_buffer); + } + brelse(journal->j_sb_buffer); + + if (ctx->journal_io) { + if (ctx->fs && ctx->fs->io != ctx->journal_io) + io_channel_close(ctx->journal_io); + ctx->journal_io = 0; + } + +#ifndef USE_INODE_IO + ext2fs_free_mem(&journal->j_inode); +#endif + ext2fs_free_mem(&journal->j_fs_dev); + ext2fs_free_mem(&journal); +} + +/* + * This function makes sure that the superblock fields regarding the + * journal are consistent. + */ +static int e2fsck_check_ext3_journal(e2fsck_t ctx) +{ + struct ext2_super_block *sb = ctx->fs->super; + journal_t *journal; + int recover = ctx->fs->super->s_feature_incompat & + EXT3_FEATURE_INCOMPAT_RECOVER; + struct problem_context pctx; + problem_t problem; + int reset = 0, force_fsck = 0; + int retval; + + /* If we don't have any journal features, don't do anything more */ + if (!(sb->s_feature_compat & EXT3_FEATURE_COMPAT_HAS_JOURNAL) && + !recover && sb->s_journal_inum == 0 && sb->s_journal_dev == 0 && + uuid_is_null(sb->s_journal_uuid)) + return 0; + + clear_problem_context(&pctx); + pctx.num = sb->s_journal_inum; + + retval = e2fsck_get_journal(ctx, &journal); + if (retval) { + if ((retval == EXT2_ET_BAD_INODE_NUM) || + (retval == EXT2_ET_BAD_BLOCK_NUM) || + (retval == EXT2_ET_JOURNAL_TOO_SMALL) || + (retval == EXT2_ET_NO_JOURNAL)) + return e2fsck_journal_fix_bad_inode(ctx, &pctx); + return retval; + } + + retval = e2fsck_journal_load(journal); + if (retval) { + if ((retval == EXT2_ET_CORRUPT_SUPERBLOCK) || + ((retval == EXT2_ET_UNSUPP_FEATURE) && + (!fix_problem(ctx, PR_0_JOURNAL_UNSUPP_INCOMPAT, + &pctx))) || + ((retval == EXT2_ET_RO_UNSUPP_FEATURE) && + (!fix_problem(ctx, PR_0_JOURNAL_UNSUPP_ROCOMPAT, + &pctx))) || + ((retval == EXT2_ET_JOURNAL_UNSUPP_VERSION) && + (!fix_problem(ctx, PR_0_JOURNAL_UNSUPP_VERSION, &pctx)))) + retval = e2fsck_journal_fix_corrupt_super(ctx, journal, + &pctx); + e2fsck_journal_release(ctx, journal, 0, 1); + return retval; + } + + /* + * We want to make the flags consistent here. We will not leave with + * needs_recovery set but has_journal clear. We can't get in a loop + * with -y, -n, or -p, only if a user isn't making up their mind. + */ +no_has_journal: + if (!(sb->s_feature_compat & EXT3_FEATURE_COMPAT_HAS_JOURNAL)) { + recover = sb->s_feature_incompat & EXT3_FEATURE_INCOMPAT_RECOVER; + pctx.str = "inode"; + if (fix_problem(ctx, PR_0_JOURNAL_HAS_JOURNAL, &pctx)) { + if (recover && + !fix_problem(ctx, PR_0_JOURNAL_RECOVER_SET, &pctx)) + goto no_has_journal; + /* + * Need a full fsck if we are releasing a + * journal stored on a reserved inode. + */ + force_fsck = recover || + (sb->s_journal_inum < EXT2_FIRST_INODE(sb)); + /* Clear all of the journal fields */ + sb->s_journal_inum = 0; + sb->s_journal_dev = 0; + memset(sb->s_journal_uuid, 0, + sizeof(sb->s_journal_uuid)); + e2fsck_clear_recover(ctx, force_fsck); + } else if (!(ctx->options & E2F_OPT_READONLY)) { + sb->s_feature_compat |= EXT3_FEATURE_COMPAT_HAS_JOURNAL; + ext2fs_mark_super_dirty(ctx->fs); + } + } + + if (sb->s_feature_compat & EXT3_FEATURE_COMPAT_HAS_JOURNAL && + !(sb->s_feature_incompat & EXT3_FEATURE_INCOMPAT_RECOVER) && + journal->j_superblock->s_start != 0) { + /* Print status information */ + fix_problem(ctx, PR_0_JOURNAL_RECOVERY_CLEAR, &pctx); + if (ctx->superblock) + problem = PR_0_JOURNAL_RUN_DEFAULT; + else + problem = PR_0_JOURNAL_RUN; + if (fix_problem(ctx, problem, &pctx)) { + ctx->options |= E2F_OPT_FORCE; + sb->s_feature_incompat |= + EXT3_FEATURE_INCOMPAT_RECOVER; + ext2fs_mark_super_dirty(ctx->fs); + } else if (fix_problem(ctx, + PR_0_JOURNAL_RESET_JOURNAL, &pctx)) { + reset = 1; + sb->s_state &= ~EXT2_VALID_FS; + ext2fs_mark_super_dirty(ctx->fs); + } + /* + * If the user answers no to the above question, we + * ignore the fact that journal apparently has data; + * accidentally replaying over valid data would be far + * worse than skipping a questionable recovery. + * + * XXX should we abort with a fatal error here? What + * will the ext3 kernel code do if a filesystem with + * !NEEDS_RECOVERY but with a non-zero + * journal->j_superblock->s_start is mounted? + */ + } + + e2fsck_journal_release(ctx, journal, reset, 0); + return retval; +} + +static errcode_t recover_ext3_journal(e2fsck_t ctx) +{ + journal_t *journal; + int retval; + + journal_init_revoke_caches(); + retval = e2fsck_get_journal(ctx, &journal); + if (retval) + return retval; + + retval = e2fsck_journal_load(journal); + if (retval) + goto errout; + + retval = journal_init_revoke(journal, 1024); + if (retval) + goto errout; + + retval = -journal_recover(journal); + if (retval) + goto errout; + + if (journal->j_superblock->s_errno) { + ctx->fs->super->s_state |= EXT2_ERROR_FS; + ext2fs_mark_super_dirty(ctx->fs); + journal->j_superblock->s_errno = 0; + mark_buffer_dirty(journal->j_sb_buffer); + } + +errout: + journal_destroy_revoke(journal); + journal_destroy_revoke_caches(); + e2fsck_journal_release(ctx, journal, 1, 0); + return retval; +} + +static int e2fsck_run_ext3_journal(e2fsck_t ctx) +{ + io_manager io_ptr = ctx->fs->io->manager; + int blocksize = ctx->fs->blocksize; + errcode_t retval, recover_retval; + + printf(_("%s: recovering journal\n"), ctx->device_name); + if (ctx->options & E2F_OPT_READONLY) { + printf(_("%s: won't do journal recovery while read-only\n"), + ctx->device_name); + return EXT2_ET_FILE_RO; + } + + if (ctx->fs->flags & EXT2_FLAG_DIRTY) + ext2fs_flush(ctx->fs); /* Force out any modifications */ + + recover_retval = recover_ext3_journal(ctx); + + /* + * Reload the filesystem context to get up-to-date data from disk + * because journal recovery will change the filesystem under us. + */ + ext2fs_close(ctx->fs); + retval = ext2fs_open(ctx->filesystem_name, EXT2_FLAG_RW, + ctx->superblock, blocksize, io_ptr, + &ctx->fs); + + if (retval) { + bb_error_msg(_("while trying to re-open %s"), + ctx->device_name); + bb_error_msg_and_die(0); + } + ctx->fs->priv_data = ctx; + + /* Set the superblock flags */ + e2fsck_clear_recover(ctx, recover_retval); + return recover_retval; +} + +/* + * This function will move the journal inode from a visible file in + * the filesystem directory hierarchy to the reserved inode if necessary. + */ +static const char * const journal_names[] = { + ".journal", "journal", ".journal.dat", "journal.dat", 0 }; + +static void e2fsck_move_ext3_journal(e2fsck_t ctx) +{ + struct ext2_super_block *sb = ctx->fs->super; + struct problem_context pctx; + struct ext2_inode inode; + ext2_filsys fs = ctx->fs; + ext2_ino_t ino; + errcode_t retval; + const char * const * cpp; + int group, mount_flags; + + clear_problem_context(&pctx); + + /* + * If the filesystem is opened read-only, or there is no + * journal, then do nothing. + */ + if ((ctx->options & E2F_OPT_READONLY) || + (sb->s_journal_inum == 0) || + !(sb->s_feature_compat & EXT3_FEATURE_COMPAT_HAS_JOURNAL)) + return; + + /* + * Read in the journal inode + */ + if (ext2fs_read_inode(fs, sb->s_journal_inum, &inode) != 0) + return; + + /* + * If it's necessary to backup the journal inode, do so. + */ + if ((sb->s_jnl_backup_type == 0) || + ((sb->s_jnl_backup_type == EXT3_JNL_BACKUP_BLOCKS) && + memcmp(inode.i_block, sb->s_jnl_blocks, EXT2_N_BLOCKS*4))) { + if (fix_problem(ctx, PR_0_BACKUP_JNL, &pctx)) { + memcpy(sb->s_jnl_blocks, inode.i_block, + EXT2_N_BLOCKS*4); + sb->s_jnl_blocks[16] = inode.i_size; + sb->s_jnl_backup_type = EXT3_JNL_BACKUP_BLOCKS; + ext2fs_mark_super_dirty(fs); + fs->flags &= ~EXT2_FLAG_MASTER_SB_ONLY; + } + } + + /* + * If the journal is already the hidden inode, then do nothing + */ + if (sb->s_journal_inum == EXT2_JOURNAL_INO) + return; + + /* + * The journal inode had better have only one link and not be readable. + */ + if (inode.i_links_count != 1) + return; + + /* + * If the filesystem is mounted, or we can't tell whether + * or not it's mounted, do nothing. + */ + retval = ext2fs_check_if_mounted(ctx->filesystem_name, &mount_flags); + if (retval || (mount_flags & EXT2_MF_MOUNTED)) + return; + + /* + * If we can't find the name of the journal inode, then do + * nothing. + */ + for (cpp = journal_names; *cpp; cpp++) { + retval = ext2fs_lookup(fs, EXT2_ROOT_INO, *cpp, + strlen(*cpp), 0, &ino); + if ((retval == 0) && (ino == sb->s_journal_inum)) + break; + } + if (*cpp == 0) + return; + + /* We need the inode bitmap to be loaded */ + retval = ext2fs_read_bitmaps(fs); + if (retval) + return; + + pctx.str = *cpp; + if (!fix_problem(ctx, PR_0_MOVE_JOURNAL, &pctx)) + return; + + /* + * OK, we've done all the checks, let's actually move the + * journal inode. Errors at this point mean we need to force + * an ext2 filesystem check. + */ + if ((retval = ext2fs_unlink(fs, EXT2_ROOT_INO, *cpp, ino, 0)) != 0) + goto err_out; + if ((retval = ext2fs_write_inode(fs, EXT2_JOURNAL_INO, &inode)) != 0) + goto err_out; + sb->s_journal_inum = EXT2_JOURNAL_INO; + ext2fs_mark_super_dirty(fs); + fs->flags &= ~EXT2_FLAG_MASTER_SB_ONLY; + inode.i_links_count = 0; + inode.i_dtime = time(0); + if ((retval = ext2fs_write_inode(fs, ino, &inode)) != 0) + goto err_out; + + group = ext2fs_group_of_ino(fs, ino); + ext2fs_unmark_inode_bitmap(fs->inode_map, ino); + ext2fs_mark_ib_dirty(fs); + fs->group_desc[group].bg_free_inodes_count++; + fs->super->s_free_inodes_count++; + return; + +err_out: + pctx.errcode = retval; + fix_problem(ctx, PR_0_ERR_MOVE_JOURNAL, &pctx); + fs->super->s_state &= ~EXT2_VALID_FS; + ext2fs_mark_super_dirty(fs); + return; +} + +/* + * message.c --- print e2fsck messages (with compression) + * + * print_e2fsck_message() prints a message to the user, using + * compression techniques and expansions of abbreviations. + * + * The following % expansions are supported: + * + * %b block number + * %B integer + * %c block number + * %Di ->ino inode number + * %Dn ->name string + * %Dr ->rec_len + * %Dl ->name_len + * %Dt ->filetype + * %d

    inode number + * %g integer + * %i inode number + * %Is -> i_size + * %IS -> i_extra_isize + * %Ib -> i_blocks + * %Il -> i_links_count + * %Im -> i_mode + * %IM -> i_mtime + * %IF -> i_faddr + * %If -> i_file_acl + * %Id -> i_dir_acl + * %Iu -> i_uid + * %Ig -> i_gid + * %j inode number + * %m + * %N + * %p ext2fs_get_pathname of directory + * %P ext2fs_get_pathname of ->ino with as + * the containing directory. (If dirent is NULL + * then return the pathname of directory ) + * %q ext2fs_get_pathname of directory + * %Q ext2fs_get_pathname of directory with as + * the containing directory. + * %s miscellaneous string + * %S backup superblock + * %X hexadecimal format + * + * The following '@' expansions are supported: + * + * @a extended attribute + * @A error allocating + * @b block + * @B bitmap + * @c compress + * @C conflicts with some other fs block + * @D deleted + * @d directory + * @e entry + * @E Entry '%Dn' in %p (%i) + * @f filesystem + * @F for @i %i (%Q) is + * @g group + * @h HTREE directory inode + * @i inode + * @I illegal + * @j journal + * @l lost+found + * @L is a link + * @m multiply-claimed + * @n invalid + * @o orphaned + * @p problem in + * @r root inode + * @s should be + * @S superblock + * @u unattached + * @v device + * @z zero-length + */ + + +/* + * This structure defines the abbreviations used by the text strings + * below. The first character in the string is the index letter. An + * abbreviation of the form '@' is expanded by looking up the index + * letter in the table below. + */ +static const char * const abbrevs[] = { + N_("aextended attribute"), + N_("Aerror allocating"), + N_("bblock"), + N_("Bbitmap"), + N_("ccompress"), + N_("Cconflicts with some other fs @b"), + N_("iinode"), + N_("Iillegal"), + N_("jjournal"), + N_("Ddeleted"), + N_("ddirectory"), + N_("eentry"), + N_("E@e '%Dn' in %p (%i)"), + N_("ffilesystem"), + N_("Ffor @i %i (%Q) is"), + N_("ggroup"), + N_("hHTREE @d @i"), + N_("llost+found"), + N_("Lis a link"), + N_("mmultiply-claimed"), + N_("ninvalid"), + N_("oorphaned"), + N_("pproblem in"), + N_("rroot @i"), + N_("sshould be"), + N_("Ssuper@b"), + N_("uunattached"), + N_("vdevice"), + N_("zzero-length"), + "@@", + 0 + }; + +/* + * Give more user friendly names to the "special" inodes. + */ +#define num_special_inodes 11 +static const char * const special_inode_name[] = +{ + N_(""), /* 0 */ + N_(""), /* 1 */ + "/", /* 2 */ + N_(""), /* 3 */ + N_(""), /* 4 */ + N_(""), /* 5 */ + N_(""), /* 6 */ + N_(""), /* 7 */ + N_(""), /* 8 */ + N_(""), /* 9 */ + N_(""), /* 10 */ +}; + +/* + * This function does "safe" printing. It will convert non-printable + * ASCII characters using '^' and M- notation. + */ +static void safe_print(const char *cp, int len) +{ + unsigned char ch; + + if (len < 0) + len = strlen(cp); + + while (len--) { + ch = *cp++; + if (ch > 128) { + fputs("M-", stdout); + ch -= 128; + } + if ((ch < 32) || (ch == 0x7f)) { + fputc('^', stdout); + ch ^= 0x40; /* ^@, ^A, ^B; ^? for DEL */ + } + fputc(ch, stdout); + } +} + + +/* + * This function prints a pathname, using the ext2fs_get_pathname + * function + */ +static void print_pathname(ext2_filsys fs, ext2_ino_t dir, ext2_ino_t ino) +{ + errcode_t retval; + char *path; + + if (!dir && (ino < num_special_inodes)) { + fputs(_(special_inode_name[ino]), stdout); + return; + } + + retval = ext2fs_get_pathname(fs, dir, ino, &path); + if (retval) + fputs("???", stdout); + else { + safe_print(path, -1); + ext2fs_free_mem(&path); + } +} + +static void print_e2fsck_message(e2fsck_t ctx, const char *msg, + struct problem_context *pctx, int first); +/* + * This function handles the '@' expansion. We allow recursive + * expansion; an @ expression can contain further '@' and '%' + * expressions. + */ +static void expand_at_expression(e2fsck_t ctx, char ch, + struct problem_context *pctx, + int *first) +{ + const char * const *cpp; + const char *str; + + /* Search for the abbreviation */ + for (cpp = abbrevs; *cpp; cpp++) { + if (ch == *cpp[0]) + break; + } + if (*cpp) { + str = _(*cpp) + 1; + if (*first && islower(*str)) { + *first = 0; + fputc(toupper(*str++), stdout); + } + print_e2fsck_message(ctx, str, pctx, *first); + } else + printf("@%c", ch); +} + +/* + * This function expands '%IX' expressions + */ +static void expand_inode_expression(char ch, + struct problem_context *ctx) +{ + struct ext2_inode *inode; + struct ext2_inode_large *large_inode; + char * time_str; + time_t t; + int do_gmt = -1; + + if (!ctx || !ctx->inode) + goto no_inode; + + inode = ctx->inode; + large_inode = (struct ext2_inode_large *) inode; + + switch (ch) { + case 's': + if (LINUX_S_ISDIR(inode->i_mode)) + printf("%u", inode->i_size); + else { + printf("%"PRIu64, (inode->i_size | + ((uint64_t) inode->i_size_high << 32))); + } + break; + case 'S': + printf("%u", large_inode->i_extra_isize); + break; + case 'b': + printf("%u", inode->i_blocks); + break; + case 'l': + printf("%d", inode->i_links_count); + break; + case 'm': + printf("0%o", inode->i_mode); + break; + case 'M': + /* The diet libc doesn't respect the TZ environemnt variable */ + if (do_gmt == -1) { + time_str = getenv("TZ"); + if (!time_str) + time_str = ""; + do_gmt = !strcmp(time_str, "GMT"); + } + t = inode->i_mtime; + time_str = asctime(do_gmt ? gmtime(&t) : localtime(&t)); + printf("%.24s", time_str); + break; + case 'F': + printf("%u", inode->i_faddr); + break; + case 'f': + printf("%u", inode->i_file_acl); + break; + case 'd': + printf("%u", (LINUX_S_ISDIR(inode->i_mode) ? + inode->i_dir_acl : 0)); + break; + case 'u': + printf("%d", (inode->i_uid | + (inode->osd2.linux2.l_i_uid_high << 16))); + break; + case 'g': + printf("%d", (inode->i_gid | + (inode->osd2.linux2.l_i_gid_high << 16))); + break; + default: + no_inode: + printf("%%I%c", ch); + break; + } +} + +/* + * This function expands '%dX' expressions + */ +static void expand_dirent_expression(char ch, + struct problem_context *ctx) +{ + struct ext2_dir_entry *dirent; + int len; + + if (!ctx || !ctx->dirent) + goto no_dirent; + + dirent = ctx->dirent; + + switch (ch) { + case 'i': + printf("%u", dirent->inode); + break; + case 'n': + len = dirent->name_len & 0xFF; + if (len > EXT2_NAME_LEN) + len = EXT2_NAME_LEN; + if (len > dirent->rec_len) + len = dirent->rec_len; + safe_print(dirent->name, len); + break; + case 'r': + printf("%u", dirent->rec_len); + break; + case 'l': + printf("%u", dirent->name_len & 0xFF); + break; + case 't': + printf("%u", dirent->name_len >> 8); + break; + default: + no_dirent: + printf("%%D%c", ch); + break; + } +} + +static void expand_percent_expression(ext2_filsys fs, char ch, + struct problem_context *ctx) +{ + if (!ctx) + goto no_context; + + switch (ch) { + case '%': + fputc('%', stdout); + break; + case 'b': + printf("%u", ctx->blk); + break; + case 'B': + printf("%"PRIi64, ctx->blkcount); + break; + case 'c': + printf("%u", ctx->blk2); + break; + case 'd': + printf("%u", ctx->dir); + break; + case 'g': + printf("%d", ctx->group); + break; + case 'i': + printf("%u", ctx->ino); + break; + case 'j': + printf("%u", ctx->ino2); + break; + case 'm': + printf("%s", error_message(ctx->errcode)); + break; + case 'N': + printf("%"PRIi64, ctx->num); + break; + case 'p': + print_pathname(fs, ctx->ino, 0); + break; + case 'P': + print_pathname(fs, ctx->ino2, + ctx->dirent ? ctx->dirent->inode : 0); + break; + case 'q': + print_pathname(fs, ctx->dir, 0); + break; + case 'Q': + print_pathname(fs, ctx->dir, ctx->ino); + break; + case 'S': + printf("%d", get_backup_sb(NULL, fs, NULL, NULL)); + break; + case 's': + printf("%s", ctx->str ? ctx->str : "NULL"); + break; + case 'X': + printf("0x%"PRIi64, ctx->num); + break; + default: + no_context: + printf("%%%c", ch); + break; + } +} + + +static void print_e2fsck_message(e2fsck_t ctx, const char *msg, + struct problem_context *pctx, int first) +{ + ext2_filsys fs = ctx->fs; + const char * cp; + int i; + + e2fsck_clear_progbar(ctx); + for (cp = msg; *cp; cp++) { + if (cp[0] == '@') { + cp++; + expand_at_expression(ctx, *cp, pctx, &first); + } else if (cp[0] == '%' && cp[1] == 'I') { + cp += 2; + expand_inode_expression(*cp, pctx); + } else if (cp[0] == '%' && cp[1] == 'D') { + cp += 2; + expand_dirent_expression(*cp, pctx); + } else if ((cp[0] == '%')) { + cp++; + expand_percent_expression(fs, *cp, pctx); + } else { + for (i=0; cp[i]; i++) + if ((cp[i] == '@') || cp[i] == '%') + break; + printf("%.*s", i, cp); + cp += i-1; + } + first = 0; + } +} + + +/* + * region.c --- code which manages allocations within a region. + */ + +struct region_el { + region_addr_t start; + region_addr_t end; + struct region_el *next; +}; + +struct region_struct { + region_addr_t min; + region_addr_t max; + struct region_el *allocated; +}; + +static region_t region_create(region_addr_t min, region_addr_t max) +{ + region_t region; + + region = malloc(sizeof(struct region_struct)); + if (!region) + return NULL; + memset(region, 0, sizeof(struct region_struct)); + region->min = min; + region->max = max; + return region; +} + +static void region_free(region_t region) +{ + struct region_el *r, *next; + + for (r = region->allocated; r; r = next) { + next = r->next; + free(r); + } + memset(region, 0, sizeof(struct region_struct)); + free(region); +} + +static int region_allocate(region_t region, region_addr_t start, int n) +{ + struct region_el *r, *new_region, *prev, *next; + region_addr_t end; + + end = start+n; + if ((start < region->min) || (end > region->max)) + return -1; + if (n == 0) + return 1; + + /* + * Search through the linked list. If we find that it + * conflicts witih something that's already allocated, return + * 1; if we can find an existing region which we can grow, do + * so. Otherwise, stop when we find the appropriate place + * insert a new region element into the linked list. + */ + for (r = region->allocated, prev=NULL; r; prev = r, r = r->next) { + if (((start >= r->start) && (start < r->end)) || + ((end > r->start) && (end <= r->end)) || + ((start <= r->start) && (end >= r->end))) + return 1; + if (end == r->start) { + r->start = start; + return 0; + } + if (start == r->end) { + if ((next = r->next)) { + if (end > next->start) + return 1; + if (end == next->start) { + r->end = next->end; + r->next = next->next; + free(next); + return 0; + } + } + r->end = end; + return 0; + } + if (start < r->start) + break; + } + /* + * Insert a new region element structure into the linked list + */ + new_region = malloc(sizeof(struct region_el)); + if (!new_region) + return -1; + new_region->start = start; + new_region->end = start + n; + new_region->next = r; + if (prev) + prev->next = new_region; + else + region->allocated = new_region; + return 0; +} + +/* + * pass1.c -- pass #1 of e2fsck: sequential scan of the inode table + * + * Pass 1 of e2fsck iterates over all the inodes in the filesystems, + * and applies the following tests to each inode: + * + * - The mode field of the inode must be legal. + * - The size and block count fields of the inode are correct. + * - A data block must not be used by another inode + * + * Pass 1 also gathers the collects the following information: + * + * - A bitmap of which inodes are in use. (inode_used_map) + * - A bitmap of which inodes are directories. (inode_dir_map) + * - A bitmap of which inodes are regular files. (inode_reg_map) + * - A bitmap of which inodes have bad fields. (inode_bad_map) + * - A bitmap of which inodes are imagic inodes. (inode_imagic_map) + * - A bitmap of which blocks are in use. (block_found_map) + * - A bitmap of which blocks are in use by two inodes (block_dup_map) + * - The data blocks of the directory inodes. (dir_map) + * + * Pass 1 is designed to stash away enough information so that the + * other passes should not need to read in the inode information + * during the normal course of a filesystem check. (Althogh if an + * inconsistency is detected, other passes may need to read in an + * inode to fix it.) + * + * Note that pass 1B will be invoked if there are any duplicate blocks + * found. + */ + + +static int process_block(ext2_filsys fs, blk_t *blocknr, + e2_blkcnt_t blockcnt, blk_t ref_blk, + int ref_offset, void *priv_data); +static int process_bad_block(ext2_filsys fs, blk_t *block_nr, + e2_blkcnt_t blockcnt, blk_t ref_blk, + int ref_offset, void *priv_data); +static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, + char *block_buf); +static void mark_table_blocks(e2fsck_t ctx); +static void alloc_imagic_map(e2fsck_t ctx); +static void mark_inode_bad(e2fsck_t ctx, ino_t ino); +static void handle_fs_bad_blocks(e2fsck_t ctx); +static void process_inodes(e2fsck_t ctx, char *block_buf); +static int process_inode_cmp(const void *a, const void *b); +static errcode_t scan_callback(ext2_filsys fs, + dgrp_t group, void * priv_data); +static void adjust_extattr_refcount(e2fsck_t ctx, ext2_refcount_t refcount, + char *block_buf, int adjust_sign); +/* static char *describe_illegal_block(ext2_filsys fs, blk_t block); */ + +static void e2fsck_write_inode_full(e2fsck_t ctx, unsigned long ino, + struct ext2_inode * inode, int bufsize, + const char *proc); + +struct process_block_struct_1 { + ext2_ino_t ino; + unsigned is_dir:1, is_reg:1, clear:1, suppress:1, + fragmented:1, compressed:1, bbcheck:1; + blk_t num_blocks; + blk_t max_blocks; + e2_blkcnt_t last_block; + int num_illegal_blocks; + blk_t previous_block; + struct ext2_inode *inode; + struct problem_context *pctx; + ext2fs_block_bitmap fs_meta_blocks; + e2fsck_t ctx; +}; + +struct process_inode_block { + ext2_ino_t ino; + struct ext2_inode inode; +}; + +struct scan_callback_struct { + e2fsck_t ctx; + char *block_buf; +}; + +/* + * For the inodes to process list. + */ +static struct process_inode_block *inodes_to_process; +static int process_inode_count; + +static __u64 ext2_max_sizes[EXT2_MAX_BLOCK_LOG_SIZE - + EXT2_MIN_BLOCK_LOG_SIZE + 1]; + +/* + * Free all memory allocated by pass1 in preparation for restarting + * things. + */ +static void unwind_pass1(void) +{ + ext2fs_free_mem(&inodes_to_process); +} + +/* + * Check to make sure a device inode is real. Returns 1 if the device + * checks out, 0 if not. + * + * Note: this routine is now also used to check FIFO's and Sockets, + * since they have the same requirement; the i_block fields should be + * zero. + */ +static int +e2fsck_pass1_check_device_inode(ext2_filsys fs, struct ext2_inode *inode) +{ + int i; + + /* + * If i_blocks is non-zero, or the index flag is set, then + * this is a bogus device/fifo/socket + */ + if ((ext2fs_inode_data_blocks(fs, inode) != 0) || + (inode->i_flags & EXT2_INDEX_FL)) + return 0; + + /* + * We should be able to do the test below all the time, but + * because the kernel doesn't forcibly clear the device + * inode's additional i_block fields, there are some rare + * occasions when a legitimate device inode will have non-zero + * additional i_block fields. So for now, we only complain + * when the immutable flag is set, which should never happen + * for devices. (And that's when the problem is caused, since + * you can't set or clear immutable flags for devices.) Once + * the kernel has been fixed we can change this... + */ + if (inode->i_flags & (EXT2_IMMUTABLE_FL | EXT2_APPEND_FL)) { + for (i=4; i < EXT2_N_BLOCKS; i++) + if (inode->i_block[i]) + return 0; + } + return 1; +} + +/* + * Check to make sure a symlink inode is real. Returns 1 if the symlink + * checks out, 0 if not. + */ +static int +e2fsck_pass1_check_symlink(ext2_filsys fs, struct ext2_inode *inode, char *buf) +{ + unsigned int len; + int i; + blk_t blocks; + + if ((inode->i_size_high || inode->i_size == 0) || + (inode->i_flags & EXT2_INDEX_FL)) + return 0; + + blocks = ext2fs_inode_data_blocks(fs, inode); + if (blocks) { + if ((inode->i_size >= fs->blocksize) || + (blocks != fs->blocksize >> 9) || + (inode->i_block[0] < fs->super->s_first_data_block) || + (inode->i_block[0] >= fs->super->s_blocks_count)) + return 0; + + for (i = 1; i < EXT2_N_BLOCKS; i++) + if (inode->i_block[i]) + return 0; + + if (io_channel_read_blk(fs->io, inode->i_block[0], 1, buf)) + return 0; + + len = strnlen(buf, fs->blocksize); + if (len == fs->blocksize) + return 0; + } else { + if (inode->i_size >= sizeof(inode->i_block)) + return 0; + + len = strnlen((char *)inode->i_block, sizeof(inode->i_block)); + if (len == sizeof(inode->i_block)) + return 0; + } + if (len != inode->i_size) + return 0; + return 1; +} + +/* + * If the immutable (or append-only) flag is set on the inode, offer + * to clear it. + */ +#define BAD_SPECIAL_FLAGS (EXT2_IMMUTABLE_FL | EXT2_APPEND_FL) +static void check_immutable(e2fsck_t ctx, struct problem_context *pctx) +{ + if (!(pctx->inode->i_flags & BAD_SPECIAL_FLAGS)) + return; + + if (!fix_problem(ctx, PR_1_SET_IMMUTABLE, pctx)) + return; + + pctx->inode->i_flags &= ~BAD_SPECIAL_FLAGS; + e2fsck_write_inode(ctx, pctx->ino, pctx->inode, "pass1"); +} + +/* + * If device, fifo or socket, check size is zero -- if not offer to + * clear it + */ +static void check_size(e2fsck_t ctx, struct problem_context *pctx) +{ + struct ext2_inode *inode = pctx->inode; + + if ((inode->i_size == 0) && (inode->i_size_high == 0)) + return; + + if (!fix_problem(ctx, PR_1_SET_NONZSIZE, pctx)) + return; + + inode->i_size = 0; + inode->i_size_high = 0; + e2fsck_write_inode(ctx, pctx->ino, pctx->inode, "pass1"); +} + +static void check_ea_in_inode(e2fsck_t ctx, struct problem_context *pctx) +{ + struct ext2_super_block *sb = ctx->fs->super; + struct ext2_inode_large *inode; + struct ext2_ext_attr_entry *entry; + char *start, *end; + int storage_size, remain, offs; + int problem = 0; + + inode = (struct ext2_inode_large *) pctx->inode; + storage_size = EXT2_INODE_SIZE(ctx->fs->super) - EXT2_GOOD_OLD_INODE_SIZE - + inode->i_extra_isize; + start = ((char *) inode) + EXT2_GOOD_OLD_INODE_SIZE + + inode->i_extra_isize + sizeof(__u32); + end = (char *) inode + EXT2_INODE_SIZE(ctx->fs->super); + entry = (struct ext2_ext_attr_entry *) start; + + /* scan all entry's headers first */ + + /* take finish entry 0UL into account */ + remain = storage_size - sizeof(__u32); + offs = end - start; + + while (!EXT2_EXT_IS_LAST_ENTRY(entry)) { + + /* header eats this space */ + remain -= sizeof(struct ext2_ext_attr_entry); + + /* is attribute name valid? */ + if (EXT2_EXT_ATTR_SIZE(entry->e_name_len) > remain) { + pctx->num = entry->e_name_len; + problem = PR_1_ATTR_NAME_LEN; + goto fix; + } + + /* attribute len eats this space */ + remain -= EXT2_EXT_ATTR_SIZE(entry->e_name_len); + + /* check value size */ + if (entry->e_value_size == 0 || entry->e_value_size > remain) { + pctx->num = entry->e_value_size; + problem = PR_1_ATTR_VALUE_SIZE; + goto fix; + } + + /* check value placement */ + if (entry->e_value_offs + + EXT2_XATTR_SIZE(entry->e_value_size) != offs) { + printf("(entry->e_value_offs + entry->e_value_size: %d, offs: %d)\n", entry->e_value_offs + entry->e_value_size, offs); + pctx->num = entry->e_value_offs; + problem = PR_1_ATTR_VALUE_OFFSET; + goto fix; + } + + /* e_value_block must be 0 in inode's ea */ + if (entry->e_value_block != 0) { + pctx->num = entry->e_value_block; + problem = PR_1_ATTR_VALUE_BLOCK; + goto fix; + } + + /* e_hash must be 0 in inode's ea */ + if (entry->e_hash != 0) { + pctx->num = entry->e_hash; + problem = PR_1_ATTR_HASH; + goto fix; + } + + remain -= entry->e_value_size; + offs -= EXT2_XATTR_SIZE(entry->e_value_size); + + entry = EXT2_EXT_ATTR_NEXT(entry); + } +fix: + /* + * it seems like a corruption. it's very unlikely we could repair + * EA(s) in automatic fashion -bzzz + */ + if (problem == 0 || !fix_problem(ctx, problem, pctx)) + return; + + /* simple remove all possible EA(s) */ + *((__u32 *)start) = 0UL; + e2fsck_write_inode_full(ctx, pctx->ino, (struct ext2_inode *)inode, + EXT2_INODE_SIZE(sb), "pass1"); +} + +static void check_inode_extra_space(e2fsck_t ctx, struct problem_context *pctx) +{ + struct ext2_super_block *sb = ctx->fs->super; + struct ext2_inode_large *inode; + __u32 *eamagic; + int min, max; + + inode = (struct ext2_inode_large *) pctx->inode; + if (EXT2_INODE_SIZE(sb) == EXT2_GOOD_OLD_INODE_SIZE) { + /* this isn't large inode. so, nothing to check */ + return; + } + + /* i_extra_isize must cover i_extra_isize + i_pad1 at least */ + min = sizeof(inode->i_extra_isize) + sizeof(inode->i_pad1); + max = EXT2_INODE_SIZE(sb) - EXT2_GOOD_OLD_INODE_SIZE; + /* + * For now we will allow i_extra_isize to be 0, but really + * implementations should never allow i_extra_isize to be 0 + */ + if (inode->i_extra_isize && + (inode->i_extra_isize < min || inode->i_extra_isize > max)) { + if (!fix_problem(ctx, PR_1_EXTRA_ISIZE, pctx)) + return; + inode->i_extra_isize = min; + e2fsck_write_inode_full(ctx, pctx->ino, pctx->inode, + EXT2_INODE_SIZE(sb), "pass1"); + return; + } + + eamagic = (__u32 *) (((char *) inode) + EXT2_GOOD_OLD_INODE_SIZE + + inode->i_extra_isize); + if (*eamagic == EXT2_EXT_ATTR_MAGIC) { + /* it seems inode has an extended attribute(s) in body */ + check_ea_in_inode(ctx, pctx); + } +} + +static void e2fsck_pass1(e2fsck_t ctx) +{ + int i; + __u64 max_sizes; + ext2_filsys fs = ctx->fs; + ext2_ino_t ino; + struct ext2_inode *inode; + ext2_inode_scan scan; + char *block_buf; + unsigned char frag, fsize; + struct problem_context pctx; + struct scan_callback_struct scan_struct; + struct ext2_super_block *sb = ctx->fs->super; + int imagic_fs; + int busted_fs_time = 0; + int inode_size; + + clear_problem_context(&pctx); + + if (!(ctx->options & E2F_OPT_PREEN)) + fix_problem(ctx, PR_1_PASS_HEADER, &pctx); + + if ((fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX) && + !(ctx->options & E2F_OPT_NO)) { + if (ext2fs_u32_list_create(&ctx->dirs_to_hash, 50)) + ctx->dirs_to_hash = 0; + } + + /* Pass 1 */ + +#define EXT2_BPP(bits) (1ULL << ((bits) - 2)) + + for (i = EXT2_MIN_BLOCK_LOG_SIZE; i <= EXT2_MAX_BLOCK_LOG_SIZE; i++) { + max_sizes = EXT2_NDIR_BLOCKS + EXT2_BPP(i); + max_sizes = max_sizes + EXT2_BPP(i) * EXT2_BPP(i); + max_sizes = max_sizes + EXT2_BPP(i) * EXT2_BPP(i) * EXT2_BPP(i); + max_sizes = (max_sizes * (1UL << i)) - 1; + ext2_max_sizes[i - EXT2_MIN_BLOCK_LOG_SIZE] = max_sizes; + } +#undef EXT2_BPP + + imagic_fs = (sb->s_feature_compat & EXT2_FEATURE_COMPAT_IMAGIC_INODES); + + /* + * Allocate bitmaps structures + */ + pctx.errcode = ext2fs_allocate_inode_bitmap(fs, _("in-use inode map"), + &ctx->inode_used_map); + if (pctx.errcode) { + pctx.num = 1; + fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + pctx.errcode = ext2fs_allocate_inode_bitmap(fs, + _("directory inode map"), &ctx->inode_dir_map); + if (pctx.errcode) { + pctx.num = 2; + fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + pctx.errcode = ext2fs_allocate_inode_bitmap(fs, + _("regular file inode map"), &ctx->inode_reg_map); + if (pctx.errcode) { + pctx.num = 6; + fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + pctx.errcode = ext2fs_allocate_block_bitmap(fs, _("in-use block map"), + &ctx->block_found_map); + if (pctx.errcode) { + pctx.num = 1; + fix_problem(ctx, PR_1_ALLOCATE_BBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + pctx.errcode = ext2fs_create_icount2(fs, 0, 0, 0, + &ctx->inode_link_info); + if (pctx.errcode) { + fix_problem(ctx, PR_1_ALLOCATE_ICOUNT, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + inode_size = EXT2_INODE_SIZE(fs->super); + inode = (struct ext2_inode *) + e2fsck_allocate_memory(ctx, inode_size, "scratch inode"); + + inodes_to_process = (struct process_inode_block *) + e2fsck_allocate_memory(ctx, + (ctx->process_inode_size * + sizeof(struct process_inode_block)), + "array of inodes to process"); + process_inode_count = 0; + + pctx.errcode = ext2fs_init_dblist(fs, 0); + if (pctx.errcode) { + fix_problem(ctx, PR_1_ALLOCATE_DBCOUNT, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + + /* + * If the last orphan field is set, clear it, since the pass1 + * processing will automatically find and clear the orphans. + * In the future, we may want to try using the last_orphan + * linked list ourselves, but for now, we clear it so that the + * ext3 mount code won't get confused. + */ + if (!(ctx->options & E2F_OPT_READONLY)) { + if (fs->super->s_last_orphan) { + fs->super->s_last_orphan = 0; + ext2fs_mark_super_dirty(fs); + } + } + + mark_table_blocks(ctx); + block_buf = (char *) e2fsck_allocate_memory(ctx, fs->blocksize * 3, + "block interate buffer"); + e2fsck_use_inode_shortcuts(ctx, 1); + ehandler_operation(_("doing inode scan")); + pctx.errcode = ext2fs_open_inode_scan(fs, ctx->inode_buffer_blocks, + &scan); + if (pctx.errcode) { + fix_problem(ctx, PR_1_ISCAN_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + ext2fs_inode_scan_flags(scan, EXT2_SF_SKIP_MISSING_ITABLE, 0); + ctx->stashed_inode = inode; + scan_struct.ctx = ctx; + scan_struct.block_buf = block_buf; + ext2fs_set_inode_callback(scan, scan_callback, &scan_struct); + if (ctx->progress) + if ((ctx->progress)(ctx, 1, 0, ctx->fs->group_desc_count)) + return; + if ((fs->super->s_wtime < fs->super->s_inodes_count) || + (fs->super->s_mtime < fs->super->s_inodes_count)) + busted_fs_time = 1; + + while (1) { + pctx.errcode = ext2fs_get_next_inode_full(scan, &ino, + inode, inode_size); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + if (pctx.errcode == EXT2_ET_BAD_BLOCK_IN_INODE_TABLE) { + continue; + } + if (pctx.errcode) { + fix_problem(ctx, PR_1_ISCAN_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + if (!ino) + break; + pctx.ino = ino; + pctx.inode = inode; + ctx->stashed_ino = ino; + if (inode->i_links_count) { + pctx.errcode = ext2fs_icount_store(ctx->inode_link_info, + ino, inode->i_links_count); + if (pctx.errcode) { + pctx.num = inode->i_links_count; + fix_problem(ctx, PR_1_ICOUNT_STORE, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + } + if (ino == EXT2_BAD_INO) { + struct process_block_struct_1 pb; + + pctx.errcode = ext2fs_copy_bitmap(ctx->block_found_map, + &pb.fs_meta_blocks); + if (pctx.errcode) { + pctx.num = 4; + fix_problem(ctx, PR_1_ALLOCATE_BBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + pb.ino = EXT2_BAD_INO; + pb.num_blocks = pb.last_block = 0; + pb.num_illegal_blocks = 0; + pb.suppress = 0; pb.clear = 0; pb.is_dir = 0; + pb.is_reg = 0; pb.fragmented = 0; pb.bbcheck = 0; + pb.inode = inode; + pb.pctx = &pctx; + pb.ctx = ctx; + pctx.errcode = ext2fs_block_iterate2(fs, ino, 0, + block_buf, process_bad_block, &pb); + ext2fs_free_block_bitmap(pb.fs_meta_blocks); + if (pctx.errcode) { + fix_problem(ctx, PR_1_BLOCK_ITERATE, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + if (pb.bbcheck) + if (!fix_problem(ctx, PR_1_BBINODE_BAD_METABLOCK_PROMPT, &pctx)) { + ctx->flags |= E2F_FLAG_ABORT; + return; + } + ext2fs_mark_inode_bitmap(ctx->inode_used_map, ino); + clear_problem_context(&pctx); + continue; + } else if (ino == EXT2_ROOT_INO) { + /* + * Make sure the root inode is a directory; if + * not, offer to clear it. It will be + * regnerated in pass #3. + */ + if (!LINUX_S_ISDIR(inode->i_mode)) { + if (fix_problem(ctx, PR_1_ROOT_NO_DIR, &pctx)) { + inode->i_dtime = time(0); + inode->i_links_count = 0; + ext2fs_icount_store(ctx->inode_link_info, + ino, 0); + e2fsck_write_inode(ctx, ino, inode, + "pass1"); + } + + } + /* + * If dtime is set, offer to clear it. mke2fs + * version 0.2b created filesystems with the + * dtime field set for the root and lost+found + * directories. We won't worry about + * /lost+found, since that can be regenerated + * easily. But we will fix the root directory + * as a special case. + */ + if (inode->i_dtime && inode->i_links_count) { + if (fix_problem(ctx, PR_1_ROOT_DTIME, &pctx)) { + inode->i_dtime = 0; + e2fsck_write_inode(ctx, ino, inode, + "pass1"); + } + } + } else if (ino == EXT2_JOURNAL_INO) { + ext2fs_mark_inode_bitmap(ctx->inode_used_map, ino); + if (fs->super->s_journal_inum == EXT2_JOURNAL_INO) { + if (!LINUX_S_ISREG(inode->i_mode) && + fix_problem(ctx, PR_1_JOURNAL_BAD_MODE, + &pctx)) { + inode->i_mode = LINUX_S_IFREG; + e2fsck_write_inode(ctx, ino, inode, + "pass1"); + } + check_blocks(ctx, &pctx, block_buf); + continue; + } + if ((inode->i_links_count || inode->i_blocks || + inode->i_blocks || inode->i_block[0]) && + fix_problem(ctx, PR_1_JOURNAL_INODE_NOT_CLEAR, + &pctx)) { + memset(inode, 0, inode_size); + ext2fs_icount_store(ctx->inode_link_info, + ino, 0); + e2fsck_write_inode_full(ctx, ino, inode, + inode_size, "pass1"); + } + } else if (ino < EXT2_FIRST_INODE(fs->super)) { + int problem = 0; + + ext2fs_mark_inode_bitmap(ctx->inode_used_map, ino); + if (ino == EXT2_BOOT_LOADER_INO) { + if (LINUX_S_ISDIR(inode->i_mode)) + problem = PR_1_RESERVED_BAD_MODE; + } else if (ino == EXT2_RESIZE_INO) { + if (inode->i_mode && + !LINUX_S_ISREG(inode->i_mode)) + problem = PR_1_RESERVED_BAD_MODE; + } else { + if (inode->i_mode != 0) + problem = PR_1_RESERVED_BAD_MODE; + } + if (problem) { + if (fix_problem(ctx, problem, &pctx)) { + inode->i_mode = 0; + e2fsck_write_inode(ctx, ino, inode, + "pass1"); + } + } + check_blocks(ctx, &pctx, block_buf); + continue; + } + /* + * Check for inodes who might have been part of the + * orphaned list linked list. They should have gotten + * dealt with by now, unless the list had somehow been + * corrupted. + * + * FIXME: In the future, inodes which are still in use + * (and which are therefore) pending truncation should + * be handled specially. Right now we just clear the + * dtime field, and the normal e2fsck handling of + * inodes where i_size and the inode blocks are + * inconsistent is to fix i_size, instead of releasing + * the extra blocks. This won't catch the inodes that + * was at the end of the orphan list, but it's better + * than nothing. The right answer is that there + * shouldn't be any bugs in the orphan list handling. :-) + */ + if (inode->i_dtime && !busted_fs_time && + inode->i_dtime < ctx->fs->super->s_inodes_count) { + if (fix_problem(ctx, PR_1_LOW_DTIME, &pctx)) { + inode->i_dtime = inode->i_links_count ? + 0 : time(0); + e2fsck_write_inode(ctx, ino, inode, + "pass1"); + } + } + + /* + * This code assumes that deleted inodes have + * i_links_count set to 0. + */ + if (!inode->i_links_count) { + if (!inode->i_dtime && inode->i_mode) { + if (fix_problem(ctx, + PR_1_ZERO_DTIME, &pctx)) { + inode->i_dtime = time(0); + e2fsck_write_inode(ctx, ino, inode, + "pass1"); + } + } + continue; + } + /* + * n.b. 0.3c ext2fs code didn't clear i_links_count for + * deleted files. Oops. + * + * Since all new ext2 implementations get this right, + * we now assume that the case of non-zero + * i_links_count and non-zero dtime means that we + * should keep the file, not delete it. + * + */ + if (inode->i_dtime) { + if (fix_problem(ctx, PR_1_SET_DTIME, &pctx)) { + inode->i_dtime = 0; + e2fsck_write_inode(ctx, ino, inode, "pass1"); + } + } + + ext2fs_mark_inode_bitmap(ctx->inode_used_map, ino); + switch (fs->super->s_creator_os) { + case EXT2_OS_LINUX: + frag = inode->osd2.linux2.l_i_frag; + fsize = inode->osd2.linux2.l_i_fsize; + break; + case EXT2_OS_HURD: + frag = inode->osd2.hurd2.h_i_frag; + fsize = inode->osd2.hurd2.h_i_fsize; + break; + case EXT2_OS_MASIX: + frag = inode->osd2.masix2.m_i_frag; + fsize = inode->osd2.masix2.m_i_fsize; + break; + default: + frag = fsize = 0; + } + + if (inode->i_faddr || frag || fsize || + (LINUX_S_ISDIR(inode->i_mode) && inode->i_dir_acl)) + mark_inode_bad(ctx, ino); + if (inode->i_flags & EXT2_IMAGIC_FL) { + if (imagic_fs) { + if (!ctx->inode_imagic_map) + alloc_imagic_map(ctx); + ext2fs_mark_inode_bitmap(ctx->inode_imagic_map, + ino); + } else { + if (fix_problem(ctx, PR_1_SET_IMAGIC, &pctx)) { + inode->i_flags &= ~EXT2_IMAGIC_FL; + e2fsck_write_inode(ctx, ino, + inode, "pass1"); + } + } + } + + check_inode_extra_space(ctx, &pctx); + + if (LINUX_S_ISDIR(inode->i_mode)) { + ext2fs_mark_inode_bitmap(ctx->inode_dir_map, ino); + e2fsck_add_dir_info(ctx, ino, 0); + ctx->fs_directory_count++; + } else if (LINUX_S_ISREG (inode->i_mode)) { + ext2fs_mark_inode_bitmap(ctx->inode_reg_map, ino); + ctx->fs_regular_count++; + } else if (LINUX_S_ISCHR (inode->i_mode) && + e2fsck_pass1_check_device_inode(fs, inode)) { + check_immutable(ctx, &pctx); + check_size(ctx, &pctx); + ctx->fs_chardev_count++; + } else if (LINUX_S_ISBLK (inode->i_mode) && + e2fsck_pass1_check_device_inode(fs, inode)) { + check_immutable(ctx, &pctx); + check_size(ctx, &pctx); + ctx->fs_blockdev_count++; + } else if (LINUX_S_ISLNK (inode->i_mode) && + e2fsck_pass1_check_symlink(fs, inode, block_buf)) { + check_immutable(ctx, &pctx); + ctx->fs_symlinks_count++; + if (ext2fs_inode_data_blocks(fs, inode) == 0) { + ctx->fs_fast_symlinks_count++; + check_blocks(ctx, &pctx, block_buf); + continue; + } + } + else if (LINUX_S_ISFIFO (inode->i_mode) && + e2fsck_pass1_check_device_inode(fs, inode)) { + check_immutable(ctx, &pctx); + check_size(ctx, &pctx); + ctx->fs_fifo_count++; + } else if ((LINUX_S_ISSOCK (inode->i_mode)) && + e2fsck_pass1_check_device_inode(fs, inode)) { + check_immutable(ctx, &pctx); + check_size(ctx, &pctx); + ctx->fs_sockets_count++; + } else + mark_inode_bad(ctx, ino); + if (inode->i_block[EXT2_IND_BLOCK]) + ctx->fs_ind_count++; + if (inode->i_block[EXT2_DIND_BLOCK]) + ctx->fs_dind_count++; + if (inode->i_block[EXT2_TIND_BLOCK]) + ctx->fs_tind_count++; + if (inode->i_block[EXT2_IND_BLOCK] || + inode->i_block[EXT2_DIND_BLOCK] || + inode->i_block[EXT2_TIND_BLOCK] || + inode->i_file_acl) { + inodes_to_process[process_inode_count].ino = ino; + inodes_to_process[process_inode_count].inode = *inode; + process_inode_count++; + } else + check_blocks(ctx, &pctx, block_buf); + + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + + if (process_inode_count >= ctx->process_inode_size) { + process_inodes(ctx, block_buf); + + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + } + } + process_inodes(ctx, block_buf); + ext2fs_close_inode_scan(scan); + ehandler_operation(0); + + /* + * If any extended attribute blocks' reference counts need to + * be adjusted, either up (ctx->refcount_extra), or down + * (ctx->refcount), then fix them. + */ + if (ctx->refcount) { + adjust_extattr_refcount(ctx, ctx->refcount, block_buf, -1); + ea_refcount_free(ctx->refcount); + ctx->refcount = 0; + } + if (ctx->refcount_extra) { + adjust_extattr_refcount(ctx, ctx->refcount_extra, + block_buf, +1); + ea_refcount_free(ctx->refcount_extra); + ctx->refcount_extra = 0; + } + + if (ctx->invalid_bitmaps) + handle_fs_bad_blocks(ctx); + + /* We don't need the block_ea_map any more */ + ext2fs_free_block_bitmap(ctx->block_ea_map); + ctx->block_ea_map = 0; + + if (ctx->flags & E2F_FLAG_RESIZE_INODE) { + ext2fs_block_bitmap save_bmap; + + save_bmap = fs->block_map; + fs->block_map = ctx->block_found_map; + clear_problem_context(&pctx); + pctx.errcode = ext2fs_create_resize_inode(fs); + if (pctx.errcode) { + fix_problem(ctx, PR_1_RESIZE_INODE_CREATE, &pctx); + /* Should never get here */ + ctx->flags |= E2F_FLAG_ABORT; + return; + } + e2fsck_read_inode(ctx, EXT2_RESIZE_INO, inode, + "recreate inode"); + inode->i_mtime = time(0); + e2fsck_write_inode(ctx, EXT2_RESIZE_INO, inode, + "recreate inode"); + fs->block_map = save_bmap; + ctx->flags &= ~E2F_FLAG_RESIZE_INODE; + } + + if (ctx->flags & E2F_FLAG_RESTART) { + /* + * Only the master copy of the superblock and block + * group descriptors are going to be written during a + * restart, so set the superblock to be used to be the + * master superblock. + */ + ctx->use_superblock = 0; + unwind_pass1(); + goto endit; + } + + if (ctx->block_dup_map) { + if (ctx->options & E2F_OPT_PREEN) { + clear_problem_context(&pctx); + fix_problem(ctx, PR_1_DUP_BLOCKS_PREENSTOP, &pctx); + } + e2fsck_pass1_dupblocks(ctx, block_buf); + } + ext2fs_free_mem(&inodes_to_process); +endit: + e2fsck_use_inode_shortcuts(ctx, 0); + + ext2fs_free_mem(&block_buf); + ext2fs_free_mem(&inode); + +} + +/* + * When the inode_scan routines call this callback at the end of the + * glock group, call process_inodes. + */ +static errcode_t scan_callback(ext2_filsys fs, + dgrp_t group, void * priv_data) +{ + struct scan_callback_struct *scan_struct; + e2fsck_t ctx; + + scan_struct = (struct scan_callback_struct *) priv_data; + ctx = scan_struct->ctx; + + process_inodes((e2fsck_t) fs->priv_data, scan_struct->block_buf); + + if (ctx->progress) + if ((ctx->progress)(ctx, 1, group+1, + ctx->fs->group_desc_count)) + return EXT2_ET_CANCEL_REQUESTED; + + return 0; +} + +/* + * Process the inodes in the "inodes to process" list. + */ +static void process_inodes(e2fsck_t ctx, char *block_buf) +{ + int i; + struct ext2_inode *old_stashed_inode; + ext2_ino_t old_stashed_ino; + const char *old_operation; + char buf[80]; + struct problem_context pctx; + + /* begin process_inodes */ + if (process_inode_count == 0) + return; + old_operation = ehandler_operation(0); + old_stashed_inode = ctx->stashed_inode; + old_stashed_ino = ctx->stashed_ino; + qsort(inodes_to_process, process_inode_count, + sizeof(struct process_inode_block), process_inode_cmp); + clear_problem_context(&pctx); + for (i=0; i < process_inode_count; i++) { + pctx.inode = ctx->stashed_inode = &inodes_to_process[i].inode; + pctx.ino = ctx->stashed_ino = inodes_to_process[i].ino; + sprintf(buf, _("reading indirect blocks of inode %u"), + pctx.ino); + ehandler_operation(buf); + check_blocks(ctx, &pctx, block_buf); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + break; + } + ctx->stashed_inode = old_stashed_inode; + ctx->stashed_ino = old_stashed_ino; + process_inode_count = 0; + /* end process inodes */ + + ehandler_operation(old_operation); +} + +static int process_inode_cmp(const void *a, const void *b) +{ + const struct process_inode_block *ib_a = + (const struct process_inode_block *) a; + const struct process_inode_block *ib_b = + (const struct process_inode_block *) b; + int ret; + + ret = (ib_a->inode.i_block[EXT2_IND_BLOCK] - + ib_b->inode.i_block[EXT2_IND_BLOCK]); + if (ret == 0) + ret = ib_a->inode.i_file_acl - ib_b->inode.i_file_acl; + return ret; +} + +/* + * Mark an inode as being bad in some what + */ +static void mark_inode_bad(e2fsck_t ctx, ino_t ino) +{ + struct problem_context pctx; + + if (!ctx->inode_bad_map) { + clear_problem_context(&pctx); + + pctx.errcode = ext2fs_allocate_inode_bitmap(ctx->fs, + _("bad inode map"), &ctx->inode_bad_map); + if (pctx.errcode) { + pctx.num = 3; + fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx); + /* Should never get here */ + ctx->flags |= E2F_FLAG_ABORT; + return; + } + } + ext2fs_mark_inode_bitmap(ctx->inode_bad_map, ino); +} + + +/* + * This procedure will allocate the inode imagic table + */ +static void alloc_imagic_map(e2fsck_t ctx) +{ + struct problem_context pctx; + + clear_problem_context(&pctx); + pctx.errcode = ext2fs_allocate_inode_bitmap(ctx->fs, + _("imagic inode map"), + &ctx->inode_imagic_map); + if (pctx.errcode) { + pctx.num = 5; + fix_problem(ctx, PR_1_ALLOCATE_IBITMAP_ERROR, &pctx); + /* Should never get here */ + ctx->flags |= E2F_FLAG_ABORT; + return; + } +} + +/* + * Marks a block as in use, setting the dup_map if it's been set + * already. Called by process_block and process_bad_block. + * + * WARNING: Assumes checks have already been done to make sure block + * is valid. This is true in both process_block and process_bad_block. + */ +static void mark_block_used(e2fsck_t ctx, blk_t block) +{ + struct problem_context pctx; + + clear_problem_context(&pctx); + + if (ext2fs_fast_test_block_bitmap(ctx->block_found_map, block)) { + if (!ctx->block_dup_map) { + pctx.errcode = ext2fs_allocate_block_bitmap(ctx->fs, + _("multiply claimed block map"), + &ctx->block_dup_map); + if (pctx.errcode) { + pctx.num = 3; + fix_problem(ctx, PR_1_ALLOCATE_BBITMAP_ERROR, + &pctx); + /* Should never get here */ + ctx->flags |= E2F_FLAG_ABORT; + return; + } + } + ext2fs_fast_mark_block_bitmap(ctx->block_dup_map, block); + } else { + ext2fs_fast_mark_block_bitmap(ctx->block_found_map, block); + } +} + +/* + * Adjust the extended attribute block's reference counts at the end + * of pass 1, either by subtracting out references for EA blocks that + * are still referenced in ctx->refcount, or by adding references for + * EA blocks that had extra references as accounted for in + * ctx->refcount_extra. + */ +static void adjust_extattr_refcount(e2fsck_t ctx, ext2_refcount_t refcount, + char *block_buf, int adjust_sign) +{ + struct ext2_ext_attr_header *header; + struct problem_context pctx; + ext2_filsys fs = ctx->fs; + blk_t blk; + __u32 should_be; + int count; + + clear_problem_context(&pctx); + + ea_refcount_intr_begin(refcount); + while (1) { + if ((blk = ea_refcount_intr_next(refcount, &count)) == 0) + break; + pctx.blk = blk; + pctx.errcode = ext2fs_read_ext_attr(fs, blk, block_buf); + if (pctx.errcode) { + fix_problem(ctx, PR_1_EXTATTR_READ_ABORT, &pctx); + return; + } + header = (struct ext2_ext_attr_header *) block_buf; + pctx.blkcount = header->h_refcount; + should_be = header->h_refcount + adjust_sign * count; + pctx.num = should_be; + if (fix_problem(ctx, PR_1_EXTATTR_REFCOUNT, &pctx)) { + header->h_refcount = should_be; + pctx.errcode = ext2fs_write_ext_attr(fs, blk, + block_buf); + if (pctx.errcode) { + fix_problem(ctx, PR_1_EXTATTR_WRITE, &pctx); + continue; + } + } + } +} + +/* + * Handle processing the extended attribute blocks + */ +static int check_ext_attr(e2fsck_t ctx, struct problem_context *pctx, + char *block_buf) +{ + ext2_filsys fs = ctx->fs; + ext2_ino_t ino = pctx->ino; + struct ext2_inode *inode = pctx->inode; + blk_t blk; + char * end; + struct ext2_ext_attr_header *header; + struct ext2_ext_attr_entry *entry; + int count; + region_t region; + + blk = inode->i_file_acl; + if (blk == 0) + return 0; + + /* + * If the Extended attribute flag isn't set, then a non-zero + * file acl means that the inode is corrupted. + * + * Or if the extended attribute block is an invalid block, + * then the inode is also corrupted. + */ + if (!(fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_EXT_ATTR) || + (blk < fs->super->s_first_data_block) || + (blk >= fs->super->s_blocks_count)) { + mark_inode_bad(ctx, ino); + return 0; + } + + /* If ea bitmap hasn't been allocated, create it */ + if (!ctx->block_ea_map) { + pctx->errcode = ext2fs_allocate_block_bitmap(fs, + _("ext attr block map"), + &ctx->block_ea_map); + if (pctx->errcode) { + pctx->num = 2; + fix_problem(ctx, PR_1_ALLOCATE_BBITMAP_ERROR, pctx); + ctx->flags |= E2F_FLAG_ABORT; + return 0; + } + } + + /* Create the EA refcount structure if necessary */ + if (!ctx->refcount) { + pctx->errcode = ea_refcount_create(0, &ctx->refcount); + if (pctx->errcode) { + pctx->num = 1; + fix_problem(ctx, PR_1_ALLOCATE_REFCOUNT, pctx); + ctx->flags |= E2F_FLAG_ABORT; + return 0; + } + } + + /* Have we seen this EA block before? */ + if (ext2fs_fast_test_block_bitmap(ctx->block_ea_map, blk)) { + if (ea_refcount_decrement(ctx->refcount, blk, 0) == 0) + return 1; + /* Ooops, this EA was referenced more than it stated */ + if (!ctx->refcount_extra) { + pctx->errcode = ea_refcount_create(0, + &ctx->refcount_extra); + if (pctx->errcode) { + pctx->num = 2; + fix_problem(ctx, PR_1_ALLOCATE_REFCOUNT, pctx); + ctx->flags |= E2F_FLAG_ABORT; + return 0; + } + } + ea_refcount_increment(ctx->refcount_extra, blk, 0); + return 1; + } + + /* + * OK, we haven't seen this EA block yet. So we need to + * validate it + */ + pctx->blk = blk; + pctx->errcode = ext2fs_read_ext_attr(fs, blk, block_buf); + if (pctx->errcode && fix_problem(ctx, PR_1_READ_EA_BLOCK, pctx)) + goto clear_extattr; + header = (struct ext2_ext_attr_header *) block_buf; + pctx->blk = inode->i_file_acl; + if (((ctx->ext_attr_ver == 1) && + (header->h_magic != EXT2_EXT_ATTR_MAGIC_v1)) || + ((ctx->ext_attr_ver == 2) && + (header->h_magic != EXT2_EXT_ATTR_MAGIC))) { + if (fix_problem(ctx, PR_1_BAD_EA_BLOCK, pctx)) + goto clear_extattr; + } + + if (header->h_blocks != 1) { + if (fix_problem(ctx, PR_1_EA_MULTI_BLOCK, pctx)) + goto clear_extattr; + } + + region = region_create(0, fs->blocksize); + if (!region) { + fix_problem(ctx, PR_1_EA_ALLOC_REGION, pctx); + ctx->flags |= E2F_FLAG_ABORT; + return 0; + } + if (region_allocate(region, 0, sizeof(struct ext2_ext_attr_header))) { + if (fix_problem(ctx, PR_1_EA_ALLOC_COLLISION, pctx)) + goto clear_extattr; + } + + entry = (struct ext2_ext_attr_entry *)(header+1); + end = block_buf + fs->blocksize; + while ((char *)entry < end && *(__u32 *)entry) { + if (region_allocate(region, (char *)entry - (char *)header, + EXT2_EXT_ATTR_LEN(entry->e_name_len))) { + if (fix_problem(ctx, PR_1_EA_ALLOC_COLLISION, pctx)) + goto clear_extattr; + } + if ((ctx->ext_attr_ver == 1 && + (entry->e_name_len == 0 || entry->e_name_index != 0)) || + (ctx->ext_attr_ver == 2 && + entry->e_name_index == 0)) { + if (fix_problem(ctx, PR_1_EA_BAD_NAME, pctx)) + goto clear_extattr; + } + if (entry->e_value_block != 0) { + if (fix_problem(ctx, PR_1_EA_BAD_VALUE, pctx)) + goto clear_extattr; + } + if (entry->e_value_size && + region_allocate(region, entry->e_value_offs, + EXT2_EXT_ATTR_SIZE(entry->e_value_size))) { + if (fix_problem(ctx, PR_1_EA_ALLOC_COLLISION, pctx)) + goto clear_extattr; + } + entry = EXT2_EXT_ATTR_NEXT(entry); + } + if (region_allocate(region, (char *)entry - (char *)header, 4)) { + if (fix_problem(ctx, PR_1_EA_ALLOC_COLLISION, pctx)) + goto clear_extattr; + } + region_free(region); + + count = header->h_refcount - 1; + if (count) + ea_refcount_store(ctx->refcount, blk, count); + mark_block_used(ctx, blk); + ext2fs_fast_mark_block_bitmap(ctx->block_ea_map, blk); + + return 1; + +clear_extattr: + inode->i_file_acl = 0; + e2fsck_write_inode(ctx, ino, inode, "check_ext_attr"); + return 0; +} + +/* Returns 1 if bad htree, 0 if OK */ +static int handle_htree(e2fsck_t ctx, struct problem_context *pctx, + ext2_ino_t ino FSCK_ATTR((unused)), + struct ext2_inode *inode, + char *block_buf) +{ + struct ext2_dx_root_info *root; + ext2_filsys fs = ctx->fs; + errcode_t retval; + blk_t blk; + + if ((!LINUX_S_ISDIR(inode->i_mode) && + fix_problem(ctx, PR_1_HTREE_NODIR, pctx)) || + (!(fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX) && + fix_problem(ctx, PR_1_HTREE_SET, pctx))) + return 1; + + blk = inode->i_block[0]; + if (((blk == 0) || + (blk < fs->super->s_first_data_block) || + (blk >= fs->super->s_blocks_count)) && + fix_problem(ctx, PR_1_HTREE_BADROOT, pctx)) + return 1; + + retval = io_channel_read_blk(fs->io, blk, 1, block_buf); + if (retval && fix_problem(ctx, PR_1_HTREE_BADROOT, pctx)) + return 1; + + /* XXX should check that beginning matches a directory */ + root = (struct ext2_dx_root_info *) (block_buf + 24); + + if ((root->reserved_zero || root->info_length < 8) && + fix_problem(ctx, PR_1_HTREE_BADROOT, pctx)) + return 1; + + pctx->num = root->hash_version; + if ((root->hash_version != EXT2_HASH_LEGACY) && + (root->hash_version != EXT2_HASH_HALF_MD4) && + (root->hash_version != EXT2_HASH_TEA) && + fix_problem(ctx, PR_1_HTREE_HASHV, pctx)) + return 1; + + if ((root->unused_flags & EXT2_HASH_FLAG_INCOMPAT) && + fix_problem(ctx, PR_1_HTREE_INCOMPAT, pctx)) + return 1; + + pctx->num = root->indirect_levels; + if ((root->indirect_levels > 1) && + fix_problem(ctx, PR_1_HTREE_DEPTH, pctx)) + return 1; + + return 0; +} + +/* + * This subroutine is called on each inode to account for all of the + * blocks used by that inode. + */ +static void check_blocks(e2fsck_t ctx, struct problem_context *pctx, + char *block_buf) +{ + ext2_filsys fs = ctx->fs; + struct process_block_struct_1 pb; + ext2_ino_t ino = pctx->ino; + struct ext2_inode *inode = pctx->inode; + int bad_size = 0; + int dirty_inode = 0; + __u64 size; + + pb.ino = ino; + pb.num_blocks = 0; + pb.last_block = -1; + pb.num_illegal_blocks = 0; + pb.suppress = 0; pb.clear = 0; + pb.fragmented = 0; + pb.compressed = 0; + pb.previous_block = 0; + pb.is_dir = LINUX_S_ISDIR(inode->i_mode); + pb.is_reg = LINUX_S_ISREG(inode->i_mode); + pb.max_blocks = 1 << (31 - fs->super->s_log_block_size); + pb.inode = inode; + pb.pctx = pctx; + pb.ctx = ctx; + pctx->ino = ino; + pctx->errcode = 0; + + if (inode->i_flags & EXT2_COMPRBLK_FL) { + if (fs->super->s_feature_incompat & + EXT2_FEATURE_INCOMPAT_COMPRESSION) + pb.compressed = 1; + else { + if (fix_problem(ctx, PR_1_COMPR_SET, pctx)) { + inode->i_flags &= ~EXT2_COMPRBLK_FL; + dirty_inode++; + } + } + } + + if (inode->i_file_acl && check_ext_attr(ctx, pctx, block_buf)) + pb.num_blocks++; + + if (ext2fs_inode_has_valid_blocks(inode)) + pctx->errcode = ext2fs_block_iterate2(fs, ino, + pb.is_dir ? BLOCK_FLAG_HOLE : 0, + block_buf, process_block, &pb); + end_problem_latch(ctx, PR_LATCH_BLOCK); + end_problem_latch(ctx, PR_LATCH_TOOBIG); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + goto out; + if (pctx->errcode) + fix_problem(ctx, PR_1_BLOCK_ITERATE, pctx); + + if (pb.fragmented && pb.num_blocks < fs->super->s_blocks_per_group) + ctx->fs_fragmented++; + + if (pb.clear) { + inode->i_links_count = 0; + ext2fs_icount_store(ctx->inode_link_info, ino, 0); + inode->i_dtime = time(0); + dirty_inode++; + ext2fs_unmark_inode_bitmap(ctx->inode_dir_map, ino); + ext2fs_unmark_inode_bitmap(ctx->inode_reg_map, ino); + ext2fs_unmark_inode_bitmap(ctx->inode_used_map, ino); + /* + * The inode was probably partially accounted for + * before processing was aborted, so we need to + * restart the pass 1 scan. + */ + ctx->flags |= E2F_FLAG_RESTART; + goto out; + } + + if (inode->i_flags & EXT2_INDEX_FL) { + if (handle_htree(ctx, pctx, ino, inode, block_buf)) { + inode->i_flags &= ~EXT2_INDEX_FL; + dirty_inode++; + } else { +#ifdef ENABLE_HTREE + e2fsck_add_dx_dir(ctx, ino, pb.last_block+1); +#endif + } + } + if (ctx->dirs_to_hash && pb.is_dir && + !(inode->i_flags & EXT2_INDEX_FL) && + ((inode->i_size / fs->blocksize) >= 3)) + ext2fs_u32_list_add(ctx->dirs_to_hash, ino); + + if (!pb.num_blocks && pb.is_dir) { + if (fix_problem(ctx, PR_1_ZERO_LENGTH_DIR, pctx)) { + inode->i_links_count = 0; + ext2fs_icount_store(ctx->inode_link_info, ino, 0); + inode->i_dtime = time(0); + dirty_inode++; + ext2fs_unmark_inode_bitmap(ctx->inode_dir_map, ino); + ext2fs_unmark_inode_bitmap(ctx->inode_reg_map, ino); + ext2fs_unmark_inode_bitmap(ctx->inode_used_map, ino); + ctx->fs_directory_count--; + goto out; + } + } + + pb.num_blocks *= (fs->blocksize / 512); + + if (pb.is_dir) { + int nblock = inode->i_size >> EXT2_BLOCK_SIZE_BITS(fs->super); + if (nblock > (pb.last_block + 1)) + bad_size = 1; + else if (nblock < (pb.last_block + 1)) { + if (((pb.last_block + 1) - nblock) > + fs->super->s_prealloc_dir_blocks) + bad_size = 2; + } + } else { + size = EXT2_I_SIZE(inode); + if ((pb.last_block >= 0) && + (size < (__u64) pb.last_block * fs->blocksize)) + bad_size = 3; + else if (size > ext2_max_sizes[fs->super->s_log_block_size]) + bad_size = 4; + } + /* i_size for symlinks is checked elsewhere */ + if (bad_size && !LINUX_S_ISLNK(inode->i_mode)) { + pctx->num = (pb.last_block+1) * fs->blocksize; + if (fix_problem(ctx, PR_1_BAD_I_SIZE, pctx)) { + inode->i_size = pctx->num; + if (!LINUX_S_ISDIR(inode->i_mode)) + inode->i_size_high = pctx->num >> 32; + dirty_inode++; + } + pctx->num = 0; + } + if (LINUX_S_ISREG(inode->i_mode) && + (inode->i_size_high || inode->i_size & 0x80000000UL)) + ctx->large_files++; + if (pb.num_blocks != inode->i_blocks) { + pctx->num = pb.num_blocks; + if (fix_problem(ctx, PR_1_BAD_I_BLOCKS, pctx)) { + inode->i_blocks = pb.num_blocks; + dirty_inode++; + } + pctx->num = 0; + } +out: + if (dirty_inode) + e2fsck_write_inode(ctx, ino, inode, "check_blocks"); +} + + +/* + * This is a helper function for check_blocks(). + */ +static int process_block(ext2_filsys fs, + blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_block FSCK_ATTR((unused)), + int ref_offset FSCK_ATTR((unused)), + void *priv_data) +{ + struct process_block_struct_1 *p; + struct problem_context *pctx; + blk_t blk = *block_nr; + int ret_code = 0; + int problem = 0; + e2fsck_t ctx; + + p = (struct process_block_struct_1 *) priv_data; + pctx = p->pctx; + ctx = p->ctx; + + if (p->compressed && (blk == EXT2FS_COMPRESSED_BLKADDR)) { + /* todo: Check that the comprblk_fl is high, that the + blkaddr pattern looks right (all non-holes up to + first EXT2FS_COMPRESSED_BLKADDR, then all + EXT2FS_COMPRESSED_BLKADDR up to end of cluster), + that the feature_incompat bit is high, and that the + inode is a regular file. If we're doing a "full + check" (a concept introduced to e2fsck by e2compr, + meaning that we look at data blocks as well as + metadata) then call some library routine that + checks the compressed data. I'll have to think + about this, because one particularly important + problem to be able to fix is to recalculate the + cluster size if necessary. I think that perhaps + we'd better do most/all e2compr-specific checks + separately, after the non-e2compr checks. If not + doing a full check, it may be useful to test that + the personality is linux; e.g. if it isn't then + perhaps this really is just an illegal block. */ + return 0; + } + + if (blk == 0) { + if (p->is_dir == 0) { + /* + * Should never happen, since only directories + * get called with BLOCK_FLAG_HOLE + */ +#ifdef DEBUG_E2FSCK + printf("process_block() called with blk == 0, " + "blockcnt=%d, inode %lu???\n", + blockcnt, p->ino); +#endif + return 0; + } + if (blockcnt < 0) + return 0; + if (blockcnt * fs->blocksize < p->inode->i_size) { + goto mark_dir; + } + return 0; + } + + /* + * Simplistic fragmentation check. We merely require that the + * file be contiguous. (Which can never be true for really + * big files that are greater than a block group.) + */ + if (!HOLE_BLKADDR(p->previous_block)) { + if (p->previous_block+1 != blk) + p->fragmented = 1; + } + p->previous_block = blk; + + if (p->is_dir && blockcnt > (1 << (21 - fs->super->s_log_block_size))) + problem = PR_1_TOOBIG_DIR; + if (p->is_reg && p->num_blocks+1 >= p->max_blocks) + problem = PR_1_TOOBIG_REG; + if (!p->is_dir && !p->is_reg && blockcnt > 0) + problem = PR_1_TOOBIG_SYMLINK; + + if (blk < fs->super->s_first_data_block || + blk >= fs->super->s_blocks_count) + problem = PR_1_ILLEGAL_BLOCK_NUM; + + if (problem) { + p->num_illegal_blocks++; + if (!p->suppress && (p->num_illegal_blocks % 12) == 0) { + if (fix_problem(ctx, PR_1_TOO_MANY_BAD_BLOCKS, pctx)) { + p->clear = 1; + return BLOCK_ABORT; + } + if (fix_problem(ctx, PR_1_SUPPRESS_MESSAGES, pctx)) { + p->suppress = 1; + set_latch_flags(PR_LATCH_BLOCK, + PRL_SUPPRESS, 0); + } + } + pctx->blk = blk; + pctx->blkcount = blockcnt; + if (fix_problem(ctx, problem, pctx)) { + blk = *block_nr = 0; + ret_code = BLOCK_CHANGED; + goto mark_dir; + } else + return 0; + } + + if (p->ino == EXT2_RESIZE_INO) { + /* + * The resize inode has already be sanity checked + * during pass #0 (the superblock checks). All we + * have to do is mark the double indirect block as + * being in use; all of the other blocks are handled + * by mark_table_blocks()). + */ + if (blockcnt == BLOCK_COUNT_DIND) + mark_block_used(ctx, blk); + } else + mark_block_used(ctx, blk); + p->num_blocks++; + if (blockcnt >= 0) + p->last_block = blockcnt; +mark_dir: + if (p->is_dir && (blockcnt >= 0)) { + pctx->errcode = ext2fs_add_dir_block(fs->dblist, p->ino, + blk, blockcnt); + if (pctx->errcode) { + pctx->blk = blk; + pctx->num = blockcnt; + fix_problem(ctx, PR_1_ADD_DBLOCK, pctx); + /* Should never get here */ + ctx->flags |= E2F_FLAG_ABORT; + return BLOCK_ABORT; + } + } + return ret_code; +} + +static int process_bad_block(ext2_filsys fs FSCK_ATTR((unused)), + blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_block FSCK_ATTR((unused)), + int ref_offset FSCK_ATTR((unused)), + void *priv_data EXT2FS_ATTR((unused))) +{ + /* + * Note: This function processes blocks for the bad blocks + * inode, which is never compressed. So we don't use HOLE_BLKADDR(). + */ + + printf("Unrecoverable Error: Found %"PRIi64" bad blocks starting at block number: %u\n", blockcnt, *block_nr); + return BLOCK_ERROR; +} + +/* + * This routine gets called at the end of pass 1 if bad blocks are + * detected in the superblock, group descriptors, inode_bitmaps, or + * block bitmaps. At this point, all of the blocks have been mapped + * out, so we can try to allocate new block(s) to replace the bad + * blocks. + */ +static void handle_fs_bad_blocks(e2fsck_t ctx) +{ + printf("Bad blocks detected on your filesystem\n" + "You should get your data off as the device will soon die\n"); +} + +/* + * This routine marks all blocks which are used by the superblock, + * group descriptors, inode bitmaps, and block bitmaps. + */ +static void mark_table_blocks(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + blk_t block, b; + dgrp_t i; + int j; + struct problem_context pctx; + + clear_problem_context(&pctx); + + block = fs->super->s_first_data_block; + for (i = 0; i < fs->group_desc_count; i++) { + pctx.group = i; + + ext2fs_reserve_super_and_bgd(fs, i, ctx->block_found_map); + + /* + * Mark the blocks used for the inode table + */ + if (fs->group_desc[i].bg_inode_table) { + for (j = 0, b = fs->group_desc[i].bg_inode_table; + j < fs->inode_blocks_per_group; + j++, b++) { + if (ext2fs_test_block_bitmap(ctx->block_found_map, + b)) { + pctx.blk = b; + if (fix_problem(ctx, + PR_1_ITABLE_CONFLICT, &pctx)) { + ctx->invalid_inode_table_flag[i]++; + ctx->invalid_bitmaps++; + } + } else { + ext2fs_mark_block_bitmap(ctx->block_found_map, + b); + } + } + } + + /* + * Mark block used for the block bitmap + */ + if (fs->group_desc[i].bg_block_bitmap) { + if (ext2fs_test_block_bitmap(ctx->block_found_map, + fs->group_desc[i].bg_block_bitmap)) { + pctx.blk = fs->group_desc[i].bg_block_bitmap; + if (fix_problem(ctx, PR_1_BB_CONFLICT, &pctx)) { + ctx->invalid_block_bitmap_flag[i]++; + ctx->invalid_bitmaps++; + } + } else { + ext2fs_mark_block_bitmap(ctx->block_found_map, + fs->group_desc[i].bg_block_bitmap); + } + + } + /* + * Mark block used for the inode bitmap + */ + if (fs->group_desc[i].bg_inode_bitmap) { + if (ext2fs_test_block_bitmap(ctx->block_found_map, + fs->group_desc[i].bg_inode_bitmap)) { + pctx.blk = fs->group_desc[i].bg_inode_bitmap; + if (fix_problem(ctx, PR_1_IB_CONFLICT, &pctx)) { + ctx->invalid_inode_bitmap_flag[i]++; + ctx->invalid_bitmaps++; + } + } else { + ext2fs_mark_block_bitmap(ctx->block_found_map, + fs->group_desc[i].bg_inode_bitmap); + } + } + block += fs->super->s_blocks_per_group; + } +} + +/* + * Thes subroutines short circuits ext2fs_get_blocks and + * ext2fs_check_directory; we use them since we already have the inode + * structure, so there's no point in letting the ext2fs library read + * the inode again. + */ +static errcode_t pass1_get_blocks(ext2_filsys fs, ext2_ino_t ino, + blk_t *blocks) +{ + e2fsck_t ctx = (e2fsck_t) fs->priv_data; + int i; + + if ((ino != ctx->stashed_ino) || !ctx->stashed_inode) + return EXT2_ET_CALLBACK_NOTHANDLED; + + for (i=0; i < EXT2_N_BLOCKS; i++) + blocks[i] = ctx->stashed_inode->i_block[i]; + return 0; +} + +static errcode_t pass1_read_inode(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode) +{ + e2fsck_t ctx = (e2fsck_t) fs->priv_data; + + if ((ino != ctx->stashed_ino) || !ctx->stashed_inode) + return EXT2_ET_CALLBACK_NOTHANDLED; + *inode = *ctx->stashed_inode; + return 0; +} + +static errcode_t pass1_write_inode(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode) +{ + e2fsck_t ctx = (e2fsck_t) fs->priv_data; + + if ((ino == ctx->stashed_ino) && ctx->stashed_inode) + *ctx->stashed_inode = *inode; + return EXT2_ET_CALLBACK_NOTHANDLED; +} + +static errcode_t pass1_check_directory(ext2_filsys fs, ext2_ino_t ino) +{ + e2fsck_t ctx = (e2fsck_t) fs->priv_data; + + if ((ino != ctx->stashed_ino) || !ctx->stashed_inode) + return EXT2_ET_CALLBACK_NOTHANDLED; + + if (!LINUX_S_ISDIR(ctx->stashed_inode->i_mode)) + return EXT2_ET_NO_DIRECTORY; + return 0; +} + +void e2fsck_use_inode_shortcuts(e2fsck_t ctx, int bool) +{ + ext2_filsys fs = ctx->fs; + + if (bool) { + fs->get_blocks = pass1_get_blocks; + fs->check_directory = pass1_check_directory; + fs->read_inode = pass1_read_inode; + fs->write_inode = pass1_write_inode; + ctx->stashed_ino = 0; + } else { + fs->get_blocks = 0; + fs->check_directory = 0; + fs->read_inode = 0; + fs->write_inode = 0; + } +} + +/* + * pass1b.c --- Pass #1b of e2fsck + * + * This file contains pass1B, pass1C, and pass1D of e2fsck. They are + * only invoked if pass 1 discovered blocks which are in use by more + * than one inode. + * + * Pass1B scans the data blocks of all the inodes again, generating a + * complete list of duplicate blocks and which inodes have claimed + * them. + * + * Pass1C does a tree-traversal of the filesystem, to determine the + * parent directories of these inodes. This step is necessary so that + * e2fsck can print out the pathnames of affected inodes. + * + * Pass1D is a reconciliation pass. For each inode with duplicate + * blocks, the user is prompted if s/he would like to clone the file + * (so that the file gets a fresh copy of the duplicated blocks) or + * simply to delete the file. + * + */ + + +/* Needed for architectures where sizeof(int) != sizeof(void *) */ +#define INT_TO_VOIDPTR(val) ((void *)(intptr_t)(val)) +#define VOIDPTR_TO_INT(ptr) ((int)(intptr_t)(ptr)) + +/* Define an extension to the ext2 library's block count information */ +#define BLOCK_COUNT_EXTATTR (-5) + +struct block_el { + blk_t block; + struct block_el *next; +}; + +struct inode_el { + ext2_ino_t inode; + struct inode_el *next; +}; + +struct dup_block { + int num_bad; + struct inode_el *inode_list; +}; + +/* + * This structure stores information about a particular inode which + * is sharing blocks with other inodes. This information is collected + * to display to the user, so that the user knows what files he or she + * is dealing with, when trying to decide how to resolve the conflict + * of multiply-claimed blocks. + */ +struct dup_inode { + ext2_ino_t dir; + int num_dupblocks; + struct ext2_inode inode; + struct block_el *block_list; +}; + +static int process_pass1b_block(ext2_filsys fs, blk_t *blocknr, + e2_blkcnt_t blockcnt, blk_t ref_blk, + int ref_offset, void *priv_data); +static void delete_file(e2fsck_t ctx, ext2_ino_t ino, + struct dup_inode *dp, char *block_buf); +static int clone_file(e2fsck_t ctx, ext2_ino_t ino, + struct dup_inode *dp, char* block_buf); +static int check_if_fs_block(e2fsck_t ctx, blk_t test_blk); + +static void pass1b(e2fsck_t ctx, char *block_buf); +static void pass1c(e2fsck_t ctx, char *block_buf); +static void pass1d(e2fsck_t ctx, char *block_buf); + +static int dup_inode_count = 0; + +static dict_t blk_dict, ino_dict; + +static ext2fs_inode_bitmap inode_dup_map; + +static int dict_int_cmp(const void *a, const void *b) +{ + intptr_t ia, ib; + + ia = (intptr_t)a; + ib = (intptr_t)b; + + return (ia-ib); +} + +/* + * Add a duplicate block record + */ +static void add_dupe(e2fsck_t ctx, ext2_ino_t ino, blk_t blk, + struct ext2_inode *inode) +{ + dnode_t *n; + struct dup_block *db; + struct dup_inode *di; + struct block_el *blk_el; + struct inode_el *ino_el; + + n = dict_lookup(&blk_dict, INT_TO_VOIDPTR(blk)); + if (n) + db = (struct dup_block *) dnode_get(n); + else { + db = (struct dup_block *) e2fsck_allocate_memory(ctx, + sizeof(struct dup_block), "duplicate block header"); + db->num_bad = 0; + db->inode_list = 0; + dict_alloc_insert(&blk_dict, INT_TO_VOIDPTR(blk), db); + } + ino_el = (struct inode_el *) e2fsck_allocate_memory(ctx, + sizeof(struct inode_el), "inode element"); + ino_el->inode = ino; + ino_el->next = db->inode_list; + db->inode_list = ino_el; + db->num_bad++; + + n = dict_lookup(&ino_dict, INT_TO_VOIDPTR(ino)); + if (n) + di = (struct dup_inode *) dnode_get(n); + else { + di = (struct dup_inode *) e2fsck_allocate_memory(ctx, + sizeof(struct dup_inode), "duplicate inode header"); + di->dir = (ino == EXT2_ROOT_INO) ? EXT2_ROOT_INO : 0 ; + di->num_dupblocks = 0; + di->block_list = 0; + di->inode = *inode; + dict_alloc_insert(&ino_dict, INT_TO_VOIDPTR(ino), di); + } + blk_el = (struct block_el *) e2fsck_allocate_memory(ctx, + sizeof(struct block_el), "block element"); + blk_el->block = blk; + blk_el->next = di->block_list; + di->block_list = blk_el; + di->num_dupblocks++; +} + +/* + * Free a duplicate inode record + */ +static void inode_dnode_free(dnode_t *node) +{ + struct dup_inode *di; + struct block_el *p, *next; + + di = (struct dup_inode *) dnode_get(node); + for (p = di->block_list; p; p = next) { + next = p->next; + free(p); + } + free(node); +} + +/* + * Free a duplicate block record + */ +static void block_dnode_free(dnode_t *node) +{ + struct dup_block *db; + struct inode_el *p, *next; + + db = (struct dup_block *) dnode_get(node); + for (p = db->inode_list; p; p = next) { + next = p->next; + free(p); + } + free(node); +} + + +/* + * Main procedure for handling duplicate blocks + */ +void e2fsck_pass1_dupblocks(e2fsck_t ctx, char *block_buf) +{ + ext2_filsys fs = ctx->fs; + struct problem_context pctx; + + clear_problem_context(&pctx); + + pctx.errcode = ext2fs_allocate_inode_bitmap(fs, + _("multiply claimed inode map"), &inode_dup_map); + if (pctx.errcode) { + fix_problem(ctx, PR_1B_ALLOCATE_IBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + + dict_init(&ino_dict, DICTCOUNT_T_MAX, dict_int_cmp); + dict_init(&blk_dict, DICTCOUNT_T_MAX, dict_int_cmp); + dict_set_allocator(&ino_dict, inode_dnode_free); + dict_set_allocator(&blk_dict, block_dnode_free); + + pass1b(ctx, block_buf); + pass1c(ctx, block_buf); + pass1d(ctx, block_buf); + + /* + * Time to free all of the accumulated data structures that we + * don't need anymore. + */ + dict_free_nodes(&ino_dict); + dict_free_nodes(&blk_dict); +} + +/* + * Scan the inodes looking for inodes that contain duplicate blocks. + */ +struct process_block_struct_1b { + e2fsck_t ctx; + ext2_ino_t ino; + int dup_blocks; + struct ext2_inode *inode; + struct problem_context *pctx; +}; + +static void pass1b(e2fsck_t ctx, char *block_buf) +{ + ext2_filsys fs = ctx->fs; + ext2_ino_t ino; + struct ext2_inode inode; + ext2_inode_scan scan; + struct process_block_struct_1b pb; + struct problem_context pctx; + + clear_problem_context(&pctx); + + if (!(ctx->options & E2F_OPT_PREEN)) + fix_problem(ctx, PR_1B_PASS_HEADER, &pctx); + pctx.errcode = ext2fs_open_inode_scan(fs, ctx->inode_buffer_blocks, + &scan); + if (pctx.errcode) { + fix_problem(ctx, PR_1B_ISCAN_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + ctx->stashed_inode = &inode; + pb.ctx = ctx; + pb.pctx = &pctx; + pctx.str = "pass1b"; + while (1) { + pctx.errcode = ext2fs_get_next_inode(scan, &ino, &inode); + if (pctx.errcode == EXT2_ET_BAD_BLOCK_IN_INODE_TABLE) + continue; + if (pctx.errcode) { + fix_problem(ctx, PR_1B_ISCAN_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + if (!ino) + break; + pctx.ino = ctx->stashed_ino = ino; + if ((ino != EXT2_BAD_INO) && + !ext2fs_test_inode_bitmap(ctx->inode_used_map, ino)) + continue; + + pb.ino = ino; + pb.dup_blocks = 0; + pb.inode = &inode; + + if (ext2fs_inode_has_valid_blocks(&inode) || + (ino == EXT2_BAD_INO)) + pctx.errcode = ext2fs_block_iterate2(fs, ino, + 0, block_buf, process_pass1b_block, &pb); + if (inode.i_file_acl) + process_pass1b_block(fs, &inode.i_file_acl, + BLOCK_COUNT_EXTATTR, 0, 0, &pb); + if (pb.dup_blocks) { + end_problem_latch(ctx, PR_LATCH_DBLOCK); + if (ino >= EXT2_FIRST_INODE(fs->super) || + ino == EXT2_ROOT_INO) + dup_inode_count++; + } + if (pctx.errcode) + fix_problem(ctx, PR_1B_BLOCK_ITERATE, &pctx); + } + ext2fs_close_inode_scan(scan); + e2fsck_use_inode_shortcuts(ctx, 0); +} + +static int process_pass1b_block(ext2_filsys fs FSCK_ATTR((unused)), + blk_t *block_nr, + e2_blkcnt_t blockcnt FSCK_ATTR((unused)), + blk_t ref_blk FSCK_ATTR((unused)), + int ref_offset FSCK_ATTR((unused)), + void *priv_data) +{ + struct process_block_struct_1b *p; + e2fsck_t ctx; + + if (HOLE_BLKADDR(*block_nr)) + return 0; + p = (struct process_block_struct_1b *) priv_data; + ctx = p->ctx; + + if (!ext2fs_test_block_bitmap(ctx->block_dup_map, *block_nr)) + return 0; + + /* OK, this is a duplicate block */ + if (p->ino != EXT2_BAD_INO) { + p->pctx->blk = *block_nr; + fix_problem(ctx, PR_1B_DUP_BLOCK, p->pctx); + } + p->dup_blocks++; + ext2fs_mark_inode_bitmap(inode_dup_map, p->ino); + + add_dupe(ctx, p->ino, *block_nr, p->inode); + + return 0; +} + +/* + * Pass 1c: Scan directories for inodes with duplicate blocks. This + * is used so that we can print pathnames when prompting the user for + * what to do. + */ +struct search_dir_struct { + int count; + ext2_ino_t first_inode; + ext2_ino_t max_inode; +}; + +static int search_dirent_proc(ext2_ino_t dir, int entry, + struct ext2_dir_entry *dirent, + int offset FSCK_ATTR((unused)), + int blocksize FSCK_ATTR((unused)), + char *buf FSCK_ATTR((unused)), + void *priv_data) +{ + struct search_dir_struct *sd; + struct dup_inode *p; + dnode_t *n; + + sd = (struct search_dir_struct *) priv_data; + + if (dirent->inode > sd->max_inode) + /* Should abort this inode, but not everything */ + return 0; + + if ((dirent->inode < sd->first_inode) || (entry < DIRENT_OTHER_FILE) || + !ext2fs_test_inode_bitmap(inode_dup_map, dirent->inode)) + return 0; + + n = dict_lookup(&ino_dict, INT_TO_VOIDPTR(dirent->inode)); + if (!n) + return 0; + p = (struct dup_inode *) dnode_get(n); + p->dir = dir; + sd->count--; + + return sd->count ? 0 : DIRENT_ABORT; +} + + +static void pass1c(e2fsck_t ctx, char *block_buf) +{ + ext2_filsys fs = ctx->fs; + struct search_dir_struct sd; + struct problem_context pctx; + + clear_problem_context(&pctx); + + if (!(ctx->options & E2F_OPT_PREEN)) + fix_problem(ctx, PR_1C_PASS_HEADER, &pctx); + + /* + * Search through all directories to translate inodes to names + * (by searching for the containing directory for that inode.) + */ + sd.count = dup_inode_count; + sd.first_inode = EXT2_FIRST_INODE(fs->super); + sd.max_inode = fs->super->s_inodes_count; + ext2fs_dblist_dir_iterate(fs->dblist, 0, block_buf, + search_dirent_proc, &sd); +} + +static void pass1d(e2fsck_t ctx, char *block_buf) +{ + ext2_filsys fs = ctx->fs; + struct dup_inode *p, *t; + struct dup_block *q; + ext2_ino_t *shared, ino; + int shared_len; + int i; + int file_ok; + int meta_data = 0; + struct problem_context pctx; + dnode_t *n, *m; + struct block_el *s; + struct inode_el *r; + + clear_problem_context(&pctx); + + if (!(ctx->options & E2F_OPT_PREEN)) + fix_problem(ctx, PR_1D_PASS_HEADER, &pctx); + e2fsck_read_bitmaps(ctx); + + pctx.num = dup_inode_count; /* dict_count(&ino_dict); */ + fix_problem(ctx, PR_1D_NUM_DUP_INODES, &pctx); + shared = (ext2_ino_t *) e2fsck_allocate_memory(ctx, + sizeof(ext2_ino_t) * dict_count(&ino_dict), + "Shared inode list"); + for (n = dict_first(&ino_dict); n; n = dict_next(&ino_dict, n)) { + p = (struct dup_inode *) dnode_get(n); + shared_len = 0; + file_ok = 1; + ino = (ext2_ino_t)VOIDPTR_TO_INT(dnode_getkey(n)); + if (ino == EXT2_BAD_INO || ino == EXT2_RESIZE_INO) + continue; + + /* + * Find all of the inodes which share blocks with this + * one. First we find all of the duplicate blocks + * belonging to this inode, and then search each block + * get the list of inodes, and merge them together. + */ + for (s = p->block_list; s; s = s->next) { + m = dict_lookup(&blk_dict, INT_TO_VOIDPTR(s->block)); + if (!m) + continue; /* Should never happen... */ + q = (struct dup_block *) dnode_get(m); + if (q->num_bad > 1) + file_ok = 0; + if (check_if_fs_block(ctx, s->block)) { + file_ok = 0; + meta_data = 1; + } + + /* + * Add all inodes used by this block to the + * shared[] --- which is a unique list, so + * if an inode is already in shared[], don't + * add it again. + */ + for (r = q->inode_list; r; r = r->next) { + if (r->inode == ino) + continue; + for (i = 0; i < shared_len; i++) + if (shared[i] == r->inode) + break; + if (i == shared_len) { + shared[shared_len++] = r->inode; + } + } + } + + /* + * Report the inode that we are working on + */ + pctx.inode = &p->inode; + pctx.ino = ino; + pctx.dir = p->dir; + pctx.blkcount = p->num_dupblocks; + pctx.num = meta_data ? shared_len+1 : shared_len; + fix_problem(ctx, PR_1D_DUP_FILE, &pctx); + pctx.blkcount = 0; + pctx.num = 0; + + if (meta_data) + fix_problem(ctx, PR_1D_SHARE_METADATA, &pctx); + + for (i = 0; i < shared_len; i++) { + m = dict_lookup(&ino_dict, INT_TO_VOIDPTR(shared[i])); + if (!m) + continue; /* should never happen */ + t = (struct dup_inode *) dnode_get(m); + /* + * Report the inode that we are sharing with + */ + pctx.inode = &t->inode; + pctx.ino = shared[i]; + pctx.dir = t->dir; + fix_problem(ctx, PR_1D_DUP_FILE_LIST, &pctx); + } + if (file_ok) { + fix_problem(ctx, PR_1D_DUP_BLOCKS_DEALT, &pctx); + continue; + } + if (fix_problem(ctx, PR_1D_CLONE_QUESTION, &pctx)) { + pctx.errcode = clone_file(ctx, ino, p, block_buf); + if (pctx.errcode) + fix_problem(ctx, PR_1D_CLONE_ERROR, &pctx); + else + continue; + } + if (fix_problem(ctx, PR_1D_DELETE_QUESTION, &pctx)) + delete_file(ctx, ino, p, block_buf); + else + ext2fs_unmark_valid(fs); + } + ext2fs_free_mem(&shared); +} + +/* + * Drop the refcount on the dup_block structure, and clear the entry + * in the block_dup_map if appropriate. + */ +static void decrement_badcount(e2fsck_t ctx, blk_t block, struct dup_block *p) +{ + p->num_bad--; + if (p->num_bad <= 0 || + (p->num_bad == 1 && !check_if_fs_block(ctx, block))) + ext2fs_unmark_block_bitmap(ctx->block_dup_map, block); +} + +static int delete_file_block(ext2_filsys fs, + blk_t *block_nr, + e2_blkcnt_t blockcnt FSCK_ATTR((unused)), + blk_t ref_block FSCK_ATTR((unused)), + int ref_offset FSCK_ATTR((unused)), + void *priv_data) +{ + struct process_block_struct_1b *pb; + struct dup_block *p; + dnode_t *n; + e2fsck_t ctx; + + pb = (struct process_block_struct_1b *) priv_data; + ctx = pb->ctx; + + if (HOLE_BLKADDR(*block_nr)) + return 0; + + if (ext2fs_test_block_bitmap(ctx->block_dup_map, *block_nr)) { + n = dict_lookup(&blk_dict, INT_TO_VOIDPTR(*block_nr)); + if (n) { + p = (struct dup_block *) dnode_get(n); + decrement_badcount(ctx, *block_nr, p); + } else + bb_error_msg(_("internal error; can't find dup_blk for %d"), + *block_nr); + } else { + ext2fs_unmark_block_bitmap(ctx->block_found_map, *block_nr); + ext2fs_block_alloc_stats(fs, *block_nr, -1); + } + + return 0; +} + +static void delete_file(e2fsck_t ctx, ext2_ino_t ino, + struct dup_inode *dp, char* block_buf) +{ + ext2_filsys fs = ctx->fs; + struct process_block_struct_1b pb; + struct ext2_inode inode; + struct problem_context pctx; + unsigned int count; + + clear_problem_context(&pctx); + pctx.ino = pb.ino = ino; + pb.dup_blocks = dp->num_dupblocks; + pb.ctx = ctx; + pctx.str = "delete_file"; + + e2fsck_read_inode(ctx, ino, &inode, "delete_file"); + if (ext2fs_inode_has_valid_blocks(&inode)) + pctx.errcode = ext2fs_block_iterate2(fs, ino, 0, block_buf, + delete_file_block, &pb); + if (pctx.errcode) + fix_problem(ctx, PR_1B_BLOCK_ITERATE, &pctx); + ext2fs_unmark_inode_bitmap(ctx->inode_used_map, ino); + ext2fs_unmark_inode_bitmap(ctx->inode_dir_map, ino); + if (ctx->inode_bad_map) + ext2fs_unmark_inode_bitmap(ctx->inode_bad_map, ino); + ext2fs_inode_alloc_stats2(fs, ino, -1, LINUX_S_ISDIR(inode.i_mode)); + + /* Inode may have changed by block_iterate, so reread it */ + e2fsck_read_inode(ctx, ino, &inode, "delete_file"); + inode.i_links_count = 0; + inode.i_dtime = time(0); + if (inode.i_file_acl && + (fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_EXT_ATTR)) { + count = 1; + pctx.errcode = ext2fs_adjust_ea_refcount(fs, inode.i_file_acl, + block_buf, -1, &count); + if (pctx.errcode == EXT2_ET_BAD_EA_BLOCK_NUM) { + pctx.errcode = 0; + count = 1; + } + if (pctx.errcode) { + pctx.blk = inode.i_file_acl; + fix_problem(ctx, PR_1B_ADJ_EA_REFCOUNT, &pctx); + } + /* + * If the count is zero, then arrange to have the + * block deleted. If the block is in the block_dup_map, + * also call delete_file_block since it will take care + * of keeping the accounting straight. + */ + if ((count == 0) || + ext2fs_test_block_bitmap(ctx->block_dup_map, + inode.i_file_acl)) + delete_file_block(fs, &inode.i_file_acl, + BLOCK_COUNT_EXTATTR, 0, 0, &pb); + } + e2fsck_write_inode(ctx, ino, &inode, "delete_file"); +} + +struct clone_struct { + errcode_t errcode; + ext2_ino_t dir; + char *buf; + e2fsck_t ctx; +}; + +static int clone_file_block(ext2_filsys fs, + blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_block FSCK_ATTR((unused)), + int ref_offset FSCK_ATTR((unused)), + void *priv_data) +{ + struct dup_block *p; + blk_t new_block; + errcode_t retval; + struct clone_struct *cs = (struct clone_struct *) priv_data; + dnode_t *n; + e2fsck_t ctx; + + ctx = cs->ctx; + + if (HOLE_BLKADDR(*block_nr)) + return 0; + + if (ext2fs_test_block_bitmap(ctx->block_dup_map, *block_nr)) { + n = dict_lookup(&blk_dict, INT_TO_VOIDPTR(*block_nr)); + if (n) { + p = (struct dup_block *) dnode_get(n); + retval = ext2fs_new_block(fs, 0, ctx->block_found_map, + &new_block); + if (retval) { + cs->errcode = retval; + return BLOCK_ABORT; + } + if (cs->dir && (blockcnt >= 0)) { + retval = ext2fs_set_dir_block(fs->dblist, + cs->dir, new_block, blockcnt); + if (retval) { + cs->errcode = retval; + return BLOCK_ABORT; + } + } + + retval = io_channel_read_blk(fs->io, *block_nr, 1, + cs->buf); + if (retval) { + cs->errcode = retval; + return BLOCK_ABORT; + } + retval = io_channel_write_blk(fs->io, new_block, 1, + cs->buf); + if (retval) { + cs->errcode = retval; + return BLOCK_ABORT; + } + decrement_badcount(ctx, *block_nr, p); + *block_nr = new_block; + ext2fs_mark_block_bitmap(ctx->block_found_map, + new_block); + ext2fs_mark_block_bitmap(fs->block_map, new_block); + return BLOCK_CHANGED; + } else + bb_error_msg(_("internal error; can't find dup_blk for %d"), + *block_nr); + } + return 0; +} + +static int clone_file(e2fsck_t ctx, ext2_ino_t ino, + struct dup_inode *dp, char* block_buf) +{ + ext2_filsys fs = ctx->fs; + errcode_t retval; + struct clone_struct cs; + struct problem_context pctx; + blk_t blk; + dnode_t *n; + struct inode_el *ino_el; + struct dup_block *db; + struct dup_inode *di; + + clear_problem_context(&pctx); + cs.errcode = 0; + cs.dir = 0; + cs.ctx = ctx; + retval = ext2fs_get_mem(fs->blocksize, &cs.buf); + if (retval) + return retval; + + if (ext2fs_test_inode_bitmap(ctx->inode_dir_map, ino)) + cs.dir = ino; + + pctx.ino = ino; + pctx.str = "clone_file"; + if (ext2fs_inode_has_valid_blocks(&dp->inode)) + pctx.errcode = ext2fs_block_iterate2(fs, ino, 0, block_buf, + clone_file_block, &cs); + ext2fs_mark_bb_dirty(fs); + if (pctx.errcode) { + fix_problem(ctx, PR_1B_BLOCK_ITERATE, &pctx); + retval = pctx.errcode; + goto errout; + } + if (cs.errcode) { + bb_error_msg(_("returned from clone_file_block")); + retval = cs.errcode; + goto errout; + } + /* The inode may have changed on disk, so we have to re-read it */ + e2fsck_read_inode(ctx, ino, &dp->inode, "clone file EA"); + blk = dp->inode.i_file_acl; + if (blk && (clone_file_block(fs, &dp->inode.i_file_acl, + BLOCK_COUNT_EXTATTR, 0, 0, &cs) == + BLOCK_CHANGED)) { + e2fsck_write_inode(ctx, ino, &dp->inode, "clone file EA"); + /* + * If we cloned the EA block, find all other inodes + * which refered to that EA block, and modify + * them to point to the new EA block. + */ + n = dict_lookup(&blk_dict, INT_TO_VOIDPTR(blk)); + db = (struct dup_block *) dnode_get(n); + for (ino_el = db->inode_list; ino_el; ino_el = ino_el->next) { + if (ino_el->inode == ino) + continue; + n = dict_lookup(&ino_dict, INT_TO_VOIDPTR(ino_el->inode)); + di = (struct dup_inode *) dnode_get(n); + if (di->inode.i_file_acl == blk) { + di->inode.i_file_acl = dp->inode.i_file_acl; + e2fsck_write_inode(ctx, ino_el->inode, + &di->inode, "clone file EA"); + decrement_badcount(ctx, blk, db); + } + } + } + retval = 0; +errout: + ext2fs_free_mem(&cs.buf); + return retval; +} + +/* + * This routine returns 1 if a block overlaps with one of the superblocks, + * group descriptors, inode bitmaps, or block bitmaps. + */ +static int check_if_fs_block(e2fsck_t ctx, blk_t test_block) +{ + ext2_filsys fs = ctx->fs; + blk_t block; + dgrp_t i; + + block = fs->super->s_first_data_block; + for (i = 0; i < fs->group_desc_count; i++) { + + /* Check superblocks/block group descriptros */ + if (ext2fs_bg_has_super(fs, i)) { + if (test_block >= block && + (test_block <= block + fs->desc_blocks)) + return 1; + } + + /* Check the inode table */ + if ((fs->group_desc[i].bg_inode_table) && + (test_block >= fs->group_desc[i].bg_inode_table) && + (test_block < (fs->group_desc[i].bg_inode_table + + fs->inode_blocks_per_group))) + return 1; + + /* Check the bitmap blocks */ + if ((test_block == fs->group_desc[i].bg_block_bitmap) || + (test_block == fs->group_desc[i].bg_inode_bitmap)) + return 1; + + block += fs->super->s_blocks_per_group; + } + return 0; +} +/* + * pass2.c --- check directory structure + * + * Pass 2 of e2fsck iterates through all active directory inodes, and + * applies to following tests to each directory entry in the directory + * blocks in the inodes: + * + * - The length of the directory entry (rec_len) should be at + * least 8 bytes, and no more than the remaining space + * left in the directory block. + * - The length of the name in the directory entry (name_len) + * should be less than (rec_len - 8). + * - The inode number in the directory entry should be within + * legal bounds. + * - The inode number should refer to a in-use inode. + * - The first entry should be '.', and its inode should be + * the inode of the directory. + * - The second entry should be '..'. + * + * To minimize disk seek time, the directory blocks are processed in + * sorted order of block numbers. + * + * Pass 2 also collects the following information: + * - The inode numbers of the subdirectories for each directory. + * + * Pass 2 relies on the following information from previous passes: + * - The directory information collected in pass 1. + * - The inode_used_map bitmap + * - The inode_bad_map bitmap + * - The inode_dir_map bitmap + * + * Pass 2 frees the following data structures + * - The inode_bad_map bitmap + * - The inode_reg_map bitmap + */ + +/* + * Keeps track of how many times an inode is referenced. + */ +static void deallocate_inode(e2fsck_t ctx, ext2_ino_t ino, char* block_buf); +static int check_dir_block(ext2_filsys fs, + struct ext2_db_entry *dir_blocks_info, + void *priv_data); +static int allocate_dir_block(e2fsck_t ctx, struct ext2_db_entry *dir_blocks_info, + struct problem_context *pctx); +static int update_dir_block(ext2_filsys fs, + blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_block, + int ref_offset, + void *priv_data); +static void clear_htree(e2fsck_t ctx, ext2_ino_t ino); +static int htree_depth(struct dx_dir_info *dx_dir, + struct dx_dirblock_info *dx_db); +static int special_dir_block_cmp(const void *a, const void *b); + +struct check_dir_struct { + char *buf; + struct problem_context pctx; + int count, max; + e2fsck_t ctx; +}; + +static void e2fsck_pass2(e2fsck_t ctx) +{ + struct ext2_super_block *sb = ctx->fs->super; + struct problem_context pctx; + ext2_filsys fs = ctx->fs; + char *buf; + struct dir_info *dir; + struct check_dir_struct cd; + struct dx_dir_info *dx_dir; + struct dx_dirblock_info *dx_db, *dx_parent; + int b; + int i, depth; + problem_t code; + int bad_dir; + + clear_problem_context(&cd.pctx); + + /* Pass 2 */ + + if (!(ctx->options & E2F_OPT_PREEN)) + fix_problem(ctx, PR_2_PASS_HEADER, &cd.pctx); + + cd.pctx.errcode = ext2fs_create_icount2(fs, EXT2_ICOUNT_OPT_INCREMENT, + 0, ctx->inode_link_info, + &ctx->inode_count); + if (cd.pctx.errcode) { + fix_problem(ctx, PR_2_ALLOCATE_ICOUNT, &cd.pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + buf = (char *) e2fsck_allocate_memory(ctx, 2*fs->blocksize, + "directory scan buffer"); + + /* + * Set up the parent pointer for the root directory, if + * present. (If the root directory is not present, we will + * create it in pass 3.) + */ + dir = e2fsck_get_dir_info(ctx, EXT2_ROOT_INO); + if (dir) + dir->parent = EXT2_ROOT_INO; + + cd.buf = buf; + cd.ctx = ctx; + cd.count = 1; + cd.max = ext2fs_dblist_count(fs->dblist); + + if (ctx->progress) + (void) (ctx->progress)(ctx, 2, 0, cd.max); + + if (fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX) + ext2fs_dblist_sort(fs->dblist, special_dir_block_cmp); + + cd.pctx.errcode = ext2fs_dblist_iterate(fs->dblist, check_dir_block, + &cd); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + if (cd.pctx.errcode) { + fix_problem(ctx, PR_2_DBLIST_ITERATE, &cd.pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + +#ifdef ENABLE_HTREE + for (i=0; (dx_dir = e2fsck_dx_dir_info_iter(ctx, &i)) != 0;) { + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + if (dx_dir->numblocks == 0) + continue; + clear_problem_context(&pctx); + bad_dir = 0; + pctx.dir = dx_dir->ino; + dx_db = dx_dir->dx_block; + if (dx_db->flags & DX_FLAG_REFERENCED) + dx_db->flags |= DX_FLAG_DUP_REF; + else + dx_db->flags |= DX_FLAG_REFERENCED; + /* + * Find all of the first and last leaf blocks, and + * update their parent's min and max hash values + */ + for (b=0, dx_db = dx_dir->dx_block; + b < dx_dir->numblocks; + b++, dx_db++) { + if ((dx_db->type != DX_DIRBLOCK_LEAF) || + !(dx_db->flags & (DX_FLAG_FIRST | DX_FLAG_LAST))) + continue; + dx_parent = &dx_dir->dx_block[dx_db->parent]; + /* + * XXX Make sure dx_parent->min_hash > dx_db->min_hash + */ + if (dx_db->flags & DX_FLAG_FIRST) + dx_parent->min_hash = dx_db->min_hash; + /* + * XXX Make sure dx_parent->max_hash < dx_db->max_hash + */ + if (dx_db->flags & DX_FLAG_LAST) + dx_parent->max_hash = dx_db->max_hash; + } + + for (b=0, dx_db = dx_dir->dx_block; + b < dx_dir->numblocks; + b++, dx_db++) { + pctx.blkcount = b; + pctx.group = dx_db->parent; + code = 0; + if (!(dx_db->flags & DX_FLAG_FIRST) && + (dx_db->min_hash < dx_db->node_min_hash)) { + pctx.blk = dx_db->min_hash; + pctx.blk2 = dx_db->node_min_hash; + code = PR_2_HTREE_MIN_HASH; + fix_problem(ctx, code, &pctx); + bad_dir++; + } + if (dx_db->type == DX_DIRBLOCK_LEAF) { + depth = htree_depth(dx_dir, dx_db); + if (depth != dx_dir->depth) { + code = PR_2_HTREE_BAD_DEPTH; + fix_problem(ctx, code, &pctx); + bad_dir++; + } + } + /* + * This test doesn't apply for the root block + * at block #0 + */ + if (b && + (dx_db->max_hash > dx_db->node_max_hash)) { + pctx.blk = dx_db->max_hash; + pctx.blk2 = dx_db->node_max_hash; + code = PR_2_HTREE_MAX_HASH; + fix_problem(ctx, code, &pctx); + bad_dir++; + } + if (!(dx_db->flags & DX_FLAG_REFERENCED)) { + code = PR_2_HTREE_NOTREF; + fix_problem(ctx, code, &pctx); + bad_dir++; + } else if (dx_db->flags & DX_FLAG_DUP_REF) { + code = PR_2_HTREE_DUPREF; + fix_problem(ctx, code, &pctx); + bad_dir++; + } + if (code == 0) + continue; + } + if (bad_dir && fix_problem(ctx, PR_2_HTREE_CLEAR, &pctx)) { + clear_htree(ctx, dx_dir->ino); + dx_dir->numblocks = 0; + } + } +#endif + ext2fs_free_mem(&buf); + ext2fs_free_dblist(fs->dblist); + + ext2fs_free_inode_bitmap(ctx->inode_bad_map); + ctx->inode_bad_map = 0; + ext2fs_free_inode_bitmap(ctx->inode_reg_map); + ctx->inode_reg_map = 0; + + clear_problem_context(&pctx); + if (ctx->large_files) { + if (!(sb->s_feature_ro_compat & + EXT2_FEATURE_RO_COMPAT_LARGE_FILE) && + fix_problem(ctx, PR_2_FEATURE_LARGE_FILES, &pctx)) { + sb->s_feature_ro_compat |= + EXT2_FEATURE_RO_COMPAT_LARGE_FILE; + ext2fs_mark_super_dirty(fs); + } + if (sb->s_rev_level == EXT2_GOOD_OLD_REV && + fix_problem(ctx, PR_1_FS_REV_LEVEL, &pctx)) { + ext2fs_update_dynamic_rev(fs); + ext2fs_mark_super_dirty(fs); + } + } else if (!ctx->large_files && + (sb->s_feature_ro_compat & + EXT2_FEATURE_RO_COMPAT_LARGE_FILE)) { + if (fs->flags & EXT2_FLAG_RW) { + sb->s_feature_ro_compat &= + ~EXT2_FEATURE_RO_COMPAT_LARGE_FILE; + ext2fs_mark_super_dirty(fs); + } + } + +} + +#define MAX_DEPTH 32000 +static int htree_depth(struct dx_dir_info *dx_dir, + struct dx_dirblock_info *dx_db) +{ + int depth = 0; + + while (dx_db->type != DX_DIRBLOCK_ROOT && depth < MAX_DEPTH) { + dx_db = &dx_dir->dx_block[dx_db->parent]; + depth++; + } + return depth; +} + +static int dict_de_cmp(const void *a, const void *b) +{ + const struct ext2_dir_entry *de_a, *de_b; + int a_len, b_len; + + de_a = (const struct ext2_dir_entry *) a; + a_len = de_a->name_len & 0xFF; + de_b = (const struct ext2_dir_entry *) b; + b_len = de_b->name_len & 0xFF; + + if (a_len != b_len) + return (a_len - b_len); + + return strncmp(de_a->name, de_b->name, a_len); +} + +/* + * This is special sort function that makes sure that directory blocks + * with a dirblock of zero are sorted to the beginning of the list. + * This guarantees that the root node of the htree directories are + * processed first, so we know what hash version to use. + */ +static int special_dir_block_cmp(const void *a, const void *b) +{ + const struct ext2_db_entry *db_a = + (const struct ext2_db_entry *) a; + const struct ext2_db_entry *db_b = + (const struct ext2_db_entry *) b; + + if (db_a->blockcnt && !db_b->blockcnt) + return 1; + + if (!db_a->blockcnt && db_b->blockcnt) + return -1; + + if (db_a->blk != db_b->blk) + return (int) (db_a->blk - db_b->blk); + + if (db_a->ino != db_b->ino) + return (int) (db_a->ino - db_b->ino); + + return (int) (db_a->blockcnt - db_b->blockcnt); +} + + +/* + * Make sure the first entry in the directory is '.', and that the + * directory entry is sane. + */ +static int check_dot(e2fsck_t ctx, + struct ext2_dir_entry *dirent, + ext2_ino_t ino, struct problem_context *pctx) +{ + struct ext2_dir_entry *nextdir; + int status = 0; + int created = 0; + int new_len; + int problem = 0; + + if (!dirent->inode) + problem = PR_2_MISSING_DOT; + else if (((dirent->name_len & 0xFF) != 1) || + (dirent->name[0] != '.')) + problem = PR_2_1ST_NOT_DOT; + else if (dirent->name[1] != '\0') + problem = PR_2_DOT_NULL_TERM; + + if (problem) { + if (fix_problem(ctx, problem, pctx)) { + if (dirent->rec_len < 12) + dirent->rec_len = 12; + dirent->inode = ino; + dirent->name_len = 1; + dirent->name[0] = '.'; + dirent->name[1] = '\0'; + status = 1; + created = 1; + } + } + if (dirent->inode != ino) { + if (fix_problem(ctx, PR_2_BAD_INODE_DOT, pctx)) { + dirent->inode = ino; + status = 1; + } + } + if (dirent->rec_len > 12) { + new_len = dirent->rec_len - 12; + if (new_len > 12) { + if (created || + fix_problem(ctx, PR_2_SPLIT_DOT, pctx)) { + nextdir = (struct ext2_dir_entry *) + ((char *) dirent + 12); + dirent->rec_len = 12; + nextdir->rec_len = new_len; + nextdir->inode = 0; + nextdir->name_len = 0; + status = 1; + } + } + } + return status; +} + +/* + * Make sure the second entry in the directory is '..', and that the + * directory entry is sane. We do not check the inode number of '..' + * here; this gets done in pass 3. + */ +static int check_dotdot(e2fsck_t ctx, + struct ext2_dir_entry *dirent, + struct dir_info *dir, struct problem_context *pctx) +{ + int problem = 0; + + if (!dirent->inode) + problem = PR_2_MISSING_DOT_DOT; + else if (((dirent->name_len & 0xFF) != 2) || + (dirent->name[0] != '.') || + (dirent->name[1] != '.')) + problem = PR_2_2ND_NOT_DOT_DOT; + else if (dirent->name[2] != '\0') + problem = PR_2_DOT_DOT_NULL_TERM; + + if (problem) { + if (fix_problem(ctx, problem, pctx)) { + if (dirent->rec_len < 12) + dirent->rec_len = 12; + /* + * Note: we don't have the parent inode just + * yet, so we will fill it in with the root + * inode. This will get fixed in pass 3. + */ + dirent->inode = EXT2_ROOT_INO; + dirent->name_len = 2; + dirent->name[0] = '.'; + dirent->name[1] = '.'; + dirent->name[2] = '\0'; + return 1; + } + return 0; + } + dir->dotdot = dirent->inode; + return 0; +} + +/* + * Check to make sure a directory entry doesn't contain any illegal + * characters. + */ +static int check_name(e2fsck_t ctx, + struct ext2_dir_entry *dirent, + struct problem_context *pctx) +{ + int i; + int fixup = -1; + int ret = 0; + + for ( i = 0; i < (dirent->name_len & 0xFF); i++) { + if (dirent->name[i] == '/' || dirent->name[i] == '\0') { + if (fixup < 0) { + fixup = fix_problem(ctx, PR_2_BAD_NAME, pctx); + } + if (fixup) { + dirent->name[i] = '.'; + ret = 1; + } + } + } + return ret; +} + +/* + * Check the directory filetype (if present) + */ + +/* + * Given a mode, return the ext2 file type + */ +static int ext2_file_type(unsigned int mode) +{ + if (LINUX_S_ISREG(mode)) + return EXT2_FT_REG_FILE; + + if (LINUX_S_ISDIR(mode)) + return EXT2_FT_DIR; + + if (LINUX_S_ISCHR(mode)) + return EXT2_FT_CHRDEV; + + if (LINUX_S_ISBLK(mode)) + return EXT2_FT_BLKDEV; + + if (LINUX_S_ISLNK(mode)) + return EXT2_FT_SYMLINK; + + if (LINUX_S_ISFIFO(mode)) + return EXT2_FT_FIFO; + + if (LINUX_S_ISSOCK(mode)) + return EXT2_FT_SOCK; + + return 0; +} + +static int check_filetype(e2fsck_t ctx, + struct ext2_dir_entry *dirent, + struct problem_context *pctx) +{ + int filetype = dirent->name_len >> 8; + int should_be = EXT2_FT_UNKNOWN; + struct ext2_inode inode; + + if (!(ctx->fs->super->s_feature_incompat & + EXT2_FEATURE_INCOMPAT_FILETYPE)) { + if (filetype == 0 || + !fix_problem(ctx, PR_2_CLEAR_FILETYPE, pctx)) + return 0; + dirent->name_len = dirent->name_len & 0xFF; + return 1; + } + + if (ext2fs_test_inode_bitmap(ctx->inode_dir_map, dirent->inode)) { + should_be = EXT2_FT_DIR; + } else if (ext2fs_test_inode_bitmap(ctx->inode_reg_map, + dirent->inode)) { + should_be = EXT2_FT_REG_FILE; + } else if (ctx->inode_bad_map && + ext2fs_test_inode_bitmap(ctx->inode_bad_map, + dirent->inode)) + should_be = 0; + else { + e2fsck_read_inode(ctx, dirent->inode, &inode, + "check_filetype"); + should_be = ext2_file_type(inode.i_mode); + } + if (filetype == should_be) + return 0; + pctx->num = should_be; + + if (fix_problem(ctx, filetype ? PR_2_BAD_FILETYPE : PR_2_SET_FILETYPE, + pctx) == 0) + return 0; + + dirent->name_len = (dirent->name_len & 0xFF) | should_be << 8; + return 1; +} + +#ifdef ENABLE_HTREE +static void parse_int_node(ext2_filsys fs, + struct ext2_db_entry *db, + struct check_dir_struct *cd, + struct dx_dir_info *dx_dir, + char *block_buf) +{ + struct ext2_dx_root_info *root; + struct ext2_dx_entry *ent; + struct ext2_dx_countlimit *limit; + struct dx_dirblock_info *dx_db; + int i, expect_limit, count; + blk_t blk; + ext2_dirhash_t min_hash = 0xffffffff; + ext2_dirhash_t max_hash = 0; + ext2_dirhash_t hash = 0, prev_hash; + + if (db->blockcnt == 0) { + root = (struct ext2_dx_root_info *) (block_buf + 24); + ent = (struct ext2_dx_entry *) (block_buf + 24 + root->info_length); + } else { + ent = (struct ext2_dx_entry *) (block_buf+8); + } + limit = (struct ext2_dx_countlimit *) ent; + + count = ext2fs_le16_to_cpu(limit->count); + expect_limit = (fs->blocksize - ((char *) ent - block_buf)) / + sizeof(struct ext2_dx_entry); + if (ext2fs_le16_to_cpu(limit->limit) != expect_limit) { + cd->pctx.num = ext2fs_le16_to_cpu(limit->limit); + if (fix_problem(cd->ctx, PR_2_HTREE_BAD_LIMIT, &cd->pctx)) + goto clear_and_exit; + } + if (count > expect_limit) { + cd->pctx.num = count; + if (fix_problem(cd->ctx, PR_2_HTREE_BAD_COUNT, &cd->pctx)) + goto clear_and_exit; + count = expect_limit; + } + + for (i=0; i < count; i++) { + prev_hash = hash; + hash = i ? (ext2fs_le32_to_cpu(ent[i].hash) & ~1) : 0; + blk = ext2fs_le32_to_cpu(ent[i].block) & 0x0ffffff; + /* Check to make sure the block is valid */ + if (blk > (blk_t) dx_dir->numblocks) { + cd->pctx.blk = blk; + if (fix_problem(cd->ctx, PR_2_HTREE_BADBLK, + &cd->pctx)) + goto clear_and_exit; + } + if (hash < prev_hash && + fix_problem(cd->ctx, PR_2_HTREE_HASH_ORDER, &cd->pctx)) + goto clear_and_exit; + dx_db = &dx_dir->dx_block[blk]; + if (dx_db->flags & DX_FLAG_REFERENCED) { + dx_db->flags |= DX_FLAG_DUP_REF; + } else { + dx_db->flags |= DX_FLAG_REFERENCED; + dx_db->parent = db->blockcnt; + } + if (hash < min_hash) + min_hash = hash; + if (hash > max_hash) + max_hash = hash; + dx_db->node_min_hash = hash; + if ((i+1) < count) + dx_db->node_max_hash = + ext2fs_le32_to_cpu(ent[i+1].hash) & ~1; + else { + dx_db->node_max_hash = 0xfffffffe; + dx_db->flags |= DX_FLAG_LAST; + } + if (i == 0) + dx_db->flags |= DX_FLAG_FIRST; + } + dx_db = &dx_dir->dx_block[db->blockcnt]; + dx_db->min_hash = min_hash; + dx_db->max_hash = max_hash; + return; + +clear_and_exit: + clear_htree(cd->ctx, cd->pctx.ino); + dx_dir->numblocks = 0; +} +#endif /* ENABLE_HTREE */ + +/* + * Given a busted directory, try to salvage it somehow. + * + */ +static void salvage_directory(ext2_filsys fs, + struct ext2_dir_entry *dirent, + struct ext2_dir_entry *prev, + unsigned int *offset) +{ + char *cp = (char *) dirent; + int left = fs->blocksize - *offset - dirent->rec_len; + int name_len = dirent->name_len & 0xFF; + + /* + * Special case of directory entry of size 8: copy what's left + * of the directory block up to cover up the invalid hole. + */ + if ((left >= 12) && (dirent->rec_len == 8)) { + memmove(cp, cp+8, left); + memset(cp + left, 0, 8); + return; + } + /* + * If the directory entry overruns the end of the directory + * block, and the name is small enough to fit, then adjust the + * record length. + */ + if ((left < 0) && + (name_len + 8 <= dirent->rec_len + left) && + dirent->inode <= fs->super->s_inodes_count && + strnlen(dirent->name, name_len) == name_len) { + dirent->rec_len += left; + return; + } + /* + * If the directory entry is a multiple of four, so it is + * valid, let the previous directory entry absorb the invalid + * one. + */ + if (prev && dirent->rec_len && (dirent->rec_len % 4) == 0) { + prev->rec_len += dirent->rec_len; + *offset += dirent->rec_len; + return; + } + /* + * Default salvage method --- kill all of the directory + * entries for the rest of the block. We will either try to + * absorb it into the previous directory entry, or create a + * new empty directory entry the rest of the directory block. + */ + if (prev) { + prev->rec_len += fs->blocksize - *offset; + *offset = fs->blocksize; + } else { + dirent->rec_len = fs->blocksize - *offset; + dirent->name_len = 0; + dirent->inode = 0; + } +} + +static int check_dir_block(ext2_filsys fs, + struct ext2_db_entry *db, + void *priv_data) +{ + struct dir_info *subdir, *dir; + struct dx_dir_info *dx_dir; +#ifdef ENABLE_HTREE + struct dx_dirblock_info *dx_db = 0; +#endif /* ENABLE_HTREE */ + struct ext2_dir_entry *dirent, *prev; + ext2_dirhash_t hash; + unsigned int offset = 0; + int dir_modified = 0; + int dot_state; + blk_t block_nr = db->blk; + ext2_ino_t ino = db->ino; + __u16 links; + struct check_dir_struct *cd; + char *buf; + e2fsck_t ctx; + int problem; + struct ext2_dx_root_info *root; + struct ext2_dx_countlimit *limit; + static dict_t de_dict; + struct problem_context pctx; + int dups_found = 0; + + cd = (struct check_dir_struct *) priv_data; + buf = cd->buf; + ctx = cd->ctx; + + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return DIRENT_ABORT; + + if (ctx->progress && (ctx->progress)(ctx, 2, cd->count++, cd->max)) + return DIRENT_ABORT; + + /* + * Make sure the inode is still in use (could have been + * deleted in the duplicate/bad blocks pass. + */ + if (!(ext2fs_test_inode_bitmap(ctx->inode_used_map, ino))) + return 0; + + cd->pctx.ino = ino; + cd->pctx.blk = block_nr; + cd->pctx.blkcount = db->blockcnt; + cd->pctx.ino2 = 0; + cd->pctx.dirent = 0; + cd->pctx.num = 0; + + if (db->blk == 0) { + if (allocate_dir_block(ctx, db, &cd->pctx)) + return 0; + block_nr = db->blk; + } + + if (db->blockcnt) + dot_state = 2; + else + dot_state = 0; + + if (ctx->dirs_to_hash && + ext2fs_u32_list_test(ctx->dirs_to_hash, ino)) + dups_found++; + + cd->pctx.errcode = ext2fs_read_dir_block(fs, block_nr, buf); + if (cd->pctx.errcode == EXT2_ET_DIR_CORRUPTED) + cd->pctx.errcode = 0; /* We'll handle this ourselves */ + if (cd->pctx.errcode) { + if (!fix_problem(ctx, PR_2_READ_DIRBLOCK, &cd->pctx)) { + ctx->flags |= E2F_FLAG_ABORT; + return DIRENT_ABORT; + } + memset(buf, 0, fs->blocksize); + } +#ifdef ENABLE_HTREE + dx_dir = e2fsck_get_dx_dir_info(ctx, ino); + if (dx_dir && dx_dir->numblocks) { + if (db->blockcnt >= dx_dir->numblocks) { + printf("XXX should never happen!!!\n"); + abort(); + } + dx_db = &dx_dir->dx_block[db->blockcnt]; + dx_db->type = DX_DIRBLOCK_LEAF; + dx_db->phys = block_nr; + dx_db->min_hash = ~0; + dx_db->max_hash = 0; + + dirent = (struct ext2_dir_entry *) buf; + limit = (struct ext2_dx_countlimit *) (buf+8); + if (db->blockcnt == 0) { + root = (struct ext2_dx_root_info *) (buf + 24); + dx_db->type = DX_DIRBLOCK_ROOT; + dx_db->flags |= DX_FLAG_FIRST | DX_FLAG_LAST; + if ((root->reserved_zero || + root->info_length < 8 || + root->indirect_levels > 1) && + fix_problem(ctx, PR_2_HTREE_BAD_ROOT, &cd->pctx)) { + clear_htree(ctx, ino); + dx_dir->numblocks = 0; + dx_db = 0; + } + dx_dir->hashversion = root->hash_version; + dx_dir->depth = root->indirect_levels + 1; + } else if ((dirent->inode == 0) && + (dirent->rec_len == fs->blocksize) && + (dirent->name_len == 0) && + (ext2fs_le16_to_cpu(limit->limit) == + ((fs->blocksize-8) / + sizeof(struct ext2_dx_entry)))) + dx_db->type = DX_DIRBLOCK_NODE; + } +#endif /* ENABLE_HTREE */ + + dict_init(&de_dict, DICTCOUNT_T_MAX, dict_de_cmp); + prev = 0; + do { + problem = 0; + dirent = (struct ext2_dir_entry *) (buf + offset); + cd->pctx.dirent = dirent; + cd->pctx.num = offset; + if (((offset + dirent->rec_len) > fs->blocksize) || + (dirent->rec_len < 12) || + ((dirent->rec_len % 4) != 0) || + (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) { + if (fix_problem(ctx, PR_2_DIR_CORRUPTED, &cd->pctx)) { + salvage_directory(fs, dirent, prev, &offset); + dir_modified++; + continue; + } else + goto abort_free_dict; + } + if ((dirent->name_len & 0xFF) > EXT2_NAME_LEN) { + if (fix_problem(ctx, PR_2_FILENAME_LONG, &cd->pctx)) { + dirent->name_len = EXT2_NAME_LEN; + dir_modified++; + } + } + + if (dot_state == 0) { + if (check_dot(ctx, dirent, ino, &cd->pctx)) + dir_modified++; + } else if (dot_state == 1) { + dir = e2fsck_get_dir_info(ctx, ino); + if (!dir) { + fix_problem(ctx, PR_2_NO_DIRINFO, &cd->pctx); + goto abort_free_dict; + } + if (check_dotdot(ctx, dirent, dir, &cd->pctx)) + dir_modified++; + } else if (dirent->inode == ino) { + problem = PR_2_LINK_DOT; + if (fix_problem(ctx, PR_2_LINK_DOT, &cd->pctx)) { + dirent->inode = 0; + dir_modified++; + goto next; + } + } + if (!dirent->inode) + goto next; + + /* + * Make sure the inode listed is a legal one. + */ + if (((dirent->inode != EXT2_ROOT_INO) && + (dirent->inode < EXT2_FIRST_INODE(fs->super))) || + (dirent->inode > fs->super->s_inodes_count)) { + problem = PR_2_BAD_INO; + } else if (!(ext2fs_test_inode_bitmap(ctx->inode_used_map, + dirent->inode))) { + /* + * If the inode is unused, offer to clear it. + */ + problem = PR_2_UNUSED_INODE; + } else if ((dot_state > 1) && + ((dirent->name_len & 0xFF) == 1) && + (dirent->name[0] == '.')) { + /* + * If there's a '.' entry in anything other + * than the first directory entry, it's a + * duplicate entry that should be removed. + */ + problem = PR_2_DUP_DOT; + } else if ((dot_state > 1) && + ((dirent->name_len & 0xFF) == 2) && + (dirent->name[0] == '.') && + (dirent->name[1] == '.')) { + /* + * If there's a '..' entry in anything other + * than the second directory entry, it's a + * duplicate entry that should be removed. + */ + problem = PR_2_DUP_DOT_DOT; + } else if ((dot_state > 1) && + (dirent->inode == EXT2_ROOT_INO)) { + /* + * Don't allow links to the root directory. + * We check this specially to make sure we + * catch this error case even if the root + * directory hasn't been created yet. + */ + problem = PR_2_LINK_ROOT; + } else if ((dot_state > 1) && + (dirent->name_len & 0xFF) == 0) { + /* + * Don't allow zero-length directory names. + */ + problem = PR_2_NULL_NAME; + } + + if (problem) { + if (fix_problem(ctx, problem, &cd->pctx)) { + dirent->inode = 0; + dir_modified++; + goto next; + } else { + ext2fs_unmark_valid(fs); + if (problem == PR_2_BAD_INO) + goto next; + } + } + + /* + * If the inode was marked as having bad fields in + * pass1, process it and offer to fix/clear it. + * (We wait until now so that we can display the + * pathname to the user.) + */ + if (ctx->inode_bad_map && + ext2fs_test_inode_bitmap(ctx->inode_bad_map, + dirent->inode)) { + if (e2fsck_process_bad_inode(ctx, ino, + dirent->inode, + buf + fs->blocksize)) { + dirent->inode = 0; + dir_modified++; + goto next; + } + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return DIRENT_ABORT; + } + + if (check_name(ctx, dirent, &cd->pctx)) + dir_modified++; + + if (check_filetype(ctx, dirent, &cd->pctx)) + dir_modified++; + +#ifdef ENABLE_HTREE + if (dx_db) { + ext2fs_dirhash(dx_dir->hashversion, dirent->name, + (dirent->name_len & 0xFF), + fs->super->s_hash_seed, &hash, 0); + if (hash < dx_db->min_hash) + dx_db->min_hash = hash; + if (hash > dx_db->max_hash) + dx_db->max_hash = hash; + } +#endif + + /* + * If this is a directory, then mark its parent in its + * dir_info structure. If the parent field is already + * filled in, then this directory has more than one + * hard link. We assume the first link is correct, + * and ask the user if he/she wants to clear this one. + */ + if ((dot_state > 1) && + (ext2fs_test_inode_bitmap(ctx->inode_dir_map, + dirent->inode))) { + subdir = e2fsck_get_dir_info(ctx, dirent->inode); + if (!subdir) { + cd->pctx.ino = dirent->inode; + fix_problem(ctx, PR_2_NO_DIRINFO, &cd->pctx); + goto abort_free_dict; + } + if (subdir->parent) { + cd->pctx.ino2 = subdir->parent; + if (fix_problem(ctx, PR_2_LINK_DIR, + &cd->pctx)) { + dirent->inode = 0; + dir_modified++; + goto next; + } + cd->pctx.ino2 = 0; + } else + subdir->parent = ino; + } + + if (dups_found) { + ; + } else if (dict_lookup(&de_dict, dirent)) { + clear_problem_context(&pctx); + pctx.ino = ino; + pctx.dirent = dirent; + fix_problem(ctx, PR_2_REPORT_DUP_DIRENT, &pctx); + if (!ctx->dirs_to_hash) + ext2fs_u32_list_create(&ctx->dirs_to_hash, 50); + if (ctx->dirs_to_hash) + ext2fs_u32_list_add(ctx->dirs_to_hash, ino); + dups_found++; + } else + dict_alloc_insert(&de_dict, dirent, dirent); + + ext2fs_icount_increment(ctx->inode_count, dirent->inode, + &links); + if (links > 1) + ctx->fs_links_count++; + ctx->fs_total_count++; + next: + prev = dirent; + offset += dirent->rec_len; + dot_state++; + } while (offset < fs->blocksize); +#ifdef ENABLE_HTREE + if (dx_db) { + cd->pctx.dir = cd->pctx.ino; + if ((dx_db->type == DX_DIRBLOCK_ROOT) || + (dx_db->type == DX_DIRBLOCK_NODE)) + parse_int_node(fs, db, cd, dx_dir, buf); + } +#endif /* ENABLE_HTREE */ + if (offset != fs->blocksize) { + cd->pctx.num = dirent->rec_len - fs->blocksize + offset; + if (fix_problem(ctx, PR_2_FINAL_RECLEN, &cd->pctx)) { + dirent->rec_len = cd->pctx.num; + dir_modified++; + } + } + if (dir_modified) { + cd->pctx.errcode = ext2fs_write_dir_block(fs, block_nr, buf); + if (cd->pctx.errcode) { + if (!fix_problem(ctx, PR_2_WRITE_DIRBLOCK, + &cd->pctx)) + goto abort_free_dict; + } + ext2fs_mark_changed(fs); + } + dict_free_nodes(&de_dict); + return 0; +abort_free_dict: + dict_free_nodes(&de_dict); + ctx->flags |= E2F_FLAG_ABORT; + return DIRENT_ABORT; +} + +/* + * This function is called to deallocate a block, and is an interator + * functioned called by deallocate inode via ext2fs_iterate_block(). + */ +static int deallocate_inode_block(ext2_filsys fs, blk_t *block_nr, + e2_blkcnt_t blockcnt FSCK_ATTR((unused)), + blk_t ref_block FSCK_ATTR((unused)), + int ref_offset FSCK_ATTR((unused)), + void *priv_data) +{ + e2fsck_t ctx = (e2fsck_t) priv_data; + + if (HOLE_BLKADDR(*block_nr)) + return 0; + if ((*block_nr < fs->super->s_first_data_block) || + (*block_nr >= fs->super->s_blocks_count)) + return 0; + ext2fs_unmark_block_bitmap(ctx->block_found_map, *block_nr); + ext2fs_block_alloc_stats(fs, *block_nr, -1); + return 0; +} + +/* + * This fuction deallocates an inode + */ +static void deallocate_inode(e2fsck_t ctx, ext2_ino_t ino, char* block_buf) +{ + ext2_filsys fs = ctx->fs; + struct ext2_inode inode; + struct problem_context pctx; + __u32 count; + + ext2fs_icount_store(ctx->inode_link_info, ino, 0); + e2fsck_read_inode(ctx, ino, &inode, "deallocate_inode"); + inode.i_links_count = 0; + inode.i_dtime = time(0); + e2fsck_write_inode(ctx, ino, &inode, "deallocate_inode"); + clear_problem_context(&pctx); + pctx.ino = ino; + + /* + * Fix up the bitmaps... + */ + e2fsck_read_bitmaps(ctx); + ext2fs_unmark_inode_bitmap(ctx->inode_used_map, ino); + ext2fs_unmark_inode_bitmap(ctx->inode_dir_map, ino); + if (ctx->inode_bad_map) + ext2fs_unmark_inode_bitmap(ctx->inode_bad_map, ino); + ext2fs_inode_alloc_stats2(fs, ino, -1, LINUX_S_ISDIR(inode.i_mode)); + + if (inode.i_file_acl && + (fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_EXT_ATTR)) { + pctx.errcode = ext2fs_adjust_ea_refcount(fs, inode.i_file_acl, + block_buf, -1, &count); + if (pctx.errcode == EXT2_ET_BAD_EA_BLOCK_NUM) { + pctx.errcode = 0; + count = 1; + } + if (pctx.errcode) { + pctx.blk = inode.i_file_acl; + fix_problem(ctx, PR_2_ADJ_EA_REFCOUNT, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + if (count == 0) { + ext2fs_unmark_block_bitmap(ctx->block_found_map, + inode.i_file_acl); + ext2fs_block_alloc_stats(fs, inode.i_file_acl, -1); + } + inode.i_file_acl = 0; + } + + if (!ext2fs_inode_has_valid_blocks(&inode)) + return; + + if (LINUX_S_ISREG(inode.i_mode) && + (inode.i_size_high || inode.i_size & 0x80000000UL)) + ctx->large_files--; + + pctx.errcode = ext2fs_block_iterate2(fs, ino, 0, block_buf, + deallocate_inode_block, ctx); + if (pctx.errcode) { + fix_problem(ctx, PR_2_DEALLOC_INODE, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } +} + +/* + * This fuction clears the htree flag on an inode + */ +static void clear_htree(e2fsck_t ctx, ext2_ino_t ino) +{ + struct ext2_inode inode; + + e2fsck_read_inode(ctx, ino, &inode, "clear_htree"); + inode.i_flags = inode.i_flags & ~EXT2_INDEX_FL; + e2fsck_write_inode(ctx, ino, &inode, "clear_htree"); + if (ctx->dirs_to_hash) + ext2fs_u32_list_add(ctx->dirs_to_hash, ino); +} + + +static int e2fsck_process_bad_inode(e2fsck_t ctx, ext2_ino_t dir, + ext2_ino_t ino, char *buf) +{ + ext2_filsys fs = ctx->fs; + struct ext2_inode inode; + int inode_modified = 0; + int not_fixed = 0; + unsigned char *frag, *fsize; + struct problem_context pctx; + int problem = 0; + + e2fsck_read_inode(ctx, ino, &inode, "process_bad_inode"); + + clear_problem_context(&pctx); + pctx.ino = ino; + pctx.dir = dir; + pctx.inode = &inode; + + if (inode.i_file_acl && + !(fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_EXT_ATTR) && + fix_problem(ctx, PR_2_FILE_ACL_ZERO, &pctx)) { + inode.i_file_acl = 0; +#if BB_BIG_ENDIAN + /* + * This is a special kludge to deal with long symlinks + * on big endian systems. i_blocks had already been + * decremented earlier in pass 1, but since i_file_acl + * hadn't yet been cleared, ext2fs_read_inode() + * assumed that the file was short symlink and would + * not have byte swapped i_block[0]. Hence, we have + * to byte-swap it here. + */ + if (LINUX_S_ISLNK(inode.i_mode) && + (fs->flags & EXT2_FLAG_SWAP_BYTES) && + (inode.i_blocks == fs->blocksize >> 9)) + inode.i_block[0] = ext2fs_swab32(inode.i_block[0]); +#endif + inode_modified++; + } else + not_fixed++; + + if (!LINUX_S_ISDIR(inode.i_mode) && !LINUX_S_ISREG(inode.i_mode) && + !LINUX_S_ISCHR(inode.i_mode) && !LINUX_S_ISBLK(inode.i_mode) && + !LINUX_S_ISLNK(inode.i_mode) && !LINUX_S_ISFIFO(inode.i_mode) && + !(LINUX_S_ISSOCK(inode.i_mode))) + problem = PR_2_BAD_MODE; + else if (LINUX_S_ISCHR(inode.i_mode) + && !e2fsck_pass1_check_device_inode(fs, &inode)) + problem = PR_2_BAD_CHAR_DEV; + else if (LINUX_S_ISBLK(inode.i_mode) + && !e2fsck_pass1_check_device_inode(fs, &inode)) + problem = PR_2_BAD_BLOCK_DEV; + else if (LINUX_S_ISFIFO(inode.i_mode) + && !e2fsck_pass1_check_device_inode(fs, &inode)) + problem = PR_2_BAD_FIFO; + else if (LINUX_S_ISSOCK(inode.i_mode) + && !e2fsck_pass1_check_device_inode(fs, &inode)) + problem = PR_2_BAD_SOCKET; + else if (LINUX_S_ISLNK(inode.i_mode) + && !e2fsck_pass1_check_symlink(fs, &inode, buf)) { + problem = PR_2_INVALID_SYMLINK; + } + + if (problem) { + if (fix_problem(ctx, problem, &pctx)) { + deallocate_inode(ctx, ino, 0); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return 0; + return 1; + } else + not_fixed++; + problem = 0; + } + + if (inode.i_faddr) { + if (fix_problem(ctx, PR_2_FADDR_ZERO, &pctx)) { + inode.i_faddr = 0; + inode_modified++; + } else + not_fixed++; + } + + switch (fs->super->s_creator_os) { + case EXT2_OS_LINUX: + frag = &inode.osd2.linux2.l_i_frag; + fsize = &inode.osd2.linux2.l_i_fsize; + break; + case EXT2_OS_HURD: + frag = &inode.osd2.hurd2.h_i_frag; + fsize = &inode.osd2.hurd2.h_i_fsize; + break; + case EXT2_OS_MASIX: + frag = &inode.osd2.masix2.m_i_frag; + fsize = &inode.osd2.masix2.m_i_fsize; + break; + default: + frag = fsize = 0; + } + if (frag && *frag) { + pctx.num = *frag; + if (fix_problem(ctx, PR_2_FRAG_ZERO, &pctx)) { + *frag = 0; + inode_modified++; + } else + not_fixed++; + pctx.num = 0; + } + if (fsize && *fsize) { + pctx.num = *fsize; + if (fix_problem(ctx, PR_2_FSIZE_ZERO, &pctx)) { + *fsize = 0; + inode_modified++; + } else + not_fixed++; + pctx.num = 0; + } + + if (inode.i_file_acl && + ((inode.i_file_acl < fs->super->s_first_data_block) || + (inode.i_file_acl >= fs->super->s_blocks_count))) { + if (fix_problem(ctx, PR_2_FILE_ACL_BAD, &pctx)) { + inode.i_file_acl = 0; + inode_modified++; + } else + not_fixed++; + } + if (inode.i_dir_acl && + LINUX_S_ISDIR(inode.i_mode)) { + if (fix_problem(ctx, PR_2_DIR_ACL_ZERO, &pctx)) { + inode.i_dir_acl = 0; + inode_modified++; + } else + not_fixed++; + } + + if (inode_modified) + e2fsck_write_inode(ctx, ino, &inode, "process_bad_inode"); + if (!not_fixed) + ext2fs_unmark_inode_bitmap(ctx->inode_bad_map, ino); + return 0; +} + + +/* + * allocate_dir_block --- this function allocates a new directory + * block for a particular inode; this is done if a directory has + * a "hole" in it, or if a directory has a illegal block number + * that was zeroed out and now needs to be replaced. + */ +static int allocate_dir_block(e2fsck_t ctx, struct ext2_db_entry *db, + struct problem_context *pctx) +{ + ext2_filsys fs = ctx->fs; + blk_t blk; + char *block; + struct ext2_inode inode; + + if (fix_problem(ctx, PR_2_DIRECTORY_HOLE, pctx) == 0) + return 1; + + /* + * Read the inode and block bitmaps in; we'll be messing with + * them. + */ + e2fsck_read_bitmaps(ctx); + + /* + * First, find a free block + */ + pctx->errcode = ext2fs_new_block(fs, 0, ctx->block_found_map, &blk); + if (pctx->errcode) { + pctx->str = "ext2fs_new_block"; + fix_problem(ctx, PR_2_ALLOC_DIRBOCK, pctx); + return 1; + } + ext2fs_mark_block_bitmap(ctx->block_found_map, blk); + ext2fs_mark_block_bitmap(fs->block_map, blk); + ext2fs_mark_bb_dirty(fs); + + /* + * Now let's create the actual data block for the inode + */ + if (db->blockcnt) + pctx->errcode = ext2fs_new_dir_block(fs, 0, 0, &block); + else + pctx->errcode = ext2fs_new_dir_block(fs, db->ino, + EXT2_ROOT_INO, &block); + + if (pctx->errcode) { + pctx->str = "ext2fs_new_dir_block"; + fix_problem(ctx, PR_2_ALLOC_DIRBOCK, pctx); + return 1; + } + + pctx->errcode = ext2fs_write_dir_block(fs, blk, block); + ext2fs_free_mem(&block); + if (pctx->errcode) { + pctx->str = "ext2fs_write_dir_block"; + fix_problem(ctx, PR_2_ALLOC_DIRBOCK, pctx); + return 1; + } + + /* + * Update the inode block count + */ + e2fsck_read_inode(ctx, db->ino, &inode, "allocate_dir_block"); + inode.i_blocks += fs->blocksize / 512; + if (inode.i_size < (db->blockcnt+1) * fs->blocksize) + inode.i_size = (db->blockcnt+1) * fs->blocksize; + e2fsck_write_inode(ctx, db->ino, &inode, "allocate_dir_block"); + + /* + * Finally, update the block pointers for the inode + */ + db->blk = blk; + pctx->errcode = ext2fs_block_iterate2(fs, db->ino, BLOCK_FLAG_HOLE, + 0, update_dir_block, db); + if (pctx->errcode) { + pctx->str = "ext2fs_block_iterate"; + fix_problem(ctx, PR_2_ALLOC_DIRBOCK, pctx); + return 1; + } + + return 0; +} + +/* + * This is a helper function for allocate_dir_block(). + */ +static int update_dir_block(ext2_filsys fs FSCK_ATTR((unused)), + blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_block FSCK_ATTR((unused)), + int ref_offset FSCK_ATTR((unused)), + void *priv_data) +{ + struct ext2_db_entry *db; + + db = (struct ext2_db_entry *) priv_data; + if (db->blockcnt == (int) blockcnt) { + *block_nr = db->blk; + return BLOCK_CHANGED; + } + return 0; +} + +/* + * pass3.c -- pass #3 of e2fsck: Check for directory connectivity + * + * Pass #3 assures that all directories are connected to the + * filesystem tree, using the following algorithm: + * + * First, the root directory is checked to make sure it exists; if + * not, e2fsck will offer to create a new one. It is then marked as + * "done". + * + * Then, pass3 interates over all directory inodes; for each directory + * it attempts to trace up the filesystem tree, using dirinfo.parent + * until it reaches a directory which has been marked "done". If it + * cannot do so, then the directory must be disconnected, and e2fsck + * will offer to reconnect it to /lost+found. While it is chasing + * parent pointers up the filesystem tree, if pass3 sees a directory + * twice, then it has detected a filesystem loop, and it will again + * offer to reconnect the directory to /lost+found in to break the + * filesystem loop. + * + * Pass 3 also contains the subroutine, e2fsck_reconnect_file() to + * reconnect inodes to /lost+found; this subroutine is also used by + * pass 4. e2fsck_reconnect_file() calls get_lost_and_found(), which + * is responsible for creating /lost+found if it does not exist. + * + * Pass 3 frees the following data structures: + * - The dirinfo directory information cache. + */ + +static void check_root(e2fsck_t ctx); +static int check_directory(e2fsck_t ctx, struct dir_info *dir, + struct problem_context *pctx); +static void fix_dotdot(e2fsck_t ctx, struct dir_info *dir, ext2_ino_t parent); + +static ext2fs_inode_bitmap inode_loop_detect; +static ext2fs_inode_bitmap inode_done_map; + +static void e2fsck_pass3(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + int i; + struct problem_context pctx; + struct dir_info *dir; + unsigned long maxdirs, count; + + clear_problem_context(&pctx); + + /* Pass 3 */ + + if (!(ctx->options & E2F_OPT_PREEN)) + fix_problem(ctx, PR_3_PASS_HEADER, &pctx); + + /* + * Allocate some bitmaps to do loop detection. + */ + pctx.errcode = ext2fs_allocate_inode_bitmap(fs, _("inode done bitmap"), + &inode_done_map); + if (pctx.errcode) { + pctx.num = 2; + fix_problem(ctx, PR_3_ALLOCATE_IBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + goto abort_exit; + } + check_root(ctx); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + goto abort_exit; + + ext2fs_mark_inode_bitmap(inode_done_map, EXT2_ROOT_INO); + + maxdirs = e2fsck_get_num_dirinfo(ctx); + count = 1; + + if (ctx->progress) + if ((ctx->progress)(ctx, 3, 0, maxdirs)) + goto abort_exit; + + for (i=0; (dir = e2fsck_dir_info_iter(ctx, &i)) != 0;) { + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + goto abort_exit; + if (ctx->progress && (ctx->progress)(ctx, 3, count++, maxdirs)) + goto abort_exit; + if (ext2fs_test_inode_bitmap(ctx->inode_dir_map, dir->ino)) + if (check_directory(ctx, dir, &pctx)) + goto abort_exit; + } + + /* + * Force the creation of /lost+found if not present + */ + if ((ctx->flags & E2F_OPT_READONLY) == 0) + e2fsck_get_lost_and_found(ctx, 1); + + /* + * If there are any directories that need to be indexed or + * optimized, do it here. + */ + e2fsck_rehash_directories(ctx); + +abort_exit: + e2fsck_free_dir_info(ctx); + ext2fs_free_inode_bitmap(inode_loop_detect); + inode_loop_detect = 0; + ext2fs_free_inode_bitmap(inode_done_map); + inode_done_map = 0; +} + +/* + * This makes sure the root inode is present; if not, we ask if the + * user wants us to create it. Not creating it is a fatal error. + */ +static void check_root(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + blk_t blk; + struct ext2_inode inode; + char * block; + struct problem_context pctx; + + clear_problem_context(&pctx); + + if (ext2fs_test_inode_bitmap(ctx->inode_used_map, EXT2_ROOT_INO)) { + /* + * If the root inode is not a directory, die here. The + * user must have answered 'no' in pass1 when we + * offered to clear it. + */ + if (!(ext2fs_test_inode_bitmap(ctx->inode_dir_map, + EXT2_ROOT_INO))) { + fix_problem(ctx, PR_3_ROOT_NOT_DIR_ABORT, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + } + return; + } + + if (!fix_problem(ctx, PR_3_NO_ROOT_INODE, &pctx)) { + fix_problem(ctx, PR_3_NO_ROOT_INODE_ABORT, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + + e2fsck_read_bitmaps(ctx); + + /* + * First, find a free block + */ + pctx.errcode = ext2fs_new_block(fs, 0, ctx->block_found_map, &blk); + if (pctx.errcode) { + pctx.str = "ext2fs_new_block"; + fix_problem(ctx, PR_3_CREATE_ROOT_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + ext2fs_mark_block_bitmap(ctx->block_found_map, blk); + ext2fs_mark_block_bitmap(fs->block_map, blk); + ext2fs_mark_bb_dirty(fs); + + /* + * Now let's create the actual data block for the inode + */ + pctx.errcode = ext2fs_new_dir_block(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, + &block); + if (pctx.errcode) { + pctx.str = "ext2fs_new_dir_block"; + fix_problem(ctx, PR_3_CREATE_ROOT_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + + pctx.errcode = ext2fs_write_dir_block(fs, blk, block); + if (pctx.errcode) { + pctx.str = "ext2fs_write_dir_block"; + fix_problem(ctx, PR_3_CREATE_ROOT_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + ext2fs_free_mem(&block); + + /* + * Set up the inode structure + */ + memset(&inode, 0, sizeof(inode)); + inode.i_mode = 040755; + inode.i_size = fs->blocksize; + inode.i_atime = inode.i_ctime = inode.i_mtime = time(0); + inode.i_links_count = 2; + inode.i_blocks = fs->blocksize / 512; + inode.i_block[0] = blk; + + /* + * Write out the inode. + */ + pctx.errcode = ext2fs_write_new_inode(fs, EXT2_ROOT_INO, &inode); + if (pctx.errcode) { + pctx.str = "ext2fs_write_inode"; + fix_problem(ctx, PR_3_CREATE_ROOT_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + + /* + * Miscellaneous bookkeeping... + */ + e2fsck_add_dir_info(ctx, EXT2_ROOT_INO, EXT2_ROOT_INO); + ext2fs_icount_store(ctx->inode_count, EXT2_ROOT_INO, 2); + ext2fs_icount_store(ctx->inode_link_info, EXT2_ROOT_INO, 2); + + ext2fs_mark_inode_bitmap(ctx->inode_used_map, EXT2_ROOT_INO); + ext2fs_mark_inode_bitmap(ctx->inode_dir_map, EXT2_ROOT_INO); + ext2fs_mark_inode_bitmap(fs->inode_map, EXT2_ROOT_INO); + ext2fs_mark_ib_dirty(fs); +} + +/* + * This subroutine is responsible for making sure that a particular + * directory is connected to the root; if it isn't we trace it up as + * far as we can go, and then offer to connect the resulting parent to + * the lost+found. We have to do loop detection; if we ever discover + * a loop, we treat that as a disconnected directory and offer to + * reparent it to lost+found. + * + * However, loop detection is expensive, because for very large + * filesystems, the inode_loop_detect bitmap is huge, and clearing it + * is non-trivial. Loops in filesystems are also a rare error case, + * and we shouldn't optimize for error cases. So we try two passes of + * the algorithm. The first time, we ignore loop detection and merely + * increment a counter; if the counter exceeds some extreme threshold, + * then we try again with the loop detection bitmap enabled. + */ +static int check_directory(e2fsck_t ctx, struct dir_info *dir, + struct problem_context *pctx) +{ + ext2_filsys fs = ctx->fs; + struct dir_info *p = dir; + int loop_pass = 0, parent_count = 0; + + if (!p) + return 0; + + while (1) { + /* + * Mark this inode as being "done"; by the time we + * return from this function, the inode we either be + * verified as being connected to the directory tree, + * or we will have offered to reconnect this to + * lost+found. + * + * If it was marked done already, then we've reached a + * parent we've already checked. + */ + if (ext2fs_mark_inode_bitmap(inode_done_map, p->ino)) + break; + + /* + * If this directory doesn't have a parent, or we've + * seen the parent once already, then offer to + * reparent it to lost+found + */ + if (!p->parent || + (loop_pass && + (ext2fs_test_inode_bitmap(inode_loop_detect, + p->parent)))) { + pctx->ino = p->ino; + if (fix_problem(ctx, PR_3_UNCONNECTED_DIR, pctx)) { + if (e2fsck_reconnect_file(ctx, pctx->ino)) + ext2fs_unmark_valid(fs); + else { + p = e2fsck_get_dir_info(ctx, pctx->ino); + p->parent = ctx->lost_and_found; + fix_dotdot(ctx, p, ctx->lost_and_found); + } + } + break; + } + p = e2fsck_get_dir_info(ctx, p->parent); + if (!p) { + fix_problem(ctx, PR_3_NO_DIRINFO, pctx); + return 0; + } + if (loop_pass) { + ext2fs_mark_inode_bitmap(inode_loop_detect, + p->ino); + } else if (parent_count++ > 2048) { + /* + * If we've run into a path depth that's + * greater than 2048, try again with the inode + * loop bitmap turned on and start from the + * top. + */ + loop_pass = 1; + if (inode_loop_detect) + ext2fs_clear_inode_bitmap(inode_loop_detect); + else { + pctx->errcode = ext2fs_allocate_inode_bitmap(fs, _("inode loop detection bitmap"), &inode_loop_detect); + if (pctx->errcode) { + pctx->num = 1; + fix_problem(ctx, + PR_3_ALLOCATE_IBITMAP_ERROR, pctx); + ctx->flags |= E2F_FLAG_ABORT; + return -1; + } + } + p = dir; + } + } + + /* + * Make sure that .. and the parent directory are the same; + * offer to fix it if not. + */ + if (dir->parent != dir->dotdot) { + pctx->ino = dir->ino; + pctx->ino2 = dir->dotdot; + pctx->dir = dir->parent; + if (fix_problem(ctx, PR_3_BAD_DOT_DOT, pctx)) + fix_dotdot(ctx, dir, dir->parent); + } + return 0; +} + +/* + * This routine gets the lost_and_found inode, making it a directory + * if necessary + */ +ext2_ino_t e2fsck_get_lost_and_found(e2fsck_t ctx, int fix) +{ + ext2_filsys fs = ctx->fs; + ext2_ino_t ino; + blk_t blk; + errcode_t retval; + struct ext2_inode inode; + char * block; + static const char name[] = "lost+found"; + struct problem_context pctx; + struct dir_info *dirinfo; + + if (ctx->lost_and_found) + return ctx->lost_and_found; + + clear_problem_context(&pctx); + + retval = ext2fs_lookup(fs, EXT2_ROOT_INO, name, + sizeof(name)-1, 0, &ino); + if (retval && !fix) + return 0; + if (!retval) { + if (ext2fs_test_inode_bitmap(ctx->inode_dir_map, ino)) { + ctx->lost_and_found = ino; + return ino; + } + + /* Lost+found isn't a directory! */ + if (!fix) + return 0; + pctx.ino = ino; + if (!fix_problem(ctx, PR_3_LPF_NOTDIR, &pctx)) + return 0; + + /* OK, unlink the old /lost+found file. */ + pctx.errcode = ext2fs_unlink(fs, EXT2_ROOT_INO, name, ino, 0); + if (pctx.errcode) { + pctx.str = "ext2fs_unlink"; + fix_problem(ctx, PR_3_CREATE_LPF_ERROR, &pctx); + return 0; + } + dirinfo = e2fsck_get_dir_info(ctx, ino); + if (dirinfo) + dirinfo->parent = 0; + e2fsck_adjust_inode_count(ctx, ino, -1); + } else if (retval != EXT2_ET_FILE_NOT_FOUND) { + pctx.errcode = retval; + fix_problem(ctx, PR_3_ERR_FIND_LPF, &pctx); + } + if (!fix_problem(ctx, PR_3_NO_LF_DIR, 0)) + return 0; + + /* + * Read the inode and block bitmaps in; we'll be messing with + * them. + */ + e2fsck_read_bitmaps(ctx); + + /* + * First, find a free block + */ + retval = ext2fs_new_block(fs, 0, ctx->block_found_map, &blk); + if (retval) { + pctx.errcode = retval; + fix_problem(ctx, PR_3_ERR_LPF_NEW_BLOCK, &pctx); + return 0; + } + ext2fs_mark_block_bitmap(ctx->block_found_map, blk); + ext2fs_block_alloc_stats(fs, blk, +1); + + /* + * Next find a free inode. + */ + retval = ext2fs_new_inode(fs, EXT2_ROOT_INO, 040700, + ctx->inode_used_map, &ino); + if (retval) { + pctx.errcode = retval; + fix_problem(ctx, PR_3_ERR_LPF_NEW_INODE, &pctx); + return 0; + } + ext2fs_mark_inode_bitmap(ctx->inode_used_map, ino); + ext2fs_mark_inode_bitmap(ctx->inode_dir_map, ino); + ext2fs_inode_alloc_stats2(fs, ino, +1, 1); + + /* + * Now let's create the actual data block for the inode + */ + retval = ext2fs_new_dir_block(fs, ino, EXT2_ROOT_INO, &block); + if (retval) { + pctx.errcode = retval; + fix_problem(ctx, PR_3_ERR_LPF_NEW_DIR_BLOCK, &pctx); + return 0; + } + + retval = ext2fs_write_dir_block(fs, blk, block); + ext2fs_free_mem(&block); + if (retval) { + pctx.errcode = retval; + fix_problem(ctx, PR_3_ERR_LPF_WRITE_BLOCK, &pctx); + return 0; + } + + /* + * Set up the inode structure + */ + memset(&inode, 0, sizeof(inode)); + inode.i_mode = 040700; + inode.i_size = fs->blocksize; + inode.i_atime = inode.i_ctime = inode.i_mtime = time(0); + inode.i_links_count = 2; + inode.i_blocks = fs->blocksize / 512; + inode.i_block[0] = blk; + + /* + * Next, write out the inode. + */ + pctx.errcode = ext2fs_write_new_inode(fs, ino, &inode); + if (pctx.errcode) { + pctx.str = "ext2fs_write_inode"; + fix_problem(ctx, PR_3_CREATE_LPF_ERROR, &pctx); + return 0; + } + /* + * Finally, create the directory link + */ + pctx.errcode = ext2fs_link(fs, EXT2_ROOT_INO, name, ino, EXT2_FT_DIR); + if (pctx.errcode) { + pctx.str = "ext2fs_link"; + fix_problem(ctx, PR_3_CREATE_LPF_ERROR, &pctx); + return 0; + } + + /* + * Miscellaneous bookkeeping that needs to be kept straight. + */ + e2fsck_add_dir_info(ctx, ino, EXT2_ROOT_INO); + e2fsck_adjust_inode_count(ctx, EXT2_ROOT_INO, 1); + ext2fs_icount_store(ctx->inode_count, ino, 2); + ext2fs_icount_store(ctx->inode_link_info, ino, 2); + ctx->lost_and_found = ino; + return ino; +} + +/* + * This routine will connect a file to lost+found + */ +int e2fsck_reconnect_file(e2fsck_t ctx, ext2_ino_t ino) +{ + ext2_filsys fs = ctx->fs; + errcode_t retval; + char name[80]; + struct problem_context pctx; + struct ext2_inode inode; + int file_type = 0; + + clear_problem_context(&pctx); + pctx.ino = ino; + + if (!ctx->bad_lost_and_found && !ctx->lost_and_found) { + if (e2fsck_get_lost_and_found(ctx, 1) == 0) + ctx->bad_lost_and_found++; + } + if (ctx->bad_lost_and_found) { + fix_problem(ctx, PR_3_NO_LPF, &pctx); + return 1; + } + + sprintf(name, "#%u", ino); + if (ext2fs_read_inode(fs, ino, &inode) == 0) + file_type = ext2_file_type(inode.i_mode); + retval = ext2fs_link(fs, ctx->lost_and_found, name, ino, file_type); + if (retval == EXT2_ET_DIR_NO_SPACE) { + if (!fix_problem(ctx, PR_3_EXPAND_LF_DIR, &pctx)) + return 1; + retval = e2fsck_expand_directory(ctx, ctx->lost_and_found, + 1, 0); + if (retval) { + pctx.errcode = retval; + fix_problem(ctx, PR_3_CANT_EXPAND_LPF, &pctx); + return 1; + } + retval = ext2fs_link(fs, ctx->lost_and_found, name, + ino, file_type); + } + if (retval) { + pctx.errcode = retval; + fix_problem(ctx, PR_3_CANT_RECONNECT, &pctx); + return 1; + } + e2fsck_adjust_inode_count(ctx, ino, 1); + + return 0; +} + +/* + * Utility routine to adjust the inode counts on an inode. + */ +errcode_t e2fsck_adjust_inode_count(e2fsck_t ctx, ext2_ino_t ino, int adj) +{ + ext2_filsys fs = ctx->fs; + errcode_t retval; + struct ext2_inode inode; + + if (!ino) + return 0; + + retval = ext2fs_read_inode(fs, ino, &inode); + if (retval) + return retval; + + if (adj == 1) { + ext2fs_icount_increment(ctx->inode_count, ino, 0); + if (inode.i_links_count == (__u16) ~0) + return 0; + ext2fs_icount_increment(ctx->inode_link_info, ino, 0); + inode.i_links_count++; + } else if (adj == -1) { + ext2fs_icount_decrement(ctx->inode_count, ino, 0); + if (inode.i_links_count == 0) + return 0; + ext2fs_icount_decrement(ctx->inode_link_info, ino, 0); + inode.i_links_count--; + } + + retval = ext2fs_write_inode(fs, ino, &inode); + if (retval) + return retval; + + return 0; +} + +/* + * Fix parent --- this routine fixes up the parent of a directory. + */ +struct fix_dotdot_struct { + ext2_filsys fs; + ext2_ino_t parent; + int done; + e2fsck_t ctx; +}; + +static int fix_dotdot_proc(struct ext2_dir_entry *dirent, + int offset FSCK_ATTR((unused)), + int blocksize FSCK_ATTR((unused)), + char *buf FSCK_ATTR((unused)), + void *priv_data) +{ + struct fix_dotdot_struct *fp = (struct fix_dotdot_struct *) priv_data; + errcode_t retval; + struct problem_context pctx; + + if ((dirent->name_len & 0xFF) != 2) + return 0; + if (strncmp(dirent->name, "..", 2)) + return 0; + + clear_problem_context(&pctx); + + retval = e2fsck_adjust_inode_count(fp->ctx, dirent->inode, -1); + if (retval) { + pctx.errcode = retval; + fix_problem(fp->ctx, PR_3_ADJUST_INODE, &pctx); + } + retval = e2fsck_adjust_inode_count(fp->ctx, fp->parent, 1); + if (retval) { + pctx.errcode = retval; + fix_problem(fp->ctx, PR_3_ADJUST_INODE, &pctx); + } + dirent->inode = fp->parent; + + fp->done++; + return DIRENT_ABORT | DIRENT_CHANGED; +} + +static void fix_dotdot(e2fsck_t ctx, struct dir_info *dir, ext2_ino_t parent) +{ + ext2_filsys fs = ctx->fs; + errcode_t retval; + struct fix_dotdot_struct fp; + struct problem_context pctx; + + fp.fs = fs; + fp.parent = parent; + fp.done = 0; + fp.ctx = ctx; + + retval = ext2fs_dir_iterate(fs, dir->ino, DIRENT_FLAG_INCLUDE_EMPTY, + 0, fix_dotdot_proc, &fp); + if (retval || !fp.done) { + clear_problem_context(&pctx); + pctx.ino = dir->ino; + pctx.errcode = retval; + fix_problem(ctx, retval ? PR_3_FIX_PARENT_ERR : + PR_3_FIX_PARENT_NOFIND, &pctx); + ext2fs_unmark_valid(fs); + } + dir->dotdot = parent; + + return; +} + +/* + * These routines are responsible for expanding a /lost+found if it is + * too small. + */ + +struct expand_dir_struct { + int num; + int guaranteed_size; + int newblocks; + int last_block; + errcode_t err; + e2fsck_t ctx; +}; + +static int expand_dir_proc(ext2_filsys fs, + blk_t *blocknr, + e2_blkcnt_t blockcnt, + blk_t ref_block FSCK_ATTR((unused)), + int ref_offset FSCK_ATTR((unused)), + void *priv_data) +{ + struct expand_dir_struct *es = (struct expand_dir_struct *) priv_data; + blk_t new_blk; + static blk_t last_blk = 0; + char *block; + errcode_t retval; + e2fsck_t ctx; + + ctx = es->ctx; + + if (es->guaranteed_size && blockcnt >= es->guaranteed_size) + return BLOCK_ABORT; + + if (blockcnt > 0) + es->last_block = blockcnt; + if (*blocknr) { + last_blk = *blocknr; + return 0; + } + retval = ext2fs_new_block(fs, last_blk, ctx->block_found_map, + &new_blk); + if (retval) { + es->err = retval; + return BLOCK_ABORT; + } + if (blockcnt > 0) { + retval = ext2fs_new_dir_block(fs, 0, 0, &block); + if (retval) { + es->err = retval; + return BLOCK_ABORT; + } + es->num--; + retval = ext2fs_write_dir_block(fs, new_blk, block); + } else { + retval = ext2fs_get_mem(fs->blocksize, &block); + if (retval) { + es->err = retval; + return BLOCK_ABORT; + } + memset(block, 0, fs->blocksize); + retval = io_channel_write_blk(fs->io, new_blk, 1, block); + } + if (retval) { + es->err = retval; + return BLOCK_ABORT; + } + ext2fs_free_mem(&block); + *blocknr = new_blk; + ext2fs_mark_block_bitmap(ctx->block_found_map, new_blk); + ext2fs_block_alloc_stats(fs, new_blk, +1); + es->newblocks++; + + if (es->num == 0) + return (BLOCK_CHANGED | BLOCK_ABORT); + else + return BLOCK_CHANGED; +} + +errcode_t e2fsck_expand_directory(e2fsck_t ctx, ext2_ino_t dir, + int num, int guaranteed_size) +{ + ext2_filsys fs = ctx->fs; + errcode_t retval; + struct expand_dir_struct es; + struct ext2_inode inode; + + if (!(fs->flags & EXT2_FLAG_RW)) + return EXT2_ET_RO_FILSYS; + + /* + * Read the inode and block bitmaps in; we'll be messing with + * them. + */ + e2fsck_read_bitmaps(ctx); + + retval = ext2fs_check_directory(fs, dir); + if (retval) + return retval; + + es.num = num; + es.guaranteed_size = guaranteed_size; + es.last_block = 0; + es.err = 0; + es.newblocks = 0; + es.ctx = ctx; + + retval = ext2fs_block_iterate2(fs, dir, BLOCK_FLAG_APPEND, + 0, expand_dir_proc, &es); + + if (es.err) + return es.err; + + /* + * Update the size and block count fields in the inode. + */ + retval = ext2fs_read_inode(fs, dir, &inode); + if (retval) + return retval; + + inode.i_size = (es.last_block + 1) * fs->blocksize; + inode.i_blocks += (fs->blocksize / 512) * es.newblocks; + + e2fsck_write_inode(ctx, dir, &inode, "expand_directory"); + + return 0; +} + +/* + * pass4.c -- pass #4 of e2fsck: Check reference counts + * + * Pass 4 frees the following data structures: + * - A bitmap of which inodes are imagic inodes. (inode_imagic_map) + */ + +/* + * This routine is called when an inode is not connected to the + * directory tree. + * + * This subroutine returns 1 then the caller shouldn't bother with the + * rest of the pass 4 tests. + */ +static int disconnect_inode(e2fsck_t ctx, ext2_ino_t i) +{ + ext2_filsys fs = ctx->fs; + struct ext2_inode inode; + struct problem_context pctx; + + e2fsck_read_inode(ctx, i, &inode, "pass4: disconnect_inode"); + clear_problem_context(&pctx); + pctx.ino = i; + pctx.inode = &inode; + + /* + * Offer to delete any zero-length files that does not have + * blocks. If there is an EA block, it might have useful + * information, so we won't prompt to delete it, but let it be + * reconnected to lost+found. + */ + if (!inode.i_blocks && (LINUX_S_ISREG(inode.i_mode) || + LINUX_S_ISDIR(inode.i_mode))) { + if (fix_problem(ctx, PR_4_ZERO_LEN_INODE, &pctx)) { + ext2fs_icount_store(ctx->inode_link_info, i, 0); + inode.i_links_count = 0; + inode.i_dtime = time(0); + e2fsck_write_inode(ctx, i, &inode, + "disconnect_inode"); + /* + * Fix up the bitmaps... + */ + e2fsck_read_bitmaps(ctx); + ext2fs_unmark_inode_bitmap(ctx->inode_used_map, i); + ext2fs_unmark_inode_bitmap(ctx->inode_dir_map, i); + ext2fs_inode_alloc_stats2(fs, i, -1, + LINUX_S_ISDIR(inode.i_mode)); + return 0; + } + } + + /* + * Prompt to reconnect. + */ + if (fix_problem(ctx, PR_4_UNATTACHED_INODE, &pctx)) { + if (e2fsck_reconnect_file(ctx, i)) + ext2fs_unmark_valid(fs); + } else { + /* + * If we don't attach the inode, then skip the + * i_links_test since there's no point in trying to + * force i_links_count to zero. + */ + ext2fs_unmark_valid(fs); + return 1; + } + return 0; +} + + +static void e2fsck_pass4(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + ext2_ino_t i; + struct ext2_inode inode; + struct problem_context pctx; + __u16 link_count, link_counted; + char *buf = 0; + int group, maxgroup; + + /* Pass 4 */ + + clear_problem_context(&pctx); + + if (!(ctx->options & E2F_OPT_PREEN)) + fix_problem(ctx, PR_4_PASS_HEADER, &pctx); + + group = 0; + maxgroup = fs->group_desc_count; + if (ctx->progress) + if ((ctx->progress)(ctx, 4, 0, maxgroup)) + return; + + for (i=1; i <= fs->super->s_inodes_count; i++) { + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + if ((i % fs->super->s_inodes_per_group) == 0) { + group++; + if (ctx->progress) + if ((ctx->progress)(ctx, 4, group, maxgroup)) + return; + } + if (i == EXT2_BAD_INO || + (i > EXT2_ROOT_INO && i < EXT2_FIRST_INODE(fs->super))) + continue; + if (!(ext2fs_test_inode_bitmap(ctx->inode_used_map, i)) || + (ctx->inode_imagic_map && + ext2fs_test_inode_bitmap(ctx->inode_imagic_map, i))) + continue; + ext2fs_icount_fetch(ctx->inode_link_info, i, &link_count); + ext2fs_icount_fetch(ctx->inode_count, i, &link_counted); + if (link_counted == 0) { + if (!buf) + buf = e2fsck_allocate_memory(ctx, + fs->blocksize, "bad_inode buffer"); + if (e2fsck_process_bad_inode(ctx, 0, i, buf)) + continue; + if (disconnect_inode(ctx, i)) + continue; + ext2fs_icount_fetch(ctx->inode_link_info, i, + &link_count); + ext2fs_icount_fetch(ctx->inode_count, i, + &link_counted); + } + if (link_counted != link_count) { + e2fsck_read_inode(ctx, i, &inode, "pass4"); + pctx.ino = i; + pctx.inode = &inode; + if (link_count != inode.i_links_count) { + pctx.num = link_count; + fix_problem(ctx, + PR_4_INCONSISTENT_COUNT, &pctx); + } + pctx.num = link_counted; + if (fix_problem(ctx, PR_4_BAD_REF_COUNT, &pctx)) { + inode.i_links_count = link_counted; + e2fsck_write_inode(ctx, i, &inode, "pass4"); + } + } + } + ext2fs_free_icount(ctx->inode_link_info); ctx->inode_link_info = 0; + ext2fs_free_icount(ctx->inode_count); ctx->inode_count = 0; + ext2fs_free_inode_bitmap(ctx->inode_imagic_map); + ctx->inode_imagic_map = 0; + ext2fs_free_mem(&buf); +} + +/* + * pass5.c --- check block and inode bitmaps against on-disk bitmaps + */ + +#define NO_BLK ((blk_t) -1) + +static void print_bitmap_problem(e2fsck_t ctx, int problem, + struct problem_context *pctx) +{ + switch (problem) { + case PR_5_BLOCK_UNUSED: + if (pctx->blk == pctx->blk2) + pctx->blk2 = 0; + else + problem = PR_5_BLOCK_RANGE_UNUSED; + break; + case PR_5_BLOCK_USED: + if (pctx->blk == pctx->blk2) + pctx->blk2 = 0; + else + problem = PR_5_BLOCK_RANGE_USED; + break; + case PR_5_INODE_UNUSED: + if (pctx->ino == pctx->ino2) + pctx->ino2 = 0; + else + problem = PR_5_INODE_RANGE_UNUSED; + break; + case PR_5_INODE_USED: + if (pctx->ino == pctx->ino2) + pctx->ino2 = 0; + else + problem = PR_5_INODE_RANGE_USED; + break; + } + fix_problem(ctx, problem, pctx); + pctx->blk = pctx->blk2 = NO_BLK; + pctx->ino = pctx->ino2 = 0; +} + +static void check_block_bitmaps(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + blk_t i; + int *free_array; + int group = 0; + unsigned int blocks = 0; + unsigned int free_blocks = 0; + int group_free = 0; + int actual, bitmap; + struct problem_context pctx; + int problem, save_problem, fixit, had_problem; + errcode_t retval; + + clear_problem_context(&pctx); + free_array = (int *) e2fsck_allocate_memory(ctx, + fs->group_desc_count * sizeof(int), "free block count array"); + + if ((fs->super->s_first_data_block < + ext2fs_get_block_bitmap_start(ctx->block_found_map)) || + (fs->super->s_blocks_count-1 > + ext2fs_get_block_bitmap_end(ctx->block_found_map))) { + pctx.num = 1; + pctx.blk = fs->super->s_first_data_block; + pctx.blk2 = fs->super->s_blocks_count -1; + pctx.ino = ext2fs_get_block_bitmap_start(ctx->block_found_map); + pctx.ino2 = ext2fs_get_block_bitmap_end(ctx->block_found_map); + fix_problem(ctx, PR_5_BMAP_ENDPOINTS, &pctx); + + ctx->flags |= E2F_FLAG_ABORT; /* fatal */ + return; + } + + if ((fs->super->s_first_data_block < + ext2fs_get_block_bitmap_start(fs->block_map)) || + (fs->super->s_blocks_count-1 > + ext2fs_get_block_bitmap_end(fs->block_map))) { + pctx.num = 2; + pctx.blk = fs->super->s_first_data_block; + pctx.blk2 = fs->super->s_blocks_count -1; + pctx.ino = ext2fs_get_block_bitmap_start(fs->block_map); + pctx.ino2 = ext2fs_get_block_bitmap_end(fs->block_map); + fix_problem(ctx, PR_5_BMAP_ENDPOINTS, &pctx); + + ctx->flags |= E2F_FLAG_ABORT; /* fatal */ + return; + } + +redo_counts: + had_problem = 0; + save_problem = 0; + pctx.blk = pctx.blk2 = NO_BLK; + for (i = fs->super->s_first_data_block; + i < fs->super->s_blocks_count; + i++) { + actual = ext2fs_fast_test_block_bitmap(ctx->block_found_map, i); + bitmap = ext2fs_fast_test_block_bitmap(fs->block_map, i); + + if (actual == bitmap) + goto do_counts; + + if (!actual && bitmap) { + /* + * Block not used, but marked in use in the bitmap. + */ + problem = PR_5_BLOCK_UNUSED; + } else { + /* + * Block used, but not marked in use in the bitmap. + */ + problem = PR_5_BLOCK_USED; + } + if (pctx.blk == NO_BLK) { + pctx.blk = pctx.blk2 = i; + save_problem = problem; + } else { + if ((problem == save_problem) && + (pctx.blk2 == i-1)) + pctx.blk2++; + else { + print_bitmap_problem(ctx, save_problem, &pctx); + pctx.blk = pctx.blk2 = i; + save_problem = problem; + } + } + ctx->flags |= E2F_FLAG_PROG_SUPPRESS; + had_problem++; + + do_counts: + if (!bitmap) { + group_free++; + free_blocks++; + } + blocks ++; + if ((blocks == fs->super->s_blocks_per_group) || + (i == fs->super->s_blocks_count-1)) { + free_array[group] = group_free; + group ++; + blocks = 0; + group_free = 0; + if (ctx->progress) + if ((ctx->progress)(ctx, 5, group, + fs->group_desc_count*2)) + return; + } + } + if (pctx.blk != NO_BLK) + print_bitmap_problem(ctx, save_problem, &pctx); + if (had_problem) + fixit = end_problem_latch(ctx, PR_LATCH_BBITMAP); + else + fixit = -1; + ctx->flags &= ~E2F_FLAG_PROG_SUPPRESS; + + if (fixit == 1) { + ext2fs_free_block_bitmap(fs->block_map); + retval = ext2fs_copy_bitmap(ctx->block_found_map, + &fs->block_map); + if (retval) { + clear_problem_context(&pctx); + fix_problem(ctx, PR_5_COPY_BBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + ext2fs_set_bitmap_padding(fs->block_map); + ext2fs_mark_bb_dirty(fs); + + /* Redo the counts */ + blocks = 0; free_blocks = 0; group_free = 0; group = 0; + memset(free_array, 0, fs->group_desc_count * sizeof(int)); + goto redo_counts; + } else if (fixit == 0) + ext2fs_unmark_valid(fs); + + for (i = 0; i < fs->group_desc_count; i++) { + if (free_array[i] != fs->group_desc[i].bg_free_blocks_count) { + pctx.group = i; + pctx.blk = fs->group_desc[i].bg_free_blocks_count; + pctx.blk2 = free_array[i]; + + if (fix_problem(ctx, PR_5_FREE_BLOCK_COUNT_GROUP, + &pctx)) { + fs->group_desc[i].bg_free_blocks_count = + free_array[i]; + ext2fs_mark_super_dirty(fs); + } else + ext2fs_unmark_valid(fs); + } + } + if (free_blocks != fs->super->s_free_blocks_count) { + pctx.group = 0; + pctx.blk = fs->super->s_free_blocks_count; + pctx.blk2 = free_blocks; + + if (fix_problem(ctx, PR_5_FREE_BLOCK_COUNT, &pctx)) { + fs->super->s_free_blocks_count = free_blocks; + ext2fs_mark_super_dirty(fs); + } else + ext2fs_unmark_valid(fs); + } + ext2fs_free_mem(&free_array); +} + +static void check_inode_bitmaps(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + ext2_ino_t i; + unsigned int free_inodes = 0; + int group_free = 0; + int dirs_count = 0; + int group = 0; + unsigned int inodes = 0; + int *free_array; + int *dir_array; + int actual, bitmap; + errcode_t retval; + struct problem_context pctx; + int problem, save_problem, fixit, had_problem; + + clear_problem_context(&pctx); + free_array = (int *) e2fsck_allocate_memory(ctx, + fs->group_desc_count * sizeof(int), "free inode count array"); + + dir_array = (int *) e2fsck_allocate_memory(ctx, + fs->group_desc_count * sizeof(int), "directory count array"); + + if ((1 < ext2fs_get_inode_bitmap_start(ctx->inode_used_map)) || + (fs->super->s_inodes_count > + ext2fs_get_inode_bitmap_end(ctx->inode_used_map))) { + pctx.num = 3; + pctx.blk = 1; + pctx.blk2 = fs->super->s_inodes_count; + pctx.ino = ext2fs_get_inode_bitmap_start(ctx->inode_used_map); + pctx.ino2 = ext2fs_get_inode_bitmap_end(ctx->inode_used_map); + fix_problem(ctx, PR_5_BMAP_ENDPOINTS, &pctx); + + ctx->flags |= E2F_FLAG_ABORT; /* fatal */ + return; + } + if ((1 < ext2fs_get_inode_bitmap_start(fs->inode_map)) || + (fs->super->s_inodes_count > + ext2fs_get_inode_bitmap_end(fs->inode_map))) { + pctx.num = 4; + pctx.blk = 1; + pctx.blk2 = fs->super->s_inodes_count; + pctx.ino = ext2fs_get_inode_bitmap_start(fs->inode_map); + pctx.ino2 = ext2fs_get_inode_bitmap_end(fs->inode_map); + fix_problem(ctx, PR_5_BMAP_ENDPOINTS, &pctx); + + ctx->flags |= E2F_FLAG_ABORT; /* fatal */ + return; + } + +redo_counts: + had_problem = 0; + save_problem = 0; + pctx.ino = pctx.ino2 = 0; + for (i = 1; i <= fs->super->s_inodes_count; i++) { + actual = ext2fs_fast_test_inode_bitmap(ctx->inode_used_map, i); + bitmap = ext2fs_fast_test_inode_bitmap(fs->inode_map, i); + + if (actual == bitmap) + goto do_counts; + + if (!actual && bitmap) { + /* + * Inode wasn't used, but marked in bitmap + */ + problem = PR_5_INODE_UNUSED; + } else /* if (actual && !bitmap) */ { + /* + * Inode used, but not in bitmap + */ + problem = PR_5_INODE_USED; + } + if (pctx.ino == 0) { + pctx.ino = pctx.ino2 = i; + save_problem = problem; + } else { + if ((problem == save_problem) && + (pctx.ino2 == i-1)) + pctx.ino2++; + else { + print_bitmap_problem(ctx, save_problem, &pctx); + pctx.ino = pctx.ino2 = i; + save_problem = problem; + } + } + ctx->flags |= E2F_FLAG_PROG_SUPPRESS; + had_problem++; + +do_counts: + if (!bitmap) { + group_free++; + free_inodes++; + } else { + if (ext2fs_test_inode_bitmap(ctx->inode_dir_map, i)) + dirs_count++; + } + inodes++; + if ((inodes == fs->super->s_inodes_per_group) || + (i == fs->super->s_inodes_count)) { + free_array[group] = group_free; + dir_array[group] = dirs_count; + group ++; + inodes = 0; + group_free = 0; + dirs_count = 0; + if (ctx->progress) + if ((ctx->progress)(ctx, 5, + group + fs->group_desc_count, + fs->group_desc_count*2)) + return; + } + } + if (pctx.ino) + print_bitmap_problem(ctx, save_problem, &pctx); + + if (had_problem) + fixit = end_problem_latch(ctx, PR_LATCH_IBITMAP); + else + fixit = -1; + ctx->flags &= ~E2F_FLAG_PROG_SUPPRESS; + + if (fixit == 1) { + ext2fs_free_inode_bitmap(fs->inode_map); + retval = ext2fs_copy_bitmap(ctx->inode_used_map, + &fs->inode_map); + if (retval) { + clear_problem_context(&pctx); + fix_problem(ctx, PR_5_COPY_IBITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + ext2fs_set_bitmap_padding(fs->inode_map); + ext2fs_mark_ib_dirty(fs); + + /* redo counts */ + inodes = 0; free_inodes = 0; group_free = 0; + dirs_count = 0; group = 0; + memset(free_array, 0, fs->group_desc_count * sizeof(int)); + memset(dir_array, 0, fs->group_desc_count * sizeof(int)); + goto redo_counts; + } else if (fixit == 0) + ext2fs_unmark_valid(fs); + + for (i = 0; i < fs->group_desc_count; i++) { + if (free_array[i] != fs->group_desc[i].bg_free_inodes_count) { + pctx.group = i; + pctx.ino = fs->group_desc[i].bg_free_inodes_count; + pctx.ino2 = free_array[i]; + if (fix_problem(ctx, PR_5_FREE_INODE_COUNT_GROUP, + &pctx)) { + fs->group_desc[i].bg_free_inodes_count = + free_array[i]; + ext2fs_mark_super_dirty(fs); + } else + ext2fs_unmark_valid(fs); + } + if (dir_array[i] != fs->group_desc[i].bg_used_dirs_count) { + pctx.group = i; + pctx.ino = fs->group_desc[i].bg_used_dirs_count; + pctx.ino2 = dir_array[i]; + + if (fix_problem(ctx, PR_5_FREE_DIR_COUNT_GROUP, + &pctx)) { + fs->group_desc[i].bg_used_dirs_count = + dir_array[i]; + ext2fs_mark_super_dirty(fs); + } else + ext2fs_unmark_valid(fs); + } + } + if (free_inodes != fs->super->s_free_inodes_count) { + pctx.group = -1; + pctx.ino = fs->super->s_free_inodes_count; + pctx.ino2 = free_inodes; + + if (fix_problem(ctx, PR_5_FREE_INODE_COUNT, &pctx)) { + fs->super->s_free_inodes_count = free_inodes; + ext2fs_mark_super_dirty(fs); + } else + ext2fs_unmark_valid(fs); + } + ext2fs_free_mem(&free_array); + ext2fs_free_mem(&dir_array); +} + +static void check_inode_end(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + ext2_ino_t end, save_inodes_count, i; + struct problem_context pctx; + + clear_problem_context(&pctx); + + end = EXT2_INODES_PER_GROUP(fs->super) * fs->group_desc_count; + pctx.errcode = ext2fs_fudge_inode_bitmap_end(fs->inode_map, end, + &save_inodes_count); + if (pctx.errcode) { + pctx.num = 1; + fix_problem(ctx, PR_5_FUDGE_BITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; /* fatal */ + return; + } + if (save_inodes_count == end) + return; + + for (i = save_inodes_count + 1; i <= end; i++) { + if (!ext2fs_test_inode_bitmap(fs->inode_map, i)) { + if (fix_problem(ctx, PR_5_INODE_BMAP_PADDING, &pctx)) { + for (i = save_inodes_count + 1; i <= end; i++) + ext2fs_mark_inode_bitmap(fs->inode_map, + i); + ext2fs_mark_ib_dirty(fs); + } else + ext2fs_unmark_valid(fs); + break; + } + } + + pctx.errcode = ext2fs_fudge_inode_bitmap_end(fs->inode_map, + save_inodes_count, 0); + if (pctx.errcode) { + pctx.num = 2; + fix_problem(ctx, PR_5_FUDGE_BITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; /* fatal */ + return; + } +} + +static void check_block_end(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + blk_t end, save_blocks_count, i; + struct problem_context pctx; + + clear_problem_context(&pctx); + + end = fs->block_map->start + + (EXT2_BLOCKS_PER_GROUP(fs->super) * fs->group_desc_count) - 1; + pctx.errcode = ext2fs_fudge_block_bitmap_end(fs->block_map, end, + &save_blocks_count); + if (pctx.errcode) { + pctx.num = 3; + fix_problem(ctx, PR_5_FUDGE_BITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; /* fatal */ + return; + } + if (save_blocks_count == end) + return; + + for (i = save_blocks_count + 1; i <= end; i++) { + if (!ext2fs_test_block_bitmap(fs->block_map, i)) { + if (fix_problem(ctx, PR_5_BLOCK_BMAP_PADDING, &pctx)) { + for (i = save_blocks_count + 1; i <= end; i++) + ext2fs_mark_block_bitmap(fs->block_map, + i); + ext2fs_mark_bb_dirty(fs); + } else + ext2fs_unmark_valid(fs); + break; + } + } + + pctx.errcode = ext2fs_fudge_block_bitmap_end(fs->block_map, + save_blocks_count, 0); + if (pctx.errcode) { + pctx.num = 4; + fix_problem(ctx, PR_5_FUDGE_BITMAP_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; /* fatal */ + return; + } +} + +static void e2fsck_pass5(e2fsck_t ctx) +{ + struct problem_context pctx; + + /* Pass 5 */ + + clear_problem_context(&pctx); + + if (!(ctx->options & E2F_OPT_PREEN)) + fix_problem(ctx, PR_5_PASS_HEADER, &pctx); + + if (ctx->progress) + if ((ctx->progress)(ctx, 5, 0, ctx->fs->group_desc_count*2)) + return; + + e2fsck_read_bitmaps(ctx); + + check_block_bitmaps(ctx); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + check_inode_bitmaps(ctx); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + check_inode_end(ctx); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + check_block_end(ctx); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + + ext2fs_free_inode_bitmap(ctx->inode_used_map); + ctx->inode_used_map = 0; + ext2fs_free_inode_bitmap(ctx->inode_dir_map); + ctx->inode_dir_map = 0; + ext2fs_free_block_bitmap(ctx->block_found_map); + ctx->block_found_map = 0; +} + +/* + * problem.c --- report filesystem problems to the user + */ + +#define PR_PREEN_OK 0x000001 /* Don't need to do preenhalt */ +#define PR_NO_OK 0x000002 /* If user answers no, don't make fs invalid */ +#define PR_NO_DEFAULT 0x000004 /* Default to no */ +#define PR_MSG_ONLY 0x000008 /* Print message only */ + +/* Bit positions 0x000ff0 are reserved for the PR_LATCH flags */ + +#define PR_FATAL 0x001000 /* Fatal error */ +#define PR_AFTER_CODE 0x002000 /* After asking the first question, */ + /* ask another */ +#define PR_PREEN_NOMSG 0x004000 /* Don't print a message if we're preening */ +#define PR_NOCOLLATE 0x008000 /* Don't collate answers for this latch */ +#define PR_NO_NOMSG 0x010000 /* Don't print a message if e2fsck -n */ +#define PR_PREEN_NO 0x020000 /* Use No as an answer if preening */ +#define PR_PREEN_NOHDR 0x040000 /* Don't print the preen header */ + + +#define PROMPT_NONE 0 +#define PROMPT_FIX 1 +#define PROMPT_CLEAR 2 +#define PROMPT_RELOCATE 3 +#define PROMPT_ALLOCATE 4 +#define PROMPT_EXPAND 5 +#define PROMPT_CONNECT 6 +#define PROMPT_CREATE 7 +#define PROMPT_SALVAGE 8 +#define PROMPT_TRUNCATE 9 +#define PROMPT_CLEAR_INODE 10 +#define PROMPT_ABORT 11 +#define PROMPT_SPLIT 12 +#define PROMPT_CONTINUE 13 +#define PROMPT_CLONE 14 +#define PROMPT_DELETE 15 +#define PROMPT_SUPPRESS 16 +#define PROMPT_UNLINK 17 +#define PROMPT_CLEAR_HTREE 18 +#define PROMPT_RECREATE 19 +#define PROMPT_NULL 20 + +struct e2fsck_problem { + problem_t e2p_code; + const char * e2p_description; + char prompt; + int flags; + problem_t second_code; +}; + +struct latch_descr { + int latch_code; + problem_t question; + problem_t end_message; + int flags; +}; + +/* + * These are the prompts which are used to ask the user if they want + * to fix a problem. + */ +static const char * const prompt[] = { + N_("(no prompt)"), /* 0 */ + N_("Fix"), /* 1 */ + N_("Clear"), /* 2 */ + N_("Relocate"), /* 3 */ + N_("Allocate"), /* 4 */ + N_("Expand"), /* 5 */ + N_("Connect to /lost+found"), /* 6 */ + N_("Create"), /* 7 */ + N_("Salvage"), /* 8 */ + N_("Truncate"), /* 9 */ + N_("Clear inode"), /* 10 */ + N_("Abort"), /* 11 */ + N_("Split"), /* 12 */ + N_("Continue"), /* 13 */ + N_("Clone multiply-claimed blocks"), /* 14 */ + N_("Delete file"), /* 15 */ + N_("Suppress messages"),/* 16 */ + N_("Unlink"), /* 17 */ + N_("Clear HTree index"),/* 18 */ + N_("Recreate"), /* 19 */ + "", /* 20 */ +}; + +/* + * These messages are printed when we are preen mode and we will be + * automatically fixing the problem. + */ +static const char * const preen_msg[] = { + N_("(NONE)"), /* 0 */ + N_("FIXED"), /* 1 */ + N_("CLEARED"), /* 2 */ + N_("RELOCATED"), /* 3 */ + N_("ALLOCATED"), /* 4 */ + N_("EXPANDED"), /* 5 */ + N_("RECONNECTED"), /* 6 */ + N_("CREATED"), /* 7 */ + N_("SALVAGED"), /* 8 */ + N_("TRUNCATED"), /* 9 */ + N_("INODE CLEARED"), /* 10 */ + N_("ABORTED"), /* 11 */ + N_("SPLIT"), /* 12 */ + N_("CONTINUING"), /* 13 */ + N_("MULTIPLY-CLAIMED BLOCKS CLONED"), /* 14 */ + N_("FILE DELETED"), /* 15 */ + N_("SUPPRESSED"), /* 16 */ + N_("UNLINKED"), /* 17 */ + N_("HTREE INDEX CLEARED"),/* 18 */ + N_("WILL RECREATE"), /* 19 */ + "", /* 20 */ +}; + +static const struct e2fsck_problem problem_table[] = { + + /* Pre-Pass 1 errors */ + + /* Block bitmap not in group */ + { PR_0_BB_NOT_GROUP, N_("@b @B for @g %g is not in @g. (@b %b)\n"), + PROMPT_RELOCATE, PR_LATCH_RELOC }, + + /* Inode bitmap not in group */ + { PR_0_IB_NOT_GROUP, N_("@i @B for @g %g is not in @g. (@b %b)\n"), + PROMPT_RELOCATE, PR_LATCH_RELOC }, + + /* Inode table not in group */ + { PR_0_ITABLE_NOT_GROUP, + N_("@i table for @g %g is not in @g. (@b %b)\n" + "WARNING: SEVERE DATA LOSS POSSIBLE.\n"), + PROMPT_RELOCATE, PR_LATCH_RELOC }, + + /* Superblock corrupt */ + { PR_0_SB_CORRUPT, + N_("\nThe @S could not be read or does not describe a correct ext2\n" + "@f. If the @v is valid and it really contains an ext2\n" + "@f (and not swap or ufs or something else), then the @S\n" + "is corrupt, and you might try running e2fsck with an alternate @S:\n" + " e2fsck -b %S <@v>\n\n"), + PROMPT_NONE, PR_FATAL }, + + /* Filesystem size is wrong */ + { PR_0_FS_SIZE_WRONG, + N_("The @f size (according to the @S) is %b @bs\n" + "The physical size of the @v is %c @bs\n" + "Either the @S or the partition table is likely to be corrupt!\n"), + PROMPT_ABORT, 0 }, + + /* Fragments not supported */ + { PR_0_NO_FRAGMENTS, + N_("@S @b_size = %b, fragsize = %c.\n" + "This version of e2fsck does not support fragment sizes different\n" + "from the @b size.\n"), + PROMPT_NONE, PR_FATAL }, + + /* Bad blocks_per_group */ + { PR_0_BLOCKS_PER_GROUP, + N_("@S @bs_per_group = %b, should have been %c\n"), + PROMPT_NONE, PR_AFTER_CODE, PR_0_SB_CORRUPT }, + + /* Bad first_data_block */ + { PR_0_FIRST_DATA_BLOCK, + N_("@S first_data_@b = %b, should have been %c\n"), + PROMPT_NONE, PR_AFTER_CODE, PR_0_SB_CORRUPT }, + + /* Adding UUID to filesystem */ + { PR_0_ADD_UUID, + N_("@f did not have a UUID; generating one.\n\n"), + PROMPT_NONE, 0 }, + + /* Relocate hint */ + { PR_0_RELOCATE_HINT, + N_("Note: if several inode or block bitmap blocks or part\n" + "of the inode table require relocation, you may wish to try\n" + "running e2fsck with the '-b %S' option first. The problem\n" + "may lie only with the primary block group descriptors, and\n" + "the backup block group descriptors may be OK.\n\n"), + PROMPT_NONE, PR_PREEN_OK | PR_NOCOLLATE }, + + /* Miscellaneous superblock corruption */ + { PR_0_MISC_CORRUPT_SUPER, + N_("Corruption found in @S. (%s = %N).\n"), + PROMPT_NONE, PR_AFTER_CODE, PR_0_SB_CORRUPT }, + + /* Error determing physical device size of filesystem */ + { PR_0_GETSIZE_ERROR, + N_("Error determining size of the physical @v: %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Inode count in superblock is incorrect */ + { PR_0_INODE_COUNT_WRONG, + N_("@i count in @S is %i, @s %j.\n"), + PROMPT_FIX, 0 }, + + { PR_0_HURD_CLEAR_FILETYPE, + N_("The Hurd does not support the filetype feature.\n"), + PROMPT_CLEAR, 0 }, + + /* Journal inode is invalid */ + { PR_0_JOURNAL_BAD_INODE, + N_("@S has an @n ext3 @j (@i %i).\n"), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* The external journal has (unsupported) multiple filesystems */ + { PR_0_JOURNAL_UNSUPP_MULTIFS, + N_("External @j has multiple @f users (unsupported).\n"), + PROMPT_NONE, PR_FATAL }, + + /* Can't find external journal */ + { PR_0_CANT_FIND_JOURNAL, + N_("Can't find external @j\n"), + PROMPT_NONE, PR_FATAL }, + + /* External journal has bad superblock */ + { PR_0_EXT_JOURNAL_BAD_SUPER, + N_("External @j has bad @S\n"), + PROMPT_NONE, PR_FATAL }, + + /* Superblock has a bad journal UUID */ + { PR_0_JOURNAL_BAD_UUID, + N_("External @j does not support this @f\n"), + PROMPT_NONE, PR_FATAL }, + + /* Journal has an unknown superblock type */ + { PR_0_JOURNAL_UNSUPP_SUPER, + N_("Ext3 @j @S is unknown type %N (unsupported).\n" + "It is likely that your copy of e2fsck is old and/or doesn't " + "support this @j format.\n" + "It is also possible the @j @S is corrupt.\n"), + PROMPT_ABORT, PR_NO_OK | PR_AFTER_CODE, PR_0_JOURNAL_BAD_SUPER }, + + /* Journal superblock is corrupt */ + { PR_0_JOURNAL_BAD_SUPER, + N_("Ext3 @j @S is corrupt.\n"), + PROMPT_FIX, PR_PREEN_OK }, + + /* Superblock flag should be cleared */ + { PR_0_JOURNAL_HAS_JOURNAL, + N_("@S doesn't have has_@j flag, but has ext3 @j %s.\n"), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* Superblock flag is incorrect */ + { PR_0_JOURNAL_RECOVER_SET, + N_("@S has ext3 needs_recovery flag set, but no @j.\n"), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* Journal has data, but recovery flag is clear */ + { PR_0_JOURNAL_RECOVERY_CLEAR, + N_("ext3 recovery flag is clear, but @j has data.\n"), + PROMPT_NONE, 0 }, + + /* Ask if we should clear the journal */ + { PR_0_JOURNAL_RESET_JOURNAL, + N_("Clear @j"), + PROMPT_NULL, PR_PREEN_NOMSG }, + + /* Ask if we should run the journal anyway */ + { PR_0_JOURNAL_RUN, + N_("Run @j anyway"), + PROMPT_NULL, 0 }, + + /* Run the journal by default */ + { PR_0_JOURNAL_RUN_DEFAULT, + N_("Recovery flag not set in backup @S, so running @j anyway.\n"), + PROMPT_NONE, 0 }, + + /* Clearing orphan inode */ + { PR_0_ORPHAN_CLEAR_INODE, + N_("%s @o @i %i (uid=%Iu, gid=%Ig, mode=%Im, size=%Is)\n"), + PROMPT_NONE, 0 }, + + /* Illegal block found in orphaned inode */ + { PR_0_ORPHAN_ILLEGAL_BLOCK_NUM, + N_("@I @b #%B (%b) found in @o @i %i.\n"), + PROMPT_NONE, 0 }, + + /* Already cleared block found in orphaned inode */ + { PR_0_ORPHAN_ALREADY_CLEARED_BLOCK, + N_("Already cleared @b #%B (%b) found in @o @i %i.\n"), + PROMPT_NONE, 0 }, + + /* Illegal orphan inode in superblock */ + { PR_0_ORPHAN_ILLEGAL_HEAD_INODE, + N_("@I @o @i %i in @S.\n"), + PROMPT_NONE, 0 }, + + /* Illegal inode in orphaned inode list */ + { PR_0_ORPHAN_ILLEGAL_INODE, + N_("@I @i %i in @o @i list.\n"), + PROMPT_NONE, 0 }, + + /* Filesystem revision is 0, but feature flags are set */ + { PR_0_FS_REV_LEVEL, + N_("@f has feature flag(s) set, but is a revision 0 @f. "), + PROMPT_FIX, PR_PREEN_OK | PR_NO_OK }, + + /* Journal superblock has an unknown read-only feature flag set */ + { PR_0_JOURNAL_UNSUPP_ROCOMPAT, + N_("Ext3 @j @S has an unknown read-only feature flag set.\n"), + PROMPT_ABORT, 0 }, + + /* Journal superblock has an unknown incompatible feature flag set */ + { PR_0_JOURNAL_UNSUPP_INCOMPAT, + N_("Ext3 @j @S has an unknown incompatible feature flag set.\n"), + PROMPT_ABORT, 0 }, + + /* Journal has unsupported version number */ + { PR_0_JOURNAL_UNSUPP_VERSION, + N_("@j version not supported by this e2fsck.\n"), + PROMPT_ABORT, 0 }, + + /* Moving journal to hidden file */ + { PR_0_MOVE_JOURNAL, + N_("Moving @j from /%s to hidden @i.\n\n"), + PROMPT_NONE, 0 }, + + /* Error moving journal to hidden file */ + { PR_0_ERR_MOVE_JOURNAL, + N_("Error moving @j: %m\n\n"), + PROMPT_NONE, 0 }, + + /* Clearing V2 journal superblock */ + { PR_0_CLEAR_V2_JOURNAL, + N_("Found @n V2 @j @S fields (from V1 @j).\n" + "Clearing fields beyond the V1 @j @S...\n\n"), + PROMPT_NONE, 0 }, + + /* Backup journal inode blocks */ + { PR_0_BACKUP_JNL, + N_("Backing up @j @i @b information.\n\n"), + PROMPT_NONE, 0 }, + + /* Reserved blocks w/o resize_inode */ + { PR_0_NONZERO_RESERVED_GDT_BLOCKS, + N_("@f does not have resize_@i enabled, but s_reserved_gdt_@bs\n" + "is %N; @s zero. "), + PROMPT_FIX, 0 }, + + /* Resize_inode not enabled, but resize inode is non-zero */ + { PR_0_CLEAR_RESIZE_INODE, + N_("Resize_@i not enabled, but the resize @i is non-zero. "), + PROMPT_CLEAR, 0 }, + + /* Resize inode invalid */ + { PR_0_RESIZE_INODE_INVALID, + N_("Resize @i not valid. "), + PROMPT_RECREATE, 0 }, + + /* Pass 1 errors */ + + /* Pass 1: Checking inodes, blocks, and sizes */ + { PR_1_PASS_HEADER, + N_("Pass 1: Checking @is, @bs, and sizes\n"), + PROMPT_NONE, 0 }, + + /* Root directory is not an inode */ + { PR_1_ROOT_NO_DIR, N_("@r is not a @d. "), + PROMPT_CLEAR, 0 }, + + /* Root directory has dtime set */ + { PR_1_ROOT_DTIME, + N_("@r has dtime set (probably due to old mke2fs). "), + PROMPT_FIX, PR_PREEN_OK }, + + /* Reserved inode has bad mode */ + { PR_1_RESERVED_BAD_MODE, + N_("Reserved @i %i (%Q) has @n mode. "), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* Deleted inode has zero dtime */ + { PR_1_ZERO_DTIME, + N_("@D @i %i has zero dtime. "), + PROMPT_FIX, PR_PREEN_OK }, + + /* Inode in use, but dtime set */ + { PR_1_SET_DTIME, + N_("@i %i is in use, but has dtime set. "), + PROMPT_FIX, PR_PREEN_OK }, + + /* Zero-length directory */ + { PR_1_ZERO_LENGTH_DIR, + N_("@i %i is a @z @d. "), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* Block bitmap conflicts with some other fs block */ + { PR_1_BB_CONFLICT, + N_("@g %g's @b @B at %b @C.\n"), + PROMPT_RELOCATE, 0 }, + + /* Inode bitmap conflicts with some other fs block */ + { PR_1_IB_CONFLICT, + N_("@g %g's @i @B at %b @C.\n"), + PROMPT_RELOCATE, 0 }, + + /* Inode table conflicts with some other fs block */ + { PR_1_ITABLE_CONFLICT, + N_("@g %g's @i table at %b @C.\n"), + PROMPT_RELOCATE, 0 }, + + /* Block bitmap is on a bad block */ + { PR_1_BB_BAD_BLOCK, + N_("@g %g's @b @B (%b) is bad. "), + PROMPT_RELOCATE, 0 }, + + /* Inode bitmap is on a bad block */ + { PR_1_IB_BAD_BLOCK, + N_("@g %g's @i @B (%b) is bad. "), + PROMPT_RELOCATE, 0 }, + + /* Inode has incorrect i_size */ + { PR_1_BAD_I_SIZE, + N_("@i %i, i_size is %Is, @s %N. "), + PROMPT_FIX, PR_PREEN_OK }, + + /* Inode has incorrect i_blocks */ + { PR_1_BAD_I_BLOCKS, + N_("@i %i, i_@bs is %Ib, @s %N. "), + PROMPT_FIX, PR_PREEN_OK }, + + /* Illegal blocknumber in inode */ + { PR_1_ILLEGAL_BLOCK_NUM, + N_("@I @b #%B (%b) in @i %i. "), + PROMPT_CLEAR, PR_LATCH_BLOCK }, + + /* Block number overlaps fs metadata */ + { PR_1_BLOCK_OVERLAPS_METADATA, + N_("@b #%B (%b) overlaps @f metadata in @i %i. "), + PROMPT_CLEAR, PR_LATCH_BLOCK }, + + /* Inode has illegal blocks (latch question) */ + { PR_1_INODE_BLOCK_LATCH, + N_("@i %i has illegal @b(s). "), + PROMPT_CLEAR, 0 }, + + /* Too many bad blocks in inode */ + { PR_1_TOO_MANY_BAD_BLOCKS, + N_("Too many illegal @bs in @i %i.\n"), + PROMPT_CLEAR_INODE, PR_NO_OK }, + + /* Illegal block number in bad block inode */ + { PR_1_BB_ILLEGAL_BLOCK_NUM, + N_("@I @b #%B (%b) in bad @b @i. "), + PROMPT_CLEAR, PR_LATCH_BBLOCK }, + + /* Bad block inode has illegal blocks (latch question) */ + { PR_1_INODE_BBLOCK_LATCH, + N_("Bad @b @i has illegal @b(s). "), + PROMPT_CLEAR, 0 }, + + /* Duplicate or bad blocks in use! */ + { PR_1_DUP_BLOCKS_PREENSTOP, + N_("Duplicate or bad @b in use!\n"), + PROMPT_NONE, 0 }, + + /* Bad block used as bad block indirect block */ + { PR_1_BBINODE_BAD_METABLOCK, + N_("Bad @b %b used as bad @b @i indirect @b. "), + PROMPT_CLEAR, PR_LATCH_BBLOCK }, + + /* Inconsistency can't be fixed prompt */ + { PR_1_BBINODE_BAD_METABLOCK_PROMPT, + N_("\nThe bad @b @i has probably been corrupted. You probably\n" + "should stop now and run ""e2fsck -c"" to scan for bad blocks\n" + "in the @f.\n"), + PROMPT_CONTINUE, PR_PREEN_NOMSG }, + + /* Bad primary block */ + { PR_1_BAD_PRIMARY_BLOCK, + N_("\nIf the @b is really bad, the @f cannot be fixed.\n"), + PROMPT_NONE, PR_AFTER_CODE, PR_1_BAD_PRIMARY_BLOCK_PROMPT }, + + /* Bad primary block prompt */ + { PR_1_BAD_PRIMARY_BLOCK_PROMPT, + N_("You can remove this @b from the bad @b list and hope\n" + "that the @b is really OK. But there are no guarantees.\n\n"), + PROMPT_CLEAR, PR_PREEN_NOMSG }, + + /* Bad primary superblock */ + { PR_1_BAD_PRIMARY_SUPERBLOCK, + N_("The primary @S (%b) is on the bad @b list.\n"), + PROMPT_NONE, PR_AFTER_CODE, PR_1_BAD_PRIMARY_BLOCK }, + + /* Bad primary block group descriptors */ + { PR_1_BAD_PRIMARY_GROUP_DESCRIPTOR, + N_("Block %b in the primary @g descriptors " + "is on the bad @b list\n"), + PROMPT_NONE, PR_AFTER_CODE, PR_1_BAD_PRIMARY_BLOCK }, + + /* Bad superblock in group */ + { PR_1_BAD_SUPERBLOCK, + N_("Warning: Group %g's @S (%b) is bad.\n"), + PROMPT_NONE, PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Bad block group descriptors in group */ + { PR_1_BAD_GROUP_DESCRIPTORS, + N_("Warning: Group %g's copy of the @g descriptors has a bad " + "@b (%b).\n"), + PROMPT_NONE, PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Block claimed for no reason */ + { PR_1_PROGERR_CLAIMED_BLOCK, + N_("Programming error? @b #%b claimed for no reason in " + "process_bad_@b.\n"), + PROMPT_NONE, PR_PREEN_OK }, + + /* Error allocating blocks for relocating metadata */ + { PR_1_RELOC_BLOCK_ALLOCATE, + N_("@A %N contiguous @b(s) in @b @g %g for %s: %m\n"), + PROMPT_NONE, PR_PREEN_OK }, + + /* Error allocating block buffer during relocation process */ + { PR_1_RELOC_MEMORY_ALLOCATE, + N_("@A @b buffer for relocating %s\n"), + PROMPT_NONE, PR_PREEN_OK }, + + /* Relocating metadata group information from X to Y */ + { PR_1_RELOC_FROM_TO, + N_("Relocating @g %g's %s from %b to %c...\n"), + PROMPT_NONE, PR_PREEN_OK }, + + /* Relocating metatdata group information to X */ + { PR_1_RELOC_TO, + N_("Relocating @g %g's %s to %c...\n"), /* xgettext:no-c-format */ + PROMPT_NONE, PR_PREEN_OK }, + + /* Block read error during relocation process */ + { PR_1_RELOC_READ_ERR, + N_("Warning: could not read @b %b of %s: %m\n"), + PROMPT_NONE, PR_PREEN_OK }, + + /* Block write error during relocation process */ + { PR_1_RELOC_WRITE_ERR, + N_("Warning: could not write @b %b for %s: %m\n"), + PROMPT_NONE, PR_PREEN_OK }, + + /* Error allocating inode bitmap */ + { PR_1_ALLOCATE_IBITMAP_ERROR, + N_("@A @i @B (%N): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error allocating block bitmap */ + { PR_1_ALLOCATE_BBITMAP_ERROR, + N_("@A @b @B (%N): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error allocating icount structure */ + { PR_1_ALLOCATE_ICOUNT, + N_("@A icount link information: %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error allocating dbcount */ + { PR_1_ALLOCATE_DBCOUNT, + N_("@A @d @b array: %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error while scanning inodes */ + { PR_1_ISCAN_ERROR, + N_("Error while scanning @is (%i): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error while iterating over blocks */ + { PR_1_BLOCK_ITERATE, + N_("Error while iterating over @bs in @i %i: %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error while storing inode count information */ + { PR_1_ICOUNT_STORE, + N_("Error storing @i count information (@i=%i, count=%N): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error while storing directory block information */ + { PR_1_ADD_DBLOCK, + N_("Error storing @d @b information " + "(@i=%i, @b=%b, num=%N): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error while reading inode (for clearing) */ + { PR_1_READ_INODE, + N_("Error reading @i %i: %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Suppress messages prompt */ + { PR_1_SUPPRESS_MESSAGES, "", PROMPT_SUPPRESS, PR_NO_OK }, + + /* Imagic flag set on an inode when filesystem doesn't support it */ + { PR_1_SET_IMAGIC, + N_("@i %i has imagic flag set. "), + PROMPT_CLEAR, 0 }, + + /* Immutable flag set on a device or socket inode */ + { PR_1_SET_IMMUTABLE, + N_("Special (@v/socket/fifo/symlink) file (@i %i) has immutable\n" + "or append-only flag set. "), + PROMPT_CLEAR, PR_PREEN_OK | PR_PREEN_NO | PR_NO_OK }, + + /* Compression flag set on an inode when filesystem doesn't support it */ + { PR_1_COMPR_SET, + N_("@i %i has @cion flag set on @f without @cion support. "), + PROMPT_CLEAR, 0 }, + + /* Non-zero size for device, fifo or socket inode */ + { PR_1_SET_NONZSIZE, + N_("Special (@v/socket/fifo) @i %i has non-zero size. "), + PROMPT_FIX, PR_PREEN_OK }, + + /* Filesystem revision is 0, but feature flags are set */ + { PR_1_FS_REV_LEVEL, + N_("@f has feature flag(s) set, but is a revision 0 @f. "), + PROMPT_FIX, PR_PREEN_OK | PR_NO_OK }, + + /* Journal inode is not in use, but contains data */ + { PR_1_JOURNAL_INODE_NOT_CLEAR, + N_("@j @i is not in use, but contains data. "), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* Journal has bad mode */ + { PR_1_JOURNAL_BAD_MODE, + N_("@j is not regular file. "), + PROMPT_FIX, PR_PREEN_OK }, + + /* Deal with inodes that were part of orphan linked list */ + { PR_1_LOW_DTIME, + N_("@i %i was part of the @o @i list. "), + PROMPT_FIX, PR_LATCH_LOW_DTIME, 0 }, + + /* Deal with inodes that were part of corrupted orphan linked + list (latch question) */ + { PR_1_ORPHAN_LIST_REFUGEES, + N_("@is that were part of a corrupted orphan linked list found. "), + PROMPT_FIX, 0 }, + + /* Error allocating refcount structure */ + { PR_1_ALLOCATE_REFCOUNT, + N_("@A refcount structure (%N): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error reading extended attribute block */ + { PR_1_READ_EA_BLOCK, + N_("Error reading @a @b %b for @i %i. "), + PROMPT_CLEAR, 0 }, + + /* Invalid extended attribute block */ + { PR_1_BAD_EA_BLOCK, + N_("@i %i has a bad @a @b %b. "), + PROMPT_CLEAR, 0 }, + + /* Error reading Extended Attribute block while fixing refcount */ + { PR_1_EXTATTR_READ_ABORT, + N_("Error reading @a @b %b (%m). "), + PROMPT_ABORT, 0 }, + + /* Extended attribute reference count incorrect */ + { PR_1_EXTATTR_REFCOUNT, + N_("@a @b %b has reference count %B, @s %N. "), + PROMPT_FIX, 0 }, + + /* Error writing Extended Attribute block while fixing refcount */ + { PR_1_EXTATTR_WRITE, + N_("Error writing @a @b %b (%m). "), + PROMPT_ABORT, 0 }, + + /* Multiple EA blocks not supported */ + { PR_1_EA_MULTI_BLOCK, + N_("@a @b %b has h_@bs > 1. "), + PROMPT_CLEAR, 0}, + + /* Error allocating EA region allocation structure */ + { PR_1_EA_ALLOC_REGION, + N_("@A @a @b %b. "), + PROMPT_ABORT, 0}, + + /* Error EA allocation collision */ + { PR_1_EA_ALLOC_COLLISION, + N_("@a @b %b is corrupt (allocation collision). "), + PROMPT_CLEAR, 0}, + + /* Bad extended attribute name */ + { PR_1_EA_BAD_NAME, + N_("@a @b %b is corrupt (@n name). "), + PROMPT_CLEAR, 0}, + + /* Bad extended attribute value */ + { PR_1_EA_BAD_VALUE, + N_("@a @b %b is corrupt (@n value). "), + PROMPT_CLEAR, 0}, + + /* Inode too big (latch question) */ + { PR_1_INODE_TOOBIG, + N_("@i %i is too big. "), PROMPT_TRUNCATE, 0 }, + + /* Directory too big */ + { PR_1_TOOBIG_DIR, + N_("@b #%B (%b) causes @d to be too big. "), + PROMPT_CLEAR, PR_LATCH_TOOBIG }, + + /* Regular file too big */ + { PR_1_TOOBIG_REG, + N_("@b #%B (%b) causes file to be too big. "), + PROMPT_CLEAR, PR_LATCH_TOOBIG }, + + /* Symlink too big */ + { PR_1_TOOBIG_SYMLINK, + N_("@b #%B (%b) causes symlink to be too big. "), + PROMPT_CLEAR, PR_LATCH_TOOBIG }, + + /* INDEX_FL flag set on a non-HTREE filesystem */ + { PR_1_HTREE_SET, + N_("@i %i has INDEX_FL flag set on @f without htree support.\n"), + PROMPT_CLEAR_HTREE, PR_PREEN_OK }, + + /* INDEX_FL flag set on a non-directory */ + { PR_1_HTREE_NODIR, + N_("@i %i has INDEX_FL flag set but is not a @d.\n"), + PROMPT_CLEAR_HTREE, PR_PREEN_OK }, + + /* Invalid root node in HTREE directory */ + { PR_1_HTREE_BADROOT, + N_("@h %i has an @n root node.\n"), + PROMPT_CLEAR_HTREE, PR_PREEN_OK }, + + /* Unsupported hash version in HTREE directory */ + { PR_1_HTREE_HASHV, + N_("@h %i has an unsupported hash version (%N)\n"), + PROMPT_CLEAR_HTREE, PR_PREEN_OK }, + + /* Incompatible flag in HTREE root node */ + { PR_1_HTREE_INCOMPAT, + N_("@h %i uses an incompatible htree root node flag.\n"), + PROMPT_CLEAR_HTREE, PR_PREEN_OK }, + + /* HTREE too deep */ + { PR_1_HTREE_DEPTH, + N_("@h %i has a tree depth (%N) which is too big\n"), + PROMPT_CLEAR_HTREE, PR_PREEN_OK }, + + /* Bad block has indirect block that conflicts with filesystem block */ + { PR_1_BB_FS_BLOCK, + N_("Bad @b @i has an indirect @b (%b) that conflicts with\n" + "@f metadata. "), + PROMPT_CLEAR, PR_LATCH_BBLOCK }, + + /* Resize inode failed */ + { PR_1_RESIZE_INODE_CREATE, + N_("Resize @i (re)creation failed: %m."), + PROMPT_ABORT, 0 }, + + /* invalid inode->i_extra_isize */ + { PR_1_EXTRA_ISIZE, + N_("@i %i has a extra size (%IS) which is @n\n"), + PROMPT_FIX, PR_PREEN_OK }, + + /* invalid ea entry->e_name_len */ + { PR_1_ATTR_NAME_LEN, + N_("@a in @i %i has a namelen (%N) which is @n\n"), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* invalid ea entry->e_value_size */ + { PR_1_ATTR_VALUE_SIZE, + N_("@a in @i %i has a value size (%N) which is @n\n"), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* invalid ea entry->e_value_offs */ + { PR_1_ATTR_VALUE_OFFSET, + N_("@a in @i %i has a value offset (%N) which is @n\n"), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* invalid ea entry->e_value_block */ + { PR_1_ATTR_VALUE_BLOCK, + N_("@a in @i %i has a value @b (%N) which is @n (must be 0)\n"), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* invalid ea entry->e_hash */ + { PR_1_ATTR_HASH, + N_("@a in @i %i has a hash (%N) which is @n (must be 0)\n"), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* Pass 1b errors */ + + /* Pass 1B: Rescan for duplicate/bad blocks */ + { PR_1B_PASS_HEADER, + N_("\nRunning additional passes to resolve @bs claimed by more than one @i...\n" + "Pass 1B: Rescanning for @m @bs\n"), + PROMPT_NONE, 0 }, + + /* Duplicate/bad block(s) header */ + { PR_1B_DUP_BLOCK_HEADER, + N_("@m @b(s) in @i %i:"), + PROMPT_NONE, 0 }, + + /* Duplicate/bad block(s) in inode */ + { PR_1B_DUP_BLOCK, + " %b", + PROMPT_NONE, PR_LATCH_DBLOCK | PR_PREEN_NOHDR }, + + /* Duplicate/bad block(s) end */ + { PR_1B_DUP_BLOCK_END, + "\n", + PROMPT_NONE, PR_PREEN_NOHDR }, + + /* Error while scanning inodes */ + { PR_1B_ISCAN_ERROR, + N_("Error while scanning inodes (%i): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error allocating inode bitmap */ + { PR_1B_ALLOCATE_IBITMAP_ERROR, + N_("@A @i @B (@i_dup_map): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error while iterating over blocks */ + { PR_1B_BLOCK_ITERATE, + N_("Error while iterating over @bs in @i %i (%s): %m\n"), + PROMPT_NONE, 0 }, + + /* Error adjusting EA refcount */ + { PR_1B_ADJ_EA_REFCOUNT, + N_("Error adjusting refcount for @a @b %b (@i %i): %m\n"), + PROMPT_NONE, 0 }, + + + /* Pass 1C: Scan directories for inodes with multiply-claimed blocks. */ + { PR_1C_PASS_HEADER, + N_("Pass 1C: Scanning directories for @is with @m @bs.\n"), + PROMPT_NONE, 0 }, + + + /* Pass 1D: Reconciling multiply-claimed blocks */ + { PR_1D_PASS_HEADER, + N_("Pass 1D: Reconciling @m @bs\n"), + PROMPT_NONE, 0 }, + + /* File has duplicate blocks */ + { PR_1D_DUP_FILE, + N_("File %Q (@i #%i, mod time %IM)\n" + " has %B @m @b(s), shared with %N file(s):\n"), + PROMPT_NONE, 0 }, + + /* List of files sharing duplicate blocks */ + { PR_1D_DUP_FILE_LIST, + N_("\t%Q (@i #%i, mod time %IM)\n"), + PROMPT_NONE, 0 }, + + /* File sharing blocks with filesystem metadata */ + { PR_1D_SHARE_METADATA, + N_("\t<@f metadata>\n"), + PROMPT_NONE, 0 }, + + /* Report of how many duplicate/bad inodes */ + { PR_1D_NUM_DUP_INODES, + N_("(There are %N @is containing @m @bs.)\n\n"), + PROMPT_NONE, 0 }, + + /* Duplicated blocks already reassigned or cloned. */ + { PR_1D_DUP_BLOCKS_DEALT, + N_("@m @bs already reassigned or cloned.\n\n"), + PROMPT_NONE, 0 }, + + /* Clone duplicate/bad blocks? */ + { PR_1D_CLONE_QUESTION, + "", PROMPT_CLONE, PR_NO_OK }, + + /* Delete file? */ + { PR_1D_DELETE_QUESTION, + "", PROMPT_DELETE, 0 }, + + /* Couldn't clone file (error) */ + { PR_1D_CLONE_ERROR, + N_("Couldn't clone file: %m\n"), PROMPT_NONE, 0 }, + + /* Pass 2 errors */ + + /* Pass 2: Checking directory structure */ + { PR_2_PASS_HEADER, + N_("Pass 2: Checking @d structure\n"), + PROMPT_NONE, 0 }, + + /* Bad inode number for '.' */ + { PR_2_BAD_INODE_DOT, + N_("@n @i number for '.' in @d @i %i.\n"), + PROMPT_FIX, 0 }, + + /* Directory entry has bad inode number */ + { PR_2_BAD_INO, + N_("@E has @n @i #: %Di.\n"), + PROMPT_CLEAR, 0 }, + + /* Directory entry has deleted or unused inode */ + { PR_2_UNUSED_INODE, + N_("@E has @D/unused @i %Di. "), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* Directry entry is link to '.' */ + { PR_2_LINK_DOT, + N_("@E @L to '.' "), + PROMPT_CLEAR, 0 }, + + /* Directory entry points to inode now located in a bad block */ + { PR_2_BB_INODE, + N_("@E points to @i (%Di) located in a bad @b.\n"), + PROMPT_CLEAR, 0 }, + + /* Directory entry contains a link to a directory */ + { PR_2_LINK_DIR, + N_("@E @L to @d %P (%Di).\n"), + PROMPT_CLEAR, 0 }, + + /* Directory entry contains a link to the root directry */ + { PR_2_LINK_ROOT, + N_("@E @L to the @r.\n"), + PROMPT_CLEAR, 0 }, + + /* Directory entry has illegal characters in its name */ + { PR_2_BAD_NAME, + N_("@E has illegal characters in its name.\n"), + PROMPT_FIX, 0 }, + + /* Missing '.' in directory inode */ + { PR_2_MISSING_DOT, + N_("Missing '.' in @d @i %i.\n"), + PROMPT_FIX, 0 }, + + /* Missing '..' in directory inode */ + { PR_2_MISSING_DOT_DOT, + N_("Missing '..' in @d @i %i.\n"), + PROMPT_FIX, 0 }, + + /* First entry in directory inode doesn't contain '.' */ + { PR_2_1ST_NOT_DOT, + N_("First @e '%Dn' (@i=%Di) in @d @i %i (%p) @s '.'\n"), + PROMPT_FIX, 0 }, + + /* Second entry in directory inode doesn't contain '..' */ + { PR_2_2ND_NOT_DOT_DOT, + N_("Second @e '%Dn' (@i=%Di) in @d @i %i @s '..'\n"), + PROMPT_FIX, 0 }, + + /* i_faddr should be zero */ + { PR_2_FADDR_ZERO, + N_("i_faddr @F %IF, @s zero.\n"), + PROMPT_CLEAR, 0 }, + + /* i_file_acl should be zero */ + { PR_2_FILE_ACL_ZERO, + N_("i_file_acl @F %If, @s zero.\n"), + PROMPT_CLEAR, 0 }, + + /* i_dir_acl should be zero */ + { PR_2_DIR_ACL_ZERO, + N_("i_dir_acl @F %Id, @s zero.\n"), + PROMPT_CLEAR, 0 }, + + /* i_frag should be zero */ + { PR_2_FRAG_ZERO, + N_("i_frag @F %N, @s zero.\n"), + PROMPT_CLEAR, 0 }, + + /* i_fsize should be zero */ + { PR_2_FSIZE_ZERO, + N_("i_fsize @F %N, @s zero.\n"), + PROMPT_CLEAR, 0 }, + + /* inode has bad mode */ + { PR_2_BAD_MODE, + N_("@i %i (%Q) has @n mode (%Im).\n"), + PROMPT_CLEAR, 0 }, + + /* directory corrupted */ + { PR_2_DIR_CORRUPTED, + N_("@d @i %i, @b %B, offset %N: @d corrupted\n"), + PROMPT_SALVAGE, 0 }, + + /* filename too long */ + { PR_2_FILENAME_LONG, + N_("@d @i %i, @b %B, offset %N: filename too long\n"), + PROMPT_TRUNCATE, 0 }, + + /* Directory inode has a missing block (hole) */ + { PR_2_DIRECTORY_HOLE, + N_("@d @i %i has an unallocated @b #%B. "), + PROMPT_ALLOCATE, 0 }, + + /* '.' is not NULL terminated */ + { PR_2_DOT_NULL_TERM, + N_("'.' @d @e in @d @i %i is not NULL terminated\n"), + PROMPT_FIX, 0 }, + + /* '..' is not NULL terminated */ + { PR_2_DOT_DOT_NULL_TERM, + N_("'..' @d @e in @d @i %i is not NULL terminated\n"), + PROMPT_FIX, 0 }, + + /* Illegal character device inode */ + { PR_2_BAD_CHAR_DEV, + N_("@i %i (%Q) is an @I character @v.\n"), + PROMPT_CLEAR, 0 }, + + /* Illegal block device inode */ + { PR_2_BAD_BLOCK_DEV, + N_("@i %i (%Q) is an @I @b @v.\n"), + PROMPT_CLEAR, 0 }, + + /* Duplicate '.' entry */ + { PR_2_DUP_DOT, + N_("@E is duplicate '.' @e.\n"), + PROMPT_FIX, 0 }, + + /* Duplicate '..' entry */ + { PR_2_DUP_DOT_DOT, + N_("@E is duplicate '..' @e.\n"), + PROMPT_FIX, 0 }, + + /* Internal error: couldn't find dir_info */ + { PR_2_NO_DIRINFO, + N_("Internal error: cannot find dir_info for %i.\n"), + PROMPT_NONE, PR_FATAL }, + + /* Final rec_len is wrong */ + { PR_2_FINAL_RECLEN, + N_("@E has rec_len of %Dr, @s %N.\n"), + PROMPT_FIX, 0 }, + + /* Error allocating icount structure */ + { PR_2_ALLOCATE_ICOUNT, + N_("@A icount structure: %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error iterating over directory blocks */ + { PR_2_DBLIST_ITERATE, + N_("Error iterating over @d @bs: %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error reading directory block */ + { PR_2_READ_DIRBLOCK, + N_("Error reading @d @b %b (@i %i): %m\n"), + PROMPT_CONTINUE, 0 }, + + /* Error writing directory block */ + { PR_2_WRITE_DIRBLOCK, + N_("Error writing @d @b %b (@i %i): %m\n"), + PROMPT_CONTINUE, 0 }, + + /* Error allocating new directory block */ + { PR_2_ALLOC_DIRBOCK, + N_("@A new @d @b for @i %i (%s): %m\n"), + PROMPT_NONE, 0 }, + + /* Error deallocating inode */ + { PR_2_DEALLOC_INODE, + N_("Error deallocating @i %i: %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Directory entry for '.' is big. Split? */ + { PR_2_SPLIT_DOT, + N_("@d @e for '.' is big. "), + PROMPT_SPLIT, PR_NO_OK }, + + /* Illegal FIFO inode */ + { PR_2_BAD_FIFO, + N_("@i %i (%Q) is an @I FIFO.\n"), + PROMPT_CLEAR, 0 }, + + /* Illegal socket inode */ + { PR_2_BAD_SOCKET, + N_("@i %i (%Q) is an @I socket.\n"), + PROMPT_CLEAR, 0 }, + + /* Directory filetype not set */ + { PR_2_SET_FILETYPE, + N_("Setting filetype for @E to %N.\n"), + PROMPT_NONE, PR_PREEN_OK | PR_NO_OK | PR_NO_NOMSG }, + + /* Directory filetype incorrect */ + { PR_2_BAD_FILETYPE, + N_("@E has an incorrect filetype (was %Dt, @s %N).\n"), + PROMPT_FIX, 0 }, + + /* Directory filetype set on filesystem */ + { PR_2_CLEAR_FILETYPE, + N_("@E has filetype set.\n"), + PROMPT_CLEAR, PR_PREEN_OK }, + + /* Directory filename is null */ + { PR_2_NULL_NAME, + N_("@E has a @z name.\n"), + PROMPT_CLEAR, 0 }, + + /* Invalid symlink */ + { PR_2_INVALID_SYMLINK, + N_("Symlink %Q (@i #%i) is @n.\n"), + PROMPT_CLEAR, 0 }, + + /* i_file_acl (extended attribute block) is bad */ + { PR_2_FILE_ACL_BAD, + N_("@a @b @F @n (%If).\n"), + PROMPT_CLEAR, 0 }, + + /* Filesystem contains large files, but has no such flag in sb */ + { PR_2_FEATURE_LARGE_FILES, + N_("@f contains large files, but lacks LARGE_FILE flag in @S.\n"), + PROMPT_FIX, 0 }, + + /* Node in HTREE directory not referenced */ + { PR_2_HTREE_NOTREF, + N_("@p @h %d: node (%B) not referenced\n"), + PROMPT_NONE, 0 }, + + /* Node in HTREE directory referenced twice */ + { PR_2_HTREE_DUPREF, + N_("@p @h %d: node (%B) referenced twice\n"), + PROMPT_NONE, 0 }, + + /* Node in HTREE directory has bad min hash */ + { PR_2_HTREE_MIN_HASH, + N_("@p @h %d: node (%B) has bad min hash\n"), + PROMPT_NONE, 0 }, + + /* Node in HTREE directory has bad max hash */ + { PR_2_HTREE_MAX_HASH, + N_("@p @h %d: node (%B) has bad max hash\n"), + PROMPT_NONE, 0 }, + + /* Clear invalid HTREE directory */ + { PR_2_HTREE_CLEAR, + N_("@n @h %d (%q). "), PROMPT_CLEAR, 0 }, + + /* Bad block in htree interior node */ + { PR_2_HTREE_BADBLK, + N_("@p @h %d (%q): bad @b number %b.\n"), + PROMPT_CLEAR_HTREE, 0 }, + + /* Error adjusting EA refcount */ + { PR_2_ADJ_EA_REFCOUNT, + N_("Error adjusting refcount for @a @b %b (@i %i): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Invalid HTREE root node */ + { PR_2_HTREE_BAD_ROOT, + N_("@p @h %d: root node is @n\n"), + PROMPT_CLEAR_HTREE, PR_PREEN_OK }, + + /* Invalid HTREE limit */ + { PR_2_HTREE_BAD_LIMIT, + N_("@p @h %d: node (%B) has @n limit (%N)\n"), + PROMPT_CLEAR_HTREE, PR_PREEN_OK }, + + /* Invalid HTREE count */ + { PR_2_HTREE_BAD_COUNT, + N_("@p @h %d: node (%B) has @n count (%N)\n"), + PROMPT_CLEAR_HTREE, PR_PREEN_OK }, + + /* HTREE interior node has out-of-order hashes in table */ + { PR_2_HTREE_HASH_ORDER, + N_("@p @h %d: node (%B) has an unordered hash table\n"), + PROMPT_CLEAR_HTREE, PR_PREEN_OK }, + + /* Node in HTREE directory has invalid depth */ + { PR_2_HTREE_BAD_DEPTH, + N_("@p @h %d: node (%B) has @n depth\n"), + PROMPT_NONE, 0 }, + + /* Duplicate directory entry found */ + { PR_2_DUPLICATE_DIRENT, + N_("Duplicate @E found. "), + PROMPT_CLEAR, 0 }, + + /* Non-unique filename found */ + { PR_2_NON_UNIQUE_FILE, /* xgettext: no-c-format */ + N_("@E has a non-unique filename.\nRename to %s"), + PROMPT_NULL, 0 }, + + /* Duplicate directory entry found */ + { PR_2_REPORT_DUP_DIRENT, + N_("Duplicate @e '%Dn' found.\n\tMarking %p (%i) to be rebuilt.\n\n"), + PROMPT_NONE, 0 }, + + /* Pass 3 errors */ + + /* Pass 3: Checking directory connectivity */ + { PR_3_PASS_HEADER, + N_("Pass 3: Checking @d connectivity\n"), + PROMPT_NONE, 0 }, + + /* Root inode not allocated */ + { PR_3_NO_ROOT_INODE, + N_("@r not allocated. "), + PROMPT_ALLOCATE, 0 }, + + /* No room in lost+found */ + { PR_3_EXPAND_LF_DIR, + N_("No room in @l @d. "), + PROMPT_EXPAND, 0 }, + + /* Unconnected directory inode */ + { PR_3_UNCONNECTED_DIR, + N_("Unconnected @d @i %i (%p)\n"), + PROMPT_CONNECT, 0 }, + + /* /lost+found not found */ + { PR_3_NO_LF_DIR, + N_("/@l not found. "), + PROMPT_CREATE, PR_PREEN_OK }, + + /* .. entry is incorrect */ + { PR_3_BAD_DOT_DOT, + N_("'..' in %Q (%i) is %P (%j), @s %q (%d).\n"), + PROMPT_FIX, 0 }, + + /* Bad or non-existent /lost+found. Cannot reconnect */ + { PR_3_NO_LPF, + N_("Bad or non-existent /@l. Cannot reconnect.\n"), + PROMPT_NONE, 0 }, + + /* Could not expand /lost+found */ + { PR_3_CANT_EXPAND_LPF, + N_("Could not expand /@l: %m\n"), + PROMPT_NONE, 0 }, + + /* Could not reconnect inode */ + { PR_3_CANT_RECONNECT, + N_("Could not reconnect %i: %m\n"), + PROMPT_NONE, 0 }, + + /* Error while trying to find /lost+found */ + { PR_3_ERR_FIND_LPF, + N_("Error while trying to find /@l: %m\n"), + PROMPT_NONE, 0 }, + + /* Error in ext2fs_new_block while creating /lost+found */ + { PR_3_ERR_LPF_NEW_BLOCK, + N_("ext2fs_new_@b: %m while trying to create /@l @d\n"), + PROMPT_NONE, 0 }, + + /* Error in ext2fs_new_inode while creating /lost+found */ + { PR_3_ERR_LPF_NEW_INODE, + N_("ext2fs_new_@i: %m while trying to create /@l @d\n"), + PROMPT_NONE, 0 }, + + /* Error in ext2fs_new_dir_block while creating /lost+found */ + { PR_3_ERR_LPF_NEW_DIR_BLOCK, + N_("ext2fs_new_dir_@b: %m while creating new @d @b\n"), + PROMPT_NONE, 0 }, + + /* Error while writing directory block for /lost+found */ + { PR_3_ERR_LPF_WRITE_BLOCK, + N_("ext2fs_write_dir_@b: %m while writing the @d @b for /@l\n"), + PROMPT_NONE, 0 }, + + /* Error while adjusting inode count */ + { PR_3_ADJUST_INODE, + N_("Error while adjusting @i count on @i %i\n"), + PROMPT_NONE, 0 }, + + /* Couldn't fix parent directory -- error */ + { PR_3_FIX_PARENT_ERR, + N_("Couldn't fix parent of @i %i: %m\n\n"), + PROMPT_NONE, 0 }, + + /* Couldn't fix parent directory -- couldn't find it */ + { PR_3_FIX_PARENT_NOFIND, + N_("Couldn't fix parent of @i %i: Couldn't find parent @d @e\n\n"), + PROMPT_NONE, 0 }, + + /* Error allocating inode bitmap */ + { PR_3_ALLOCATE_IBITMAP_ERROR, + N_("@A @i @B (%N): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error creating root directory */ + { PR_3_CREATE_ROOT_ERROR, + N_("Error creating root @d (%s): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error creating lost and found directory */ + { PR_3_CREATE_LPF_ERROR, + N_("Error creating /@l @d (%s): %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Root inode is not directory; aborting */ + { PR_3_ROOT_NOT_DIR_ABORT, + N_("@r is not a @d; aborting.\n"), + PROMPT_NONE, PR_FATAL }, + + /* Cannot proceed without a root inode. */ + { PR_3_NO_ROOT_INODE_ABORT, + N_("Cannot proceed without a @r.\n"), + PROMPT_NONE, PR_FATAL }, + + /* Internal error: couldn't find dir_info */ + { PR_3_NO_DIRINFO, + N_("Internal error: cannot find dir_info for %i.\n"), + PROMPT_NONE, PR_FATAL }, + + /* Lost+found not a directory */ + { PR_3_LPF_NOTDIR, + N_("/@l is not a @d (ino=%i)\n"), + PROMPT_UNLINK, 0 }, + + /* Pass 3A Directory Optimization */ + + /* Pass 3A: Optimizing directories */ + { PR_3A_PASS_HEADER, + N_("Pass 3A: Optimizing directories\n"), + PROMPT_NONE, PR_PREEN_NOMSG }, + + /* Error iterating over directories */ + { PR_3A_OPTIMIZE_ITER, + N_("Failed to create dirs_to_hash iterator: %m"), + PROMPT_NONE, 0 }, + + /* Error rehash directory */ + { PR_3A_OPTIMIZE_DIR_ERR, + N_("Failed to optimize directory %q (%d): %m"), + PROMPT_NONE, 0 }, + + /* Rehashing dir header */ + { PR_3A_OPTIMIZE_DIR_HEADER, + N_("Optimizing directories: "), + PROMPT_NONE, PR_MSG_ONLY }, + + /* Rehashing directory %d */ + { PR_3A_OPTIMIZE_DIR, + " %d", + PROMPT_NONE, PR_LATCH_OPTIMIZE_DIR | PR_PREEN_NOHDR}, + + /* Rehashing dir end */ + { PR_3A_OPTIMIZE_DIR_END, + "\n", + PROMPT_NONE, PR_PREEN_NOHDR }, + + /* Pass 4 errors */ + + /* Pass 4: Checking reference counts */ + { PR_4_PASS_HEADER, + N_("Pass 4: Checking reference counts\n"), + PROMPT_NONE, 0 }, + + /* Unattached zero-length inode */ + { PR_4_ZERO_LEN_INODE, + N_("@u @z @i %i. "), + PROMPT_CLEAR, PR_PREEN_OK|PR_NO_OK }, + + /* Unattached inode */ + { PR_4_UNATTACHED_INODE, + N_("@u @i %i\n"), + PROMPT_CONNECT, 0 }, + + /* Inode ref count wrong */ + { PR_4_BAD_REF_COUNT, + N_("@i %i ref count is %Il, @s %N. "), + PROMPT_FIX, PR_PREEN_OK }, + + { PR_4_INCONSISTENT_COUNT, + N_("WARNING: PROGRAMMING BUG IN E2FSCK!\n" + "\tOR SOME BONEHEAD (YOU) IS CHECKING A MOUNTED (LIVE) FILESYSTEM.\n" + "@i_link_info[%i] is %N, @i.i_links_count is %Il. " + "They @s the same!\n"), + PROMPT_NONE, 0 }, + + /* Pass 5 errors */ + + /* Pass 5: Checking group summary information */ + { PR_5_PASS_HEADER, + N_("Pass 5: Checking @g summary information\n"), + PROMPT_NONE, 0 }, + + /* Padding at end of inode bitmap is not set. */ + { PR_5_INODE_BMAP_PADDING, + N_("Padding at end of @i @B is not set. "), + PROMPT_FIX, PR_PREEN_OK }, + + /* Padding at end of block bitmap is not set. */ + { PR_5_BLOCK_BMAP_PADDING, + N_("Padding at end of @b @B is not set. "), + PROMPT_FIX, PR_PREEN_OK }, + + /* Block bitmap differences header */ + { PR_5_BLOCK_BITMAP_HEADER, + N_("@b @B differences: "), + PROMPT_NONE, PR_PREEN_OK | PR_PREEN_NOMSG}, + + /* Block not used, but marked in bitmap */ + { PR_5_BLOCK_UNUSED, + " -%b", + PROMPT_NONE, PR_LATCH_BBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Block used, but not marked used in bitmap */ + { PR_5_BLOCK_USED, + " +%b", + PROMPT_NONE, PR_LATCH_BBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Block bitmap differences end */ + { PR_5_BLOCK_BITMAP_END, + "\n", + PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Inode bitmap differences header */ + { PR_5_INODE_BITMAP_HEADER, + N_("@i @B differences: "), + PROMPT_NONE, PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Inode not used, but marked in bitmap */ + { PR_5_INODE_UNUSED, + " -%i", + PROMPT_NONE, PR_LATCH_IBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Inode used, but not marked used in bitmap */ + { PR_5_INODE_USED, + " +%i", + PROMPT_NONE, PR_LATCH_IBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Inode bitmap differences end */ + { PR_5_INODE_BITMAP_END, + "\n", + PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Free inodes count for group wrong */ + { PR_5_FREE_INODE_COUNT_GROUP, + N_("Free @is count wrong for @g #%g (%i, counted=%j).\n"), + PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Directories count for group wrong */ + { PR_5_FREE_DIR_COUNT_GROUP, + N_("Directories count wrong for @g #%g (%i, counted=%j).\n"), + PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Free inodes count wrong */ + { PR_5_FREE_INODE_COUNT, + N_("Free @is count wrong (%i, counted=%j).\n"), + PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Free blocks count for group wrong */ + { PR_5_FREE_BLOCK_COUNT_GROUP, + N_("Free @bs count wrong for @g #%g (%b, counted=%c).\n"), + PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Free blocks count wrong */ + { PR_5_FREE_BLOCK_COUNT, + N_("Free @bs count wrong (%b, counted=%c).\n"), + PROMPT_FIX, PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Programming error: bitmap endpoints don't match */ + { PR_5_BMAP_ENDPOINTS, + N_("PROGRAMMING ERROR: @f (#%N) @B endpoints (%b, %c) don't " + "match calculated @B endpoints (%i, %j)\n"), + PROMPT_NONE, PR_FATAL }, + + /* Internal error: fudging end of bitmap */ + { PR_5_FUDGE_BITMAP_ERROR, + N_("Internal error: fudging end of bitmap (%N)\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error copying in replacement inode bitmap */ + { PR_5_COPY_IBITMAP_ERROR, + N_("Error copying in replacement @i @B: %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Error copying in replacement block bitmap */ + { PR_5_COPY_BBITMAP_ERROR, + N_("Error copying in replacement @b @B: %m\n"), + PROMPT_NONE, PR_FATAL }, + + /* Block range not used, but marked in bitmap */ + { PR_5_BLOCK_RANGE_UNUSED, + " -(%b--%c)", + PROMPT_NONE, PR_LATCH_BBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Block range used, but not marked used in bitmap */ + { PR_5_BLOCK_RANGE_USED, + " +(%b--%c)", + PROMPT_NONE, PR_LATCH_BBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Inode range not used, but marked in bitmap */ + { PR_5_INODE_RANGE_UNUSED, + " -(%i--%j)", + PROMPT_NONE, PR_LATCH_IBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG }, + + /* Inode range used, but not marked used in bitmap */ + { PR_5_INODE_RANGE_USED, + " +(%i--%j)", + PROMPT_NONE, PR_LATCH_IBITMAP | PR_PREEN_OK | PR_PREEN_NOMSG }, + + { 0 } +}; + +/* + * This is the latch flags register. It allows several problems to be + * "latched" together. This means that the user has to answer but one + * question for the set of problems, and all of the associated + * problems will be either fixed or not fixed. + */ +static struct latch_descr pr_latch_info[] = { + { PR_LATCH_BLOCK, PR_1_INODE_BLOCK_LATCH, 0 }, + { PR_LATCH_BBLOCK, PR_1_INODE_BBLOCK_LATCH, 0 }, + { PR_LATCH_IBITMAP, PR_5_INODE_BITMAP_HEADER, PR_5_INODE_BITMAP_END }, + { PR_LATCH_BBITMAP, PR_5_BLOCK_BITMAP_HEADER, PR_5_BLOCK_BITMAP_END }, + { PR_LATCH_RELOC, PR_0_RELOCATE_HINT, 0 }, + { PR_LATCH_DBLOCK, PR_1B_DUP_BLOCK_HEADER, PR_1B_DUP_BLOCK_END }, + { PR_LATCH_LOW_DTIME, PR_1_ORPHAN_LIST_REFUGEES, 0 }, + { PR_LATCH_TOOBIG, PR_1_INODE_TOOBIG, 0 }, + { PR_LATCH_OPTIMIZE_DIR, PR_3A_OPTIMIZE_DIR_HEADER, PR_3A_OPTIMIZE_DIR_END }, + { -1, 0, 0 }, +}; + +static const struct e2fsck_problem *find_problem(problem_t code) +{ + int i; + + for (i=0; problem_table[i].e2p_code; i++) { + if (problem_table[i].e2p_code == code) + return &problem_table[i]; + } + return 0; +} + +static struct latch_descr *find_latch(int code) +{ + int i; + + for (i=0; pr_latch_info[i].latch_code >= 0; i++) { + if (pr_latch_info[i].latch_code == code) + return &pr_latch_info[i]; + } + return 0; +} + +int end_problem_latch(e2fsck_t ctx, int mask) +{ + struct latch_descr *ldesc; + struct problem_context pctx; + int answer = -1; + + ldesc = find_latch(mask); + if (ldesc->end_message && (ldesc->flags & PRL_LATCHED)) { + clear_problem_context(&pctx); + answer = fix_problem(ctx, ldesc->end_message, &pctx); + } + ldesc->flags &= ~(PRL_VARIABLE); + return answer; +} + +int set_latch_flags(int mask, int setflags, int clearflags) +{ + struct latch_descr *ldesc; + + ldesc = find_latch(mask); + if (!ldesc) + return -1; + ldesc->flags |= setflags; + ldesc->flags &= ~clearflags; + return 0; +} + +void clear_problem_context(struct problem_context *ctx) +{ + memset(ctx, 0, sizeof(struct problem_context)); + ctx->blkcount = -1; + ctx->group = -1; +} + +int fix_problem(e2fsck_t ctx, problem_t code, struct problem_context *pctx) +{ + ext2_filsys fs = ctx->fs; + const struct e2fsck_problem *ptr; + struct latch_descr *ldesc = 0; + const char *message; + int def_yn, answer, ans; + int print_answer = 0; + int suppress = 0; + + ptr = find_problem(code); + if (!ptr) { + printf(_("Unhandled error code (0x%x)!\n"), code); + return 0; + } + def_yn = 1; + if ((ptr->flags & PR_NO_DEFAULT) || + ((ptr->flags & PR_PREEN_NO) && (ctx->options & E2F_OPT_PREEN)) || + (ctx->options & E2F_OPT_NO)) + def_yn= 0; + + /* + * Do special latch processing. This is where we ask the + * latch question, if it exists + */ + if (ptr->flags & PR_LATCH_MASK) { + ldesc = find_latch(ptr->flags & PR_LATCH_MASK); + if (ldesc->question && !(ldesc->flags & PRL_LATCHED)) { + ans = fix_problem(ctx, ldesc->question, pctx); + if (ans == 1) + ldesc->flags |= PRL_YES; + if (ans == 0) + ldesc->flags |= PRL_NO; + ldesc->flags |= PRL_LATCHED; + } + if (ldesc->flags & PRL_SUPPRESS) + suppress++; + } + if ((ptr->flags & PR_PREEN_NOMSG) && + (ctx->options & E2F_OPT_PREEN)) + suppress++; + if ((ptr->flags & PR_NO_NOMSG) && + (ctx->options & E2F_OPT_NO)) + suppress++; + if (!suppress) { + message = ptr->e2p_description; + if ((ctx->options & E2F_OPT_PREEN) && + !(ptr->flags & PR_PREEN_NOHDR)) { + printf("%s: ", ctx->device_name ? + ctx->device_name : ctx->filesystem_name); + } + if (*message) + print_e2fsck_message(ctx, _(message), pctx, 1); + } + if (!(ptr->flags & PR_PREEN_OK) && (ptr->prompt != PROMPT_NONE)) + preenhalt(ctx); + + if (ptr->flags & PR_FATAL) + bb_error_msg_and_die(0); + + if (ptr->prompt == PROMPT_NONE) { + if (ptr->flags & PR_NOCOLLATE) + answer = -1; + else + answer = def_yn; + } else { + if (ctx->options & E2F_OPT_PREEN) { + answer = def_yn; + if (!(ptr->flags & PR_PREEN_NOMSG)) + print_answer = 1; + } else if ((ptr->flags & PR_LATCH_MASK) && + (ldesc->flags & (PRL_YES | PRL_NO))) { + if (!suppress) + print_answer = 1; + if (ldesc->flags & PRL_YES) + answer = 1; + else + answer = 0; + } else + answer = ask(ctx, _(prompt[(int) ptr->prompt]), def_yn); + if (!answer && !(ptr->flags & PR_NO_OK)) + ext2fs_unmark_valid(fs); + + if (print_answer) + printf("%s.\n", answer ? + _(preen_msg[(int) ptr->prompt]) : _("IGNORED")); + + } + + if ((ptr->prompt == PROMPT_ABORT) && answer) + bb_error_msg_and_die(0); + + if (ptr->flags & PR_AFTER_CODE) + answer = fix_problem(ctx, ptr->second_code, pctx); + + return answer; +} + +/* + * linux/fs/recovery.c + * + * Written by Stephen C. Tweedie , 1999 + */ + +/* + * Maintain information about the progress of the recovery job, so that + * the different passes can carry information between them. + */ +struct recovery_info +{ + tid_t start_transaction; + tid_t end_transaction; + + int nr_replays; + int nr_revokes; + int nr_revoke_hits; +}; + +enum passtype {PASS_SCAN, PASS_REVOKE, PASS_REPLAY}; +static int do_one_pass(journal_t *journal, + struct recovery_info *info, enum passtype pass); +static int scan_revoke_records(journal_t *, struct buffer_head *, + tid_t, struct recovery_info *); + +/* + * Read a block from the journal + */ + +static int jread(struct buffer_head **bhp, journal_t *journal, + unsigned int offset) +{ + int err; + unsigned long blocknr; + struct buffer_head *bh; + + *bhp = NULL; + + err = journal_bmap(journal, offset, &blocknr); + + if (err) { + printf("JBD: bad block at offset %u\n", offset); + return err; + } + + bh = getblk(journal->j_dev, blocknr, journal->j_blocksize); + if (!bh) + return -ENOMEM; + + if (!buffer_uptodate(bh)) { + /* If this is a brand new buffer, start readahead. + Otherwise, we assume we are already reading it. */ + if (!buffer_req(bh)) + do_readahead(journal, offset); + wait_on_buffer(bh); + } + + if (!buffer_uptodate(bh)) { + printf("JBD: Failed to read block at offset %u\n", offset); + brelse(bh); + return -EIO; + } + + *bhp = bh; + return 0; +} + + +/* + * Count the number of in-use tags in a journal descriptor block. + */ + +static int count_tags(struct buffer_head *bh, int size) +{ + char * tagp; + journal_block_tag_t * tag; + int nr = 0; + + tagp = &bh->b_data[sizeof(journal_header_t)]; + + while ((tagp - bh->b_data + sizeof(journal_block_tag_t)) <= size) { + tag = (journal_block_tag_t *) tagp; + + nr++; + tagp += sizeof(journal_block_tag_t); + if (!(tag->t_flags & htonl(JFS_FLAG_SAME_UUID))) + tagp += 16; + + if (tag->t_flags & htonl(JFS_FLAG_LAST_TAG)) + break; + } + + return nr; +} + + +/* Make sure we wrap around the log correctly! */ +#define wrap(journal, var) \ +do { \ + if (var >= (journal)->j_last) \ + var -= ((journal)->j_last - (journal)->j_first); \ +} while (0) + +/** + * int journal_recover(journal_t *journal) - recovers a on-disk journal + * @journal: the journal to recover + * + * The primary function for recovering the log contents when mounting a + * journaled device. + * + * Recovery is done in three passes. In the first pass, we look for the + * end of the log. In the second, we assemble the list of revoke + * blocks. In the third and final pass, we replay any un-revoked blocks + * in the log. + */ +int journal_recover(journal_t *journal) +{ + int err; + journal_superblock_t * sb; + + struct recovery_info info; + + memset(&info, 0, sizeof(info)); + sb = journal->j_superblock; + + /* + * The journal superblock's s_start field (the current log head) + * is always zero if, and only if, the journal was cleanly + * unmounted. + */ + + if (!sb->s_start) { + journal->j_transaction_sequence = ntohl(sb->s_sequence) + 1; + return 0; + } + + err = do_one_pass(journal, &info, PASS_SCAN); + if (!err) + err = do_one_pass(journal, &info, PASS_REVOKE); + if (!err) + err = do_one_pass(journal, &info, PASS_REPLAY); + + /* Restart the log at the next transaction ID, thus invalidating + * any existing commit records in the log. */ + journal->j_transaction_sequence = ++info.end_transaction; + + journal_clear_revoke(journal); + sync_blockdev(journal->j_fs_dev); + return err; +} + +static int do_one_pass(journal_t *journal, + struct recovery_info *info, enum passtype pass) +{ + unsigned int first_commit_ID, next_commit_ID; + unsigned long next_log_block; + int err, success = 0; + journal_superblock_t * sb; + journal_header_t * tmp; + struct buffer_head * bh; + unsigned int sequence; + int blocktype; + + /* Precompute the maximum metadata descriptors in a descriptor block */ + int MAX_BLOCKS_PER_DESC; + MAX_BLOCKS_PER_DESC = ((journal->j_blocksize-sizeof(journal_header_t)) + / sizeof(journal_block_tag_t)); + + /* + * First thing is to establish what we expect to find in the log + * (in terms of transaction IDs), and where (in terms of log + * block offsets): query the superblock. + */ + + sb = journal->j_superblock; + next_commit_ID = ntohl(sb->s_sequence); + next_log_block = ntohl(sb->s_start); + + first_commit_ID = next_commit_ID; + if (pass == PASS_SCAN) + info->start_transaction = first_commit_ID; + + /* + * Now we walk through the log, transaction by transaction, + * making sure that each transaction has a commit block in the + * expected place. Each complete transaction gets replayed back + * into the main filesystem. + */ + + while (1) { + int flags; + char * tagp; + journal_block_tag_t * tag; + struct buffer_head * obh; + struct buffer_head * nbh; + + /* If we already know where to stop the log traversal, + * check right now that we haven't gone past the end of + * the log. */ + + if (pass != PASS_SCAN) + if (tid_geq(next_commit_ID, info->end_transaction)) + break; + + /* Skip over each chunk of the transaction looking + * either the next descriptor block or the final commit + * record. */ + + err = jread(&bh, journal, next_log_block); + if (err) + goto failed; + + next_log_block++; + wrap(journal, next_log_block); + + /* What kind of buffer is it? + * + * If it is a descriptor block, check that it has the + * expected sequence number. Otherwise, we're all done + * here. */ + + tmp = (journal_header_t *)bh->b_data; + + if (tmp->h_magic != htonl(JFS_MAGIC_NUMBER)) { + brelse(bh); + break; + } + + blocktype = ntohl(tmp->h_blocktype); + sequence = ntohl(tmp->h_sequence); + + if (sequence != next_commit_ID) { + brelse(bh); + break; + } + + /* OK, we have a valid descriptor block which matches + * all of the sequence number checks. What are we going + * to do with it? That depends on the pass... */ + + switch(blocktype) { + case JFS_DESCRIPTOR_BLOCK: + /* If it is a valid descriptor block, replay it + * in pass REPLAY; otherwise, just skip over the + * blocks it describes. */ + if (pass != PASS_REPLAY) { + next_log_block += + count_tags(bh, journal->j_blocksize); + wrap(journal, next_log_block); + brelse(bh); + continue; + } + + /* A descriptor block: we can now write all of + * the data blocks. Yay, useful work is finally + * getting done here! */ + + tagp = &bh->b_data[sizeof(journal_header_t)]; + while ((tagp - bh->b_data +sizeof(journal_block_tag_t)) + <= journal->j_blocksize) { + unsigned long io_block; + + tag = (journal_block_tag_t *) tagp; + flags = ntohl(tag->t_flags); + + io_block = next_log_block++; + wrap(journal, next_log_block); + err = jread(&obh, journal, io_block); + if (err) { + /* Recover what we can, but + * report failure at the end. */ + success = err; + printf("JBD: IO error %d recovering " + "block %ld in log\n", + err, io_block); + } else { + unsigned long blocknr; + + blocknr = ntohl(tag->t_blocknr); + + /* If the block has been + * revoked, then we're all done + * here. */ + if (journal_test_revoke + (journal, blocknr, + next_commit_ID)) { + brelse(obh); + ++info->nr_revoke_hits; + goto skip_write; + } + + /* Find a buffer for the new + * data being restored */ + nbh = getblk(journal->j_fs_dev, + blocknr, + journal->j_blocksize); + if (nbh == NULL) { + printf("JBD: Out of memory " + "during recovery.\n"); + err = -ENOMEM; + brelse(bh); + brelse(obh); + goto failed; + } + + lock_buffer(nbh); + memcpy(nbh->b_data, obh->b_data, + journal->j_blocksize); + if (flags & JFS_FLAG_ESCAPE) { + *((unsigned int *)bh->b_data) = + htonl(JFS_MAGIC_NUMBER); + } + + mark_buffer_uptodate(nbh, 1); + mark_buffer_dirty(nbh); + ++info->nr_replays; + /* ll_rw_block(WRITE, 1, &nbh); */ + unlock_buffer(nbh); + brelse(obh); + brelse(nbh); + } + + skip_write: + tagp += sizeof(journal_block_tag_t); + if (!(flags & JFS_FLAG_SAME_UUID)) + tagp += 16; + + if (flags & JFS_FLAG_LAST_TAG) + break; + } + + brelse(bh); + continue; + + case JFS_COMMIT_BLOCK: + /* Found an expected commit block: not much to + * do other than move on to the next sequence + * number. */ + brelse(bh); + next_commit_ID++; + continue; + + case JFS_REVOKE_BLOCK: + /* If we aren't in the REVOKE pass, then we can + * just skip over this block. */ + if (pass != PASS_REVOKE) { + brelse(bh); + continue; + } + + err = scan_revoke_records(journal, bh, + next_commit_ID, info); + brelse(bh); + if (err) + goto failed; + continue; + + default: + goto done; + } + } + + done: + /* + * We broke out of the log scan loop: either we came to the + * known end of the log or we found an unexpected block in the + * log. If the latter happened, then we know that the "current" + * transaction marks the end of the valid log. + */ + + if (pass == PASS_SCAN) + info->end_transaction = next_commit_ID; + else { + /* It's really bad news if different passes end up at + * different places (but possible due to IO errors). */ + if (info->end_transaction != next_commit_ID) { + printf("JBD: recovery pass %d ended at " + "transaction %u, expected %u\n", + pass, next_commit_ID, info->end_transaction); + if (!success) + success = -EIO; + } + } + + return success; + + failed: + return err; +} + + +/* Scan a revoke record, marking all blocks mentioned as revoked. */ + +static int scan_revoke_records(journal_t *journal, struct buffer_head *bh, + tid_t sequence, struct recovery_info *info) +{ + journal_revoke_header_t *header; + int offset, max; + + header = (journal_revoke_header_t *) bh->b_data; + offset = sizeof(journal_revoke_header_t); + max = ntohl(header->r_count); + + while (offset < max) { + unsigned long blocknr; + int err; + + blocknr = ntohl(* ((unsigned int *) (bh->b_data+offset))); + offset += 4; + err = journal_set_revoke(journal, blocknr, sequence); + if (err) + return err; + ++info->nr_revokes; + } + return 0; +} + + +/* + * rehash.c --- rebuild hash tree directories + * + * This algorithm is designed for simplicity of implementation and to + * pack the directory as much as possible. It however requires twice + * as much memory as the size of the directory. The maximum size + * directory supported using a 4k blocksize is roughly a gigabyte, and + * so there may very well be problems with machines that don't have + * virtual memory, and obscenely large directories. + * + * An alternate algorithm which is much more disk intensive could be + * written, and probably will need to be written in the future. The + * design goals of such an algorithm are: (a) use (roughly) constant + * amounts of memory, no matter how large the directory, (b) the + * directory must be safe at all times, even if e2fsck is interrupted + * in the middle, (c) we must use minimal amounts of extra disk + * blocks. This pretty much requires an incremental approach, where + * we are reading from one part of the directory, and inserting into + * the front half. So the algorithm will have to keep track of a + * moving block boundary between the new tree and the old tree, and + * files will need to be moved from the old directory and inserted + * into the new tree. If the new directory requires space which isn't + * yet available, blocks from the beginning part of the old directory + * may need to be moved to the end of the directory to make room for + * the new tree: + * + * -------------------------------------------------------- + * | new tree | | old tree | + * -------------------------------------------------------- + * ^ ptr ^ptr + * tail new head old + * + * This is going to be a pain in the tuckus to implement, and will + * require a lot more disk accesses. So I'm going to skip it for now; + * it's only really going to be an issue for really, really big + * filesystems (when we reach the level of tens of millions of files + * in a single directory). It will probably be easier to simply + * require that e2fsck use VM first. + */ + +struct fill_dir_struct { + char *buf; + struct ext2_inode *inode; + int err; + e2fsck_t ctx; + struct hash_entry *harray; + int max_array, num_array; + int dir_size; + int compress; + ino_t parent; +}; + +struct hash_entry { + ext2_dirhash_t hash; + ext2_dirhash_t minor_hash; + struct ext2_dir_entry *dir; +}; + +struct out_dir { + int num; + int max; + char *buf; + ext2_dirhash_t *hashes; +}; + +static int fill_dir_block(ext2_filsys fs, + blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_block FSCK_ATTR((unused)), + int ref_offset FSCK_ATTR((unused)), + void *priv_data) +{ + struct fill_dir_struct *fd = (struct fill_dir_struct *) priv_data; + struct hash_entry *new_array, *ent; + struct ext2_dir_entry *dirent; + char *dir; + unsigned int offset, dir_offset; + + if (blockcnt < 0) + return 0; + + offset = blockcnt * fs->blocksize; + if (offset + fs->blocksize > fd->inode->i_size) { + fd->err = EXT2_ET_DIR_CORRUPTED; + return BLOCK_ABORT; + } + dir = (fd->buf+offset); + if (HOLE_BLKADDR(*block_nr)) { + memset(dir, 0, fs->blocksize); + dirent = (struct ext2_dir_entry *) dir; + dirent->rec_len = fs->blocksize; + } else { + fd->err = ext2fs_read_dir_block(fs, *block_nr, dir); + if (fd->err) + return BLOCK_ABORT; + } + /* While the directory block is "hot", index it. */ + dir_offset = 0; + while (dir_offset < fs->blocksize) { + dirent = (struct ext2_dir_entry *) (dir + dir_offset); + if (((dir_offset + dirent->rec_len) > fs->blocksize) || + (dirent->rec_len < 8) || + ((dirent->rec_len % 4) != 0) || + (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) { + fd->err = EXT2_ET_DIR_CORRUPTED; + return BLOCK_ABORT; + } + dir_offset += dirent->rec_len; + if (dirent->inode == 0) + continue; + if (!fd->compress && ((dirent->name_len&0xFF) == 1) && + (dirent->name[0] == '.')) + continue; + if (!fd->compress && ((dirent->name_len&0xFF) == 2) && + (dirent->name[0] == '.') && (dirent->name[1] == '.')) { + fd->parent = dirent->inode; + continue; + } + if (fd->num_array >= fd->max_array) { + new_array = realloc(fd->harray, + sizeof(struct hash_entry) * (fd->max_array+500)); + if (!new_array) { + fd->err = ENOMEM; + return BLOCK_ABORT; + } + fd->harray = new_array; + fd->max_array += 500; + } + ent = fd->harray + fd->num_array++; + ent->dir = dirent; + fd->dir_size += EXT2_DIR_REC_LEN(dirent->name_len & 0xFF); + if (fd->compress) + ent->hash = ent->minor_hash = 0; + else { + fd->err = ext2fs_dirhash(fs->super->s_def_hash_version, + dirent->name, + dirent->name_len & 0xFF, + fs->super->s_hash_seed, + &ent->hash, &ent->minor_hash); + if (fd->err) + return BLOCK_ABORT; + } + } + + return 0; +} + +/* Used for sorting the hash entry */ +static int name_cmp(const void *a, const void *b) +{ + const struct hash_entry *he_a = (const struct hash_entry *) a; + const struct hash_entry *he_b = (const struct hash_entry *) b; + int ret; + int min_len; + + min_len = he_a->dir->name_len; + if (min_len > he_b->dir->name_len) + min_len = he_b->dir->name_len; + + ret = strncmp(he_a->dir->name, he_b->dir->name, min_len); + if (ret == 0) { + if (he_a->dir->name_len > he_b->dir->name_len) + ret = 1; + else if (he_a->dir->name_len < he_b->dir->name_len) + ret = -1; + else + ret = he_b->dir->inode - he_a->dir->inode; + } + return ret; +} + +/* Used for sorting the hash entry */ +static int hash_cmp(const void *a, const void *b) +{ + const struct hash_entry *he_a = (const struct hash_entry *) a; + const struct hash_entry *he_b = (const struct hash_entry *) b; + int ret; + + if (he_a->hash > he_b->hash) + ret = 1; + else if (he_a->hash < he_b->hash) + ret = -1; + else { + if (he_a->minor_hash > he_b->minor_hash) + ret = 1; + else if (he_a->minor_hash < he_b->minor_hash) + ret = -1; + else + ret = name_cmp(a, b); + } + return ret; +} + +static errcode_t alloc_size_dir(ext2_filsys fs, struct out_dir *outdir, + int blocks) +{ + void *new_mem; + + if (outdir->max) { + new_mem = realloc(outdir->buf, blocks * fs->blocksize); + if (!new_mem) + return ENOMEM; + outdir->buf = new_mem; + new_mem = realloc(outdir->hashes, + blocks * sizeof(ext2_dirhash_t)); + if (!new_mem) + return ENOMEM; + outdir->hashes = new_mem; + } else { + outdir->buf = malloc(blocks * fs->blocksize); + outdir->hashes = malloc(blocks * sizeof(ext2_dirhash_t)); + outdir->num = 0; + } + outdir->max = blocks; + return 0; +} + +static void free_out_dir(struct out_dir *outdir) +{ + free(outdir->buf); + free(outdir->hashes); + outdir->max = 0; + outdir->num =0; +} + +static errcode_t get_next_block(ext2_filsys fs, struct out_dir *outdir, + char ** ret) +{ + errcode_t retval; + + if (outdir->num >= outdir->max) { + retval = alloc_size_dir(fs, outdir, outdir->max + 50); + if (retval) + return retval; + } + *ret = outdir->buf + (outdir->num++ * fs->blocksize); + memset(*ret, 0, fs->blocksize); + return 0; +} + +/* + * This function is used to make a unique filename. We do this by + * appending ~0, and then incrementing the number. However, we cannot + * expand the length of the filename beyond the padding available in + * the directory entry. + */ +static void mutate_name(char *str, __u16 *len) +{ + int i; + __u16 l = *len & 0xFF, h = *len & 0xff00; + + /* + * First check to see if it looks the name has been mutated + * already + */ + for (i = l-1; i > 0; i--) { + if (!isdigit(str[i])) + break; + } + if ((i == l-1) || (str[i] != '~')) { + if (((l-1) & 3) < 2) + l += 2; + else + l = (l+3) & ~3; + str[l-2] = '~'; + str[l-1] = '0'; + *len = l | h; + return; + } + for (i = l-1; i >= 0; i--) { + if (isdigit(str[i])) { + if (str[i] == '9') + str[i] = '0'; + else { + str[i]++; + return; + } + continue; + } + if (i == 1) { + if (str[0] == 'z') + str[0] = 'A'; + else if (str[0] == 'Z') { + str[0] = '~'; + str[1] = '0'; + } else + str[0]++; + } else if (i > 0) { + str[i] = '1'; + str[i-1] = '~'; + } else { + if (str[0] == '~') + str[0] = 'a'; + else + str[0]++; + } + break; + } +} + +static int duplicate_search_and_fix(e2fsck_t ctx, ext2_filsys fs, + ext2_ino_t ino, + struct fill_dir_struct *fd) +{ + struct problem_context pctx; + struct hash_entry *ent, *prev; + int i, j; + int fixed = 0; + char new_name[256]; + __u16 new_len; + + clear_problem_context(&pctx); + pctx.ino = ino; + + for (i=1; i < fd->num_array; i++) { + ent = fd->harray + i; + prev = ent - 1; + if (!ent->dir->inode || + ((ent->dir->name_len & 0xFF) != + (prev->dir->name_len & 0xFF)) || + (strncmp(ent->dir->name, prev->dir->name, + ent->dir->name_len & 0xFF))) + continue; + pctx.dirent = ent->dir; + if ((ent->dir->inode == prev->dir->inode) && + fix_problem(ctx, PR_2_DUPLICATE_DIRENT, &pctx)) { + e2fsck_adjust_inode_count(ctx, ent->dir->inode, -1); + ent->dir->inode = 0; + fixed++; + continue; + } + memcpy(new_name, ent->dir->name, ent->dir->name_len & 0xFF); + new_len = ent->dir->name_len; + mutate_name(new_name, &new_len); + for (j=0; j < fd->num_array; j++) { + if ((i==j) || + ((ent->dir->name_len & 0xFF) != + (fd->harray[j].dir->name_len & 0xFF)) || + (strncmp(new_name, fd->harray[j].dir->name, + new_len & 0xFF))) + continue; + mutate_name(new_name, &new_len); + + j = -1; + } + new_name[new_len & 0xFF] = 0; + pctx.str = new_name; + if (fix_problem(ctx, PR_2_NON_UNIQUE_FILE, &pctx)) { + memcpy(ent->dir->name, new_name, new_len & 0xFF); + ent->dir->name_len = new_len; + ext2fs_dirhash(fs->super->s_def_hash_version, + ent->dir->name, + ent->dir->name_len & 0xFF, + fs->super->s_hash_seed, + &ent->hash, &ent->minor_hash); + fixed++; + } + } + return fixed; +} + + +static errcode_t copy_dir_entries(ext2_filsys fs, + struct fill_dir_struct *fd, + struct out_dir *outdir) +{ + errcode_t retval; + char *block_start; + struct hash_entry *ent; + struct ext2_dir_entry *dirent; + int i, rec_len, left; + ext2_dirhash_t prev_hash; + int offset; + + outdir->max = 0; + retval = alloc_size_dir(fs, outdir, + (fd->dir_size / fs->blocksize) + 2); + if (retval) + return retval; + outdir->num = fd->compress ? 0 : 1; + offset = 0; + outdir->hashes[0] = 0; + prev_hash = 1; + if ((retval = get_next_block(fs, outdir, &block_start))) + return retval; + dirent = (struct ext2_dir_entry *) block_start; + left = fs->blocksize; + for (i=0; i < fd->num_array; i++) { + ent = fd->harray + i; + if (ent->dir->inode == 0) + continue; + rec_len = EXT2_DIR_REC_LEN(ent->dir->name_len & 0xFF); + if (rec_len > left) { + if (left) + dirent->rec_len += left; + if ((retval = get_next_block(fs, outdir, + &block_start))) + return retval; + offset = 0; + } + left = fs->blocksize - offset; + dirent = (struct ext2_dir_entry *) (block_start + offset); + if (offset == 0) { + if (ent->hash == prev_hash) + outdir->hashes[outdir->num-1] = ent->hash | 1; + else + outdir->hashes[outdir->num-1] = ent->hash; + } + dirent->inode = ent->dir->inode; + dirent->name_len = ent->dir->name_len; + dirent->rec_len = rec_len; + memcpy(dirent->name, ent->dir->name, dirent->name_len & 0xFF); + offset += rec_len; + left -= rec_len; + if (left < 12) { + dirent->rec_len += left; + offset += left; + left = 0; + } + prev_hash = ent->hash; + } + if (left) + dirent->rec_len += left; + + return 0; +} + + +static struct ext2_dx_root_info *set_root_node(ext2_filsys fs, char *buf, + ext2_ino_t ino, ext2_ino_t parent) +{ + struct ext2_dir_entry *dir; + struct ext2_dx_root_info *root; + struct ext2_dx_countlimit *limits; + int filetype = 0; + + if (fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_FILETYPE) + filetype = EXT2_FT_DIR << 8; + + memset(buf, 0, fs->blocksize); + dir = (struct ext2_dir_entry *) buf; + dir->inode = ino; + dir->name[0] = '.'; + dir->name_len = 1 | filetype; + dir->rec_len = 12; + dir = (struct ext2_dir_entry *) (buf + 12); + dir->inode = parent; + dir->name[0] = '.'; + dir->name[1] = '.'; + dir->name_len = 2 | filetype; + dir->rec_len = fs->blocksize - 12; + + root = (struct ext2_dx_root_info *) (buf+24); + root->reserved_zero = 0; + root->hash_version = fs->super->s_def_hash_version; + root->info_length = 8; + root->indirect_levels = 0; + root->unused_flags = 0; + + limits = (struct ext2_dx_countlimit *) (buf+32); + limits->limit = (fs->blocksize - 32) / sizeof(struct ext2_dx_entry); + limits->count = 0; + + return root; +} + + +static struct ext2_dx_entry *set_int_node(ext2_filsys fs, char *buf) +{ + struct ext2_dir_entry *dir; + struct ext2_dx_countlimit *limits; + + memset(buf, 0, fs->blocksize); + dir = (struct ext2_dir_entry *) buf; + dir->inode = 0; + dir->rec_len = fs->blocksize; + + limits = (struct ext2_dx_countlimit *) (buf+8); + limits->limit = (fs->blocksize - 8) / sizeof(struct ext2_dx_entry); + limits->count = 0; + + return (struct ext2_dx_entry *) limits; +} + +/* + * This function takes the leaf nodes which have been written in + * outdir, and populates the root node and any necessary interior nodes. + */ +static errcode_t calculate_tree(ext2_filsys fs, + struct out_dir *outdir, + ext2_ino_t ino, + ext2_ino_t parent) +{ + struct ext2_dx_root_info *root_info; + struct ext2_dx_entry *root, *dx_ent = 0; + struct ext2_dx_countlimit *root_limit, *limit; + errcode_t retval; + char * block_start; + int i, c1, c2, nblks; + int limit_offset, root_offset; + + root_info = set_root_node(fs, outdir->buf, ino, parent); + root_offset = limit_offset = ((char *) root_info - outdir->buf) + + root_info->info_length; + root_limit = (struct ext2_dx_countlimit *) (outdir->buf + limit_offset); + c1 = root_limit->limit; + nblks = outdir->num; + + /* Write out the pointer blocks */ + if (nblks-1 <= c1) { + /* Just write out the root block, and we're done */ + root = (struct ext2_dx_entry *) (outdir->buf + root_offset); + for (i=1; i < nblks; i++) { + root->block = ext2fs_cpu_to_le32(i); + if (i != 1) + root->hash = + ext2fs_cpu_to_le32(outdir->hashes[i]); + root++; + c1--; + } + } else { + c2 = 0; + limit = 0; + root_info->indirect_levels = 1; + for (i=1; i < nblks; i++) { + if (c1 == 0) + return ENOSPC; + if (c2 == 0) { + if (limit) + limit->limit = limit->count = + ext2fs_cpu_to_le16(limit->limit); + root = (struct ext2_dx_entry *) + (outdir->buf + root_offset); + root->block = ext2fs_cpu_to_le32(outdir->num); + if (i != 1) + root->hash = + ext2fs_cpu_to_le32(outdir->hashes[i]); + if ((retval = get_next_block(fs, outdir, + &block_start))) + return retval; + dx_ent = set_int_node(fs, block_start); + limit = (struct ext2_dx_countlimit *) dx_ent; + c2 = limit->limit; + root_offset += sizeof(struct ext2_dx_entry); + c1--; + } + dx_ent->block = ext2fs_cpu_to_le32(i); + if (c2 != limit->limit) + dx_ent->hash = + ext2fs_cpu_to_le32(outdir->hashes[i]); + dx_ent++; + c2--; + } + limit->count = ext2fs_cpu_to_le16(limit->limit - c2); + limit->limit = ext2fs_cpu_to_le16(limit->limit); + } + root_limit = (struct ext2_dx_countlimit *) (outdir->buf + limit_offset); + root_limit->count = ext2fs_cpu_to_le16(root_limit->limit - c1); + root_limit->limit = ext2fs_cpu_to_le16(root_limit->limit); + + return 0; +} + +struct write_dir_struct { + struct out_dir *outdir; + errcode_t err; + e2fsck_t ctx; + int cleared; +}; + +/* + * Helper function which writes out a directory block. + */ +static int write_dir_block(ext2_filsys fs, + blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_block FSCK_ATTR((unused)), + int ref_offset FSCK_ATTR((unused)), + void *priv_data) +{ + struct write_dir_struct *wd = (struct write_dir_struct *) priv_data; + blk_t blk; + char *dir; + + if (*block_nr == 0) + return 0; + if (blockcnt >= wd->outdir->num) { + e2fsck_read_bitmaps(wd->ctx); + blk = *block_nr; + ext2fs_unmark_block_bitmap(wd->ctx->block_found_map, blk); + ext2fs_block_alloc_stats(fs, blk, -1); + *block_nr = 0; + wd->cleared++; + return BLOCK_CHANGED; + } + if (blockcnt < 0) + return 0; + + dir = wd->outdir->buf + (blockcnt * fs->blocksize); + wd->err = ext2fs_write_dir_block(fs, *block_nr, dir); + if (wd->err) + return BLOCK_ABORT; + return 0; +} + +static errcode_t write_directory(e2fsck_t ctx, ext2_filsys fs, + struct out_dir *outdir, + ext2_ino_t ino, int compress) +{ + struct write_dir_struct wd; + errcode_t retval; + struct ext2_inode inode; + + retval = e2fsck_expand_directory(ctx, ino, -1, outdir->num); + if (retval) + return retval; + + wd.outdir = outdir; + wd.err = 0; + wd.ctx = ctx; + wd.cleared = 0; + + retval = ext2fs_block_iterate2(fs, ino, 0, 0, + write_dir_block, &wd); + if (retval) + return retval; + if (wd.err) + return wd.err; + + e2fsck_read_inode(ctx, ino, &inode, "rehash_dir"); + if (compress) + inode.i_flags &= ~EXT2_INDEX_FL; + else + inode.i_flags |= EXT2_INDEX_FL; + inode.i_size = outdir->num * fs->blocksize; + inode.i_blocks -= (fs->blocksize / 512) * wd.cleared; + e2fsck_write_inode(ctx, ino, &inode, "rehash_dir"); + + return 0; +} + +static errcode_t e2fsck_rehash_dir(e2fsck_t ctx, ext2_ino_t ino) +{ + ext2_filsys fs = ctx->fs; + errcode_t retval; + struct ext2_inode inode; + char *dir_buf = 0; + struct fill_dir_struct fd; + struct out_dir outdir; + + outdir.max = outdir.num = 0; + outdir.buf = 0; + outdir.hashes = 0; + e2fsck_read_inode(ctx, ino, &inode, "rehash_dir"); + + retval = ENOMEM; + fd.harray = 0; + dir_buf = malloc(inode.i_size); + if (!dir_buf) + goto errout; + + fd.max_array = inode.i_size / 32; + fd.num_array = 0; + fd.harray = malloc(fd.max_array * sizeof(struct hash_entry)); + if (!fd.harray) + goto errout; + + fd.ctx = ctx; + fd.buf = dir_buf; + fd.inode = &inode; + fd.err = 0; + fd.dir_size = 0; + fd.compress = 0; + if (!(fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX) || + (inode.i_size / fs->blocksize) < 2) + fd.compress = 1; + fd.parent = 0; + + /* Read in the entire directory into memory */ + retval = ext2fs_block_iterate2(fs, ino, 0, 0, + fill_dir_block, &fd); + if (fd.err) { + retval = fd.err; + goto errout; + } + + /* Sort the list */ +resort: + if (fd.compress) + qsort(fd.harray+2, fd.num_array-2, + sizeof(struct hash_entry), name_cmp); + else + qsort(fd.harray, fd.num_array, + sizeof(struct hash_entry), hash_cmp); + + /* + * Look for duplicates + */ + if (duplicate_search_and_fix(ctx, fs, ino, &fd)) + goto resort; + + if (ctx->options & E2F_OPT_NO) { + retval = 0; + goto errout; + } + + /* + * Copy the directory entries. In a htree directory these + * will become the leaf nodes. + */ + retval = copy_dir_entries(fs, &fd, &outdir); + if (retval) + goto errout; + + free(dir_buf); dir_buf = 0; + + if (!fd.compress) { + /* Calculate the interior nodes */ + retval = calculate_tree(fs, &outdir, ino, fd.parent); + if (retval) + goto errout; + } + + retval = write_directory(ctx, fs, &outdir, ino, fd.compress); + +errout: + free(dir_buf); + free(fd.harray); + + free_out_dir(&outdir); + return retval; +} + +void e2fsck_rehash_directories(e2fsck_t ctx) +{ + struct problem_context pctx; + struct dir_info *dir; + ext2_u32_iterate iter; + ext2_ino_t ino; + errcode_t retval; + int i, cur, max, all_dirs, dir_index, first = 1; + + all_dirs = ctx->options & E2F_OPT_COMPRESS_DIRS; + + if (!ctx->dirs_to_hash && !all_dirs) + return; + + e2fsck_get_lost_and_found(ctx, 0); + + clear_problem_context(&pctx); + + dir_index = ctx->fs->super->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX; + cur = 0; + if (all_dirs) { + i = 0; + max = e2fsck_get_num_dirinfo(ctx); + } else { + retval = ext2fs_u32_list_iterate_begin(ctx->dirs_to_hash, + &iter); + if (retval) { + pctx.errcode = retval; + fix_problem(ctx, PR_3A_OPTIMIZE_ITER, &pctx); + return; + } + max = ext2fs_u32_list_count(ctx->dirs_to_hash); + } + while (1) { + if (all_dirs) { + if ((dir = e2fsck_dir_info_iter(ctx, &i)) == 0) + break; + ino = dir->ino; + } else { + if (!ext2fs_u32_list_iterate(iter, &ino)) + break; + } + if (ino == ctx->lost_and_found) + continue; + pctx.dir = ino; + if (first) { + fix_problem(ctx, PR_3A_PASS_HEADER, &pctx); + first = 0; + } + pctx.errcode = e2fsck_rehash_dir(ctx, ino); + if (pctx.errcode) { + end_problem_latch(ctx, PR_LATCH_OPTIMIZE_DIR); + fix_problem(ctx, PR_3A_OPTIMIZE_DIR_ERR, &pctx); + } + if (ctx->progress && !ctx->progress_fd) + e2fsck_simple_progress(ctx, "Rebuilding directory", + 100.0 * (float) (++cur) / (float) max, ino); + } + end_problem_latch(ctx, PR_LATCH_OPTIMIZE_DIR); + if (!all_dirs) + ext2fs_u32_list_iterate_end(iter); + + ext2fs_u32_list_free(ctx->dirs_to_hash); + ctx->dirs_to_hash = 0; +} + +/* + * linux/fs/revoke.c + * + * Journal revoke routines for the generic filesystem journaling code; + * part of the ext2fs journaling system. + * + * Revoke is the mechanism used to prevent old log records for deleted + * metadata from being replayed on top of newer data using the same + * blocks. The revoke mechanism is used in two separate places: + * + * + Commit: during commit we write the entire list of the current + * transaction's revoked blocks to the journal + * + * + Recovery: during recovery we record the transaction ID of all + * revoked blocks. If there are multiple revoke records in the log + * for a single block, only the last one counts, and if there is a log + * entry for a block beyond the last revoke, then that log entry still + * gets replayed. + * + * We can get interactions between revokes and new log data within a + * single transaction: + * + * Block is revoked and then journaled: + * The desired end result is the journaling of the new block, so we + * cancel the revoke before the transaction commits. + * + * Block is journaled and then revoked: + * The revoke must take precedence over the write of the block, so we + * need either to cancel the journal entry or to write the revoke + * later in the log than the log block. In this case, we choose the + * latter: journaling a block cancels any revoke record for that block + * in the current transaction, so any revoke for that block in the + * transaction must have happened after the block was journaled and so + * the revoke must take precedence. + * + * Block is revoked and then written as data: + * The data write is allowed to succeed, but the revoke is _not_ + * cancelled. We still need to prevent old log records from + * overwriting the new data. We don't even need to clear the revoke + * bit here. + * + * Revoke information on buffers is a tri-state value: + * + * RevokeValid clear: no cached revoke status, need to look it up + * RevokeValid set, Revoked clear: + * buffer has not been revoked, and cancel_revoke + * need do nothing. + * RevokeValid set, Revoked set: + * buffer has been revoked. + */ + +static kmem_cache_t *revoke_record_cache; +static kmem_cache_t *revoke_table_cache; + +/* Each revoke record represents one single revoked block. During + journal replay, this involves recording the transaction ID of the + last transaction to revoke this block. */ + +struct jbd_revoke_record_s +{ + struct list_head hash; + tid_t sequence; /* Used for recovery only */ + unsigned long blocknr; +}; + + +/* The revoke table is just a simple hash table of revoke records. */ +struct jbd_revoke_table_s +{ + /* It is conceivable that we might want a larger hash table + * for recovery. Must be a power of two. */ + int hash_size; + int hash_shift; + struct list_head *hash_table; +}; + + +/* Utility functions to maintain the revoke table */ + +/* Borrowed from buffer.c: this is a tried and tested block hash function */ +static int hash(journal_t *journal, unsigned long block) +{ + struct jbd_revoke_table_s *table = journal->j_revoke; + int hash_shift = table->hash_shift; + + return ((block << (hash_shift - 6)) ^ + (block >> 13) ^ + (block << (hash_shift - 12))) & (table->hash_size - 1); +} + +static int insert_revoke_hash(journal_t *journal, unsigned long blocknr, + tid_t seq) +{ + struct list_head *hash_list; + struct jbd_revoke_record_s *record; + + record = kmem_cache_alloc(revoke_record_cache, GFP_NOFS); + if (!record) + goto oom; + + record->sequence = seq; + record->blocknr = blocknr; + hash_list = &journal->j_revoke->hash_table[hash(journal, blocknr)]; + list_add(&record->hash, hash_list); + return 0; + +oom: + return -ENOMEM; +} + +/* Find a revoke record in the journal's hash table. */ + +static struct jbd_revoke_record_s *find_revoke_record(journal_t *journal, + unsigned long blocknr) +{ + struct list_head *hash_list; + struct jbd_revoke_record_s *record; + + hash_list = &journal->j_revoke->hash_table[hash(journal, blocknr)]; + + record = (struct jbd_revoke_record_s *) hash_list->next; + while (&(record->hash) != hash_list) { + if (record->blocknr == blocknr) + return record; + record = (struct jbd_revoke_record_s *) record->hash.next; + } + return NULL; +} + +int journal_init_revoke_caches(void) +{ + revoke_record_cache = do_cache_create(sizeof(struct jbd_revoke_record_s)); + if (revoke_record_cache == 0) + return -ENOMEM; + + revoke_table_cache = do_cache_create(sizeof(struct jbd_revoke_table_s)); + if (revoke_table_cache == 0) { + do_cache_destroy(revoke_record_cache); + revoke_record_cache = NULL; + return -ENOMEM; + } + return 0; +} + +void journal_destroy_revoke_caches(void) +{ + do_cache_destroy(revoke_record_cache); + revoke_record_cache = 0; + do_cache_destroy(revoke_table_cache); + revoke_table_cache = 0; +} + +/* Initialise the revoke table for a given journal to a given size. */ + +int journal_init_revoke(journal_t *journal, int hash_size) +{ + int shift, tmp; + + journal->j_revoke = kmem_cache_alloc(revoke_table_cache, GFP_KERNEL); + if (!journal->j_revoke) + return -ENOMEM; + + /* Check that the hash_size is a power of two */ + journal->j_revoke->hash_size = hash_size; + + shift = 0; + tmp = hash_size; + while((tmp >>= 1UL) != 0UL) + shift++; + journal->j_revoke->hash_shift = shift; + + journal->j_revoke->hash_table = malloc(hash_size * sizeof(struct list_head)); + if (!journal->j_revoke->hash_table) { + free(journal->j_revoke); + journal->j_revoke = NULL; + return -ENOMEM; + } + + for (tmp = 0; tmp < hash_size; tmp++) + INIT_LIST_HEAD(&journal->j_revoke->hash_table[tmp]); + + return 0; +} + +/* Destoy a journal's revoke table. The table must already be empty! */ + +void journal_destroy_revoke(journal_t *journal) +{ + struct jbd_revoke_table_s *table; + struct list_head *hash_list; + int i; + + table = journal->j_revoke; + if (!table) + return; + + for (i=0; ihash_size; i++) { + hash_list = &table->hash_table[i]; + } + + free(table->hash_table); + free(table); + journal->j_revoke = NULL; +} + +/* + * Revoke support for recovery. + * + * Recovery needs to be able to: + * + * record all revoke records, including the tid of the latest instance + * of each revoke in the journal + * + * check whether a given block in a given transaction should be replayed + * (ie. has not been revoked by a revoke record in that or a subsequent + * transaction) + * + * empty the revoke table after recovery. + */ + +/* + * First, setting revoke records. We create a new revoke record for + * every block ever revoked in the log as we scan it for recovery, and + * we update the existing records if we find multiple revokes for a + * single block. + */ + +int journal_set_revoke(journal_t *journal, unsigned long blocknr, + tid_t sequence) +{ + struct jbd_revoke_record_s *record; + + record = find_revoke_record(journal, blocknr); + if (record) { + /* If we have multiple occurences, only record the + * latest sequence number in the hashed record */ + if (tid_gt(sequence, record->sequence)) + record->sequence = sequence; + return 0; + } + return insert_revoke_hash(journal, blocknr, sequence); +} + +/* + * Test revoke records. For a given block referenced in the log, has + * that block been revoked? A revoke record with a given transaction + * sequence number revokes all blocks in that transaction and earlier + * ones, but later transactions still need replayed. + */ + +int journal_test_revoke(journal_t *journal, unsigned long blocknr, + tid_t sequence) +{ + struct jbd_revoke_record_s *record; + + record = find_revoke_record(journal, blocknr); + if (!record) + return 0; + if (tid_gt(sequence, record->sequence)) + return 0; + return 1; +} + +/* + * Finally, once recovery is over, we need to clear the revoke table so + * that it can be reused by the running filesystem. + */ + +void journal_clear_revoke(journal_t *journal) +{ + int i; + struct list_head *hash_list; + struct jbd_revoke_record_s *record; + struct jbd_revoke_table_s *revoke_var; + + revoke_var = journal->j_revoke; + + for (i = 0; i < revoke_var->hash_size; i++) { + hash_list = &revoke_var->hash_table[i]; + while (!list_empty(hash_list)) { + record = (struct jbd_revoke_record_s*) hash_list->next; + list_del(&record->hash); + free(record); + } + } +} + +/* + * e2fsck.c - superblock checks + */ + +#define MIN_CHECK 1 +#define MAX_CHECK 2 + +static void check_super_value(e2fsck_t ctx, const char *descr, + unsigned long value, int flags, + unsigned long min_val, unsigned long max_val) +{ + struct problem_context pctx; + + if (((flags & MIN_CHECK) && (value < min_val)) || + ((flags & MAX_CHECK) && (value > max_val))) { + clear_problem_context(&pctx); + pctx.num = value; + pctx.str = descr; + fix_problem(ctx, PR_0_MISC_CORRUPT_SUPER, &pctx); + ctx->flags |= E2F_FLAG_ABORT; /* never get here! */ + } +} + +/* + * This routine may get stubbed out in special compilations of the + * e2fsck code.. + */ +#ifndef EXT2_SPECIAL_DEVICE_SIZE +static errcode_t e2fsck_get_device_size(e2fsck_t ctx) +{ + return (ext2fs_get_device_size(ctx->filesystem_name, + EXT2_BLOCK_SIZE(ctx->fs->super), + &ctx->num_blocks)); +} +#endif + +/* + * helper function to release an inode + */ +struct process_block_struct { + e2fsck_t ctx; + char *buf; + struct problem_context *pctx; + int truncating; + int truncate_offset; + e2_blkcnt_t truncate_block; + int truncated_blocks; + int abort; + errcode_t errcode; +}; + +static int release_inode_block(ext2_filsys fs, blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_blk FSCK_ATTR((unused)), + int ref_offset FSCK_ATTR((unused)), + void *priv_data) +{ + struct process_block_struct *pb; + e2fsck_t ctx; + struct problem_context *pctx; + blk_t blk = *block_nr; + int retval = 0; + + pb = (struct process_block_struct *) priv_data; + ctx = pb->ctx; + pctx = pb->pctx; + + pctx->blk = blk; + pctx->blkcount = blockcnt; + + if (HOLE_BLKADDR(blk)) + return 0; + + if ((blk < fs->super->s_first_data_block) || + (blk >= fs->super->s_blocks_count)) { + fix_problem(ctx, PR_0_ORPHAN_ILLEGAL_BLOCK_NUM, pctx); + return_abort: + pb->abort = 1; + return BLOCK_ABORT; + } + + if (!ext2fs_test_block_bitmap(fs->block_map, blk)) { + fix_problem(ctx, PR_0_ORPHAN_ALREADY_CLEARED_BLOCK, pctx); + goto return_abort; + } + + /* + * If we are deleting an orphan, then we leave the fields alone. + * If we are truncating an orphan, then update the inode fields + * and clean up any partial block data. + */ + if (pb->truncating) { + /* + * We only remove indirect blocks if they are + * completely empty. + */ + if (blockcnt < 0) { + int i, limit; + blk_t *bp; + + pb->errcode = io_channel_read_blk(fs->io, blk, 1, + pb->buf); + if (pb->errcode) + goto return_abort; + + limit = fs->blocksize >> 2; + for (i = 0, bp = (blk_t *) pb->buf; + i < limit; i++, bp++) + if (*bp) + return 0; + } + /* + * We don't remove direct blocks until we've reached + * the truncation block. + */ + if (blockcnt >= 0 && blockcnt < pb->truncate_block) + return 0; + /* + * If part of the last block needs truncating, we do + * it here. + */ + if ((blockcnt == pb->truncate_block) && pb->truncate_offset) { + pb->errcode = io_channel_read_blk(fs->io, blk, 1, + pb->buf); + if (pb->errcode) + goto return_abort; + memset(pb->buf + pb->truncate_offset, 0, + fs->blocksize - pb->truncate_offset); + pb->errcode = io_channel_write_blk(fs->io, blk, 1, + pb->buf); + if (pb->errcode) + goto return_abort; + } + pb->truncated_blocks++; + *block_nr = 0; + retval |= BLOCK_CHANGED; + } + + ext2fs_block_alloc_stats(fs, blk, -1); + return retval; +} + +/* + * This function releases an inode. Returns 1 if an inconsistency was + * found. If the inode has a link count, then it is being truncated and + * not deleted. + */ +static int release_inode_blocks(e2fsck_t ctx, ext2_ino_t ino, + struct ext2_inode *inode, char *block_buf, + struct problem_context *pctx) +{ + struct process_block_struct pb; + ext2_filsys fs = ctx->fs; + errcode_t retval; + __u32 count; + + if (!ext2fs_inode_has_valid_blocks(inode)) + return 0; + + pb.buf = block_buf + 3 * ctx->fs->blocksize; + pb.ctx = ctx; + pb.abort = 0; + pb.errcode = 0; + pb.pctx = pctx; + if (inode->i_links_count) { + pb.truncating = 1; + pb.truncate_block = (e2_blkcnt_t) + ((((long long)inode->i_size_high << 32) + + inode->i_size + fs->blocksize - 1) / + fs->blocksize); + pb.truncate_offset = inode->i_size % fs->blocksize; + } else { + pb.truncating = 0; + pb.truncate_block = 0; + pb.truncate_offset = 0; + } + pb.truncated_blocks = 0; + retval = ext2fs_block_iterate2(fs, ino, BLOCK_FLAG_DEPTH_TRAVERSE, + block_buf, release_inode_block, &pb); + if (retval) { + bb_error_msg(_("while calling ext2fs_block_iterate for inode %d"), + ino); + return 1; + } + if (pb.abort) + return 1; + + /* Refresh the inode since ext2fs_block_iterate may have changed it */ + e2fsck_read_inode(ctx, ino, inode, "release_inode_blocks"); + + if (pb.truncated_blocks) + inode->i_blocks -= pb.truncated_blocks * + (fs->blocksize / 512); + + if (inode->i_file_acl) { + retval = ext2fs_adjust_ea_refcount(fs, inode->i_file_acl, + block_buf, -1, &count); + if (retval == EXT2_ET_BAD_EA_BLOCK_NUM) { + retval = 0; + count = 1; + } + if (retval) { + bb_error_msg(_("while calling ext2fs_adjust_ea_refocunt for inode %d"), + ino); + return 1; + } + if (count == 0) + ext2fs_block_alloc_stats(fs, inode->i_file_acl, -1); + inode->i_file_acl = 0; + } + return 0; +} + +/* + * This function releases all of the orphan inodes. It returns 1 if + * it hit some error, and 0 on success. + */ +static int release_orphan_inodes(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + ext2_ino_t ino, next_ino; + struct ext2_inode inode; + struct problem_context pctx; + char *block_buf; + + if ((ino = fs->super->s_last_orphan) == 0) + return 0; + + /* + * Win or lose, we won't be using the head of the orphan inode + * list again. + */ + fs->super->s_last_orphan = 0; + ext2fs_mark_super_dirty(fs); + + /* + * If the filesystem contains errors, don't run the orphan + * list, since the orphan list can't be trusted; and we're + * going to be running a full e2fsck run anyway... + */ + if (fs->super->s_state & EXT2_ERROR_FS) + return 0; + + if ((ino < EXT2_FIRST_INODE(fs->super)) || + (ino > fs->super->s_inodes_count)) { + clear_problem_context(&pctx); + pctx.ino = ino; + fix_problem(ctx, PR_0_ORPHAN_ILLEGAL_HEAD_INODE, &pctx); + return 1; + } + + block_buf = (char *) e2fsck_allocate_memory(ctx, fs->blocksize * 4, + "block iterate buffer"); + e2fsck_read_bitmaps(ctx); + + while (ino) { + e2fsck_read_inode(ctx, ino, &inode, "release_orphan_inodes"); + clear_problem_context(&pctx); + pctx.ino = ino; + pctx.inode = &inode; + pctx.str = inode.i_links_count ? _("Truncating") : + _("Clearing"); + + fix_problem(ctx, PR_0_ORPHAN_CLEAR_INODE, &pctx); + + next_ino = inode.i_dtime; + if (next_ino && + ((next_ino < EXT2_FIRST_INODE(fs->super)) || + (next_ino > fs->super->s_inodes_count))) { + pctx.ino = next_ino; + fix_problem(ctx, PR_0_ORPHAN_ILLEGAL_INODE, &pctx); + goto return_abort; + } + + if (release_inode_blocks(ctx, ino, &inode, block_buf, &pctx)) + goto return_abort; + + if (!inode.i_links_count) { + ext2fs_inode_alloc_stats2(fs, ino, -1, + LINUX_S_ISDIR(inode.i_mode)); + inode.i_dtime = time(0); + } else { + inode.i_dtime = 0; + } + e2fsck_write_inode(ctx, ino, &inode, "delete_file"); + ino = next_ino; + } + ext2fs_free_mem(&block_buf); + return 0; +return_abort: + ext2fs_free_mem(&block_buf); + return 1; +} + +/* + * Check the resize inode to make sure it is sane. We check both for + * the case where on-line resizing is not enabled (in which case the + * resize inode should be cleared) as well as the case where on-line + * resizing is enabled. + */ +static void check_resize_inode(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + struct ext2_inode inode; + struct problem_context pctx; + int i, j, gdt_off, ind_off; + blk_t blk, pblk, expect; + __u32 *dind_buf = 0, *ind_buf; + errcode_t retval; + + clear_problem_context(&pctx); + + /* + * If the resize inode feature isn't set, then + * s_reserved_gdt_blocks must be zero. + */ + if (!(fs->super->s_feature_compat & + EXT2_FEATURE_COMPAT_RESIZE_INODE)) { + if (fs->super->s_reserved_gdt_blocks) { + pctx.num = fs->super->s_reserved_gdt_blocks; + if (fix_problem(ctx, PR_0_NONZERO_RESERVED_GDT_BLOCKS, + &pctx)) { + fs->super->s_reserved_gdt_blocks = 0; + ext2fs_mark_super_dirty(fs); + } + } + } + + /* Read the resize inode */ + pctx.ino = EXT2_RESIZE_INO; + retval = ext2fs_read_inode(fs, EXT2_RESIZE_INO, &inode); + if (retval) { + if (fs->super->s_feature_compat & + EXT2_FEATURE_COMPAT_RESIZE_INODE) + ctx->flags |= E2F_FLAG_RESIZE_INODE; + return; + } + + /* + * If the resize inode feature isn't set, check to make sure + * the resize inode is cleared; then we're done. + */ + if (!(fs->super->s_feature_compat & + EXT2_FEATURE_COMPAT_RESIZE_INODE)) { + for (i=0; i < EXT2_N_BLOCKS; i++) { + if (inode.i_block[i]) + break; + } + if ((i < EXT2_N_BLOCKS) && + fix_problem(ctx, PR_0_CLEAR_RESIZE_INODE, &pctx)) { + memset(&inode, 0, sizeof(inode)); + e2fsck_write_inode(ctx, EXT2_RESIZE_INO, &inode, + "clear_resize"); + } + return; + } + + /* + * The resize inode feature is enabled; check to make sure the + * only block in use is the double indirect block + */ + blk = inode.i_block[EXT2_DIND_BLOCK]; + for (i=0; i < EXT2_N_BLOCKS; i++) { + if (i != EXT2_DIND_BLOCK && inode.i_block[i]) + break; + } + if ((i < EXT2_N_BLOCKS) || !blk || !inode.i_links_count || + !(inode.i_mode & LINUX_S_IFREG) || + (blk < fs->super->s_first_data_block || + blk >= fs->super->s_blocks_count)) { + resize_inode_invalid: + if (fix_problem(ctx, PR_0_RESIZE_INODE_INVALID, &pctx)) { + memset(&inode, 0, sizeof(inode)); + e2fsck_write_inode(ctx, EXT2_RESIZE_INO, &inode, + "clear_resize"); + ctx->flags |= E2F_FLAG_RESIZE_INODE; + } + if (!(ctx->options & E2F_OPT_READONLY)) { + fs->super->s_state &= ~EXT2_VALID_FS; + ext2fs_mark_super_dirty(fs); + } + goto cleanup; + } + dind_buf = (__u32 *) e2fsck_allocate_memory(ctx, fs->blocksize * 2, + "resize dind buffer"); + ind_buf = (__u32 *) ((char *) dind_buf + fs->blocksize); + + retval = ext2fs_read_ind_block(fs, blk, dind_buf); + if (retval) + goto resize_inode_invalid; + + gdt_off = fs->desc_blocks; + pblk = fs->super->s_first_data_block + 1 + fs->desc_blocks; + for (i = 0; i < fs->super->s_reserved_gdt_blocks / 4; + i++, gdt_off++, pblk++) { + gdt_off %= fs->blocksize/4; + if (dind_buf[gdt_off] != pblk) + goto resize_inode_invalid; + retval = ext2fs_read_ind_block(fs, pblk, ind_buf); + if (retval) + goto resize_inode_invalid; + ind_off = 0; + for (j = 1; j < fs->group_desc_count; j++) { + if (!ext2fs_bg_has_super(fs, j)) + continue; + expect = pblk + (j * fs->super->s_blocks_per_group); + if (ind_buf[ind_off] != expect) + goto resize_inode_invalid; + ind_off++; + } + } + +cleanup: + ext2fs_free_mem(&dind_buf); + + } + +static void check_super_block(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + blk_t first_block, last_block; + struct ext2_super_block *sb = fs->super; + struct ext2_group_desc *gd; + blk_t blocks_per_group = fs->super->s_blocks_per_group; + blk_t bpg_max; + int inodes_per_block; + int ipg_max; + int inode_size; + dgrp_t i; + blk_t should_be; + struct problem_context pctx; + __u32 free_blocks = 0, free_inodes = 0; + + inodes_per_block = EXT2_INODES_PER_BLOCK(fs->super); + ipg_max = inodes_per_block * (blocks_per_group - 4); + if (ipg_max > EXT2_MAX_INODES_PER_GROUP(sb)) + ipg_max = EXT2_MAX_INODES_PER_GROUP(sb); + bpg_max = 8 * EXT2_BLOCK_SIZE(sb); + if (bpg_max > EXT2_MAX_BLOCKS_PER_GROUP(sb)) + bpg_max = EXT2_MAX_BLOCKS_PER_GROUP(sb); + + ctx->invalid_inode_bitmap_flag = (int *) e2fsck_allocate_memory(ctx, + sizeof(int) * fs->group_desc_count, "invalid_inode_bitmap"); + ctx->invalid_block_bitmap_flag = (int *) e2fsck_allocate_memory(ctx, + sizeof(int) * fs->group_desc_count, "invalid_block_bitmap"); + ctx->invalid_inode_table_flag = (int *) e2fsck_allocate_memory(ctx, + sizeof(int) * fs->group_desc_count, "invalid_inode_table"); + + clear_problem_context(&pctx); + + /* + * Verify the super block constants... + */ + check_super_value(ctx, "inodes_count", sb->s_inodes_count, + MIN_CHECK, 1, 0); + check_super_value(ctx, "blocks_count", sb->s_blocks_count, + MIN_CHECK, 1, 0); + check_super_value(ctx, "first_data_block", sb->s_first_data_block, + MAX_CHECK, 0, sb->s_blocks_count); + check_super_value(ctx, "log_block_size", sb->s_log_block_size, + MIN_CHECK | MAX_CHECK, 0, + EXT2_MAX_BLOCK_LOG_SIZE - EXT2_MIN_BLOCK_LOG_SIZE); + check_super_value(ctx, "log_frag_size", sb->s_log_frag_size, + MIN_CHECK | MAX_CHECK, 0, sb->s_log_block_size); + check_super_value(ctx, "frags_per_group", sb->s_frags_per_group, + MIN_CHECK | MAX_CHECK, sb->s_blocks_per_group, + bpg_max); + check_super_value(ctx, "blocks_per_group", sb->s_blocks_per_group, + MIN_CHECK | MAX_CHECK, 8, bpg_max); + check_super_value(ctx, "inodes_per_group", sb->s_inodes_per_group, + MIN_CHECK | MAX_CHECK, inodes_per_block, ipg_max); + check_super_value(ctx, "r_blocks_count", sb->s_r_blocks_count, + MAX_CHECK, 0, sb->s_blocks_count / 2); + check_super_value(ctx, "reserved_gdt_blocks", + sb->s_reserved_gdt_blocks, MAX_CHECK, 0, + fs->blocksize/4); + inode_size = EXT2_INODE_SIZE(sb); + check_super_value(ctx, "inode_size", + inode_size, MIN_CHECK | MAX_CHECK, + EXT2_GOOD_OLD_INODE_SIZE, fs->blocksize); + if (inode_size & (inode_size - 1)) { + pctx.num = inode_size; + pctx.str = "inode_size"; + fix_problem(ctx, PR_0_MISC_CORRUPT_SUPER, &pctx); + ctx->flags |= E2F_FLAG_ABORT; /* never get here! */ + return; + } + + if (!ctx->num_blocks) { + pctx.errcode = e2fsck_get_device_size(ctx); + if (pctx.errcode && pctx.errcode != EXT2_ET_UNIMPLEMENTED) { + fix_problem(ctx, PR_0_GETSIZE_ERROR, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + if ((pctx.errcode != EXT2_ET_UNIMPLEMENTED) && + (ctx->num_blocks < sb->s_blocks_count)) { + pctx.blk = sb->s_blocks_count; + pctx.blk2 = ctx->num_blocks; + if (fix_problem(ctx, PR_0_FS_SIZE_WRONG, &pctx)) { + ctx->flags |= E2F_FLAG_ABORT; + return; + } + } + } + + if (sb->s_log_block_size != (__u32) sb->s_log_frag_size) { + pctx.blk = EXT2_BLOCK_SIZE(sb); + pctx.blk2 = EXT2_FRAG_SIZE(sb); + fix_problem(ctx, PR_0_NO_FRAGMENTS, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + + should_be = sb->s_frags_per_group >> + (sb->s_log_block_size - sb->s_log_frag_size); + if (sb->s_blocks_per_group != should_be) { + pctx.blk = sb->s_blocks_per_group; + pctx.blk2 = should_be; + fix_problem(ctx, PR_0_BLOCKS_PER_GROUP, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + + should_be = (sb->s_log_block_size == 0) ? 1 : 0; + if (sb->s_first_data_block != should_be) { + pctx.blk = sb->s_first_data_block; + pctx.blk2 = should_be; + fix_problem(ctx, PR_0_FIRST_DATA_BLOCK, &pctx); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + + should_be = sb->s_inodes_per_group * fs->group_desc_count; + if (sb->s_inodes_count != should_be) { + pctx.ino = sb->s_inodes_count; + pctx.ino2 = should_be; + if (fix_problem(ctx, PR_0_INODE_COUNT_WRONG, &pctx)) { + sb->s_inodes_count = should_be; + ext2fs_mark_super_dirty(fs); + } + } + + /* + * Verify the group descriptors.... + */ + first_block = sb->s_first_data_block; + last_block = first_block + blocks_per_group; + + for (i = 0, gd=fs->group_desc; i < fs->group_desc_count; i++, gd++) { + pctx.group = i; + + if (i == fs->group_desc_count - 1) + last_block = sb->s_blocks_count; + if ((gd->bg_block_bitmap < first_block) || + (gd->bg_block_bitmap >= last_block)) { + pctx.blk = gd->bg_block_bitmap; + if (fix_problem(ctx, PR_0_BB_NOT_GROUP, &pctx)) + gd->bg_block_bitmap = 0; + } + if (gd->bg_block_bitmap == 0) { + ctx->invalid_block_bitmap_flag[i]++; + ctx->invalid_bitmaps++; + } + if ((gd->bg_inode_bitmap < first_block) || + (gd->bg_inode_bitmap >= last_block)) { + pctx.blk = gd->bg_inode_bitmap; + if (fix_problem(ctx, PR_0_IB_NOT_GROUP, &pctx)) + gd->bg_inode_bitmap = 0; + } + if (gd->bg_inode_bitmap == 0) { + ctx->invalid_inode_bitmap_flag[i]++; + ctx->invalid_bitmaps++; + } + if ((gd->bg_inode_table < first_block) || + ((gd->bg_inode_table + + fs->inode_blocks_per_group - 1) >= last_block)) { + pctx.blk = gd->bg_inode_table; + if (fix_problem(ctx, PR_0_ITABLE_NOT_GROUP, &pctx)) + gd->bg_inode_table = 0; + } + if (gd->bg_inode_table == 0) { + ctx->invalid_inode_table_flag[i]++; + ctx->invalid_bitmaps++; + } + free_blocks += gd->bg_free_blocks_count; + free_inodes += gd->bg_free_inodes_count; + first_block += sb->s_blocks_per_group; + last_block += sb->s_blocks_per_group; + + if ((gd->bg_free_blocks_count > sb->s_blocks_per_group) || + (gd->bg_free_inodes_count > sb->s_inodes_per_group) || + (gd->bg_used_dirs_count > sb->s_inodes_per_group)) + ext2fs_unmark_valid(fs); + + } + + /* + * Update the global counts from the block group counts. This + * is needed for an experimental patch which eliminates + * locking the entire filesystem when allocating blocks or + * inodes; if the filesystem is not unmounted cleanly, the + * global counts may not be accurate. + */ + if ((free_blocks != sb->s_free_blocks_count) || + (free_inodes != sb->s_free_inodes_count)) { + if (ctx->options & E2F_OPT_READONLY) + ext2fs_unmark_valid(fs); + else { + sb->s_free_blocks_count = free_blocks; + sb->s_free_inodes_count = free_inodes; + ext2fs_mark_super_dirty(fs); + } + } + + if ((sb->s_free_blocks_count > sb->s_blocks_count) || + (sb->s_free_inodes_count > sb->s_inodes_count)) + ext2fs_unmark_valid(fs); + + + /* + * If we have invalid bitmaps, set the error state of the + * filesystem. + */ + if (ctx->invalid_bitmaps && !(ctx->options & E2F_OPT_READONLY)) { + sb->s_state &= ~EXT2_VALID_FS; + ext2fs_mark_super_dirty(fs); + } + + clear_problem_context(&pctx); + + /* + * If the UUID field isn't assigned, assign it. + */ + if (!(ctx->options & E2F_OPT_READONLY) && uuid_is_null(sb->s_uuid)) { + if (fix_problem(ctx, PR_0_ADD_UUID, &pctx)) { + uuid_generate(sb->s_uuid); + ext2fs_mark_super_dirty(fs); + fs->flags &= ~EXT2_FLAG_MASTER_SB_ONLY; + } + } + + /* FIXME - HURD support? + * For the Hurd, check to see if the filetype option is set, + * since it doesn't support it. + */ + if (!(ctx->options & E2F_OPT_READONLY) && + fs->super->s_creator_os == EXT2_OS_HURD && + (fs->super->s_feature_incompat & + EXT2_FEATURE_INCOMPAT_FILETYPE)) { + if (fix_problem(ctx, PR_0_HURD_CLEAR_FILETYPE, &pctx)) { + fs->super->s_feature_incompat &= + ~EXT2_FEATURE_INCOMPAT_FILETYPE; + ext2fs_mark_super_dirty(fs); + + } + } + + /* + * If we have any of the compatibility flags set, we need to have a + * revision 1 filesystem. Most kernels will not check the flags on + * a rev 0 filesystem and we may have corruption issues because of + * the incompatible changes to the filesystem. + */ + if (!(ctx->options & E2F_OPT_READONLY) && + fs->super->s_rev_level == EXT2_GOOD_OLD_REV && + (fs->super->s_feature_compat || + fs->super->s_feature_ro_compat || + fs->super->s_feature_incompat) && + fix_problem(ctx, PR_0_FS_REV_LEVEL, &pctx)) { + ext2fs_update_dynamic_rev(fs); + ext2fs_mark_super_dirty(fs); + } + + check_resize_inode(ctx); + + /* + * Clean up any orphan inodes, if present. + */ + if (!(ctx->options & E2F_OPT_READONLY) && release_orphan_inodes(ctx)) { + fs->super->s_state &= ~EXT2_VALID_FS; + ext2fs_mark_super_dirty(fs); + } + + /* + * Move the ext3 journal file, if necessary. + */ + e2fsck_move_ext3_journal(ctx); + return; +} + +/* + * swapfs.c --- byte-swap an ext2 filesystem + */ + +#ifdef ENABLE_SWAPFS + +struct swap_block_struct { + ext2_ino_t ino; + int isdir; + errcode_t errcode; + char *dir_buf; + struct ext2_inode *inode; +}; + +/* + * This is a helper function for block_iterate. We mark all of the + * indirect and direct blocks as changed, so that block_iterate will + * write them out. + */ +static int swap_block(ext2_filsys fs, blk_t *block_nr, int blockcnt, + void *priv_data) +{ + errcode_t retval; + + struct swap_block_struct *sb = (struct swap_block_struct *) priv_data; + + if (sb->isdir && (blockcnt >= 0) && *block_nr) { + retval = ext2fs_read_dir_block(fs, *block_nr, sb->dir_buf); + if (retval) { + sb->errcode = retval; + return BLOCK_ABORT; + } + retval = ext2fs_write_dir_block(fs, *block_nr, sb->dir_buf); + if (retval) { + sb->errcode = retval; + return BLOCK_ABORT; + } + } + if (blockcnt >= 0) { + if (blockcnt < EXT2_NDIR_BLOCKS) + return 0; + return BLOCK_CHANGED; + } + if (blockcnt == BLOCK_COUNT_IND) { + if (*block_nr == sb->inode->i_block[EXT2_IND_BLOCK]) + return 0; + return BLOCK_CHANGED; + } + if (blockcnt == BLOCK_COUNT_DIND) { + if (*block_nr == sb->inode->i_block[EXT2_DIND_BLOCK]) + return 0; + return BLOCK_CHANGED; + } + if (blockcnt == BLOCK_COUNT_TIND) { + if (*block_nr == sb->inode->i_block[EXT2_TIND_BLOCK]) + return 0; + return BLOCK_CHANGED; + } + return BLOCK_CHANGED; +} + +/* + * This function is responsible for byte-swapping all of the indirect, + * block pointers. It is also responsible for byte-swapping directories. + */ +static void swap_inode_blocks(e2fsck_t ctx, ext2_ino_t ino, char *block_buf, + struct ext2_inode *inode) +{ + errcode_t retval; + struct swap_block_struct sb; + + sb.ino = ino; + sb.inode = inode; + sb.dir_buf = block_buf + ctx->fs->blocksize*3; + sb.errcode = 0; + sb.isdir = 0; + if (LINUX_S_ISDIR(inode->i_mode)) + sb.isdir = 1; + + retval = ext2fs_block_iterate(ctx->fs, ino, 0, block_buf, + swap_block, &sb); + if (retval) { + bb_error_msg(_("while calling ext2fs_block_iterate")); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + if (sb.errcode) { + bb_error_msg(_("while calling iterator function")); + ctx->flags |= E2F_FLAG_ABORT; + return; + } +} + +static void swap_inodes(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + dgrp_t group; + unsigned int i; + ext2_ino_t ino = 1; + char *buf, *block_buf; + errcode_t retval; + struct ext2_inode * inode; + + e2fsck_use_inode_shortcuts(ctx, 1); + + retval = ext2fs_get_mem(fs->blocksize * fs->inode_blocks_per_group, + &buf); + if (retval) { + bb_error_msg(_("while allocating inode buffer")); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + block_buf = (char *) e2fsck_allocate_memory(ctx, fs->blocksize * 4, + "block interate buffer"); + for (group = 0; group < fs->group_desc_count; group++) { + retval = io_channel_read_blk(fs->io, + fs->group_desc[group].bg_inode_table, + fs->inode_blocks_per_group, buf); + if (retval) { + bb_error_msg(_("while reading inode table (group %d)"), + group); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + inode = (struct ext2_inode *) buf; + for (i=0; i < fs->super->s_inodes_per_group; + i++, ino++, inode++) { + ctx->stashed_ino = ino; + ctx->stashed_inode = inode; + + if (fs->flags & EXT2_FLAG_SWAP_BYTES_READ) + ext2fs_swap_inode(fs, inode, inode, 0); + + /* + * Skip deleted files. + */ + if (inode->i_links_count == 0) + continue; + + if (LINUX_S_ISDIR(inode->i_mode) || + ((inode->i_block[EXT2_IND_BLOCK] || + inode->i_block[EXT2_DIND_BLOCK] || + inode->i_block[EXT2_TIND_BLOCK]) && + ext2fs_inode_has_valid_blocks(inode))) + swap_inode_blocks(ctx, ino, block_buf, inode); + + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + + if (fs->flags & EXT2_FLAG_SWAP_BYTES_WRITE) + ext2fs_swap_inode(fs, inode, inode, 1); + } + retval = io_channel_write_blk(fs->io, + fs->group_desc[group].bg_inode_table, + fs->inode_blocks_per_group, buf); + if (retval) { + bb_error_msg(_("while writing inode table (group %d)"), + group); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + } + ext2fs_free_mem(&buf); + ext2fs_free_mem(&block_buf); + e2fsck_use_inode_shortcuts(ctx, 0); + ext2fs_flush_icache(fs); +} + +#if defined(__powerpc__) && BB_BIG_ENDIAN +/* + * On the PowerPC, the big-endian variant of the ext2 filesystem + * has its bitmaps stored as 32-bit words with bit 0 as the LSB + * of each word. Thus a bitmap with only bit 0 set would be, as + * a string of bytes, 00 00 00 01 00 ... + * To cope with this, we byte-reverse each word of a bitmap if + * we have a big-endian filesystem, that is, if we are *not* + * byte-swapping other word-sized numbers. + */ +#define EXT2_BIG_ENDIAN_BITMAPS +#endif + +#ifdef EXT2_BIG_ENDIAN_BITMAPS +static void ext2fs_swap_bitmap(ext2fs_generic_bitmap bmap) +{ + __u32 *p = (__u32 *) bmap->bitmap; + int n, nbytes = (bmap->end - bmap->start + 7) / 8; + + for (n = nbytes / sizeof(__u32); n > 0; --n, ++p) + *p = ext2fs_swab32(*p); +} +#endif + + +#ifdef ENABLE_SWAPFS +static void swap_filesys(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + if (!(ctx->options & E2F_OPT_PREEN)) + printf(_("Pass 0: Doing byte-swap of filesystem\n")); + + /* Byte swap */ + + if (fs->super->s_mnt_count) { + fprintf(stderr, _("%s: the filesystem must be freshly " + "checked using fsck\n" + "and not mounted before trying to " + "byte-swap it.\n"), ctx->device_name); + ctx->flags |= E2F_FLAG_ABORT; + return; + } + if (fs->flags & EXT2_FLAG_SWAP_BYTES) { + fs->flags &= ~(EXT2_FLAG_SWAP_BYTES| + EXT2_FLAG_SWAP_BYTES_WRITE); + fs->flags |= EXT2_FLAG_SWAP_BYTES_READ; + } else { + fs->flags &= ~EXT2_FLAG_SWAP_BYTES_READ; + fs->flags |= EXT2_FLAG_SWAP_BYTES_WRITE; + } + swap_inodes(ctx); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + return; + if (fs->flags & EXT2_FLAG_SWAP_BYTES_WRITE) + fs->flags |= EXT2_FLAG_SWAP_BYTES; + fs->flags &= ~(EXT2_FLAG_SWAP_BYTES_READ| + EXT2_FLAG_SWAP_BYTES_WRITE); + +#ifdef EXT2_BIG_ENDIAN_BITMAPS + e2fsck_read_bitmaps(ctx); + ext2fs_swap_bitmap(fs->inode_map); + ext2fs_swap_bitmap(fs->block_map); + fs->flags |= EXT2_FLAG_BB_DIRTY | EXT2_FLAG_IB_DIRTY; +#endif + fs->flags &= ~EXT2_FLAG_MASTER_SB_ONLY; + ext2fs_flush(fs); + fs->flags |= EXT2_FLAG_MASTER_SB_ONLY; +} +#endif /* ENABLE_SWAPFS */ + +#endif + +/* + * util.c --- miscellaneous utilities + */ + + +void *e2fsck_allocate_memory(e2fsck_t ctx, unsigned int size, + const char *description) +{ + void *ret; + char buf[256]; + + ret = malloc(size); + if (!ret) { + sprintf(buf, "Can't allocate %s\n", description); + bb_error_msg_and_die(buf); + } + memset(ret, 0, size); + return ret; +} + +static char *string_copy(const char *str, int len) +{ + char *ret; + + if (!str) + return NULL; + if (!len) + len = strlen(str); + ret = malloc(len+1); + if (ret) { + strncpy(ret, str, len); + ret[len] = 0; + } + return ret; +} + +#ifndef HAVE_CONIO_H +static int read_a_char(void) +{ + char c; + int r; + int fail = 0; + + while(1) { + if (e2fsck_global_ctx && + (e2fsck_global_ctx->flags & E2F_FLAG_CANCEL)) { + return 3; + } + r = read(0, &c, 1); + if (r == 1) + return c; + if (fail++ > 100) + break; + } + return EOF; +} +#endif + +static int ask_yn(const char * string, int def) +{ + int c; + const char *defstr; + static const char short_yes[] = "yY"; + static const char short_no[] = "nN"; + +#ifdef HAVE_TERMIOS_H + struct termios termios, tmp; + + tcgetattr (0, &termios); + tmp = termios; + tmp.c_lflag &= ~(ICANON | ECHO); + tmp.c_cc[VMIN] = 1; + tmp.c_cc[VTIME] = 0; + tcsetattr (0, TCSANOW, &tmp); +#endif + + if (def == 1) + defstr = ""; + else if (def == 0) + defstr = ""; + else + defstr = " (y/n)"; + printf("%s%s? ", string, defstr); + while (1) { + fflush (stdout); + if ((c = read_a_char()) == EOF) + break; + if (c == 3) { +#ifdef HAVE_TERMIOS_H + tcsetattr (0, TCSANOW, &termios); +#endif + if (e2fsck_global_ctx && + e2fsck_global_ctx->flags & E2F_FLAG_SETJMP_OK) { + puts("\n"); + longjmp(e2fsck_global_ctx->abort_loc, 1); + } + puts(_("cancelled!\n")); + return 0; + } + if (strchr(short_yes, (char) c)) { + def = 1; + break; + } + else if (strchr(short_no, (char) c)) { + def = 0; + break; + } + else if ((c == ' ' || c == '\n') && (def != -1)) + break; + } + if (def) + puts("yes\n"); + else + puts ("no\n"); +#ifdef HAVE_TERMIOS_H + tcsetattr (0, TCSANOW, &termios); +#endif + return def; +} + +int ask (e2fsck_t ctx, const char * string, int def) +{ + if (ctx->options & E2F_OPT_NO) { + printf(_("%s? no\n\n"), string); + return 0; + } + if (ctx->options & E2F_OPT_YES) { + printf(_("%s? yes\n\n"), string); + return 1; + } + if (ctx->options & E2F_OPT_PREEN) { + printf("%s? %s\n\n", string, def ? _("yes") : _("no")); + return def; + } + return ask_yn(string, def); +} + +void e2fsck_read_bitmaps(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + errcode_t retval; + + if (ctx->invalid_bitmaps) { + bb_error_msg(_("e2fsck_read_bitmaps: illegal bitmap block(s) for %s"), + ctx->device_name); + bb_error_msg_and_die(0); + } + + ehandler_operation(_("reading inode and block bitmaps")); + retval = ext2fs_read_bitmaps(fs); + ehandler_operation(0); + if (retval) { + bb_error_msg(_("while retrying to read bitmaps for %s"), + ctx->device_name); + bb_error_msg_and_die(0); + } +} + +static void e2fsck_write_bitmaps(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + errcode_t retval; + + if (ext2fs_test_bb_dirty(fs)) { + ehandler_operation(_("writing block bitmaps")); + retval = ext2fs_write_block_bitmap(fs); + ehandler_operation(0); + if (retval) { + bb_error_msg(_("while retrying to write block bitmaps for %s"), + ctx->device_name); + bb_error_msg_and_die(0); + } + } + + if (ext2fs_test_ib_dirty(fs)) { + ehandler_operation(_("writing inode bitmaps")); + retval = ext2fs_write_inode_bitmap(fs); + ehandler_operation(0); + if (retval) { + bb_error_msg(_("while retrying to write inode bitmaps for %s"), + ctx->device_name); + bb_error_msg_and_die(0); + } + } +} + +void preenhalt(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + + if (!(ctx->options & E2F_OPT_PREEN)) + return; + fprintf(stderr, _("\n\n%s: UNEXPECTED INCONSISTENCY; " + "RUN fsck MANUALLY.\n\t(i.e., without -a or -p options)\n"), + ctx->device_name); + if (fs != NULL) { + fs->super->s_state |= EXT2_ERROR_FS; + ext2fs_mark_super_dirty(fs); + ext2fs_close(fs); + } + exit(EXIT_UNCORRECTED); +} + +void e2fsck_read_inode(e2fsck_t ctx, unsigned long ino, + struct ext2_inode * inode, const char *proc) +{ + int retval; + + retval = ext2fs_read_inode(ctx->fs, ino, inode); + if (retval) { + bb_error_msg(_("while reading inode %ld in %s"), ino, proc); + bb_error_msg_and_die(0); + } +} + +extern void e2fsck_write_inode_full(e2fsck_t ctx, unsigned long ino, + struct ext2_inode * inode, int bufsize, + const char *proc) +{ + int retval; + + retval = ext2fs_write_inode_full(ctx->fs, ino, inode, bufsize); + if (retval) { + bb_error_msg(_("while writing inode %ld in %s"), ino, proc); + bb_error_msg_and_die(0); + } +} + +extern void e2fsck_write_inode(e2fsck_t ctx, unsigned long ino, + struct ext2_inode * inode, const char *proc) +{ + int retval; + + retval = ext2fs_write_inode(ctx->fs, ino, inode); + if (retval) { + bb_error_msg(_("while writing inode %ld in %s"), ino, proc); + bb_error_msg_and_die(0); + } +} + +blk_t get_backup_sb(e2fsck_t ctx, ext2_filsys fs, const char *name, + io_manager manager) +{ + struct ext2_super_block *sb; + io_channel io = NULL; + void *buf = NULL; + int blocksize; + blk_t superblock, ret_sb = 8193; + + if (fs && fs->super) { + ret_sb = (fs->super->s_blocks_per_group + + fs->super->s_first_data_block); + if (ctx) { + ctx->superblock = ret_sb; + ctx->blocksize = fs->blocksize; + } + return ret_sb; + } + + if (ctx) { + if (ctx->blocksize) { + ret_sb = ctx->blocksize * 8; + if (ctx->blocksize == 1024) + ret_sb++; + ctx->superblock = ret_sb; + return ret_sb; + } + ctx->superblock = ret_sb; + ctx->blocksize = 1024; + } + + if (!name || !manager) + goto cleanup; + + if (manager->open(name, 0, &io) != 0) + goto cleanup; + + if (ext2fs_get_mem(SUPERBLOCK_SIZE, &buf)) + goto cleanup; + sb = (struct ext2_super_block *) buf; + + for (blocksize = EXT2_MIN_BLOCK_SIZE; + blocksize <= EXT2_MAX_BLOCK_SIZE ; blocksize *= 2) { + superblock = blocksize*8; + if (blocksize == 1024) + superblock++; + io_channel_set_blksize(io, blocksize); + if (io_channel_read_blk(io, superblock, + -SUPERBLOCK_SIZE, buf)) + continue; +#if BB_BIG_ENDIAN + if (sb->s_magic == ext2fs_swab16(EXT2_SUPER_MAGIC)) + ext2fs_swap_super(sb); +#endif + if (sb->s_magic == EXT2_SUPER_MAGIC) { + ret_sb = superblock; + if (ctx) { + ctx->superblock = superblock; + ctx->blocksize = blocksize; + } + break; + } + } + +cleanup: + if (io) + io_channel_close(io); + ext2fs_free_mem(&buf); + return ret_sb; +} + + +/* + * This function runs through the e2fsck passes and calls them all, + * returning restart, abort, or cancel as necessary... + */ +typedef void (*pass_t)(e2fsck_t ctx); + +static const pass_t e2fsck_passes[] = { + e2fsck_pass1, e2fsck_pass2, e2fsck_pass3, e2fsck_pass4, + e2fsck_pass5, 0 }; + +#define E2F_FLAG_RUN_RETURN (E2F_FLAG_SIGNAL_MASK|E2F_FLAG_RESTART) + +static int e2fsck_run(e2fsck_t ctx) +{ + int i; + pass_t e2fsck_pass; + + if (setjmp(ctx->abort_loc)) { + ctx->flags &= ~E2F_FLAG_SETJMP_OK; + return (ctx->flags & E2F_FLAG_RUN_RETURN); + } + ctx->flags |= E2F_FLAG_SETJMP_OK; + + for (i=0; (e2fsck_pass = e2fsck_passes[i]); i++) { + if (ctx->flags & E2F_FLAG_RUN_RETURN) + break; + e2fsck_pass(ctx); + if (ctx->progress) + (void) (ctx->progress)(ctx, 0, 0, 0); + } + ctx->flags &= ~E2F_FLAG_SETJMP_OK; + + if (ctx->flags & E2F_FLAG_RUN_RETURN) + return (ctx->flags & E2F_FLAG_RUN_RETURN); + return 0; +} + + +/* + * unix.c - The unix-specific code for e2fsck + */ + + +/* Command line options */ +static int swapfs; +#ifdef ENABLE_SWAPFS +static int normalize_swapfs; +#endif +static int cflag; /* check disk */ +static int show_version_only; +static int verbose; + +#define P_E2(singular, plural, n) n, ((n) == 1 ? singular : plural) + +static void show_stats(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + int inodes, inodes_used, blocks, blocks_used; + int dir_links; + int num_files, num_links; + int frag_percent; + + dir_links = 2 * ctx->fs_directory_count - 1; + num_files = ctx->fs_total_count - dir_links; + num_links = ctx->fs_links_count - dir_links; + inodes = fs->super->s_inodes_count; + inodes_used = (fs->super->s_inodes_count - + fs->super->s_free_inodes_count); + blocks = fs->super->s_blocks_count; + blocks_used = (fs->super->s_blocks_count - + fs->super->s_free_blocks_count); + + frag_percent = (10000 * ctx->fs_fragmented) / inodes_used; + frag_percent = (frag_percent + 5) / 10; + + if (!verbose) { + printf("%s: %d/%d files (%0d.%d%% non-contiguous), %d/%d blocks\n", + ctx->device_name, inodes_used, inodes, + frag_percent / 10, frag_percent % 10, + blocks_used, blocks); + return; + } + printf("\n%8d inode%s used (%d%%)\n", P_E2("", "s", inodes_used), + 100 * inodes_used / inodes); + printf("%8d non-contiguous inode%s (%0d.%d%%)\n", + P_E2("", "s", ctx->fs_fragmented), + frag_percent / 10, frag_percent % 10); + printf(_(" # of inodes with ind/dind/tind blocks: %d/%d/%d\n"), + ctx->fs_ind_count, ctx->fs_dind_count, ctx->fs_tind_count); + printf("%8d block%s used (%d%%)\n", P_E2("", "s", blocks_used), + (int) ((long long) 100 * blocks_used / blocks)); + printf("%8d large file%s\n", P_E2("", "s", ctx->large_files)); + printf("\n%8d regular file%s\n", P_E2("", "s", ctx->fs_regular_count)); + printf("%8d director%s\n", P_E2("y", "ies", ctx->fs_directory_count)); + printf("%8d character device file%s\n", P_E2("", "s", ctx->fs_chardev_count)); + printf("%8d block device file%s\n", P_E2("", "s", ctx->fs_blockdev_count)); + printf("%8d fifo%s\n", P_E2("", "s", ctx->fs_fifo_count)); + printf("%8d link%s\n", P_E2("", "s", ctx->fs_links_count - dir_links)); + printf("%8d symbolic link%s", P_E2("", "s", ctx->fs_symlinks_count)); + printf(" (%d fast symbolic link%s)\n", P_E2("", "s", ctx->fs_fast_symlinks_count)); + printf("%8d socket%s--------\n\n", P_E2("", "s", ctx->fs_sockets_count)); + printf("%8d file%s\n", P_E2("", "s", ctx->fs_total_count - dir_links)); +} + +static void check_mount(e2fsck_t ctx) +{ + errcode_t retval; + int cont; + + retval = ext2fs_check_if_mounted(ctx->filesystem_name, + &ctx->mount_flags); + if (retval) { + bb_error_msg(_("while determining whether %s is mounted."), + ctx->filesystem_name); + return; + } + + /* + * If the filesystem isn't mounted, or it's the root filesystem + * and it's mounted read-only, then everything's fine. + */ + if ((!(ctx->mount_flags & EXT2_MF_MOUNTED)) || + ((ctx->mount_flags & EXT2_MF_ISROOT) && + (ctx->mount_flags & EXT2_MF_READONLY))) + return; + + if (ctx->options & E2F_OPT_READONLY) { + printf(_("Warning! %s is mounted.\n"), ctx->filesystem_name); + return; + } + + printf(_("%s is mounted. "), ctx->filesystem_name); + if (!ctx->interactive) + bb_error_msg_and_die(_("Cannot continue, aborting.")); + printf(_("\n\n\007\007\007\007WARNING!!! " + "Running e2fsck on a mounted filesystem may cause\n" + "SEVERE filesystem damage.\007\007\007\n\n")); + cont = ask_yn(_("Do you really want to continue"), -1); + if (!cont) { + printf(_("check aborted.\n")); + exit (0); + } + return; +} + +static int is_on_batt(void) +{ + FILE *f; + DIR *d; + char tmp[80], tmp2[80], fname[80]; + unsigned int acflag; + struct dirent* de; + + f = fopen("/proc/apm", "r"); + if (f) { + if (fscanf(f, "%s %s %s %x", tmp, tmp, tmp, &acflag) != 4) + acflag = 1; + fclose(f); + return (acflag != 1); + } + d = opendir("/proc/acpi/ac_adapter"); + if (d) { + while ((de=readdir(d)) != NULL) { + if (!strncmp(".", de->d_name, 1)) + continue; + snprintf(fname, 80, "/proc/acpi/ac_adapter/%s/state", + de->d_name); + f = fopen(fname, "r"); + if (!f) + continue; + if (fscanf(f, "%s %s", tmp2, tmp) != 2) + tmp[0] = 0; + fclose(f); + if (strncmp(tmp, "off-line", 8) == 0) { + closedir(d); + return 1; + } + } + closedir(d); + } + return 0; +} + +/* + * This routine checks to see if a filesystem can be skipped; if so, + * it will exit with EXIT_OK. Under some conditions it will print a + * message explaining why a check is being forced. + */ +static void check_if_skip(e2fsck_t ctx) +{ + ext2_filsys fs = ctx->fs; + const char *reason = NULL; + unsigned int reason_arg = 0; + long next_check; + int batt = is_on_batt(); + time_t now = time(0); + + if ((ctx->options & E2F_OPT_FORCE) || cflag || swapfs) + return; + + if ((fs->super->s_state & EXT2_ERROR_FS) || + !ext2fs_test_valid(fs)) + reason = _(" contains a file system with errors"); + else if ((fs->super->s_state & EXT2_VALID_FS) == 0) + reason = _(" was not cleanly unmounted"); + else if ((fs->super->s_max_mnt_count > 0) && + (fs->super->s_mnt_count >= + (unsigned) fs->super->s_max_mnt_count)) { + reason = _(" has been mounted %u times without being checked"); + reason_arg = fs->super->s_mnt_count; + if (batt && (fs->super->s_mnt_count < + (unsigned) fs->super->s_max_mnt_count*2)) + reason = 0; + } else if (fs->super->s_checkinterval && + ((now - fs->super->s_lastcheck) >= + fs->super->s_checkinterval)) { + reason = _(" has gone %u days without being checked"); + reason_arg = (now - fs->super->s_lastcheck)/(3600*24); + if (batt && ((now - fs->super->s_lastcheck) < + fs->super->s_checkinterval*2)) + reason = 0; + } + if (reason) { + fputs(ctx->device_name, stdout); + printf(reason, reason_arg); + fputs(_(", check forced.\n"), stdout); + return; + } + printf(_("%s: clean, %d/%d files, %d/%d blocks"), ctx->device_name, + fs->super->s_inodes_count - fs->super->s_free_inodes_count, + fs->super->s_inodes_count, + fs->super->s_blocks_count - fs->super->s_free_blocks_count, + fs->super->s_blocks_count); + next_check = 100000; + if (fs->super->s_max_mnt_count > 0) { + next_check = fs->super->s_max_mnt_count - fs->super->s_mnt_count; + if (next_check <= 0) + next_check = 1; + } + if (fs->super->s_checkinterval && + ((now - fs->super->s_lastcheck) >= fs->super->s_checkinterval)) + next_check = 1; + if (next_check <= 5) { + if (next_check == 1) + fputs(_(" (check after next mount)"), stdout); + else + printf(_(" (check in %ld mounts)"), next_check); + } + fputc('\n', stdout); + ext2fs_close(fs); + ctx->fs = NULL; + e2fsck_free_context(ctx); + exit(EXIT_OK); +} + +/* + * For completion notice + */ +struct percent_tbl { + int max_pass; + int table[32]; +}; +static const struct percent_tbl e2fsck_tbl = { + 5, { 0, 70, 90, 92, 95, 100 } +}; + +static char bar[128], spaces[128]; + +static float calc_percent(const struct percent_tbl *tbl, int pass, int curr, + int max) +{ + float percent; + + if (pass <= 0) + return 0.0; + if (pass > tbl->max_pass || max == 0) + return 100.0; + percent = ((float) curr) / ((float) max); + return ((percent * (tbl->table[pass] - tbl->table[pass-1])) + + tbl->table[pass-1]); +} + +void e2fsck_clear_progbar(e2fsck_t ctx) +{ + if (!(ctx->flags & E2F_FLAG_PROG_BAR)) + return; + + printf("%s%s\r%s", ctx->start_meta, spaces + (sizeof(spaces) - 80), + ctx->stop_meta); + fflush(stdout); + ctx->flags &= ~E2F_FLAG_PROG_BAR; +} + +int e2fsck_simple_progress(e2fsck_t ctx, const char *label, float percent, + unsigned int dpynum) +{ + static const char spinner[] = "\\|/-"; + int i; + unsigned int tick; + struct timeval tv; + int dpywidth; + int fixed_percent; + + if (ctx->flags & E2F_FLAG_PROG_SUPPRESS) + return 0; + + /* + * Calculate the new progress position. If the + * percentage hasn't changed, then we skip out right + * away. + */ + fixed_percent = (int) ((10 * percent) + 0.5); + if (ctx->progress_last_percent == fixed_percent) + return 0; + ctx->progress_last_percent = fixed_percent; + + /* + * If we've already updated the spinner once within + * the last 1/8th of a second, no point doing it + * again. + */ + gettimeofday(&tv, NULL); + tick = (tv.tv_sec << 3) + (tv.tv_usec / (1000000 / 8)); + if ((tick == ctx->progress_last_time) && + (fixed_percent != 0) && (fixed_percent != 1000)) + return 0; + ctx->progress_last_time = tick; + + /* + * Advance the spinner, and note that the progress bar + * will be on the screen + */ + ctx->progress_pos = (ctx->progress_pos+1) & 3; + ctx->flags |= E2F_FLAG_PROG_BAR; + + dpywidth = 66 - strlen(label); + dpywidth = 8 * (dpywidth / 8); + if (dpynum) + dpywidth -= 8; + + i = ((percent * dpywidth) + 50) / 100; + printf("%s%s: |%s%s", ctx->start_meta, label, + bar + (sizeof(bar) - (i+1)), + spaces + (sizeof(spaces) - (dpywidth - i + 1))); + if (fixed_percent == 1000) + fputc('|', stdout); + else + fputc(spinner[ctx->progress_pos & 3], stdout); + printf(" %4.1f%% ", percent); + if (dpynum) + printf("%u\r", dpynum); + else + fputs(" \r", stdout); + fputs(ctx->stop_meta, stdout); + + if (fixed_percent == 1000) + e2fsck_clear_progbar(ctx); + fflush(stdout); + + return 0; +} + +static int e2fsck_update_progress(e2fsck_t ctx, int pass, + unsigned long cur, unsigned long max) +{ + char buf[80]; + float percent; + + if (pass == 0) + return 0; + + if (ctx->progress_fd) { + sprintf(buf, "%d %lu %lu\n", pass, cur, max); + write(ctx->progress_fd, buf, strlen(buf)); + } else { + percent = calc_percent(&e2fsck_tbl, pass, cur, max); + e2fsck_simple_progress(ctx, ctx->device_name, + percent, 0); + } + return 0; +} + +static void reserve_stdio_fds(void) +{ + int fd; + + while (1) { + fd = open(bb_dev_null, O_RDWR); + if (fd > 2) + break; + if (fd < 0) { + fprintf(stderr, _("ERROR: Cannot open " + "/dev/null (%s)\n"), + strerror(errno)); + break; + } + } + close(fd); +} + +static void signal_progress_on(int sig FSCK_ATTR((unused))) +{ + e2fsck_t ctx = e2fsck_global_ctx; + + if (!ctx) + return; + + ctx->progress = e2fsck_update_progress; + ctx->progress_fd = 0; +} + +static void signal_progress_off(int sig FSCK_ATTR((unused))) +{ + e2fsck_t ctx = e2fsck_global_ctx; + + if (!ctx) + return; + + e2fsck_clear_progbar(ctx); + ctx->progress = 0; +} + +static void signal_cancel(int sig FSCK_ATTR((unused))) +{ + e2fsck_t ctx = e2fsck_global_ctx; + + if (!ctx) + exit(FSCK_CANCELED); + + ctx->flags |= E2F_FLAG_CANCEL; +} + +static void parse_extended_opts(e2fsck_t ctx, const char *opts) +{ + char *buf, *token, *next, *p, *arg; + int ea_ver; + int extended_usage = 0; + + buf = string_copy(opts, 0); + for (token = buf; token && *token; token = next) { + p = strchr(token, ','); + next = 0; + if (p) { + *p = 0; + next = p+1; + } + arg = strchr(token, '='); + if (arg) { + *arg = 0; + arg++; + } + if (strcmp(token, "ea_ver") == 0) { + if (!arg) { + extended_usage++; + continue; + } + ea_ver = strtoul(arg, &p, 0); + if (*p || + ((ea_ver != 1) && (ea_ver != 2))) { + fprintf(stderr, + _("Invalid EA version.\n")); + extended_usage++; + continue; + } + ctx->ext_attr_ver = ea_ver; + } else { + fprintf(stderr, _("Unknown extended option: %s\n"), + token); + extended_usage++; + } + } + if (extended_usage) { + bb_error_msg_and_die( + "Extended options are separated by commas, " + "and may take an argument which\n" + "is set off by an equals ('=') sign. " + "Valid extended options are:\n" + "\tea_ver=\n\n"); + } +} + + +static errcode_t PRS(int argc, char *argv[], e2fsck_t *ret_ctx) +{ + int flush = 0; + int c, fd; + e2fsck_t ctx; + errcode_t retval; + struct sigaction sa; + char *extended_opts = 0; + + retval = e2fsck_allocate_context(&ctx); + if (retval) + return retval; + + *ret_ctx = ctx; + + setvbuf(stdout, NULL, _IONBF, BUFSIZ); + setvbuf(stderr, NULL, _IONBF, BUFSIZ); + if (isatty(0) && isatty(1)) { + ctx->interactive = 1; + } else { + ctx->start_meta[0] = '\001'; + ctx->stop_meta[0] = '\002'; + } + memset(bar, '=', sizeof(bar)-1); + memset(spaces, ' ', sizeof(spaces)-1); + blkid_get_cache(&ctx->blkid, NULL); + + if (argc && *argv) + ctx->program_name = *argv; + else + ctx->program_name = "e2fsck"; + while ((c = getopt (argc, argv, "panyrcC:B:dE:fvtFVM:b:I:j:P:l:L:N:SsDk")) != EOF) + switch (c) { + case 'C': + ctx->progress = e2fsck_update_progress; + ctx->progress_fd = atoi(optarg); + if (!ctx->progress_fd) + break; + /* Validate the file descriptor to avoid disasters */ + fd = dup(ctx->progress_fd); + if (fd < 0) { + fprintf(stderr, + _("Error validating file descriptor %d: %s\n"), + ctx->progress_fd, + error_message(errno)); + bb_error_msg_and_die(_("Invalid completion information file descriptor")); + } else + close(fd); + break; + case 'D': + ctx->options |= E2F_OPT_COMPRESS_DIRS; + break; + case 'E': + extended_opts = optarg; + break; + case 'p': + case 'a': + if (ctx->options & (E2F_OPT_YES|E2F_OPT_NO)) { + conflict_opt: + bb_error_msg_and_die(_("Only one the options -p/-a, -n or -y may be specified.")); + } + ctx->options |= E2F_OPT_PREEN; + break; + case 'n': + if (ctx->options & (E2F_OPT_YES|E2F_OPT_PREEN)) + goto conflict_opt; + ctx->options |= E2F_OPT_NO; + break; + case 'y': + if (ctx->options & (E2F_OPT_PREEN|E2F_OPT_NO)) + goto conflict_opt; + ctx->options |= E2F_OPT_YES; + break; + case 't': + /* FIXME - This needs to go away in a future path - will change binary */ + fprintf(stderr, _("The -t option is not " + "supported on this version of e2fsck.\n")); + break; + case 'c': + if (cflag++) + ctx->options |= E2F_OPT_WRITECHECK; + ctx->options |= E2F_OPT_CHECKBLOCKS; + break; + case 'r': + /* What we do by default, anyway! */ + break; + case 'b': + ctx->use_superblock = atoi(optarg); + ctx->flags |= E2F_FLAG_SB_SPECIFIED; + break; + case 'B': + ctx->blocksize = atoi(optarg); + break; + case 'I': + ctx->inode_buffer_blocks = atoi(optarg); + break; + case 'j': + ctx->journal_name = string_copy(optarg, 0); + break; + case 'P': + ctx->process_inode_size = atoi(optarg); + break; + case 'd': + ctx->options |= E2F_OPT_DEBUG; + break; + case 'f': + ctx->options |= E2F_OPT_FORCE; + break; + case 'F': + flush = 1; + break; + case 'v': + verbose = 1; + break; + case 'V': + show_version_only = 1; + break; + case 'N': + ctx->device_name = optarg; + break; +#ifdef ENABLE_SWAPFS + case 's': + normalize_swapfs = 1; + case 'S': + swapfs = 1; + break; +#else + case 's': + case 'S': + fprintf(stderr, _("Byte-swapping filesystems " + "not compiled in this version " + "of e2fsck\n")); + exit(1); +#endif + default: + bb_show_usage(); + } + if (show_version_only) + return 0; + if (optind != argc - 1) + bb_show_usage(); + if ((ctx->options & E2F_OPT_NO) && + !cflag && !swapfs && !(ctx->options & E2F_OPT_COMPRESS_DIRS)) + ctx->options |= E2F_OPT_READONLY; + ctx->io_options = strchr(argv[optind], '?'); + if (ctx->io_options) + *ctx->io_options++ = 0; + ctx->filesystem_name = blkid_get_devname(ctx->blkid, argv[optind], 0); + if (!ctx->filesystem_name) { + bb_error_msg(_("Unable to resolve '%s'"), argv[optind]); + bb_error_msg_and_die(0); + } + if (extended_opts) + parse_extended_opts(ctx, extended_opts); + + if (flush) { + fd = open(ctx->filesystem_name, O_RDONLY, 0); + if (fd < 0) { + bb_error_msg(_("while opening %s for flushing"), + ctx->filesystem_name); + bb_error_msg_and_die(0); + } + if ((retval = ext2fs_sync_device(fd, 1))) { + bb_error_msg(_("while trying to flush %s"), + ctx->filesystem_name); + bb_error_msg_and_die(0); + } + close(fd); + } +#ifdef ENABLE_SWAPFS + if (swapfs && cflag) { + fprintf(stderr, _("Incompatible options not " + "allowed when byte-swapping.\n")); + exit(EXIT_USAGE); + } +#endif + /* + * Set up signal action + */ + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = signal_cancel; + sigaction(SIGINT, &sa, 0); + sigaction(SIGTERM, &sa, 0); +#ifdef SA_RESTART + sa.sa_flags = SA_RESTART; +#endif + e2fsck_global_ctx = ctx; + sa.sa_handler = signal_progress_on; + sigaction(SIGUSR1, &sa, 0); + sa.sa_handler = signal_progress_off; + sigaction(SIGUSR2, &sa, 0); + + /* Update our PATH to include /sbin if we need to run badblocks */ + if (cflag) + e2fs_set_sbin_path(); + return 0; +} + +static const char my_ver_string[] = E2FSPROGS_VERSION; +static const char my_ver_date[] = E2FSPROGS_DATE; + +int e2fsck_main (int argc, char *argv[]) +{ + errcode_t retval; + int exit_value = EXIT_OK; + ext2_filsys fs = 0; + io_manager io_ptr; + struct ext2_super_block *sb; + const char *lib_ver_date; + int my_ver, lib_ver; + e2fsck_t ctx; + struct problem_context pctx; + int flags, run_result; + + clear_problem_context(&pctx); + + my_ver = ext2fs_parse_version_string(my_ver_string); + lib_ver = ext2fs_get_library_version(0, &lib_ver_date); + if (my_ver > lib_ver) { + fprintf( stderr, _("Error: ext2fs library version " + "out of date!\n")); + show_version_only++; + } + + retval = PRS(argc, argv, &ctx); + if (retval) { + bb_error_msg(_("while trying to initialize program")); + exit(EXIT_ERROR); + } + reserve_stdio_fds(); + + if (!(ctx->options & E2F_OPT_PREEN) || show_version_only) + fprintf(stderr, "e2fsck %s (%s)\n", my_ver_string, + my_ver_date); + + if (show_version_only) { + fprintf(stderr, _("\tUsing %s, %s\n"), + error_message(EXT2_ET_BASE), lib_ver_date); + exit(EXIT_OK); + } + + check_mount(ctx); + + if (!(ctx->options & E2F_OPT_PREEN) && + !(ctx->options & E2F_OPT_NO) && + !(ctx->options & E2F_OPT_YES)) { + if (!ctx->interactive) + bb_error_msg_and_die(_("need terminal for interactive repairs")); + } + ctx->superblock = ctx->use_superblock; +restart: +#ifdef CONFIG_TESTIO_DEBUG + io_ptr = test_io_manager; + test_io_backing_manager = unix_io_manager; +#else + io_ptr = unix_io_manager; +#endif + flags = 0; + if ((ctx->options & E2F_OPT_READONLY) == 0) + flags |= EXT2_FLAG_RW; + + if (ctx->superblock && ctx->blocksize) { + retval = ext2fs_open2(ctx->filesystem_name, ctx->io_options, + flags, ctx->superblock, ctx->blocksize, + io_ptr, &fs); + } else if (ctx->superblock) { + int blocksize; + for (blocksize = EXT2_MIN_BLOCK_SIZE; + blocksize <= EXT2_MAX_BLOCK_SIZE; blocksize *= 2) { + retval = ext2fs_open2(ctx->filesystem_name, + ctx->io_options, flags, + ctx->superblock, blocksize, + io_ptr, &fs); + if (!retval) + break; + } + } else + retval = ext2fs_open2(ctx->filesystem_name, ctx->io_options, + flags, 0, 0, io_ptr, &fs); + if (!ctx->superblock && !(ctx->options & E2F_OPT_PREEN) && + !(ctx->flags & E2F_FLAG_SB_SPECIFIED) && + ((retval == EXT2_ET_BAD_MAGIC) || + ((retval == 0) && ext2fs_check_desc(fs)))) { + if (!fs || (fs->group_desc_count > 1)) { + printf(_("%s trying backup blocks...\n"), + retval ? _("Couldn't find ext2 superblock,") : + _("Group descriptors look bad...")); + get_backup_sb(ctx, fs, ctx->filesystem_name, io_ptr); + if (fs) + ext2fs_close(fs); + goto restart; + } + } + if (retval) { + bb_error_msg(_("while trying to open %s"), + ctx->filesystem_name); + if (retval == EXT2_ET_REV_TOO_HIGH) { + printf(_("The filesystem revision is apparently " + "too high for this version of e2fsck.\n" + "(Or the filesystem superblock " + "is corrupt)\n\n")); + fix_problem(ctx, PR_0_SB_CORRUPT, &pctx); + } else if (retval == EXT2_ET_SHORT_READ) + printf(_("Could this be a zero-length partition?\n")); + else if ((retval == EPERM) || (retval == EACCES)) + printf(_("You must have %s access to the " + "filesystem or be root\n"), + (ctx->options & E2F_OPT_READONLY) ? + "r/o" : "r/w"); + else if (retval == ENXIO) + printf(_("Possibly non-existent or swap device?\n")); +#ifdef EROFS + else if (retval == EROFS) + printf(_("Disk write-protected; use the -n option " + "to do a read-only\n" + "check of the device.\n")); +#endif + else + fix_problem(ctx, PR_0_SB_CORRUPT, &pctx); + bb_error_msg_and_die(0); + } + ctx->fs = fs; + fs->priv_data = ctx; + sb = fs->super; + if (sb->s_rev_level > E2FSCK_CURRENT_REV) { + bb_error_msg(_("while trying to open %s"), + ctx->filesystem_name); + get_newer: + bb_error_msg_and_die(_("Get a newer version of e2fsck!")); + } + + /* + * Set the device name, which is used whenever we print error + * or informational messages to the user. + */ + if (ctx->device_name == 0 && + (sb->s_volume_name[0] != 0)) { + ctx->device_name = string_copy(sb->s_volume_name, + sizeof(sb->s_volume_name)); + } + if (ctx->device_name == 0) + ctx->device_name = ctx->filesystem_name; + + /* + * Make sure the ext3 superblock fields are consistent. + */ + retval = e2fsck_check_ext3_journal(ctx); + if (retval) { + bb_error_msg(_("while checking ext3 journal for %s"), + ctx->device_name); + bb_error_msg_and_die(0); + } + + /* + * Check to see if we need to do ext3-style recovery. If so, + * do it, and then restart the fsck. + */ + if (sb->s_feature_incompat & EXT3_FEATURE_INCOMPAT_RECOVER) { + if (ctx->options & E2F_OPT_READONLY) { + printf(_("Warning: skipping journal recovery " + "because doing a read-only filesystem " + "check.\n")); + io_channel_flush(ctx->fs->io); + } else { + if (ctx->flags & E2F_FLAG_RESTARTED) { + /* + * Whoops, we attempted to run the + * journal twice. This should never + * happen, unless the hardware or + * device driver is being bogus. + */ + bb_error_msg(_("cannot set superblock flags on %s"), ctx->device_name); + bb_error_msg_and_die(0); + } + retval = e2fsck_run_ext3_journal(ctx); + if (retval) { + bb_error_msg(_("while recovering ext3 journal of %s"), + ctx->device_name); + bb_error_msg_and_die(0); + } + ext2fs_close(ctx->fs); + ctx->fs = 0; + ctx->flags |= E2F_FLAG_RESTARTED; + goto restart; + } + } + + /* + * Check for compatibility with the feature sets. We need to + * be more stringent than ext2fs_open(). + */ + if ((sb->s_feature_compat & ~EXT2_LIB_FEATURE_COMPAT_SUPP) || + (sb->s_feature_incompat & ~EXT2_LIB_FEATURE_INCOMPAT_SUPP)) { + bb_error_msg("(%s)", ctx->device_name); + goto get_newer; + } + if (sb->s_feature_ro_compat & ~EXT2_LIB_FEATURE_RO_COMPAT_SUPP) { + bb_error_msg("(%s)", ctx->device_name); + goto get_newer; + } +#ifdef ENABLE_COMPRESSION + /* FIXME - do we support this at all? */ + if (sb->s_feature_incompat & EXT2_FEATURE_INCOMPAT_COMPRESSION) + bb_error_msg(_("Warning: compression support is experimental.")); +#endif +#ifndef ENABLE_HTREE + if (sb->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX) { + bb_error_msg(_("E2fsck not compiled with HTREE support,\n\t" + "but filesystem %s has HTREE directories."), + ctx->device_name); + goto get_newer; + } +#endif + + /* + * If the user specified a specific superblock, presumably the + * master superblock has been trashed. So we mark the + * superblock as dirty, so it can be written out. + */ + if (ctx->superblock && + !(ctx->options & E2F_OPT_READONLY)) + ext2fs_mark_super_dirty(fs); + + /* + * We only update the master superblock because (a) paranoia; + * we don't want to corrupt the backup superblocks, and (b) we + * don't need to update the mount count and last checked + * fields in the backup superblock (the kernel doesn't + * update the backup superblocks anyway). + */ + fs->flags |= EXT2_FLAG_MASTER_SB_ONLY; + + ehandler_init(fs->io); + + if (ctx->superblock) + set_latch_flags(PR_LATCH_RELOC, PRL_LATCHED, 0); + ext2fs_mark_valid(fs); + check_super_block(ctx); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + bb_error_msg_and_die(0); + check_if_skip(ctx); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + bb_error_msg_and_die(0); +#ifdef ENABLE_SWAPFS + +#ifdef WORDS_BIGENDIAN +#define NATIVE_FLAG EXT2_FLAG_SWAP_BYTES +#else +#define NATIVE_FLAG 0 +#endif + + + if (normalize_swapfs) { + if ((fs->flags & EXT2_FLAG_SWAP_BYTES) == NATIVE_FLAG) { + fprintf(stderr, _("%s: Filesystem byte order " + "already normalized.\n"), ctx->device_name); + bb_error_msg_and_die(0); + } + } + if (swapfs) { + swap_filesys(ctx); + if (ctx->flags & E2F_FLAG_SIGNAL_MASK) + bb_error_msg_and_die(0); + } +#endif + + /* + * Mark the system as valid, 'til proven otherwise + */ + ext2fs_mark_valid(fs); + + retval = ext2fs_read_bb_inode(fs, &fs->badblocks); + if (retval) { + bb_error_msg(_("while reading bad blocks inode")); + preenhalt(ctx); + printf(_("This doesn't bode well," + " but we'll try to go on...\n")); + } + + run_result = e2fsck_run(ctx); + e2fsck_clear_progbar(ctx); + if (run_result == E2F_FLAG_RESTART) { + printf(_("Restarting e2fsck from the beginning...\n")); + retval = e2fsck_reset_context(ctx); + if (retval) { + bb_error_msg(_("while resetting context")); + bb_error_msg_and_die(0); + } + ext2fs_close(fs); + goto restart; + } + if (run_result & E2F_FLAG_CANCEL) { + printf(_("%s: e2fsck canceled.\n"), ctx->device_name ? + ctx->device_name : ctx->filesystem_name); + exit_value |= FSCK_CANCELED; + } + if (run_result & E2F_FLAG_ABORT) + bb_error_msg_and_die(_("aborted")); + + /* Cleanup */ + if (ext2fs_test_changed(fs)) { + exit_value |= EXIT_NONDESTRUCT; + if (!(ctx->options & E2F_OPT_PREEN)) + printf(_("\n%s: ***** FILE SYSTEM WAS MODIFIED *****\n"), + ctx->device_name); + if (ctx->mount_flags & EXT2_MF_ISROOT) { + printf(_("%s: ***** REBOOT LINUX *****\n"), + ctx->device_name); + exit_value |= EXIT_DESTRUCT; + } + } + if (!ext2fs_test_valid(fs)) { + printf(_("\n%s: ********** WARNING: Filesystem still has " + "errors **********\n\n"), ctx->device_name); + exit_value |= EXIT_UNCORRECTED; + exit_value &= ~EXIT_NONDESTRUCT; + } + if (exit_value & FSCK_CANCELED) + exit_value &= ~EXIT_NONDESTRUCT; + else { + show_stats(ctx); + if (!(ctx->options & E2F_OPT_READONLY)) { + if (ext2fs_test_valid(fs)) { + if (!(sb->s_state & EXT2_VALID_FS)) + exit_value |= EXIT_NONDESTRUCT; + sb->s_state = EXT2_VALID_FS; + } else + sb->s_state &= ~EXT2_VALID_FS; + sb->s_mnt_count = 0; + sb->s_lastcheck = time(NULL); + ext2fs_mark_super_dirty(fs); + } + } + + e2fsck_write_bitmaps(ctx); + + ext2fs_close(fs); + ctx->fs = NULL; + free(ctx->filesystem_name); + free(ctx->journal_name); + e2fsck_free_context(ctx); + + return exit_value; +} diff --git a/e2fsprogs/e2fsck.h b/e2fsprogs/e2fsck.h new file mode 100644 index 000000000..e520632a0 --- /dev/null +++ b/e2fsprogs/e2fsck.h @@ -0,0 +1,640 @@ +/* vi: set sw=4 ts=4: */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ext2fs/kernel-list.h" +#include +#include + +/* + * Now pull in the real linux/jfs.h definitions. + */ +#include "ext2fs/kernel-jbd.h" + + + +#include "fsck.h" + +#include "ext2fs/ext2_fs.h" +#include "blkid/blkid.h" +#include "ext2fs/ext2_ext_attr.h" +#include "uuid/uuid.h" +#include "busybox.h" + +#ifdef HAVE_CONIO_H +#undef HAVE_TERMIOS_H +#include +#define read_a_char() getch() +#else +#ifdef HAVE_TERMIOS_H +#include +#endif +#endif + + +/* + * The last ext2fs revision level that this version of e2fsck is able to + * support + */ +#define E2FSCK_CURRENT_REV 1 + +/* Used by the region allocation code */ +typedef __u32 region_addr_t; +typedef struct region_struct *region_t; + +struct dx_dirblock_info { + int type; + blk_t phys; + int flags; + blk_t parent; + ext2_dirhash_t min_hash; + ext2_dirhash_t max_hash; + ext2_dirhash_t node_min_hash; + ext2_dirhash_t node_max_hash; +}; + +/* +These defines are used in the type field of dx_dirblock_info +*/ + +#define DX_DIRBLOCK_ROOT 1 +#define DX_DIRBLOCK_LEAF 2 +#define DX_DIRBLOCK_NODE 3 + + +/* +The following defines are used in the 'flags' field of a dx_dirblock_info +*/ +#define DX_FLAG_REFERENCED 1 +#define DX_FLAG_DUP_REF 2 +#define DX_FLAG_FIRST 4 +#define DX_FLAG_LAST 8 + +/* + * E2fsck options + */ +#define E2F_OPT_READONLY 0x0001 +#define E2F_OPT_PREEN 0x0002 +#define E2F_OPT_YES 0x0004 +#define E2F_OPT_NO 0x0008 +#define E2F_OPT_TIME 0x0010 +#define E2F_OPT_CHECKBLOCKS 0x0040 +#define E2F_OPT_DEBUG 0x0080 +#define E2F_OPT_FORCE 0x0100 +#define E2F_OPT_WRITECHECK 0x0200 +#define E2F_OPT_COMPRESS_DIRS 0x0400 + +/* + * E2fsck flags + */ +#define E2F_FLAG_ABORT 0x0001 /* Abort signaled */ +#define E2F_FLAG_CANCEL 0x0002 /* Cancel signaled */ +#define E2F_FLAG_SIGNAL_MASK 0x0003 +#define E2F_FLAG_RESTART 0x0004 /* Restart signaled */ + +#define E2F_FLAG_SETJMP_OK 0x0010 /* Setjmp valid for abort */ + +#define E2F_FLAG_PROG_BAR 0x0020 /* Progress bar on screen */ +#define E2F_FLAG_PROG_SUPPRESS 0x0040 /* Progress suspended */ +#define E2F_FLAG_JOURNAL_INODE 0x0080 /* Create a new ext3 journal inode */ +#define E2F_FLAG_SB_SPECIFIED 0x0100 /* The superblock was explicitly + * specified by the user */ +#define E2F_FLAG_RESTARTED 0x0200 /* E2fsck has been restarted */ +#define E2F_FLAG_RESIZE_INODE 0x0400 /* Request to recreate resize inode */ + + +/*Don't know where these come from*/ +#define READ 0 +#define WRITE 1 +#define cpu_to_be32(n) htonl(n) +#define be32_to_cpu(n) ntohl(n) + +/* + * We define a set of "latch groups"; these are problems which are + * handled as a set. The user answers once for a particular latch + * group. + */ +#define PR_LATCH_MASK 0x0ff0 /* Latch mask */ +#define PR_LATCH_BLOCK 0x0010 /* Latch for illegal blocks (pass 1) */ +#define PR_LATCH_BBLOCK 0x0020 /* Latch for bad block inode blocks (pass 1) */ +#define PR_LATCH_IBITMAP 0x0030 /* Latch for pass 5 inode bitmap proc. */ +#define PR_LATCH_BBITMAP 0x0040 /* Latch for pass 5 inode bitmap proc. */ +#define PR_LATCH_RELOC 0x0050 /* Latch for superblock relocate hint */ +#define PR_LATCH_DBLOCK 0x0060 /* Latch for pass 1b dup block headers */ +#define PR_LATCH_LOW_DTIME 0x0070 /* Latch for pass1 orphaned list refugees */ +#define PR_LATCH_TOOBIG 0x0080 /* Latch for file to big errors */ +#define PR_LATCH_OPTIMIZE_DIR 0x0090 /* Latch for optimize directories */ + +#define PR_LATCH(x) ((((x) & PR_LATCH_MASK) >> 4) - 1) + +/* + * Latch group descriptor flags + */ +#define PRL_YES 0x0001 /* Answer yes */ +#define PRL_NO 0x0002 /* Answer no */ +#define PRL_LATCHED 0x0004 /* The latch group is latched */ +#define PRL_SUPPRESS 0x0008 /* Suppress all latch group questions */ + +#define PRL_VARIABLE 0x000f /* All the flags that need to be reset */ + +/* + * Pre-Pass 1 errors + */ + +#define PR_0_BB_NOT_GROUP 0x000001 /* Block bitmap not in group */ +#define PR_0_IB_NOT_GROUP 0x000002 /* Inode bitmap not in group */ +#define PR_0_ITABLE_NOT_GROUP 0x000003 /* Inode table not in group */ +#define PR_0_SB_CORRUPT 0x000004 /* Superblock corrupt */ +#define PR_0_FS_SIZE_WRONG 0x000005 /* Filesystem size is wrong */ +#define PR_0_NO_FRAGMENTS 0x000006 /* Fragments not supported */ +#define PR_0_BLOCKS_PER_GROUP 0x000007 /* Bad blocks_per_group */ +#define PR_0_FIRST_DATA_BLOCK 0x000008 /* Bad first_data_block */ +#define PR_0_ADD_UUID 0x000009 /* Adding UUID to filesystem */ +#define PR_0_RELOCATE_HINT 0x00000A /* Relocate hint */ +#define PR_0_MISC_CORRUPT_SUPER 0x00000B /* Miscellaneous superblock corruption */ +#define PR_0_GETSIZE_ERROR 0x00000C /* Error determing physical device size of filesystem */ +#define PR_0_INODE_COUNT_WRONG 0x00000D /* Inode count in the superblock incorrect */ +#define PR_0_HURD_CLEAR_FILETYPE 0x00000E /* The Hurd does not support the filetype feature */ +#define PR_0_JOURNAL_BAD_INODE 0x00000F /* The Hurd does not support the filetype feature */ +#define PR_0_JOURNAL_UNSUPP_MULTIFS 0x000010 /* The external journal has multiple filesystems (which we can't handle yet) */ +#define PR_0_CANT_FIND_JOURNAL 0x000011 /* Can't find external journal */ +#define PR_0_EXT_JOURNAL_BAD_SUPER 0x000012/* External journal has bad superblock */ +#define PR_0_JOURNAL_BAD_UUID 0x000013 /* Superblock has a bad journal UUID */ +#define PR_0_JOURNAL_UNSUPP_SUPER 0x000014 /* Journal has an unknown superblock type */ +#define PR_0_JOURNAL_BAD_SUPER 0x000015 /* Journal superblock is corrupt */ +#define PR_0_JOURNAL_HAS_JOURNAL 0x000016 /* Journal superblock is corrupt */ +#define PR_0_JOURNAL_RECOVER_SET 0x000017 /* Superblock has recovery flag set but no journal */ +#define PR_0_JOURNAL_RECOVERY_CLEAR 0x000018 /* Journal has data, but recovery flag is clear */ +#define PR_0_JOURNAL_RESET_JOURNAL 0x000019 /* Ask if we should clear the journal */ +#define PR_0_FS_REV_LEVEL 0x00001A /* Filesystem revision is 0, but feature flags are set */ +#define PR_0_ORPHAN_CLEAR_INODE 0x000020 /* Clearing orphan inode */ +#define PR_0_ORPHAN_ILLEGAL_BLOCK_NUM 0x000021 /* Illegal block found in orphaned inode */ +#define PR_0_ORPHAN_ALREADY_CLEARED_BLOCK 0x000022 /* Already cleared block found in orphaned inode */ +#define PR_0_ORPHAN_ILLEGAL_HEAD_INODE 0x000023 /* Illegal orphan inode in superblock */ +#define PR_0_ORPHAN_ILLEGAL_INODE 0x000024 /* Illegal inode in orphaned inode list */ +#define PR_0_JOURNAL_UNSUPP_ROCOMPAT 0x000025 /* Journal has unsupported read-only feature - abort */ +#define PR_0_JOURNAL_UNSUPP_INCOMPAT 0x000026 /* Journal has unsupported incompatible feature - abort */ +#define PR_0_JOURNAL_UNSUPP_VERSION 0x000027 /* Journal has unsupported version number */ +#define PR_0_MOVE_JOURNAL 0x000028 /* Moving journal to hidden file */ +#define PR_0_ERR_MOVE_JOURNAL 0x000029 /* Error moving journal */ +#define PR_0_CLEAR_V2_JOURNAL 0x00002A /* Clearing V2 journal superblock */ +#define PR_0_JOURNAL_RUN 0x00002B /* Run journal anyway */ +#define PR_0_JOURNAL_RUN_DEFAULT 0x00002C /* Run journal anyway by default */ +#define PR_0_BACKUP_JNL 0x00002D /* Backup journal inode blocks */ +#define PR_0_NONZERO_RESERVED_GDT_BLOCKS 0x00002E /* Reserved blocks w/o resize_inode */ +#define PR_0_CLEAR_RESIZE_INODE 0x00002F /* Resize_inode not enabled, but resize inode is non-zero */ +#define PR_0_RESIZE_INODE_INVALID 0x000030 /* Resize inode invalid */ + +/* + * Pass 1 errors + */ + +#define PR_1_PASS_HEADER 0x010000 /* Pass 1: Checking inodes, blocks, and sizes */ +#define PR_1_ROOT_NO_DIR 0x010001 /* Root directory is not an inode */ +#define PR_1_ROOT_DTIME 0x010002 /* Root directory has dtime set */ +#define PR_1_RESERVED_BAD_MODE 0x010003 /* Reserved inode has bad mode */ +#define PR_1_ZERO_DTIME 0x010004 /* Deleted inode has zero dtime */ +#define PR_1_SET_DTIME 0x010005 /* Inode in use, but dtime set */ +#define PR_1_ZERO_LENGTH_DIR 0x010006 /* Zero-length directory */ +#define PR_1_BB_CONFLICT 0x010007 /* Block bitmap conflicts with some other fs block */ +#define PR_1_IB_CONFLICT 0x010008 /* Inode bitmap conflicts with some other fs block */ +#define PR_1_ITABLE_CONFLICT 0x010009 /* Inode table conflicts with some other fs block */ +#define PR_1_BB_BAD_BLOCK 0x01000A /* Block bitmap is on a bad block */ +#define PR_1_IB_BAD_BLOCK 0x01000B /* Inode bitmap is on a bad block */ +#define PR_1_BAD_I_SIZE 0x01000C /* Inode has incorrect i_size */ +#define PR_1_BAD_I_BLOCKS 0x01000D /* Inode has incorrect i_blocks */ +#define PR_1_ILLEGAL_BLOCK_NUM 0x01000E /* Illegal block number in inode */ +#define PR_1_BLOCK_OVERLAPS_METADATA 0x01000F /* Block number overlaps fs metadata */ +#define PR_1_INODE_BLOCK_LATCH 0x010010 /* Inode has illegal blocks (latch question) */ +#define PR_1_TOO_MANY_BAD_BLOCKS 0x010011 /* Too many bad blocks in inode */ +#define PR_1_BB_ILLEGAL_BLOCK_NUM 0x010012 /* Illegal block number in bad block inode */ +#define PR_1_INODE_BBLOCK_LATCH 0x010013 /* Bad block inode has illegal blocks (latch question) */ +#define PR_1_DUP_BLOCKS_PREENSTOP 0x010014 /* Duplicate or bad blocks in use! */ +#define PR_1_BBINODE_BAD_METABLOCK 0x010015 /* Bad block used as bad block indirect block */ +#define PR_1_BBINODE_BAD_METABLOCK_PROMPT 0x010016 /* Inconsistency can't be fixed prompt */ +#define PR_1_BAD_PRIMARY_BLOCK 0x010017 /* Bad primary block */ +#define PR_1_BAD_PRIMARY_BLOCK_PROMPT 0x010018 /* Bad primary block prompt */ +#define PR_1_BAD_PRIMARY_SUPERBLOCK 0x010019 /* Bad primary superblock */ +#define PR_1_BAD_PRIMARY_GROUP_DESCRIPTOR 0x01001A /* Bad primary block group descriptors */ +#define PR_1_BAD_SUPERBLOCK 0x01001B /* Bad superblock in group */ +#define PR_1_BAD_GROUP_DESCRIPTORS 0x01001C /* Bad block group descriptors in group */ +#define PR_1_PROGERR_CLAIMED_BLOCK 0x01001D /* Block claimed for no reason */ +#define PR_1_RELOC_BLOCK_ALLOCATE 0x01001E /* Error allocating blocks for relocating metadata */ +#define PR_1_RELOC_MEMORY_ALLOCATE 0x01001F /* Error allocating block buffer during relocation process */ +#define PR_1_RELOC_FROM_TO 0x010020 /* Relocating metadata group information from X to Y */ +#define PR_1_RELOC_TO 0x010021 /* Relocating metatdata group information to X */ +#define PR_1_RELOC_READ_ERR 0x010022 /* Block read error during relocation process */ +#define PR_1_RELOC_WRITE_ERR 0x010023 /* Block write error during relocation process */ +#define PR_1_ALLOCATE_IBITMAP_ERROR 0x010024 /* Error allocating inode bitmap */ +#define PR_1_ALLOCATE_BBITMAP_ERROR 0x010025 /* Error allocating block bitmap */ +#define PR_1_ALLOCATE_ICOUNT 0x010026 /* Error allocating icount structure */ +#define PR_1_ALLOCATE_DBCOUNT 0x010027 /* Error allocating dbcount */ +#define PR_1_ISCAN_ERROR 0x010028 /* Error while scanning inodes */ +#define PR_1_BLOCK_ITERATE 0x010029 /* Error while iterating over blocks */ +#define PR_1_ICOUNT_STORE 0x01002A /* Error while storing inode count information */ +#define PR_1_ADD_DBLOCK 0x01002B /* Error while storing directory block information */ +#define PR_1_READ_INODE 0x01002C /* Error while reading inode (for clearing) */ +#define PR_1_SUPPRESS_MESSAGES 0x01002D /* Suppress messages prompt */ +#define PR_1_SET_IMAGIC 0x01002F /* Imagic flag set on an inode when filesystem doesn't support it */ +#define PR_1_SET_IMMUTABLE 0x010030 /* Immutable flag set on a device or socket inode */ +#define PR_1_COMPR_SET 0x010031 /* Compression flag set on a non-compressed filesystem */ +#define PR_1_SET_NONZSIZE 0x010032 /* Non-zero size on on device, fifo or socket inode */ +#define PR_1_FS_REV_LEVEL 0x010033 /* Filesystem revision is 0, but feature flags are set */ +#define PR_1_JOURNAL_INODE_NOT_CLEAR 0x010034 /* Journal inode not in use, needs clearing */ +#define PR_1_JOURNAL_BAD_MODE 0x010035 /* Journal inode has wrong mode */ +#define PR_1_LOW_DTIME 0x010036 /* Inode that was part of orphan linked list */ +#define PR_1_ORPHAN_LIST_REFUGEES 0x010037 /* Latch question which asks how to deal with low dtime inodes */ +#define PR_1_ALLOCATE_REFCOUNT 0x010038 /* Error allocating refcount structure */ +#define PR_1_READ_EA_BLOCK 0x010039 /* Error reading Extended Attribute block */ +#define PR_1_BAD_EA_BLOCK 0x01003A /* Invalid Extended Attribute block */ +#define PR_1_EXTATTR_READ_ABORT 0x01003B /* Error reading Extended Attribute block while fixing refcount -- abort */ +#define PR_1_EXTATTR_REFCOUNT 0x01003C /* Extended attribute reference count incorrect */ +#define PR_1_EXTATTR_WRITE 0x01003D /* Error writing Extended Attribute block while fixing refcount */ +#define PR_1_EA_MULTI_BLOCK 0x01003E /* Multiple EA blocks not supported */ +#define PR_1_EA_ALLOC_REGION 0x01003F /* Error allocating EA region allocation structure */ +#define PR_1_EA_ALLOC_COLLISION 0x010040 /* Error EA allocation collision */ +#define PR_1_EA_BAD_NAME 0x010041 /* Bad extended attribute name */ +#define PR_1_EA_BAD_VALUE 0x010042 /* Bad extended attribute value */ +#define PR_1_INODE_TOOBIG 0x010043 /* Inode too big (latch question) */ +#define PR_1_TOOBIG_DIR 0x010044 /* Directory too big */ +#define PR_1_TOOBIG_REG 0x010045 /* Regular file too big */ +#define PR_1_TOOBIG_SYMLINK 0x010046 /* Symlink too big */ +#define PR_1_HTREE_SET 0x010047 /* INDEX_FL flag set on a non-HTREE filesystem */ +#define PR_1_HTREE_NODIR 0x010048 /* INDEX_FL flag set on a non-directory */ +#define PR_1_HTREE_BADROOT 0x010049 /* Invalid root node in HTREE directory */ +#define PR_1_HTREE_HASHV 0x01004A /* Unsupported hash version in HTREE directory */ +#define PR_1_HTREE_INCOMPAT 0x01004B /* Incompatible flag in HTREE root node */ +#define PR_1_HTREE_DEPTH 0x01004C /* HTREE too deep */ +#define PR_1_BB_FS_BLOCK 0x01004D /* Bad block has indirect block that conflicts with filesystem block */ +#define PR_1_RESIZE_INODE_CREATE 0x01004E /* Resize inode failed */ +#define PR_1_EXTRA_ISIZE 0x01004F /* inode->i_size is too long */ +#define PR_1_ATTR_NAME_LEN 0x010050 /* attribute name is too long */ +#define PR_1_ATTR_VALUE_OFFSET 0x010051 /* wrong EA value offset */ +#define PR_1_ATTR_VALUE_BLOCK 0x010052 /* wrong EA blocknumber */ +#define PR_1_ATTR_VALUE_SIZE 0x010053 /* wrong EA value size */ +#define PR_1_ATTR_HASH 0x010054 /* wrong EA hash value */ + +/* + * Pass 1b errors + */ + +#define PR_1B_PASS_HEADER 0x011000 /* Pass 1B: Rescan for duplicate/bad blocks */ +#define PR_1B_DUP_BLOCK_HEADER 0x011001 /* Duplicate/bad block(s) header */ +#define PR_1B_DUP_BLOCK 0x011002 /* Duplicate/bad block(s) in inode */ +#define PR_1B_DUP_BLOCK_END 0x011003 /* Duplicate/bad block(s) end */ +#define PR_1B_ISCAN_ERROR 0x011004 /* Error while scanning inodes */ +#define PR_1B_ALLOCATE_IBITMAP_ERROR 0x011005 /* Error allocating inode bitmap */ +#define PR_1B_BLOCK_ITERATE 0x0110006 /* Error while iterating over blocks */ +#define PR_1B_ADJ_EA_REFCOUNT 0x0110007 /* Error adjusting EA refcount */ +#define PR_1C_PASS_HEADER 0x012000 /* Pass 1C: Scan directories for inodes with dup blocks. */ +#define PR_1D_PASS_HEADER 0x013000 /* Pass 1D: Reconciling duplicate blocks */ +#define PR_1D_DUP_FILE 0x013001 /* File has duplicate blocks */ +#define PR_1D_DUP_FILE_LIST 0x013002 /* List of files sharing duplicate blocks */ +#define PR_1D_SHARE_METADATA 0x013003 /* File sharing blocks with filesystem metadata */ +#define PR_1D_NUM_DUP_INODES 0x013004 /* Report of how many duplicate/bad inodes */ +#define PR_1D_DUP_BLOCKS_DEALT 0x013005 /* Duplicated blocks already reassigned or cloned. */ +#define PR_1D_CLONE_QUESTION 0x013006 /* Clone duplicate/bad blocks? */ +#define PR_1D_DELETE_QUESTION 0x013007 /* Delete file? */ +#define PR_1D_CLONE_ERROR 0x013008 /* Couldn't clone file (error) */ + +/* + * Pass 2 errors + */ + +#define PR_2_PASS_HEADER 0x020000 /* Pass 2: Checking directory structure */ +#define PR_2_BAD_INODE_DOT 0x020001 /* Bad inode number for '.' */ +#define PR_2_BAD_INO 0x020002 /* Directory entry has bad inode number */ +#define PR_2_UNUSED_INODE 0x020003 /* Directory entry has deleted or unused inode */ +#define PR_2_LINK_DOT 0x020004 /* Directry entry is link to '.' */ +#define PR_2_BB_INODE 0x020005 /* Directory entry points to inode now located in a bad block */ +#define PR_2_LINK_DIR 0x020006 /* Directory entry contains a link to a directory */ +#define PR_2_LINK_ROOT 0x020007 /* Directory entry contains a link to the root directry */ +#define PR_2_BAD_NAME 0x020008 /* Directory entry has illegal characters in its name */ +#define PR_2_MISSING_DOT 0x020009 /* Missing '.' in directory inode */ +#define PR_2_MISSING_DOT_DOT 0x02000A /* Missing '..' in directory inode */ +#define PR_2_1ST_NOT_DOT 0x02000B /* First entry in directory inode doesn't contain '.' */ +#define PR_2_2ND_NOT_DOT_DOT 0x02000C /* Second entry in directory inode doesn't contain '..' */ +#define PR_2_FADDR_ZERO 0x02000D /* i_faddr should be zero */ +#define PR_2_FILE_ACL_ZERO 0x02000E /* i_file_acl should be zero */ +#define PR_2_DIR_ACL_ZERO 0x02000F /* i_dir_acl should be zero */ +#define PR_2_FRAG_ZERO 0x020010 /* i_frag should be zero */ +#define PR_2_FSIZE_ZERO 0x020011 /* i_fsize should be zero */ +#define PR_2_BAD_MODE 0x020012 /* inode has bad mode */ +#define PR_2_DIR_CORRUPTED 0x020013 /* directory corrupted */ +#define PR_2_FILENAME_LONG 0x020014 /* filename too long */ +#define PR_2_DIRECTORY_HOLE 0x020015 /* Directory inode has a missing block (hole) */ +#define PR_2_DOT_NULL_TERM 0x020016 /* '.' is not NULL terminated */ +#define PR_2_DOT_DOT_NULL_TERM 0x020017 /* '..' is not NULL terminated */ +#define PR_2_BAD_CHAR_DEV 0x020018 /* Illegal character device in inode */ +#define PR_2_BAD_BLOCK_DEV 0x020019 /* Illegal block device in inode */ +#define PR_2_DUP_DOT 0x02001A /* Duplicate '.' entry */ +#define PR_2_DUP_DOT_DOT 0x02001B /* Duplicate '..' entry */ +#define PR_2_NO_DIRINFO 0x02001C /* Internal error: couldn't find dir_info */ +#define PR_2_FINAL_RECLEN 0x02001D /* Final rec_len is wrong */ +#define PR_2_ALLOCATE_ICOUNT 0x02001E /* Error allocating icount structure */ +#define PR_2_DBLIST_ITERATE 0x02001F /* Error iterating over directory blocks */ +#define PR_2_READ_DIRBLOCK 0x020020 /* Error reading directory block */ +#define PR_2_WRITE_DIRBLOCK 0x020021 /* Error writing directory block */ +#define PR_2_ALLOC_DIRBOCK 0x020022 /* Error allocating new directory block */ +#define PR_2_DEALLOC_INODE 0x020023 /* Error deallocating inode */ +#define PR_2_SPLIT_DOT 0x020024 /* Directory entry for '.' is big. Split? */ +#define PR_2_BAD_FIFO 0x020025 /* Illegal FIFO */ +#define PR_2_BAD_SOCKET 0x020026 /* Illegal socket */ +#define PR_2_SET_FILETYPE 0x020027 /* Directory filetype not set */ +#define PR_2_BAD_FILETYPE 0x020028 /* Directory filetype incorrect */ +#define PR_2_CLEAR_FILETYPE 0x020029 /* Directory filetype set when it shouldn't be */ +#define PR_2_NULL_NAME 0x020030 /* Directory filename can't be zero-length */ +#define PR_2_INVALID_SYMLINK 0x020031 /* Invalid symlink */ +#define PR_2_FILE_ACL_BAD 0x020032 /* i_file_acl (extended attribute) is bad */ +#define PR_2_FEATURE_LARGE_FILES 0x020033 /* Filesystem contains large files, but has no such flag in sb */ +#define PR_2_HTREE_NOTREF 0x020034 /* Node in HTREE directory not referenced */ +#define PR_2_HTREE_DUPREF 0x020035 /* Node in HTREE directory referenced twice */ +#define PR_2_HTREE_MIN_HASH 0x020036 /* Node in HTREE directory has bad min hash */ +#define PR_2_HTREE_MAX_HASH 0x020037 /* Node in HTREE directory has bad max hash */ +#define PR_2_HTREE_CLEAR 0x020038 /* Clear invalid HTREE directory */ +#define PR_2_HTREE_BADBLK 0x02003A /* Bad block in htree interior node */ +#define PR_2_ADJ_EA_REFCOUNT 0x02003B /* Error adjusting EA refcount */ +#define PR_2_HTREE_BAD_ROOT 0x02003C /* Invalid HTREE root node */ +#define PR_2_HTREE_BAD_LIMIT 0x02003D /* Invalid HTREE limit */ +#define PR_2_HTREE_BAD_COUNT 0x02003E /* Invalid HTREE count */ +#define PR_2_HTREE_HASH_ORDER 0x02003F /* HTREE interior node has out-of-order hashes in table */ +#define PR_2_HTREE_BAD_DEPTH 0x020040 /* Node in HTREE directory has bad depth */ +#define PR_2_DUPLICATE_DIRENT 0x020041 /* Duplicate directory entry found */ +#define PR_2_NON_UNIQUE_FILE 0x020042 /* Non-unique filename found */ +#define PR_2_REPORT_DUP_DIRENT 0x020043 /* Duplicate directory entry found */ + +/* + * Pass 3 errors + */ + +#define PR_3_PASS_HEADER 0x030000 /* Pass 3: Checking directory connectivity */ +#define PR_3_NO_ROOT_INODE 0x030001 /* Root inode not allocated */ +#define PR_3_EXPAND_LF_DIR 0x030002 /* No room in lost+found */ +#define PR_3_UNCONNECTED_DIR 0x030003 /* Unconnected directory inode */ +#define PR_3_NO_LF_DIR 0x030004 /* /lost+found not found */ +#define PR_3_BAD_DOT_DOT 0x030005 /* .. entry is incorrect */ +#define PR_3_NO_LPF 0x030006 /* Bad or non-existent /lost+found. Cannot reconnect */ +#define PR_3_CANT_EXPAND_LPF 0x030007 /* Could not expand /lost+found */ +#define PR_3_CANT_RECONNECT 0x030008 /* Could not reconnect inode */ +#define PR_3_ERR_FIND_LPF 0x030009 /* Error while trying to find /lost+found */ +#define PR_3_ERR_LPF_NEW_BLOCK 0x03000A /* Error in ext2fs_new_block while creating /lost+found */ +#define PR_3_ERR_LPF_NEW_INODE 0x03000B /* Error in ext2fs_new_inode while creating /lost+found */ +#define PR_3_ERR_LPF_NEW_DIR_BLOCK 0x03000C /* Error in ext2fs_new_dir_block while creating /lost+found */ +#define PR_3_ERR_LPF_WRITE_BLOCK 0x03000D /* Error while writing directory block for /lost+found */ +#define PR_3_ADJUST_INODE 0x03000E /* Error while adjusting inode count */ +#define PR_3_FIX_PARENT_ERR 0x03000F /* Couldn't fix parent directory -- error */ +#define PR_3_FIX_PARENT_NOFIND 0x030010 /* Couldn't fix parent directory -- couldn't find it */ +#define PR_3_ALLOCATE_IBITMAP_ERROR 0x030011 /* Error allocating inode bitmap */ +#define PR_3_CREATE_ROOT_ERROR 0x030012 /* Error creating root directory */ +#define PR_3_CREATE_LPF_ERROR 0x030013 /* Error creating lost and found directory */ +#define PR_3_ROOT_NOT_DIR_ABORT 0x030014 /* Root inode is not directory; aborting */ +#define PR_3_NO_ROOT_INODE_ABORT 0x030015 /* Cannot proceed without a root inode. */ +#define PR_3_NO_DIRINFO 0x030016 /* Internal error: couldn't find dir_info */ +#define PR_3_LPF_NOTDIR 0x030017 /* Lost+found is not a directory */ + +/* + * Pass 3a --- rehashing diretories + */ +#define PR_3A_PASS_HEADER 0x031000 /* Pass 3a: Reindexing directories */ +#define PR_3A_OPTIMIZE_ITER 0x031001 /* Error iterating over directories */ +#define PR_3A_OPTIMIZE_DIR_ERR 0x031002 /* Error rehash directory */ +#define PR_3A_OPTIMIZE_DIR_HEADER 0x031003 /* Rehashing dir header */ +#define PR_3A_OPTIMIZE_DIR 0x031004 /* Rehashing directory %d */ +#define PR_3A_OPTIMIZE_DIR_END 0x031005 /* Rehashing dir end */ + +/* + * Pass 4 errors + */ + +#define PR_4_PASS_HEADER 0x040000 /* Pass 4: Checking reference counts */ +#define PR_4_ZERO_LEN_INODE 0x040001 /* Unattached zero-length inode */ +#define PR_4_UNATTACHED_INODE 0x040002 /* Unattached inode */ +#define PR_4_BAD_REF_COUNT 0x040003 /* Inode ref count wrong */ +#define PR_4_INCONSISTENT_COUNT 0x040004 /* Inconsistent inode count information cached */ + +/* + * Pass 5 errors + */ + +#define PR_5_PASS_HEADER 0x050000 /* Pass 5: Checking group summary information */ +#define PR_5_INODE_BMAP_PADDING 0x050001 /* Padding at end of inode bitmap is not set. */ +#define PR_5_BLOCK_BMAP_PADDING 0x050002 /* Padding at end of block bitmap is not set. */ +#define PR_5_BLOCK_BITMAP_HEADER 0x050003 /* Block bitmap differences header */ +#define PR_5_BLOCK_UNUSED 0x050004 /* Block not used, but marked in bitmap */ +#define PR_5_BLOCK_USED 0x050005 /* Block used, but not marked used in bitmap */ +#define PR_5_BLOCK_BITMAP_END 0x050006 /* Block bitmap differences end */ +#define PR_5_INODE_BITMAP_HEADER 0x050007 /* Inode bitmap differences header */ +#define PR_5_INODE_UNUSED 0x050008 /* Inode not used, but marked in bitmap */ +#define PR_5_INODE_USED 0x050009 /* Inode used, but not marked used in bitmap */ +#define PR_5_INODE_BITMAP_END 0x05000A /* Inode bitmap differences end */ +#define PR_5_FREE_INODE_COUNT_GROUP 0x05000B /* Free inodes count for group wrong */ +#define PR_5_FREE_DIR_COUNT_GROUP 0x05000C /* Directories count for group wrong */ +#define PR_5_FREE_INODE_COUNT 0x05000D /* Free inodes count wrong */ +#define PR_5_FREE_BLOCK_COUNT_GROUP 0x05000E /* Free blocks count for group wrong */ +#define PR_5_FREE_BLOCK_COUNT 0x05000F /* Free blocks count wrong */ +#define PR_5_BMAP_ENDPOINTS 0x050010 /* Programming error: bitmap endpoints don't match */ +#define PR_5_FUDGE_BITMAP_ERROR 0x050011 /* Internal error: fudging end of bitmap */ +#define PR_5_COPY_IBITMAP_ERROR 0x050012 /* Error copying in replacement inode bitmap */ +#define PR_5_COPY_BBITMAP_ERROR 0x050013 /* Error copying in replacement block bitmap */ +#define PR_5_BLOCK_RANGE_UNUSED 0x050014 /* Block range not used, but marked in bitmap */ +#define PR_5_BLOCK_RANGE_USED 0x050015 /* Block range used, but not marked used in bitmap */ +#define PR_5_INODE_RANGE_UNUSED 0x050016 /* Inode range not used, but marked in bitmap */ +#define PR_5_INODE_RANGE_USED 0x050017 /* Inode rangeused, but not marked used in bitmap */ + + +/* + * The directory information structure; stores directory information + * collected in earlier passes, to avoid disk i/o in fetching the + * directory information. + */ +struct dir_info { + ext2_ino_t ino; /* Inode number */ + ext2_ino_t dotdot; /* Parent according to '..' */ + ext2_ino_t parent; /* Parent according to treewalk */ +}; + + + +/* + * The indexed directory information structure; stores information for + * directories which contain a hash tree index. + */ +struct dx_dir_info { + ext2_ino_t ino; /* Inode number */ + int numblocks; /* number of blocks */ + int hashversion; + short depth; /* depth of tree */ + struct dx_dirblock_info *dx_block; /* Array of size numblocks */ +}; + +/* + * Define the extended attribute refcount structure + */ +typedef struct ea_refcount *ext2_refcount_t; + +struct e2fsck_struct { + ext2_filsys fs; + const char *program_name; + char *filesystem_name; + char *device_name; + char *io_options; + int flags; /* E2fsck internal flags */ + int options; + blk_t use_superblock; /* sb requested by user */ + blk_t superblock; /* sb used to open fs */ + int blocksize; /* blocksize */ + blk_t num_blocks; /* Total number of blocks */ + int mount_flags; + blkid_cache blkid; /* blkid cache */ + + jmp_buf abort_loc; + + unsigned long abort_code; + + int (*progress)(e2fsck_t ctx, int pass, unsigned long cur, + unsigned long max); + + ext2fs_inode_bitmap inode_used_map; /* Inodes which are in use */ + ext2fs_inode_bitmap inode_bad_map; /* Inodes which are bad somehow */ + ext2fs_inode_bitmap inode_dir_map; /* Inodes which are directories */ + ext2fs_inode_bitmap inode_imagic_map; /* AFS inodes */ + ext2fs_inode_bitmap inode_reg_map; /* Inodes which are regular files*/ + + ext2fs_block_bitmap block_found_map; /* Blocks which are in use */ + ext2fs_block_bitmap block_dup_map; /* Blks referenced more than once */ + ext2fs_block_bitmap block_ea_map; /* Blocks which are used by EA's */ + + /* + * Inode count arrays + */ + ext2_icount_t inode_count; + ext2_icount_t inode_link_info; + + ext2_refcount_t refcount; + ext2_refcount_t refcount_extra; + + /* + * Array of flags indicating whether an inode bitmap, block + * bitmap, or inode table is invalid + */ + int *invalid_inode_bitmap_flag; + int *invalid_block_bitmap_flag; + int *invalid_inode_table_flag; + int invalid_bitmaps; /* There are invalid bitmaps/itable */ + + /* + * Block buffer + */ + char *block_buf; + + /* + * For pass1_check_directory and pass1_get_blocks + */ + ext2_ino_t stashed_ino; + struct ext2_inode *stashed_inode; + + /* + * Location of the lost and found directory + */ + ext2_ino_t lost_and_found; + int bad_lost_and_found; + + /* + * Directory information + */ + int dir_info_count; + int dir_info_size; + struct dir_info *dir_info; + + /* + * Indexed directory information + */ + int dx_dir_info_count; + int dx_dir_info_size; + struct dx_dir_info *dx_dir_info; + + /* + * Directories to hash + */ + ext2_u32_list dirs_to_hash; + + /* + * Tuning parameters + */ + int process_inode_size; + int inode_buffer_blocks; + + /* + * ext3 journal support + */ + io_channel journal_io; + char *journal_name; + + /* + * How we display the progress update (for unix) + */ + int progress_fd; + int progress_pos; + int progress_last_percent; + unsigned int progress_last_time; + int interactive; /* Are we connected directly to a tty? */ + char start_meta[2], stop_meta[2]; + + /* File counts */ + int fs_directory_count; + int fs_regular_count; + int fs_blockdev_count; + int fs_chardev_count; + int fs_links_count; + int fs_symlinks_count; + int fs_fast_symlinks_count; + int fs_fifo_count; + int fs_total_count; + int fs_sockets_count; + int fs_ind_count; + int fs_dind_count; + int fs_tind_count; + int fs_fragmented; + int large_files; + int fs_ext_attr_inodes; + int fs_ext_attr_blocks; + + int ext_attr_ver; + + /* + * For the use of callers of the e2fsck functions; not used by + * e2fsck functions themselves. + */ + void *priv_data; +}; + + +#define tid_gt(x, y) ((x - y) > 0) + +static inline int tid_geq(tid_t x, tid_t y) +{ + int difference = (x - y); + return (difference >= 0); +} + + diff --git a/e2fsprogs/e2p/Kbuild b/e2fsprogs/e2p/Kbuild new file mode 100644 index 000000000..c0ff824e3 --- /dev/null +++ b/e2fsprogs/e2p/Kbuild @@ -0,0 +1,15 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +NEEDED-$(CONFIG_CHATTR) = y +NEEDED-$(CONFIG_LSATTR) = y +NEEDED-$(CONFIG_MKE2FS) = y +NEEDED-$(CONFIG_TUNE2FS) = y + +lib-y:= +lib-$(NEEDED-y) += fgetsetflags.o fgetsetversion.o pf.o iod.o mntopts.o \ + feature.o ls.o uuid.o pe.o ostype.o ps.o hashstr.o \ + parse_num.o diff --git a/e2fsprogs/e2p/e2p.h b/e2fsprogs/e2p/e2p.h new file mode 100644 index 000000000..2a2367b9f --- /dev/null +++ b/e2fsprogs/e2p/e2p.h @@ -0,0 +1,64 @@ +/* vi: set sw=4 ts=4: */ +#include "busybox.h" +#include /* Needed by dirent.h on netbsd */ +#include +#include + +#include "../ext2fs/ext2_fs.h" + +#define E2P_FEATURE_COMPAT 0 +#define E2P_FEATURE_INCOMPAT 1 +#define E2P_FEATURE_RO_INCOMPAT 2 +#ifndef EXT3_FEATURE_INCOMPAT_EXTENTS +#define EXT3_FEATURE_INCOMPAT_EXTENTS 0x0040 +#endif + +/* `options' for print_flags() */ + +#define PFOPT_LONG 1 /* Must be 1 for compatibility with `int long_format'. */ + +/*int fgetversion (const char * name, unsigned long * version);*/ +/*int fsetversion (const char * name, unsigned long version);*/ +int fgetsetversion(const char * name, unsigned long * get_version, unsigned long set_version); +#define fgetversion(name, version) fgetsetversion(name, version, 0) +#define fsetversion(name, version) fgetsetversion(name, NULL, version) + +/*int fgetflags (const char * name, unsigned long * flags);*/ +/*int fsetflags (const char * name, unsigned long flags);*/ +int fgetsetflags(const char * name, unsigned long * get_flags, unsigned long set_flags); +#define fgetflags(name, flags) fgetsetflags(name, flags, 0) +#define fsetflags(name, flags) fgetsetflags(name, NULL, flags) + +int getflags (int fd, unsigned long * flags); +int getversion (int fd, unsigned long * version); +int iterate_on_dir (const char * dir_name, + int (*func) (const char *, struct dirent *, void *), + void * private); +/*void list_super(struct ext2_super_block * s);*/ +void list_super2(struct ext2_super_block * s, FILE *f); +#define list_super(s) list_super2(s, stdout) +void print_fs_errors (FILE * f, unsigned short errors); +void print_flags (FILE * f, unsigned long flags, unsigned options); +void print_fs_state (FILE * f, unsigned short state); +int setflags (int fd, unsigned long flags); +int setversion (int fd, unsigned long version); + +const char *e2p_feature2string(int compat, unsigned int mask); +int e2p_string2feature(char *string, int *compat, unsigned int *mask); +int e2p_edit_feature(const char *str, __u32 *compat_array, __u32 *ok_array); + +int e2p_is_null_uuid(void *uu); +void e2p_uuid_to_str(void *uu, char *out); +const char *e2p_uuid2str(void *uu); + +const char *e2p_hash2string(int num); +int e2p_string2hash(char *string); + +const char *e2p_mntopt2string(unsigned int mask); +int e2p_string2mntopt(char *string, unsigned int *mask); +int e2p_edit_mntopts(const char *str, __u32 *mntopts, __u32 ok); + +unsigned long parse_num_blocks(const char *arg, int log_block_size); + +char *e2p_os2string(int os_type); +int e2p_string2os(char *str); diff --git a/e2fsprogs/e2p/feature.c b/e2fsprogs/e2p/feature.c new file mode 100644 index 000000000..b45754f97 --- /dev/null +++ b/e2fsprogs/e2p/feature.c @@ -0,0 +1,187 @@ +/* vi: set sw=4 ts=4: */ +/* + * feature.c --- convert between features and strings + * + * Copyright (C) 1999 Theodore Ts'o + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + * + */ + +#include +#include +#include +#include +#include + +#include "e2p.h" + +struct feature { + int compat; + unsigned int mask; + const char *string; +}; + +static const struct feature feature_list[] = { + { E2P_FEATURE_COMPAT, EXT2_FEATURE_COMPAT_DIR_PREALLOC, + "dir_prealloc" }, + { E2P_FEATURE_COMPAT, EXT3_FEATURE_COMPAT_HAS_JOURNAL, + "has_journal" }, + { E2P_FEATURE_COMPAT, EXT2_FEATURE_COMPAT_IMAGIC_INODES, + "imagic_inodes" }, + { E2P_FEATURE_COMPAT, EXT2_FEATURE_COMPAT_EXT_ATTR, + "ext_attr" }, + { E2P_FEATURE_COMPAT, EXT2_FEATURE_COMPAT_DIR_INDEX, + "dir_index" }, + { E2P_FEATURE_COMPAT, EXT2_FEATURE_COMPAT_RESIZE_INODE, + "resize_inode" }, + { E2P_FEATURE_RO_INCOMPAT, EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER, + "sparse_super" }, + { E2P_FEATURE_RO_INCOMPAT, EXT2_FEATURE_RO_COMPAT_LARGE_FILE, + "large_file" }, + { E2P_FEATURE_INCOMPAT, EXT2_FEATURE_INCOMPAT_COMPRESSION, + "compression" }, + { E2P_FEATURE_INCOMPAT, EXT2_FEATURE_INCOMPAT_FILETYPE, + "filetype" }, + { E2P_FEATURE_INCOMPAT, EXT3_FEATURE_INCOMPAT_RECOVER, + "needs_recovery" }, + { E2P_FEATURE_INCOMPAT, EXT3_FEATURE_INCOMPAT_JOURNAL_DEV, + "journal_dev" }, + { E2P_FEATURE_INCOMPAT, EXT3_FEATURE_INCOMPAT_EXTENTS, + "extents" }, + { E2P_FEATURE_INCOMPAT, EXT2_FEATURE_INCOMPAT_META_BG, + "meta_bg" }, + { 0, 0, 0 }, +}; + +const char *e2p_feature2string(int compat, unsigned int mask) +{ + const struct feature *f; + static char buf[20]; + char fchar; + int fnum; + + for (f = feature_list; f->string; f++) { + if ((compat == f->compat) && + (mask == f->mask)) + return f->string; + } + switch (compat) { + case E2P_FEATURE_COMPAT: + fchar = 'C'; + break; + case E2P_FEATURE_INCOMPAT: + fchar = 'I'; + break; + case E2P_FEATURE_RO_INCOMPAT: + fchar = 'R'; + break; + default: + fchar = '?'; + break; + } + for (fnum = 0; mask >>= 1; fnum++); + sprintf(buf, "FEATURE_%c%d", fchar, fnum); + return buf; +} + +int e2p_string2feature(char *string, int *compat_type, unsigned int *mask) +{ + const struct feature *f; + char *eptr; + int num; + + for (f = feature_list; f->string; f++) { + if (!strcasecmp(string, f->string)) { + *compat_type = f->compat; + *mask = f->mask; + return 0; + } + } + if (strncasecmp(string, "FEATURE_", 8)) + return 1; + + switch (string[8]) { + case 'c': + case 'C': + *compat_type = E2P_FEATURE_COMPAT; + break; + case 'i': + case 'I': + *compat_type = E2P_FEATURE_INCOMPAT; + break; + case 'r': + case 'R': + *compat_type = E2P_FEATURE_RO_INCOMPAT; + break; + default: + return 1; + } + if (string[9] == 0) + return 1; + num = strtol(string+9, &eptr, 10); + if (num > 32 || num < 0) + return 1; + if (*eptr) + return 1; + *mask = 1 << num; + return 0; +} + +static inline char *skip_over_blanks(char *cp) +{ + while (*cp && isspace(*cp)) + cp++; + return cp; +} + +static inline char *skip_over_word(char *cp) +{ + while (*cp && !isspace(*cp) && *cp != ',') + cp++; + return cp; +} + +/* + * Edit a feature set array as requested by the user. The ok_array, + * if set, allows the application to limit what features the user is + * allowed to set or clear using this function. + */ +int e2p_edit_feature(const char *str, __u32 *compat_array, __u32 *ok_array) +{ + char *cp, *buf, *next; + int neg; + unsigned int mask; + int compat_type; + + buf = xstrdup(str); + cp = buf; + while (cp && *cp) { + neg = 0; + cp = skip_over_blanks(cp); + next = skip_over_word(cp); + if (*next == 0) + next = 0; + else + *next = 0; + switch (*cp) { + case '-': + case '^': + neg++; + case '+': + cp++; + break; + } + if (e2p_string2feature(cp, &compat_type, &mask)) + return 1; + if (ok_array && !(ok_array[compat_type] & mask)) + return 1; + if (neg) + compat_array[compat_type] &= ~mask; + else + compat_array[compat_type] |= mask; + cp = next ? next+1 : 0; + } + return 0; +} diff --git a/e2fsprogs/e2p/fgetsetflags.c b/e2fsprogs/e2p/fgetsetflags.c new file mode 100644 index 000000000..008b79850 --- /dev/null +++ b/e2fsprogs/e2p/fgetsetflags.c @@ -0,0 +1,70 @@ +/* vi: set sw=4 ts=4: */ +/* + * fgetflags.c - Get a file flags on an ext2 file system + * fsetflags.c - Set a file flags on an ext2 file system + * + * Copyright (C) 1993, 1994 Remy Card + * Laboratoire MASI, Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + */ + +/* + * History: + * 93/10/30 - Creation + */ + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include +#ifdef HAVE_EXT2_IOCTLS +#include +#include +#endif + +#include "e2p.h" + +#ifdef O_LARGEFILE +#define OPEN_FLAGS (O_RDONLY|O_NONBLOCK|O_LARGEFILE) +#else +#define OPEN_FLAGS (O_RDONLY|O_NONBLOCK) +#endif + +int fgetsetflags (const char * name, unsigned long * get_flags, unsigned long set_flags) +{ +#ifdef HAVE_EXT2_IOCTLS + struct stat buf; + int fd, r, f, save_errno = 0; + + if (!stat(name, &buf) && + !S_ISREG(buf.st_mode) && !S_ISDIR(buf.st_mode)) { + goto notsupp; + } + fd = open (name, OPEN_FLAGS); + if (fd == -1) + return -1; + if (!get_flags) { + f = (int) set_flags; + r = ioctl (fd, EXT2_IOC_SETFLAGS, &f); + } else { + r = ioctl (fd, EXT2_IOC_GETFLAGS, &f); + *get_flags = f; + } + if (r == -1) + save_errno = errno; + close (fd); + if (save_errno) + errno = save_errno; + return r; +notsupp: +#endif /* HAVE_EXT2_IOCTLS */ + errno = EOPNOTSUPP; + return -1; +} diff --git a/e2fsprogs/e2p/fgetsetversion.c b/e2fsprogs/e2p/fgetsetversion.c new file mode 100644 index 000000000..8d79054d6 --- /dev/null +++ b/e2fsprogs/e2p/fgetsetversion.c @@ -0,0 +1,70 @@ +/* vi: set sw=4 ts=4: */ +/* + * fgetversion.c - Get a file version on an ext2 file system + * fsetversion.c - Set a file version on an ext2 file system + * + * + * Copyright (C) 1993, 1994 Remy Card + * Laboratoire MASI, Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + */ + +/* + * History: + * 93/10/30 - Creation + */ + +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include + +#include "e2p.h" + +#ifdef O_LARGEFILE +#define OPEN_FLAGS (O_RDONLY|O_NONBLOCK|O_LARGEFILE) +#else +#define OPEN_FLAGS (O_RDONLY|O_NONBLOCK) +#endif + +/* + To do fsetversion: unsigned long *ptr_version must be set to NULL. + and unsigned long version must be set to a value + To do fgetversion: unsigned long *ptr_version must NOT be set to NULL + and unsigned long version is ignored. + TITO. +*/ + +int fgetsetversion (const char * name, unsigned long * get_version, unsigned long set_version) +{ +#ifdef HAVE_EXT2_IOCTLS + int fd, r, ver, save_errno = 0; + + fd = open (name, OPEN_FLAGS); + if (fd == -1) + return -1; + if (!get_version) { + ver = (int) set_version; + r = ioctl (fd, EXT2_IOC_SETVERSION, &ver); + } else { + r = ioctl (fd, EXT2_IOC_GETVERSION, &ver); + *get_version = ver; + } + if (r == -1) + save_errno = errno; + close (fd); + if (save_errno) + errno = save_errno; + return r; +#else /* ! HAVE_EXT2_IOCTLS */ + errno = EOPNOTSUPP; + return -1; +#endif /* ! HAVE_EXT2_IOCTLS */ +} diff --git a/e2fsprogs/e2p/hashstr.c b/e2fsprogs/e2p/hashstr.c new file mode 100644 index 000000000..697ffadc3 --- /dev/null +++ b/e2fsprogs/e2p/hashstr.c @@ -0,0 +1,70 @@ +/* vi: set sw=4 ts=4: */ +/* + * feature.c --- convert between features and strings + * + * Copyright (C) 1999 Theodore Ts'o + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + * + */ + +#include +#include +#include +#include +#include + +#include "e2p.h" + +struct hash { + int num; + const char *string; +}; + +static const struct hash hash_list[] = { + { EXT2_HASH_LEGACY, "legacy" }, + { EXT2_HASH_HALF_MD4, "half_md4" }, + { EXT2_HASH_TEA, "tea" }, + { 0, 0 }, +}; + +const char *e2p_hash2string(int num) +{ + const struct hash *p; + static char buf[20]; + + for (p = hash_list; p->string; p++) { + if (num == p->num) + return p->string; + } + sprintf(buf, "HASHALG_%d", num); + return buf; +} + +/* + * Returns the hash algorithm, or -1 on error + */ +int e2p_string2hash(char *string) +{ + const struct hash *p; + char *eptr; + int num; + + for (p = hash_list; p->string; p++) { + if (!strcasecmp(string, p->string)) { + return p->num; + } + } + if (strncasecmp(string, "HASHALG_", 8)) + return -1; + + if (string[8] == 0) + return -1; + num = strtol(string+8, &eptr, 10); + if (num > 255 || num < 0) + return -1; + if (*eptr) + return -1; + return num; +} diff --git a/e2fsprogs/e2p/iod.c b/e2fsprogs/e2p/iod.c new file mode 100644 index 000000000..8d4c5cdcb --- /dev/null +++ b/e2fsprogs/e2p/iod.c @@ -0,0 +1,52 @@ +/* vi: set sw=4 ts=4: */ +/* + * iod.c - Iterate a function on each entry of a directory + * + * Copyright (C) 1993, 1994 Remy Card + * Laboratoire MASI, Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + */ + +/* + * History: + * 93/10/30 - Creation + */ + +#include "e2p.h" +#include +#include +#include + +int iterate_on_dir (const char * dir_name, + int (*func) (const char *, struct dirent *, void *), + void * private) +{ + DIR * dir; + struct dirent *de, *dep; + int max_len, len; + + max_len = PATH_MAX + sizeof(struct dirent); + de = (struct dirent *)xmalloc(max_len+1); + memset(de, 0, max_len+1); + + dir = opendir (dir_name); + if (dir == NULL) { + free(de); + return -1; + } + while ((dep = readdir (dir))) { + len = sizeof(struct dirent); + if (len < dep->d_reclen) + len = dep->d_reclen; + if (len > max_len) + len = max_len; + memcpy(de, dep, len); + (*func) (dir_name, de, private); + } + free(de); + closedir(dir); + return 0; +} diff --git a/e2fsprogs/e2p/ls.c b/e2fsprogs/e2p/ls.c new file mode 100644 index 000000000..9d29db6af --- /dev/null +++ b/e2fsprogs/e2p/ls.c @@ -0,0 +1,273 @@ +/* vi: set sw=4 ts=4: */ +/* + * ls.c - List the contents of an ext2fs superblock + * + * Copyright (C) 1992, 1993, 1994 Remy Card + * Laboratoire MASI, Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * Copyright (C) 1995, 1996, 1997 Theodore Ts'o + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "e2p.h" + +static void print_user(unsigned short uid, FILE *f) +{ + struct passwd *pw = getpwuid(uid); + fprintf(f, "%u (user %s)\n", uid, + (pw == NULL ? "unknown" : pw->pw_name)); +} + +static void print_group(unsigned short gid, FILE *f) +{ + struct group *gr = getgrgid(gid); + fprintf(f, "%u (group %s)\n", gid, + (gr == NULL ? "unknown" : gr->gr_name)); +} + +#define MONTH_INT (86400 * 30) +#define WEEK_INT (86400 * 7) +#define DAY_INT (86400) +#define HOUR_INT (60 * 60) +#define MINUTE_INT (60) + +static const char *interval_string(unsigned int secs) +{ + static char buf[256], tmp[80]; + int hr, min, num; + + buf[0] = 0; + + if (secs == 0) + return ""; + + if (secs >= MONTH_INT) { + num = secs / MONTH_INT; + secs -= num*MONTH_INT; + sprintf(buf, "%d month%s", num, (num>1) ? "s" : ""); + } + if (secs >= WEEK_INT) { + num = secs / WEEK_INT; + secs -= num*WEEK_INT; + sprintf(tmp, "%s%d week%s", buf[0] ? ", " : "", + num, (num>1) ? "s" : ""); + strcat(buf, tmp); + } + if (secs >= DAY_INT) { + num = secs / DAY_INT; + secs -= num*DAY_INT; + sprintf(tmp, "%s%d day%s", buf[0] ? ", " : "", + num, (num>1) ? "s" : ""); + strcat(buf, tmp); + } + if (secs > 0) { + hr = secs / HOUR_INT; + secs -= hr*HOUR_INT; + min = secs / MINUTE_INT; + secs -= min*MINUTE_INT; + sprintf(tmp, "%s%d:%02d:%02d", buf[0] ? ", " : "", + hr, min, secs); + strcat(buf, tmp); + } + return buf; +} + +static void print_features(struct ext2_super_block * s, FILE *f) +{ +#ifdef EXT2_DYNAMIC_REV + int i, j, printed=0; + __u32 *mask = &s->s_feature_compat, m; + + fprintf(f, "Filesystem features: "); + for (i=0; i <3; i++,mask++) { + for (j=0,m=1; j < 32; j++, m<<=1) { + if (*mask & m) { + fprintf(f, " %s", e2p_feature2string(i, m)); + printed++; + } + } + } + if (printed == 0) + fprintf(f, " (none)"); + fprintf(f, "\n"); +#endif +} + +static void print_mntopts(struct ext2_super_block * s, FILE *f) +{ +#ifdef EXT2_DYNAMIC_REV + int i, printed=0; + __u32 mask = s->s_default_mount_opts, m; + + fprintf(f, "Default mount options: "); + if (mask & EXT3_DEFM_JMODE) { + fprintf(f, " %s", e2p_mntopt2string(mask & EXT3_DEFM_JMODE)); + printed++; + } + for (i=0,m=1; i < 32; i++, m<<=1) { + if (m & EXT3_DEFM_JMODE) + continue; + if (mask & m) { + fprintf(f, " %s", e2p_mntopt2string(m)); + printed++; + } + } + if (printed == 0) + fprintf(f, " (none)"); + fprintf(f, "\n"); +#endif +} + + +#ifndef EXT2_INODE_SIZE +#define EXT2_INODE_SIZE(s) sizeof(struct ext2_inode) +#endif + +#ifndef EXT2_GOOD_OLD_REV +#define EXT2_GOOD_OLD_REV 0 +#endif + +void list_super2(struct ext2_super_block * sb, FILE *f) +{ + int inode_blocks_per_group; + char buf[80], *str; + time_t tm; + + inode_blocks_per_group = (((sb->s_inodes_per_group * + EXT2_INODE_SIZE(sb)) + + EXT2_BLOCK_SIZE(sb) - 1) / + EXT2_BLOCK_SIZE(sb)); + if (sb->s_volume_name[0]) { + memset(buf, 0, sizeof(buf)); + strncpy(buf, sb->s_volume_name, sizeof(sb->s_volume_name)); + } else + strcpy(buf, ""); + fprintf(f, "Filesystem volume name: %s\n", buf); + if (sb->s_last_mounted[0]) { + memset(buf, 0, sizeof(buf)); + strncpy(buf, sb->s_last_mounted, sizeof(sb->s_last_mounted)); + } else + strcpy(buf, ""); + fprintf(f, + "Last mounted on: %s\n" + "Filesystem UUID: %s\n" + "Filesystem magic number: 0x%04X\n" + "Filesystem revision #: %d", + buf, e2p_uuid2str(sb->s_uuid), sb->s_magic, sb->s_rev_level); + if (sb->s_rev_level == EXT2_GOOD_OLD_REV) { + fprintf(f, " (original)\n"); +#ifdef EXT2_DYNAMIC_REV + } else if (sb->s_rev_level == EXT2_DYNAMIC_REV) { + fprintf(f, " (dynamic)\n"); +#endif + } else + fprintf(f, " (unknown)\n"); + print_features(sb, f); + print_mntopts(sb, f); + fprintf(f, "Filesystem state: "); + print_fs_state (f, sb->s_state); + fprintf(f, "\nErrors behavior: "); + print_fs_errors(f, sb->s_errors); + str = e2p_os2string(sb->s_creator_os); + fprintf(f, + "\n" + "Filesystem OS type: %s\n" + "Inode count: %u\n" + "Block count: %u\n" + "Reserved block count: %u\n" + "Free blocks: %u\n" + "Free inodes: %u\n" + "First block: %u\n" + "Block size: %u\n" + "Fragment size: %u\n", + str, sb->s_inodes_count, sb->s_blocks_count, sb->s_r_blocks_count, + sb->s_free_blocks_count, sb->s_free_inodes_count, + sb->s_first_data_block, EXT2_BLOCK_SIZE(sb), EXT2_FRAG_SIZE(sb)); + free(str); + if (sb->s_reserved_gdt_blocks) + fprintf(f, "Reserved GDT blocks: %u\n", + sb->s_reserved_gdt_blocks); + fprintf(f, + "Blocks per group: %u\n" + "Fragments per group: %u\n" + "Inodes per group: %u\n" + "Inode blocks per group: %u\n", + sb->s_blocks_per_group, sb->s_frags_per_group, + sb->s_inodes_per_group, inode_blocks_per_group); + if (sb->s_first_meta_bg) + fprintf(f, "First meta block group: %u\n", + sb->s_first_meta_bg); + if (sb->s_mkfs_time) { + tm = sb->s_mkfs_time; + fprintf(f, "Filesystem created: %s", ctime(&tm)); + } + tm = sb->s_mtime; + fprintf(f, "Last mount time: %s", + sb->s_mtime ? ctime(&tm) : "n/a\n"); + tm = sb->s_wtime; + fprintf(f, + "Last write time: %s" + "Mount count: %u\n" + "Maximum mount count: %d\n", + ctime(&tm), sb->s_mnt_count, sb->s_max_mnt_count); + tm = sb->s_lastcheck; + fprintf(f, + "Last checked: %s" + "Check interval: %u (%s)\n", + ctime(&tm), + sb->s_checkinterval, interval_string(sb->s_checkinterval)); + if (sb->s_checkinterval) + { + time_t next; + + next = sb->s_lastcheck + sb->s_checkinterval; + fprintf(f, "Next check after: %s", ctime(&next)); + } + fprintf(f, "Reserved blocks uid: "); + print_user(sb->s_def_resuid, f); + fprintf(f, "Reserved blocks gid: "); + print_group(sb->s_def_resgid, f); + if (sb->s_rev_level >= EXT2_DYNAMIC_REV) { + fprintf(f, + "First inode: %d\n" + "Inode size: %d\n", + sb->s_first_ino, sb->s_inode_size); + } + if (!e2p_is_null_uuid(sb->s_journal_uuid)) + fprintf(f, "Journal UUID: %s\n", + e2p_uuid2str(sb->s_journal_uuid)); + if (sb->s_journal_inum) + fprintf(f, "Journal inode: %u\n", + sb->s_journal_inum); + if (sb->s_journal_dev) + fprintf(f, "Journal device: 0x%04x\n", + sb->s_journal_dev); + if (sb->s_last_orphan) + fprintf(f, "First orphan inode: %u\n", + sb->s_last_orphan); + if ((sb->s_feature_compat & EXT2_FEATURE_COMPAT_DIR_INDEX) || + sb->s_def_hash_version) + fprintf(f, "Default directory hash: %s\n", + e2p_hash2string(sb->s_def_hash_version)); + if (!e2p_is_null_uuid(sb->s_hash_seed)) + fprintf(f, "Directory Hash Seed: %s\n", + e2p_uuid2str(sb->s_hash_seed)); + if (sb->s_jnl_backup_type) { + fprintf(f, "Journal backup: "); + if (sb->s_jnl_backup_type == 1) + fprintf(f, "inode blocks\n"); + else + fprintf(f, "type %u\n", sb->s_jnl_backup_type); + } +} diff --git a/e2fsprogs/e2p/mntopts.c b/e2fsprogs/e2p/mntopts.c new file mode 100644 index 000000000..17c26c480 --- /dev/null +++ b/e2fsprogs/e2p/mntopts.c @@ -0,0 +1,134 @@ +/* vi: set sw=4 ts=4: */ +/* + * mountopts.c --- convert between default mount options and strings + * + * Copyright (C) 2002 Theodore Ts'o + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + * + */ + +#include +#include +#include +#include +#include + +#include "e2p.h" + +struct mntopt { + unsigned int mask; + const char *string; +}; + +static const struct mntopt mntopt_list[] = { + { EXT2_DEFM_DEBUG, "debug" }, + { EXT2_DEFM_BSDGROUPS, "bsdgroups" }, + { EXT2_DEFM_XATTR_USER, "user_xattr" }, + { EXT2_DEFM_ACL, "acl" }, + { EXT2_DEFM_UID16, "uid16" }, + { EXT3_DEFM_JMODE_DATA, "journal_data" }, + { EXT3_DEFM_JMODE_ORDERED, "journal_data_ordered" }, + { EXT3_DEFM_JMODE_WBACK, "journal_data_writeback" }, + { 0, 0 }, +}; + +const char *e2p_mntopt2string(unsigned int mask) +{ + const struct mntopt *f; + static char buf[20]; + int fnum; + + for (f = mntopt_list; f->string; f++) { + if (mask == f->mask) + return f->string; + } + for (fnum = 0; mask >>= 1; fnum++); + sprintf(buf, "MNTOPT_%d", fnum); + return buf; +} + +int e2p_string2mntopt(char *string, unsigned int *mask) +{ + const struct mntopt *f; + char *eptr; + int num; + + for (f = mntopt_list; f->string; f++) { + if (!strcasecmp(string, f->string)) { + *mask = f->mask; + return 0; + } + } + if (strncasecmp(string, "MNTOPT_", 8)) + return 1; + + if (string[8] == 0) + return 1; + num = strtol(string+8, &eptr, 10); + if (num > 32 || num < 0) + return 1; + if (*eptr) + return 1; + *mask = 1 << num; + return 0; +} + +static char *skip_over_blanks(char *cp) +{ + while (*cp && isspace(*cp)) + cp++; + return cp; +} + +static char *skip_over_word(char *cp) +{ + while (*cp && !isspace(*cp) && *cp != ',') + cp++; + return cp; +} + +/* + * Edit a mntopt set array as requested by the user. The ok + * parameter, if non-zero, allows the application to limit what + * mntopts the user is allowed to set or clear using this function. + */ +int e2p_edit_mntopts(const char *str, __u32 *mntopts, __u32 ok) +{ + char *cp, *buf, *next; + int neg; + unsigned int mask; + + buf = xstrdup(str); + cp = buf; + while (cp && *cp) { + neg = 0; + cp = skip_over_blanks(cp); + next = skip_over_word(cp); + if (*next == 0) + next = 0; + else + *next = 0; + switch (*cp) { + case '-': + case '^': + neg++; + case '+': + cp++; + break; + } + if (e2p_string2mntopt(cp, &mask)) + return 1; + if (ok && !(ok & mask)) + return 1; + if (mask & EXT3_DEFM_JMODE) + *mntopts &= ~EXT3_DEFM_JMODE; + if (neg) + *mntopts &= ~mask; + else + *mntopts |= mask; + cp = next ? next+1 : 0; + } + return 0; +} diff --git a/e2fsprogs/e2p/ostype.c b/e2fsprogs/e2p/ostype.c new file mode 100644 index 000000000..0e111d408 --- /dev/null +++ b/e2fsprogs/e2p/ostype.c @@ -0,0 +1,74 @@ +/* vi: set sw=4 ts=4: */ +/* + * getostype.c - Get the Filesystem OS type + * + * Copyright (C) 2004,2005 Theodore Ts'o + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + */ + +#include "e2p.h" +#include +#include + +static const char * const os_tab[] = + { "Linux", + "Hurd", + "Masix", + "FreeBSD", + "Lites", + 0 }; + +/* + * Convert an os_type to a string + */ +char *e2p_os2string(int os_type) +{ + const char *os; + char *ret; + + if (os_type <= EXT2_OS_LITES) + os = os_tab[os_type]; + else + os = "(unknown os)"; + + ret = xstrdup(os); + return ret; +} + +/* + * Convert an os_type to a string + */ +int e2p_string2os(char *str) +{ + const char * const *cpp; + int i = 0; + + for (cpp = os_tab; *cpp; cpp++, i++) { + if (!strcasecmp(str, *cpp)) + return i; + } + return -1; +} + +#ifdef TEST_PROGRAM +int main(int argc, char **argv) +{ + char *s; + int i, os; + + for (i=0; i <= EXT2_OS_LITES; i++) { + s = e2p_os2string(i); + os = e2p_string2os(s); + printf("%d: %s (%d)\n", i, s, os); + if (i != os) { + fprintf(stderr, "Failure!\n"); + exit(1); + } + } + exit(0); +} +#endif + + diff --git a/e2fsprogs/e2p/parse_num.c b/e2fsprogs/e2p/parse_num.c new file mode 100644 index 000000000..6db076f9c --- /dev/null +++ b/e2fsprogs/e2p/parse_num.c @@ -0,0 +1,65 @@ +/* vi: set sw=4 ts=4: */ +/* + * parse_num.c - Parse the number of blocks + * + * Copyright (C) 2004,2005 Theodore Ts'o + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + */ + +#include "e2p.h" + +#include + +unsigned long parse_num_blocks(const char *arg, int log_block_size) +{ + char *p; + unsigned long long num; + + num = strtoull(arg, &p, 0); + + if (p[0] && p[1]) + return 0; + + switch (*p) { /* Using fall-through logic */ + case 'T': case 't': + num <<= 10; + case 'G': case 'g': + num <<= 10; + case 'M': case 'm': + num <<= 10; + case 'K': case 'k': + num >>= log_block_size; + break; + case 's': + num >>= 1; + break; + case '\0': + break; + default: + return 0; + } + return num; +} + +#ifdef DEBUG +#include +#include + +main(int argc, char **argv) +{ + unsigned long num; + int log_block_size = 0; + + if (argc != 2) { + fprintf(stderr, "Usage: %s arg\n", argv[0]); + exit(1); + } + + num = parse_num_blocks(argv[1], log_block_size); + + printf("Parsed number: %lu\n", num); + exit(0); +} +#endif diff --git a/e2fsprogs/e2p/pe.c b/e2fsprogs/e2p/pe.c new file mode 100644 index 000000000..835274b54 --- /dev/null +++ b/e2fsprogs/e2p/pe.c @@ -0,0 +1,32 @@ +/* vi: set sw=4 ts=4: */ +/* + * pe.c - Print a second extended filesystem errors behavior + * + * Copyright (C) 1992, 1993, 1994 Remy Card + * Laboratoire MASI, Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + */ + +/* + * History: + * 94/01/09 - Creation + */ + +#include + +#include "e2p.h" + +void print_fs_errors(FILE *f, unsigned short errors) +{ + char *disp = NULL; + switch (errors) { + case EXT2_ERRORS_CONTINUE: disp = "Continue"; break; + case EXT2_ERRORS_RO: disp = "Remount read-only"; break; + case EXT2_ERRORS_PANIC: disp = "Panic"; break; + default: disp = "Unknown (continue)"; + } + fprintf(f, disp); +} diff --git a/e2fsprogs/e2p/pf.c b/e2fsprogs/e2p/pf.c new file mode 100644 index 000000000..55d4bc487 --- /dev/null +++ b/e2fsprogs/e2p/pf.c @@ -0,0 +1,74 @@ +/* vi: set sw=4 ts=4: */ +/* + * pf.c - Print file attributes on an ext2 file system + * + * Copyright (C) 1993, 1994 Remy Card + * Laboratoire MASI, Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + */ + +/* + * History: + * 93/10/30 - Creation + */ + +#include + +#include "e2p.h" + +struct flags_name { + unsigned long flag; + const char *short_name; + const char *long_name; +}; + +static const struct flags_name flags_array[] = { + { EXT2_SECRM_FL, "s", "Secure_Deletion" }, + { EXT2_UNRM_FL, "u" , "Undelete" }, + { EXT2_SYNC_FL, "S", "Synchronous_Updates" }, + { EXT2_DIRSYNC_FL, "D", "Synchronous_Directory_Updates" }, + { EXT2_IMMUTABLE_FL, "i", "Immutable" }, + { EXT2_APPEND_FL, "a", "Append_Only" }, + { EXT2_NODUMP_FL, "d", "No_Dump" }, + { EXT2_NOATIME_FL, "A", "No_Atime" }, + { EXT2_COMPR_FL, "c", "Compression_Requested" }, +#ifdef ENABLE_COMPRESSION + { EXT2_COMPRBLK_FL, "B", "Compressed_File" }, + { EXT2_DIRTY_FL, "Z", "Compressed_Dirty_File" }, + { EXT2_NOCOMPR_FL, "X", "Compression_Raw_Access" }, + { EXT2_ECOMPR_FL, "E", "Compression_Error" }, +#endif + { EXT3_JOURNAL_DATA_FL, "j", "Journaled_Data" }, + { EXT2_INDEX_FL, "I", "Indexed_direcctory" }, + { EXT2_NOTAIL_FL, "t", "No_Tailmerging" }, + { EXT2_TOPDIR_FL, "T", "Top_of_Directory_Hierarchies" }, + { 0, NULL, NULL } +}; + +void print_flags (FILE * f, unsigned long flags, unsigned options) +{ + int long_opt = (options & PFOPT_LONG); + const struct flags_name *fp; + int first = 1; + + for (fp = flags_array; fp->flag != 0; fp++) { + if (flags & fp->flag) { + if (long_opt) { + if (first) + first = 0; + else + fputs(", ", f); + fputs(fp->long_name, f); + } else + fputs(fp->short_name, f); + } else { + if (!long_opt) + fputs("-", f); + } + } + if (long_opt && first) + fputs("---", f); +} diff --git a/e2fsprogs/e2p/ps.c b/e2fsprogs/e2p/ps.c new file mode 100644 index 000000000..a6b4099db --- /dev/null +++ b/e2fsprogs/e2p/ps.c @@ -0,0 +1,27 @@ +/* vi: set sw=4 ts=4: */ +/* + * ps.c - Print filesystem state + * + * Copyright (C) 1993, 1994 Remy Card + * Laboratoire MASI, Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * This file can be redistributed under the terms of the GNU Library General + * Public License + */ + +/* + * History: + * 93/12/22 - Creation + */ + +#include + +#include "e2p.h" + +void print_fs_state(FILE *f, unsigned short state) +{ + fprintf(f, (state & EXT2_VALID_FS ? " clean" : " not clean")); + if (state & EXT2_ERROR_FS) + fprintf(f, " with errors"); +} diff --git a/e2fsprogs/e2p/uuid.c b/e2fsprogs/e2p/uuid.c new file mode 100644 index 000000000..474d64a5a --- /dev/null +++ b/e2fsprogs/e2p/uuid.c @@ -0,0 +1,78 @@ +/* vi: set sw=4 ts=4: */ +/* + * uuid.c -- utility routines for manipulating UUID's. + */ + +#include +#include +#include "../ext2fs/ext2_types.h" + +#include "e2p.h" + +struct uuid { + __u32 time_low; + __u16 time_mid; + __u16 time_hi_and_version; + __u16 clock_seq; + __u8 node[6]; +}; + +/* Returns 1 if the uuid is the NULL uuid */ +int e2p_is_null_uuid(void *uu) +{ + __u8 *cp; + int i; + + for (i=0, cp = uu; i < 16; i++) + if (*cp) + return 0; + return 1; +} + +static void e2p_unpack_uuid(void *in, struct uuid *uu) +{ + __u8 *ptr = in; + __u32 tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + tmp = (tmp << 8) | *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->time_low = tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->time_mid = tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->time_hi_and_version = tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->clock_seq = tmp; + + memcpy(uu->node, ptr, 6); +} + +void e2p_uuid_to_str(void *uu, char *out) +{ + struct uuid uuid; + + e2p_unpack_uuid(uu, &uuid); + sprintf(out, + "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + uuid.time_low, uuid.time_mid, uuid.time_hi_and_version, + uuid.clock_seq >> 8, uuid.clock_seq & 0xFF, + uuid.node[0], uuid.node[1], uuid.node[2], + uuid.node[3], uuid.node[4], uuid.node[5]); +} + +const char *e2p_uuid2str(void *uu) +{ + static char buf[80]; + if (e2p_is_null_uuid(uu)) + return ""; + e2p_uuid_to_str(uu, buf); + return buf; +} diff --git a/e2fsprogs/ext2fs/Kbuild b/e2fsprogs/ext2fs/Kbuild new file mode 100644 index 000000000..185887a44 --- /dev/null +++ b/e2fsprogs/ext2fs/Kbuild @@ -0,0 +1,23 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +NEEDED-$(CONFIG_E2FSCK) = y +NEEDED-$(CONFIG_FSCK) = y +NEEDED-$(CONFIG_MKE2FS) = y +NEEDED-$(CONFIG_TUNE2FS) = y + +lib-y:= +lib-$(NEEDED-y) += gen_bitmap.o bitops.o ismounted.o mkjournal.o unix_io.o \ + rw_bitmaps.o initialize.o bitmaps.o block.o \ + ind_block.o inode.o freefs.o alloc_stats.o closefs.o \ + openfs.o io_manager.o finddev.o read_bb.o alloc.o badblocks.o \ + getsize.o getsectsize.o alloc_tables.o read_bb_file.o mkdir.o \ + bb_inode.o newdir.o alloc_sb.o lookup.o dirblock.o expanddir.o \ + dir_iterate.o link.o res_gdt.o icount.o get_pathname.o dblist.o \ + dirhash.o version.o flushb.o unlink.o check_desc.o valid_blk.o \ + ext_attr.o bmap.o dblist_dir.o ext2fs_inline.o swapfs.o + +CFLAGS += -include $(srctree)/e2fsprogs/e2fsbb.h diff --git a/e2fsprogs/ext2fs/alloc.c b/e2fsprogs/ext2fs/alloc.c new file mode 100644 index 000000000..590f23a7e --- /dev/null +++ b/e2fsprogs/ext2fs/alloc.c @@ -0,0 +1,174 @@ +/* vi: set sw=4 ts=4: */ +/* + * alloc.c --- allocate new inodes, blocks for ext2fs + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + * + */ + +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * Right now, just search forward from the parent directory's block + * group to find the next free inode. + * + * Should have a special policy for directories. + */ +errcode_t ext2fs_new_inode(ext2_filsys fs, ext2_ino_t dir, + int mode EXT2FS_ATTR((unused)), + ext2fs_inode_bitmap map, ext2_ino_t *ret) +{ + ext2_ino_t dir_group = 0; + ext2_ino_t i; + ext2_ino_t start_inode; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (!map) + map = fs->inode_map; + if (!map) + return EXT2_ET_NO_INODE_BITMAP; + + if (dir > 0) + dir_group = (dir - 1) / EXT2_INODES_PER_GROUP(fs->super); + + start_inode = (dir_group * EXT2_INODES_PER_GROUP(fs->super)) + 1; + if (start_inode < EXT2_FIRST_INODE(fs->super)) + start_inode = EXT2_FIRST_INODE(fs->super); + i = start_inode; + + do { + if (!ext2fs_fast_test_inode_bitmap(map, i)) + break; + i++; + if (i > fs->super->s_inodes_count) + i = EXT2_FIRST_INODE(fs->super); + } while (i != start_inode); + + if (ext2fs_test_inode_bitmap(map, i)) + return EXT2_ET_INODE_ALLOC_FAIL; + *ret = i; + return 0; +} + +/* + * Stupid algorithm --- we now just search forward starting from the + * goal. Should put in a smarter one someday.... + */ +errcode_t ext2fs_new_block(ext2_filsys fs, blk_t goal, + ext2fs_block_bitmap map, blk_t *ret) +{ + blk_t i; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (!map) + map = fs->block_map; + if (!map) + return EXT2_ET_NO_BLOCK_BITMAP; + if (!goal || (goal >= fs->super->s_blocks_count)) + goal = fs->super->s_first_data_block; + i = goal; + do { + if (!ext2fs_fast_test_block_bitmap(map, i)) { + *ret = i; + return 0; + } + i++; + if (i >= fs->super->s_blocks_count) + i = fs->super->s_first_data_block; + } while (i != goal); + return EXT2_ET_BLOCK_ALLOC_FAIL; +} + +/* + * This function zeros out the allocated block, and updates all of the + * appropriate filesystem records. + */ +errcode_t ext2fs_alloc_block(ext2_filsys fs, blk_t goal, + char *block_buf, blk_t *ret) +{ + errcode_t retval; + blk_t block; + char *buf = 0; + + if (!block_buf) { + retval = ext2fs_get_mem(fs->blocksize, &buf); + if (retval) + return retval; + block_buf = buf; + } + memset(block_buf, 0, fs->blocksize); + + if (!fs->block_map) { + retval = ext2fs_read_block_bitmap(fs); + if (retval) + goto fail; + } + + retval = ext2fs_new_block(fs, goal, 0, &block); + if (retval) + goto fail; + + retval = io_channel_write_blk(fs->io, block, 1, block_buf); + if (retval) + goto fail; + + ext2fs_block_alloc_stats(fs, block, +1); + *ret = block; + return 0; + +fail: + if (buf) + ext2fs_free_mem(&buf); + return retval; +} + +errcode_t ext2fs_get_free_blocks(ext2_filsys fs, blk_t start, blk_t finish, + int num, ext2fs_block_bitmap map, blk_t *ret) +{ + blk_t b = start; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (!map) + map = fs->block_map; + if (!map) + return EXT2_ET_NO_BLOCK_BITMAP; + if (!b) + b = fs->super->s_first_data_block; + if (!finish) + finish = start; + if (!num) + num = 1; + do { + if (b+num-1 > fs->super->s_blocks_count) + b = fs->super->s_first_data_block; + if (ext2fs_fast_test_block_bitmap_range(map, b, num)) { + *ret = b; + return 0; + } + b++; + } while (b != finish); + return EXT2_ET_BLOCK_ALLOC_FAIL; +} + diff --git a/e2fsprogs/ext2fs/alloc_sb.c b/e2fsprogs/ext2fs/alloc_sb.c new file mode 100644 index 000000000..a7437c96f --- /dev/null +++ b/e2fsprogs/ext2fs/alloc_sb.c @@ -0,0 +1,58 @@ +/* vi: set sw=4 ts=4: */ +/* + * alloc_sb.c --- Allocate the superblock and block group descriptors for a + * newly initialized filesystem. Used by mke2fs when initializing a filesystem + * + * Copyright (C) 1994, 1995, 1996, 2003 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +int ext2fs_reserve_super_and_bgd(ext2_filsys fs, + dgrp_t group, + ext2fs_block_bitmap bmap) +{ + blk_t super_blk, old_desc_blk, new_desc_blk; + int j, old_desc_blocks, num_blocks; + + num_blocks = ext2fs_super_and_bgd_loc(fs, group, &super_blk, + &old_desc_blk, &new_desc_blk, 0); + + if (fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG) + old_desc_blocks = fs->super->s_first_meta_bg; + else + old_desc_blocks = + fs->desc_blocks + fs->super->s_reserved_gdt_blocks; + + if (super_blk || (group == 0)) + ext2fs_mark_block_bitmap(bmap, super_blk); + + if (old_desc_blk) { + for (j=0; j < old_desc_blocks; j++) + ext2fs_mark_block_bitmap(bmap, old_desc_blk + j); + } + if (new_desc_blk) + ext2fs_mark_block_bitmap(bmap, new_desc_blk); + + return num_blocks; +} diff --git a/e2fsprogs/ext2fs/alloc_stats.c b/e2fsprogs/ext2fs/alloc_stats.c new file mode 100644 index 000000000..f3ab06a23 --- /dev/null +++ b/e2fsprogs/ext2fs/alloc_stats.c @@ -0,0 +1,53 @@ +/* vi: set sw=4 ts=4: */ +/* + * alloc_stats.c --- Update allocation statistics for ext2fs + * + * Copyright (C) 2001 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + * + */ + +#include + +#include "ext2_fs.h" +#include "ext2fs.h" + +void ext2fs_inode_alloc_stats2(ext2_filsys fs, ext2_ino_t ino, + int inuse, int isdir) +{ + int group = ext2fs_group_of_ino(fs, ino); + + if (inuse > 0) + ext2fs_mark_inode_bitmap(fs->inode_map, ino); + else + ext2fs_unmark_inode_bitmap(fs->inode_map, ino); + fs->group_desc[group].bg_free_inodes_count -= inuse; + if (isdir) + fs->group_desc[group].bg_used_dirs_count += inuse; + fs->super->s_free_inodes_count -= inuse; + ext2fs_mark_super_dirty(fs); + ext2fs_mark_ib_dirty(fs); +} + +void ext2fs_inode_alloc_stats(ext2_filsys fs, ext2_ino_t ino, int inuse) +{ + ext2fs_inode_alloc_stats2(fs, ino, inuse, 0); +} + +void ext2fs_block_alloc_stats(ext2_filsys fs, blk_t blk, int inuse) +{ + int group = ext2fs_group_of_blk(fs, blk); + + if (inuse > 0) + ext2fs_mark_block_bitmap(fs->block_map, blk); + else + ext2fs_unmark_block_bitmap(fs->block_map, blk); + fs->group_desc[group].bg_free_blocks_count -= inuse; + fs->super->s_free_blocks_count -= inuse; + ext2fs_mark_super_dirty(fs); + ext2fs_mark_bb_dirty(fs); +} diff --git a/e2fsprogs/ext2fs/alloc_tables.c b/e2fsprogs/ext2fs/alloc_tables.c new file mode 100644 index 000000000..b2d786ed8 --- /dev/null +++ b/e2fsprogs/ext2fs/alloc_tables.c @@ -0,0 +1,118 @@ +/* vi: set sw=4 ts=4: */ +/* + * alloc_tables.c --- Allocate tables for a newly initialized + * filesystem. Used by mke2fs when initializing a filesystem + * + * Copyright (C) 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +errcode_t ext2fs_allocate_group_table(ext2_filsys fs, dgrp_t group, + ext2fs_block_bitmap bmap) +{ + errcode_t retval; + blk_t group_blk, start_blk, last_blk, new_blk, blk; + int j; + + group_blk = fs->super->s_first_data_block + + (group * fs->super->s_blocks_per_group); + + last_blk = group_blk + fs->super->s_blocks_per_group; + if (last_blk >= fs->super->s_blocks_count) + last_blk = fs->super->s_blocks_count - 1; + + if (!bmap) + bmap = fs->block_map; + + /* + * Allocate the block and inode bitmaps, if necessary + */ + if (fs->stride) { + start_blk = group_blk + fs->inode_blocks_per_group; + start_blk += ((fs->stride * group) % + (last_blk - start_blk)); + if (start_blk > last_blk) + start_blk = group_blk; + } else + start_blk = group_blk; + + if (!fs->group_desc[group].bg_block_bitmap) { + retval = ext2fs_get_free_blocks(fs, start_blk, last_blk, + 1, bmap, &new_blk); + if (retval == EXT2_ET_BLOCK_ALLOC_FAIL) + retval = ext2fs_get_free_blocks(fs, group_blk, + last_blk, 1, bmap, &new_blk); + if (retval) + return retval; + ext2fs_mark_block_bitmap(bmap, new_blk); + fs->group_desc[group].bg_block_bitmap = new_blk; + } + + if (!fs->group_desc[group].bg_inode_bitmap) { + retval = ext2fs_get_free_blocks(fs, start_blk, last_blk, + 1, bmap, &new_blk); + if (retval == EXT2_ET_BLOCK_ALLOC_FAIL) + retval = ext2fs_get_free_blocks(fs, group_blk, + last_blk, 1, bmap, &new_blk); + if (retval) + return retval; + ext2fs_mark_block_bitmap(bmap, new_blk); + fs->group_desc[group].bg_inode_bitmap = new_blk; + } + + /* + * Allocate the inode table + */ + if (!fs->group_desc[group].bg_inode_table) { + retval = ext2fs_get_free_blocks(fs, group_blk, last_blk, + fs->inode_blocks_per_group, + bmap, &new_blk); + if (retval) + return retval; + for (j=0, blk = new_blk; + j < fs->inode_blocks_per_group; + j++, blk++) + ext2fs_mark_block_bitmap(bmap, blk); + fs->group_desc[group].bg_inode_table = new_blk; + } + + + return 0; +} + + + +errcode_t ext2fs_allocate_tables(ext2_filsys fs) +{ + errcode_t retval; + dgrp_t i; + + for (i = 0; i < fs->group_desc_count; i++) { + retval = ext2fs_allocate_group_table(fs, i, fs->block_map); + if (retval) + return retval; + } + return 0; +} + diff --git a/e2fsprogs/ext2fs/badblocks.c b/e2fsprogs/ext2fs/badblocks.c new file mode 100644 index 000000000..7804396d7 --- /dev/null +++ b/e2fsprogs/ext2fs/badblocks.c @@ -0,0 +1,328 @@ +/* vi: set sw=4 ts=4: */ +/* + * badblocks.c --- routines to manipulate the bad block structure + * + * Copyright (C) 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fsP.h" + +/* + * Helper function for making a badblocks list + */ +static errcode_t make_u32_list(int size, int num, __u32 *list, + ext2_u32_list *ret) +{ + ext2_u32_list bb; + errcode_t retval; + + retval = ext2fs_get_mem(sizeof(struct ext2_struct_u32_list), &bb); + if (retval) + return retval; + memset(bb, 0, sizeof(struct ext2_struct_u32_list)); + bb->magic = EXT2_ET_MAGIC_BADBLOCKS_LIST; + bb->size = size ? size : 10; + bb->num = num; + retval = ext2fs_get_mem(bb->size * sizeof(blk_t), &bb->list); + if (!bb->list) { + ext2fs_free_mem(&bb); + return retval; + } + if (list) + memcpy(bb->list, list, bb->size * sizeof(blk_t)); + else + memset(bb->list, 0, bb->size * sizeof(blk_t)); + *ret = bb; + return 0; +} + + +/* + * This procedure creates an empty u32 list. + */ +errcode_t ext2fs_u32_list_create(ext2_u32_list *ret, int size) +{ + return make_u32_list(size, 0, 0, ret); +} + +/* + * This procedure creates an empty badblocks list. + */ +errcode_t ext2fs_badblocks_list_create(ext2_badblocks_list *ret, int size) +{ + return make_u32_list(size, 0, 0, (ext2_badblocks_list *) ret); +} + + +/* + * This procedure copies a badblocks list + */ +errcode_t ext2fs_u32_copy(ext2_u32_list src, ext2_u32_list *dest) +{ + errcode_t retval; + + retval = make_u32_list(src->size, src->num, src->list, dest); + if (retval) + return retval; + (*dest)->badblocks_flags = src->badblocks_flags; + return 0; +} + +errcode_t ext2fs_badblocks_copy(ext2_badblocks_list src, + ext2_badblocks_list *dest) +{ + return ext2fs_u32_copy((ext2_u32_list) src, + (ext2_u32_list *) dest); +} + +/* + * This procedure frees a badblocks list. + * + * (note: moved to closefs.c) + */ + + +/* + * This procedure adds a block to a badblocks list. + */ +errcode_t ext2fs_u32_list_add(ext2_u32_list bb, __u32 blk) +{ + errcode_t retval; + int i, j; + unsigned long old_size; + + EXT2_CHECK_MAGIC(bb, EXT2_ET_MAGIC_BADBLOCKS_LIST); + + if (bb->num >= bb->size) { + old_size = bb->size * sizeof(__u32); + bb->size += 100; + retval = ext2fs_resize_mem(old_size, bb->size * sizeof(__u32), + &bb->list); + if (retval) { + bb->size -= 100; + return retval; + } + } + + /* + * Add special case code for appending to the end of the list + */ + i = bb->num-1; + if ((bb->num != 0) && (bb->list[i] == blk)) + return 0; + if ((bb->num == 0) || (bb->list[i] < blk)) { + bb->list[bb->num++] = blk; + return 0; + } + + j = bb->num; + for (i=0; i < bb->num; i++) { + if (bb->list[i] == blk) + return 0; + if (bb->list[i] > blk) { + j = i; + break; + } + } + for (i=bb->num; i > j; i--) + bb->list[i] = bb->list[i-1]; + bb->list[j] = blk; + bb->num++; + return 0; +} + +errcode_t ext2fs_badblocks_list_add(ext2_badblocks_list bb, blk_t blk) +{ + return ext2fs_u32_list_add((ext2_u32_list) bb, (__u32) blk); +} + +/* + * This procedure finds a particular block is on a badblocks + * list. + */ +int ext2fs_u32_list_find(ext2_u32_list bb, __u32 blk) +{ + int low, high, mid; + + if (bb->magic != EXT2_ET_MAGIC_BADBLOCKS_LIST) + return -1; + + if (bb->num == 0) + return -1; + + low = 0; + high = bb->num-1; + if (blk == bb->list[low]) + return low; + if (blk == bb->list[high]) + return high; + + while (low < high) { + mid = (low+high)/2; + if (mid == low || mid == high) + break; + if (blk == bb->list[mid]) + return mid; + if (blk < bb->list[mid]) + high = mid; + else + low = mid; + } + return -1; +} + +/* + * This procedure tests to see if a particular block is on a badblocks + * list. + */ +int ext2fs_u32_list_test(ext2_u32_list bb, __u32 blk) +{ + if (ext2fs_u32_list_find(bb, blk) < 0) + return 0; + else + return 1; +} + +int ext2fs_badblocks_list_test(ext2_badblocks_list bb, blk_t blk) +{ + return ext2fs_u32_list_test((ext2_u32_list) bb, (__u32) blk); +} + + +/* + * Remove a block from the badblock list + */ +int ext2fs_u32_list_del(ext2_u32_list bb, __u32 blk) +{ + int remloc, i; + + if (bb->num == 0) + return -1; + + remloc = ext2fs_u32_list_find(bb, blk); + if (remloc < 0) + return -1; + + for (i = remloc ; i < bb->num-1; i++) + bb->list[i] = bb->list[i+1]; + bb->num--; + return 0; +} + +void ext2fs_badblocks_list_del(ext2_u32_list bb, __u32 blk) +{ + ext2fs_u32_list_del(bb, blk); +} + +errcode_t ext2fs_u32_list_iterate_begin(ext2_u32_list bb, + ext2_u32_iterate *ret) +{ + ext2_u32_iterate iter; + errcode_t retval; + + EXT2_CHECK_MAGIC(bb, EXT2_ET_MAGIC_BADBLOCKS_LIST); + + retval = ext2fs_get_mem(sizeof(struct ext2_struct_u32_iterate), &iter); + if (retval) + return retval; + + iter->magic = EXT2_ET_MAGIC_BADBLOCKS_ITERATE; + iter->bb = bb; + iter->ptr = 0; + *ret = iter; + return 0; +} + +errcode_t ext2fs_badblocks_list_iterate_begin(ext2_badblocks_list bb, + ext2_badblocks_iterate *ret) +{ + return ext2fs_u32_list_iterate_begin((ext2_u32_list) bb, + (ext2_u32_iterate *) ret); +} + + +int ext2fs_u32_list_iterate(ext2_u32_iterate iter, __u32 *blk) +{ + ext2_u32_list bb; + + if (iter->magic != EXT2_ET_MAGIC_BADBLOCKS_ITERATE) + return 0; + + bb = iter->bb; + + if (bb->magic != EXT2_ET_MAGIC_BADBLOCKS_LIST) + return 0; + + if (iter->ptr < bb->num) { + *blk = bb->list[iter->ptr++]; + return 1; + } + *blk = 0; + return 0; +} + +int ext2fs_badblocks_list_iterate(ext2_badblocks_iterate iter, blk_t *blk) +{ + return ext2fs_u32_list_iterate((ext2_u32_iterate) iter, + (__u32 *) blk); +} + + +void ext2fs_u32_list_iterate_end(ext2_u32_iterate iter) +{ + if (!iter || (iter->magic != EXT2_ET_MAGIC_BADBLOCKS_ITERATE)) + return; + + iter->bb = 0; + ext2fs_free_mem(&iter); +} + +void ext2fs_badblocks_list_iterate_end(ext2_badblocks_iterate iter) +{ + ext2fs_u32_list_iterate_end((ext2_u32_iterate) iter); +} + + +int ext2fs_u32_list_equal(ext2_u32_list bb1, ext2_u32_list bb2) +{ + EXT2_CHECK_MAGIC(bb1, EXT2_ET_MAGIC_BADBLOCKS_LIST); + EXT2_CHECK_MAGIC(bb2, EXT2_ET_MAGIC_BADBLOCKS_LIST); + + if (bb1->num != bb2->num) + return 0; + + if (memcmp(bb1->list, bb2->list, bb1->num * sizeof(blk_t)) != 0) + return 0; + return 1; +} + +int ext2fs_badblocks_equal(ext2_badblocks_list bb1, ext2_badblocks_list bb2) +{ + return ext2fs_u32_list_equal((ext2_u32_list) bb1, + (ext2_u32_list) bb2); +} + +int ext2fs_u32_list_count(ext2_u32_list bb) +{ + return bb->num; +} diff --git a/e2fsprogs/ext2fs/bb_compat.c b/e2fsprogs/ext2fs/bb_compat.c new file mode 100644 index 000000000..419ac7785 --- /dev/null +++ b/e2fsprogs/ext2fs/bb_compat.c @@ -0,0 +1,64 @@ +/* vi: set sw=4 ts=4: */ +/* + * bb_compat.c --- compatibility badblocks routines + * + * Copyright (C) 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fsP.h" + +errcode_t badblocks_list_create(badblocks_list *ret, int size) +{ + return ext2fs_badblocks_list_create(ret, size); +} + +void badblocks_list_free(badblocks_list bb) +{ + ext2fs_badblocks_list_free(bb); +} + +errcode_t badblocks_list_add(badblocks_list bb, blk_t blk) +{ + return ext2fs_badblocks_list_add(bb, blk); +} + +int badblocks_list_test(badblocks_list bb, blk_t blk) +{ + return ext2fs_badblocks_list_test(bb, blk); +} + +errcode_t badblocks_list_iterate_begin(badblocks_list bb, + badblocks_iterate *ret) +{ + return ext2fs_badblocks_list_iterate_begin(bb, ret); +} + +int badblocks_list_iterate(badblocks_iterate iter, blk_t *blk) +{ + return ext2fs_badblocks_list_iterate(iter, blk); +} + +void badblocks_list_iterate_end(badblocks_iterate iter) +{ + ext2fs_badblocks_list_iterate_end(iter); +} diff --git a/e2fsprogs/ext2fs/bb_inode.c b/e2fsprogs/ext2fs/bb_inode.c new file mode 100644 index 000000000..855f86eac --- /dev/null +++ b/e2fsprogs/ext2fs/bb_inode.c @@ -0,0 +1,268 @@ +/* vi: set sw=4 ts=4: */ +/* + * bb_inode.c --- routines to update the bad block inode. + * + * WARNING: This routine modifies a lot of state in the filesystem; if + * this routine returns an error, the bad block inode may be in an + * inconsistent state. + * + * Copyright (C) 1994, 1995 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +struct set_badblock_record { + ext2_badblocks_iterate bb_iter; + int bad_block_count; + blk_t *ind_blocks; + int max_ind_blocks; + int ind_blocks_size; + int ind_blocks_ptr; + char *block_buf; + errcode_t err; +}; + +static int set_bad_block_proc(ext2_filsys fs, blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_block, int ref_offset, + void *priv_data); +static int clear_bad_block_proc(ext2_filsys fs, blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_block, int ref_offset, + void *priv_data); + +/* + * Given a bad blocks bitmap, update the bad blocks inode to reflect + * the map. + */ +errcode_t ext2fs_update_bb_inode(ext2_filsys fs, ext2_badblocks_list bb_list) +{ + errcode_t retval; + struct set_badblock_record rec; + struct ext2_inode inode; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (!fs->block_map) + return EXT2_ET_NO_BLOCK_BITMAP; + + rec.bad_block_count = 0; + rec.ind_blocks_size = rec.ind_blocks_ptr = 0; + rec.max_ind_blocks = 10; + retval = ext2fs_get_mem(rec.max_ind_blocks * sizeof(blk_t), + &rec.ind_blocks); + if (retval) + return retval; + memset(rec.ind_blocks, 0, rec.max_ind_blocks * sizeof(blk_t)); + retval = ext2fs_get_mem(fs->blocksize, &rec.block_buf); + if (retval) + goto cleanup; + memset(rec.block_buf, 0, fs->blocksize); + rec.err = 0; + + /* + * First clear the old bad blocks (while saving the indirect blocks) + */ + retval = ext2fs_block_iterate2(fs, EXT2_BAD_INO, + BLOCK_FLAG_DEPTH_TRAVERSE, 0, + clear_bad_block_proc, &rec); + if (retval) + goto cleanup; + if (rec.err) { + retval = rec.err; + goto cleanup; + } + + /* + * Now set the bad blocks! + * + * First, mark the bad blocks as used. This prevents a bad + * block from being used as an indirecto block for the bad + * block inode (!). + */ + if (bb_list) { + retval = ext2fs_badblocks_list_iterate_begin(bb_list, + &rec.bb_iter); + if (retval) + goto cleanup; + retval = ext2fs_block_iterate2(fs, EXT2_BAD_INO, + BLOCK_FLAG_APPEND, 0, + set_bad_block_proc, &rec); + ext2fs_badblocks_list_iterate_end(rec.bb_iter); + if (retval) + goto cleanup; + if (rec.err) { + retval = rec.err; + goto cleanup; + } + } + + /* + * Update the bad block inode's mod time and block count + * field. + */ + retval = ext2fs_read_inode(fs, EXT2_BAD_INO, &inode); + if (retval) + goto cleanup; + + inode.i_atime = inode.i_mtime = time(0); + if (!inode.i_ctime) + inode.i_ctime = time(0); + inode.i_blocks = rec.bad_block_count * (fs->blocksize / 512); + inode.i_size = rec.bad_block_count * fs->blocksize; + + retval = ext2fs_write_inode(fs, EXT2_BAD_INO, &inode); + if (retval) + goto cleanup; + +cleanup: + ext2fs_free_mem(&rec.ind_blocks); + ext2fs_free_mem(&rec.block_buf); + return retval; +} + +/* + * Helper function for update_bb_inode() + * + * Clear the bad blocks in the bad block inode, while saving the + * indirect blocks. + */ +#ifdef __TURBOC__ +# pragma argsused +#endif +static int clear_bad_block_proc(ext2_filsys fs, blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct set_badblock_record *rec = (struct set_badblock_record *) + priv_data; + errcode_t retval; + unsigned long old_size; + + if (!*block_nr) + return 0; + + /* + * If the block number is outrageous, clear it and ignore it. + */ + if (*block_nr >= fs->super->s_blocks_count || + *block_nr < fs->super->s_first_data_block) { + *block_nr = 0; + return BLOCK_CHANGED; + } + + if (blockcnt < 0) { + if (rec->ind_blocks_size >= rec->max_ind_blocks) { + old_size = rec->max_ind_blocks * sizeof(blk_t); + rec->max_ind_blocks += 10; + retval = ext2fs_resize_mem(old_size, + rec->max_ind_blocks * sizeof(blk_t), + &rec->ind_blocks); + if (retval) { + rec->max_ind_blocks -= 10; + rec->err = retval; + return BLOCK_ABORT; + } + } + rec->ind_blocks[rec->ind_blocks_size++] = *block_nr; + } + + /* + * Mark the block as unused, and update accounting information + */ + ext2fs_block_alloc_stats(fs, *block_nr, -1); + + *block_nr = 0; + return BLOCK_CHANGED; +} + + +/* + * Helper function for update_bb_inode() + * + * Set the block list in the bad block inode, using the supplied bitmap. + */ +#ifdef __TURBOC__ + #pragma argsused +#endif +static int set_bad_block_proc(ext2_filsys fs, blk_t *block_nr, + e2_blkcnt_t blockcnt, + blk_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct set_badblock_record *rec = (struct set_badblock_record *) + priv_data; + errcode_t retval; + blk_t blk; + + if (blockcnt >= 0) { + /* + * Get the next bad block. + */ + if (!ext2fs_badblocks_list_iterate(rec->bb_iter, &blk)) + return BLOCK_ABORT; + rec->bad_block_count++; + } else { + /* + * An indirect block; fetch a block from the + * previously used indirect block list. The block + * most be not marked as used; if so, get another one. + * If we run out of reserved indirect blocks, allocate + * a new one. + */ + retry: + if (rec->ind_blocks_ptr < rec->ind_blocks_size) { + blk = rec->ind_blocks[rec->ind_blocks_ptr++]; + if (ext2fs_test_block_bitmap(fs->block_map, blk)) + goto retry; + } else { + retval = ext2fs_new_block(fs, 0, 0, &blk); + if (retval) { + rec->err = retval; + return BLOCK_ABORT; + } + } + retval = io_channel_write_blk(fs->io, blk, 1, rec->block_buf); + if (retval) { + rec->err = retval; + return BLOCK_ABORT; + } + } + + /* + * Update block counts + */ + ext2fs_block_alloc_stats(fs, blk, +1); + + *block_nr = blk; + return BLOCK_CHANGED; +} + + + + + + diff --git a/e2fsprogs/ext2fs/bitmaps.c b/e2fsprogs/ext2fs/bitmaps.c new file mode 100644 index 000000000..712a4a9b1 --- /dev/null +++ b/e2fsprogs/ext2fs/bitmaps.c @@ -0,0 +1,213 @@ +/* vi: set sw=4 ts=4: */ +/* + * bitmaps.c --- routines to read, write, and manipulate the inode and + * block bitmaps. + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +static errcode_t make_bitmap(__u32 start, __u32 end, __u32 real_end, + const char *descr, char *init_map, + ext2fs_generic_bitmap *ret) +{ + ext2fs_generic_bitmap bitmap; + errcode_t retval; + size_t size; + + retval = ext2fs_get_mem(sizeof(struct ext2fs_struct_generic_bitmap), + &bitmap); + if (retval) + return retval; + + bitmap->magic = EXT2_ET_MAGIC_GENERIC_BITMAP; + bitmap->fs = NULL; + bitmap->start = start; + bitmap->end = end; + bitmap->real_end = real_end; + bitmap->base_error_code = EXT2_ET_BAD_GENERIC_MARK; + if (descr) { + retval = ext2fs_get_mem(strlen(descr)+1, &bitmap->description); + if (retval) { + ext2fs_free_mem(&bitmap); + return retval; + } + strcpy(bitmap->description, descr); + } else + bitmap->description = 0; + + size = (size_t) (((bitmap->real_end - bitmap->start) / 8) + 1); + retval = ext2fs_get_mem(size, &bitmap->bitmap); + if (retval) { + ext2fs_free_mem(&bitmap->description); + ext2fs_free_mem(&bitmap); + return retval; + } + + if (init_map) + memcpy(bitmap->bitmap, init_map, size); + else + memset(bitmap->bitmap, 0, size); + *ret = bitmap; + return 0; +} + +errcode_t ext2fs_allocate_generic_bitmap(__u32 start, + __u32 end, + __u32 real_end, + const char *descr, + ext2fs_generic_bitmap *ret) +{ + return make_bitmap(start, end, real_end, descr, 0, ret); +} + +errcode_t ext2fs_copy_bitmap(ext2fs_generic_bitmap src, + ext2fs_generic_bitmap *dest) +{ + errcode_t retval; + ext2fs_generic_bitmap new_map; + + retval = make_bitmap(src->start, src->end, src->real_end, + src->description, src->bitmap, &new_map); + if (retval) + return retval; + new_map->magic = src->magic; + new_map->fs = src->fs; + new_map->base_error_code = src->base_error_code; + *dest = new_map; + return 0; +} + +void ext2fs_set_bitmap_padding(ext2fs_generic_bitmap map) +{ + __u32 i, j; + + for (i=map->end+1, j = i - map->start; i <= map->real_end; i++, j++) + ext2fs_set_bit(j, map->bitmap); + + return; +} + +errcode_t ext2fs_allocate_inode_bitmap(ext2_filsys fs, + const char *descr, + ext2fs_inode_bitmap *ret) +{ + ext2fs_inode_bitmap bitmap; + errcode_t retval; + __u32 start, end, real_end; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + fs->write_bitmaps = ext2fs_write_bitmaps; + + start = 1; + end = fs->super->s_inodes_count; + real_end = (EXT2_INODES_PER_GROUP(fs->super) * fs->group_desc_count); + + retval = ext2fs_allocate_generic_bitmap(start, end, real_end, + descr, &bitmap); + if (retval) + return retval; + + bitmap->magic = EXT2_ET_MAGIC_INODE_BITMAP; + bitmap->fs = fs; + bitmap->base_error_code = EXT2_ET_BAD_INODE_MARK; + + *ret = bitmap; + return 0; +} + +errcode_t ext2fs_allocate_block_bitmap(ext2_filsys fs, + const char *descr, + ext2fs_block_bitmap *ret) +{ + ext2fs_block_bitmap bitmap; + errcode_t retval; + __u32 start, end, real_end; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + fs->write_bitmaps = ext2fs_write_bitmaps; + + start = fs->super->s_first_data_block; + end = fs->super->s_blocks_count-1; + real_end = (EXT2_BLOCKS_PER_GROUP(fs->super) + * fs->group_desc_count)-1 + start; + + retval = ext2fs_allocate_generic_bitmap(start, end, real_end, + descr, &bitmap); + if (retval) + return retval; + + bitmap->magic = EXT2_ET_MAGIC_BLOCK_BITMAP; + bitmap->fs = fs; + bitmap->base_error_code = EXT2_ET_BAD_BLOCK_MARK; + + *ret = bitmap; + return 0; +} + +errcode_t ext2fs_fudge_inode_bitmap_end(ext2fs_inode_bitmap bitmap, + ext2_ino_t end, ext2_ino_t *oend) +{ + EXT2_CHECK_MAGIC(bitmap, EXT2_ET_MAGIC_INODE_BITMAP); + + if (end > bitmap->real_end) + return EXT2_ET_FUDGE_INODE_BITMAP_END; + if (oend) + *oend = bitmap->end; + bitmap->end = end; + return 0; +} + +errcode_t ext2fs_fudge_block_bitmap_end(ext2fs_block_bitmap bitmap, + blk_t end, blk_t *oend) +{ + EXT2_CHECK_MAGIC(bitmap, EXT2_ET_MAGIC_BLOCK_BITMAP); + + if (end > bitmap->real_end) + return EXT2_ET_FUDGE_BLOCK_BITMAP_END; + if (oend) + *oend = bitmap->end; + bitmap->end = end; + return 0; +} + +void ext2fs_clear_inode_bitmap(ext2fs_inode_bitmap bitmap) +{ + if (!bitmap || (bitmap->magic != EXT2_ET_MAGIC_INODE_BITMAP)) + return; + + memset(bitmap->bitmap, 0, + (size_t) (((bitmap->real_end - bitmap->start) / 8) + 1)); +} + +void ext2fs_clear_block_bitmap(ext2fs_block_bitmap bitmap) +{ + if (!bitmap || (bitmap->magic != EXT2_ET_MAGIC_BLOCK_BITMAP)) + return; + + memset(bitmap->bitmap, 0, + (size_t) (((bitmap->real_end - bitmap->start) / 8) + 1)); +} diff --git a/e2fsprogs/ext2fs/bitops.c b/e2fsprogs/ext2fs/bitops.c new file mode 100644 index 000000000..987061130 --- /dev/null +++ b/e2fsprogs/ext2fs/bitops.c @@ -0,0 +1,91 @@ +/* vi: set sw=4 ts=4: */ +/* + * bitops.c --- Bitmap frobbing code. See bitops.h for the inlined + * routines. + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +#ifndef _EXT2_HAVE_ASM_BITOPS_ + +/* + * For the benefit of those who are trying to port Linux to another + * architecture, here are some C-language equivalents. You should + * recode these in the native assmebly language, if at all possible. + * + * C language equivalents written by Theodore Ts'o, 9/26/92. + * Modified by Pete A. Zaitcev 7/14/95 to be portable to big endian + * systems, as well as non-32 bit systems. + */ + +int ext2fs_set_bit(unsigned int nr,void * addr) +{ + int mask, retval; + unsigned char *ADDR = (unsigned char *) addr; + + ADDR += nr >> 3; + mask = 1 << (nr & 0x07); + retval = mask & *ADDR; + *ADDR |= mask; + return retval; +} + +int ext2fs_clear_bit(unsigned int nr, void * addr) +{ + int mask, retval; + unsigned char *ADDR = (unsigned char *) addr; + + ADDR += nr >> 3; + mask = 1 << (nr & 0x07); + retval = mask & *ADDR; + *ADDR &= ~mask; + return retval; +} + +int ext2fs_test_bit(unsigned int nr, const void * addr) +{ + int mask; + const unsigned char *ADDR = (const unsigned char *) addr; + + ADDR += nr >> 3; + mask = 1 << (nr & 0x07); + return (mask & *ADDR); +} + +#endif /* !_EXT2_HAVE_ASM_BITOPS_ */ + +void ext2fs_warn_bitmap(errcode_t errcode, unsigned long arg, + const char *description) +{ +#ifndef OMIT_COM_ERR + if (description) + bb_error_msg("#%lu for %s", arg, description); + else + bb_error_msg("#%lu", arg); +#endif +} + +void ext2fs_warn_bitmap2(ext2fs_generic_bitmap bitmap, + int code, unsigned long arg) +{ +#ifndef OMIT_COM_ERR + if (bitmap->description) + bb_error_msg("#%lu for %s", arg, bitmap->description); + else + bb_error_msg("#%lu", arg); +#endif +} + diff --git a/e2fsprogs/ext2fs/bitops.h b/e2fsprogs/ext2fs/bitops.h new file mode 100644 index 000000000..6dd30862b --- /dev/null +++ b/e2fsprogs/ext2fs/bitops.h @@ -0,0 +1,107 @@ +/* vi: set sw=4 ts=4: */ +/* + * bitops.h --- Bitmap frobbing code. The byte swapping routines are + * also included here. + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + * + * i386 bitops operations taken from , Copyright 1992, + * Linus Torvalds. + */ + +#include +#include + +extern int ext2fs_set_bit(unsigned int nr,void * addr); +extern int ext2fs_clear_bit(unsigned int nr, void * addr); +extern int ext2fs_test_bit(unsigned int nr, const void * addr); +extern __u16 ext2fs_swab16(__u16 val); +extern __u32 ext2fs_swab32(__u32 val); + +#ifdef WORDS_BIGENDIAN +#define ext2fs_cpu_to_le32(x) ext2fs_swab32((x)) +#define ext2fs_le32_to_cpu(x) ext2fs_swab32((x)) +#define ext2fs_cpu_to_le16(x) ext2fs_swab16((x)) +#define ext2fs_le16_to_cpu(x) ext2fs_swab16((x)) +#define ext2fs_cpu_to_be32(x) ((__u32)(x)) +#define ext2fs_be32_to_cpu(x) ((__u32)(x)) +#define ext2fs_cpu_to_be16(x) ((__u16)(x)) +#define ext2fs_be16_to_cpu(x) ((__u16)(x)) +#else +#define ext2fs_cpu_to_le32(x) ((__u32)(x)) +#define ext2fs_le32_to_cpu(x) ((__u32)(x)) +#define ext2fs_cpu_to_le16(x) ((__u16)(x)) +#define ext2fs_le16_to_cpu(x) ((__u16)(x)) +#define ext2fs_cpu_to_be32(x) ext2fs_swab32((x)) +#define ext2fs_be32_to_cpu(x) ext2fs_swab32((x)) +#define ext2fs_cpu_to_be16(x) ext2fs_swab16((x)) +#define ext2fs_be16_to_cpu(x) ext2fs_swab16((x)) +#endif + +/* + * EXT2FS bitmap manipulation routines. + */ + +/* Support for sending warning messages from the inline subroutines */ +extern const char *ext2fs_block_string; +extern const char *ext2fs_inode_string; +extern const char *ext2fs_mark_string; +extern const char *ext2fs_unmark_string; +extern const char *ext2fs_test_string; +extern void ext2fs_warn_bitmap(errcode_t errcode, unsigned long arg, + const char *description); +extern void ext2fs_warn_bitmap2(ext2fs_generic_bitmap bitmap, + int code, unsigned long arg); + +extern int ext2fs_mark_block_bitmap(ext2fs_block_bitmap bitmap, blk_t block); +extern int ext2fs_unmark_block_bitmap(ext2fs_block_bitmap bitmap, + blk_t block); +extern int ext2fs_test_block_bitmap(ext2fs_block_bitmap bitmap, blk_t block); + +extern int ext2fs_mark_inode_bitmap(ext2fs_inode_bitmap bitmap, ext2_ino_t inode); +extern int ext2fs_unmark_inode_bitmap(ext2fs_inode_bitmap bitmap, + ext2_ino_t inode); +extern int ext2fs_test_inode_bitmap(ext2fs_inode_bitmap bitmap, ext2_ino_t inode); + +extern void ext2fs_fast_mark_block_bitmap(ext2fs_block_bitmap bitmap, + blk_t block); +extern void ext2fs_fast_unmark_block_bitmap(ext2fs_block_bitmap bitmap, + blk_t block); +extern int ext2fs_fast_test_block_bitmap(ext2fs_block_bitmap bitmap, + blk_t block); + +extern void ext2fs_fast_mark_inode_bitmap(ext2fs_inode_bitmap bitmap, + ext2_ino_t inode); +extern void ext2fs_fast_unmark_inode_bitmap(ext2fs_inode_bitmap bitmap, + ext2_ino_t inode); +extern int ext2fs_fast_test_inode_bitmap(ext2fs_inode_bitmap bitmap, + ext2_ino_t inode); +extern blk_t ext2fs_get_block_bitmap_start(ext2fs_block_bitmap bitmap); +extern ext2_ino_t ext2fs_get_inode_bitmap_start(ext2fs_inode_bitmap bitmap); +extern blk_t ext2fs_get_block_bitmap_end(ext2fs_block_bitmap bitmap); +extern ext2_ino_t ext2fs_get_inode_bitmap_end(ext2fs_inode_bitmap bitmap); + +extern void ext2fs_mark_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num); +extern void ext2fs_unmark_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num); +extern int ext2fs_test_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num); +extern void ext2fs_fast_mark_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num); +extern void ext2fs_fast_unmark_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num); +extern int ext2fs_fast_test_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num); +extern void ext2fs_set_bitmap_padding(ext2fs_generic_bitmap map); + +/* These two routines moved to gen_bitmap.c */ +extern int ext2fs_mark_generic_bitmap(ext2fs_generic_bitmap bitmap, + __u32 bitno); +extern int ext2fs_unmark_generic_bitmap(ext2fs_generic_bitmap bitmap, + blk_t bitno); diff --git a/e2fsprogs/ext2fs/block.c b/e2fsprogs/ext2fs/block.c new file mode 100644 index 000000000..0a757b201 --- /dev/null +++ b/e2fsprogs/ext2fs/block.c @@ -0,0 +1,438 @@ +/* vi: set sw=4 ts=4: */ +/* + * block.c --- iterate over all blocks in an inode + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +struct block_context { + ext2_filsys fs; + int (*func)(ext2_filsys fs, + blk_t *blocknr, + e2_blkcnt_t bcount, + blk_t ref_blk, + int ref_offset, + void *priv_data); + e2_blkcnt_t bcount; + int bsize; + int flags; + errcode_t errcode; + char *ind_buf; + char *dind_buf; + char *tind_buf; + void *priv_data; +}; + +static int block_iterate_ind(blk_t *ind_block, blk_t ref_block, + int ref_offset, struct block_context *ctx) +{ + int ret = 0, changed = 0; + int i, flags, limit, offset; + blk_t *block_nr; + + limit = ctx->fs->blocksize >> 2; + if (!(ctx->flags & BLOCK_FLAG_DEPTH_TRAVERSE) && + !(ctx->flags & BLOCK_FLAG_DATA_ONLY)) + ret = (*ctx->func)(ctx->fs, ind_block, + BLOCK_COUNT_IND, ref_block, + ref_offset, ctx->priv_data); + if (!*ind_block || (ret & BLOCK_ABORT)) { + ctx->bcount += limit; + return ret; + } + if (*ind_block >= ctx->fs->super->s_blocks_count || + *ind_block < ctx->fs->super->s_first_data_block) { + ctx->errcode = EXT2_ET_BAD_IND_BLOCK; + ret |= BLOCK_ERROR; + return ret; + } + ctx->errcode = ext2fs_read_ind_block(ctx->fs, *ind_block, + ctx->ind_buf); + if (ctx->errcode) { + ret |= BLOCK_ERROR; + return ret; + } + + block_nr = (blk_t *) ctx->ind_buf; + offset = 0; + if (ctx->flags & BLOCK_FLAG_APPEND) { + for (i = 0; i < limit; i++, ctx->bcount++, block_nr++) { + flags = (*ctx->func)(ctx->fs, block_nr, ctx->bcount, + *ind_block, offset, + ctx->priv_data); + changed |= flags; + if (flags & BLOCK_ABORT) { + ret |= BLOCK_ABORT; + break; + } + offset += sizeof(blk_t); + } + } else { + for (i = 0; i < limit; i++, ctx->bcount++, block_nr++) { + if (*block_nr == 0) + continue; + flags = (*ctx->func)(ctx->fs, block_nr, ctx->bcount, + *ind_block, offset, + ctx->priv_data); + changed |= flags; + if (flags & BLOCK_ABORT) { + ret |= BLOCK_ABORT; + break; + } + offset += sizeof(blk_t); + } + } + if (changed & BLOCK_CHANGED) { + ctx->errcode = ext2fs_write_ind_block(ctx->fs, *ind_block, + ctx->ind_buf); + if (ctx->errcode) + ret |= BLOCK_ERROR | BLOCK_ABORT; + } + if ((ctx->flags & BLOCK_FLAG_DEPTH_TRAVERSE) && + !(ctx->flags & BLOCK_FLAG_DATA_ONLY) && + !(ret & BLOCK_ABORT)) + ret |= (*ctx->func)(ctx->fs, ind_block, + BLOCK_COUNT_IND, ref_block, + ref_offset, ctx->priv_data); + return ret; +} + +static int block_iterate_dind(blk_t *dind_block, blk_t ref_block, + int ref_offset, struct block_context *ctx) +{ + int ret = 0, changed = 0; + int i, flags, limit, offset; + blk_t *block_nr; + + limit = ctx->fs->blocksize >> 2; + if (!(ctx->flags & (BLOCK_FLAG_DEPTH_TRAVERSE | + BLOCK_FLAG_DATA_ONLY))) + ret = (*ctx->func)(ctx->fs, dind_block, + BLOCK_COUNT_DIND, ref_block, + ref_offset, ctx->priv_data); + if (!*dind_block || (ret & BLOCK_ABORT)) { + ctx->bcount += limit*limit; + return ret; + } + if (*dind_block >= ctx->fs->super->s_blocks_count || + *dind_block < ctx->fs->super->s_first_data_block) { + ctx->errcode = EXT2_ET_BAD_DIND_BLOCK; + ret |= BLOCK_ERROR; + return ret; + } + ctx->errcode = ext2fs_read_ind_block(ctx->fs, *dind_block, + ctx->dind_buf); + if (ctx->errcode) { + ret |= BLOCK_ERROR; + return ret; + } + + block_nr = (blk_t *) ctx->dind_buf; + offset = 0; + if (ctx->flags & BLOCK_FLAG_APPEND) { + for (i = 0; i < limit; i++, block_nr++) { + flags = block_iterate_ind(block_nr, + *dind_block, offset, + ctx); + changed |= flags; + if (flags & (BLOCK_ABORT | BLOCK_ERROR)) { + ret |= flags & (BLOCK_ABORT | BLOCK_ERROR); + break; + } + offset += sizeof(blk_t); + } + } else { + for (i = 0; i < limit; i++, block_nr++) { + if (*block_nr == 0) { + ctx->bcount += limit; + continue; + } + flags = block_iterate_ind(block_nr, + *dind_block, offset, + ctx); + changed |= flags; + if (flags & (BLOCK_ABORT | BLOCK_ERROR)) { + ret |= flags & (BLOCK_ABORT | BLOCK_ERROR); + break; + } + offset += sizeof(blk_t); + } + } + if (changed & BLOCK_CHANGED) { + ctx->errcode = ext2fs_write_ind_block(ctx->fs, *dind_block, + ctx->dind_buf); + if (ctx->errcode) + ret |= BLOCK_ERROR | BLOCK_ABORT; + } + if ((ctx->flags & BLOCK_FLAG_DEPTH_TRAVERSE) && + !(ctx->flags & BLOCK_FLAG_DATA_ONLY) && + !(ret & BLOCK_ABORT)) + ret |= (*ctx->func)(ctx->fs, dind_block, + BLOCK_COUNT_DIND, ref_block, + ref_offset, ctx->priv_data); + return ret; +} + +static int block_iterate_tind(blk_t *tind_block, blk_t ref_block, + int ref_offset, struct block_context *ctx) +{ + int ret = 0, changed = 0; + int i, flags, limit, offset; + blk_t *block_nr; + + limit = ctx->fs->blocksize >> 2; + if (!(ctx->flags & (BLOCK_FLAG_DEPTH_TRAVERSE | + BLOCK_FLAG_DATA_ONLY))) + ret = (*ctx->func)(ctx->fs, tind_block, + BLOCK_COUNT_TIND, ref_block, + ref_offset, ctx->priv_data); + if (!*tind_block || (ret & BLOCK_ABORT)) { + ctx->bcount += limit*limit*limit; + return ret; + } + if (*tind_block >= ctx->fs->super->s_blocks_count || + *tind_block < ctx->fs->super->s_first_data_block) { + ctx->errcode = EXT2_ET_BAD_TIND_BLOCK; + ret |= BLOCK_ERROR; + return ret; + } + ctx->errcode = ext2fs_read_ind_block(ctx->fs, *tind_block, + ctx->tind_buf); + if (ctx->errcode) { + ret |= BLOCK_ERROR; + return ret; + } + + block_nr = (blk_t *) ctx->tind_buf; + offset = 0; + if (ctx->flags & BLOCK_FLAG_APPEND) { + for (i = 0; i < limit; i++, block_nr++) { + flags = block_iterate_dind(block_nr, + *tind_block, + offset, ctx); + changed |= flags; + if (flags & (BLOCK_ABORT | BLOCK_ERROR)) { + ret |= flags & (BLOCK_ABORT | BLOCK_ERROR); + break; + } + offset += sizeof(blk_t); + } + } else { + for (i = 0; i < limit; i++, block_nr++) { + if (*block_nr == 0) { + ctx->bcount += limit*limit; + continue; + } + flags = block_iterate_dind(block_nr, + *tind_block, + offset, ctx); + changed |= flags; + if (flags & (BLOCK_ABORT | BLOCK_ERROR)) { + ret |= flags & (BLOCK_ABORT | BLOCK_ERROR); + break; + } + offset += sizeof(blk_t); + } + } + if (changed & BLOCK_CHANGED) { + ctx->errcode = ext2fs_write_ind_block(ctx->fs, *tind_block, + ctx->tind_buf); + if (ctx->errcode) + ret |= BLOCK_ERROR | BLOCK_ABORT; + } + if ((ctx->flags & BLOCK_FLAG_DEPTH_TRAVERSE) && + !(ctx->flags & BLOCK_FLAG_DATA_ONLY) && + !(ret & BLOCK_ABORT)) + ret |= (*ctx->func)(ctx->fs, tind_block, + BLOCK_COUNT_TIND, ref_block, + ref_offset, ctx->priv_data); + + return ret; +} + +errcode_t ext2fs_block_iterate2(ext2_filsys fs, + ext2_ino_t ino, + int flags, + char *block_buf, + int (*func)(ext2_filsys fs, + blk_t *blocknr, + e2_blkcnt_t blockcnt, + blk_t ref_blk, + int ref_offset, + void *priv_data), + void *priv_data) +{ + int i; + int got_inode = 0; + int ret = 0; + blk_t blocks[EXT2_N_BLOCKS]; /* directory data blocks */ + struct ext2_inode inode; + errcode_t retval; + struct block_context ctx; + int limit; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + /* + * Check to see if we need to limit large files + */ + if (flags & BLOCK_FLAG_NO_LARGE) { + ctx.errcode = ext2fs_read_inode(fs, ino, &inode); + if (ctx.errcode) + return ctx.errcode; + got_inode = 1; + if (!LINUX_S_ISDIR(inode.i_mode) && + (inode.i_size_high != 0)) + return EXT2_ET_FILE_TOO_BIG; + } + + retval = ext2fs_get_blocks(fs, ino, blocks); + if (retval) + return retval; + + limit = fs->blocksize >> 2; + + ctx.fs = fs; + ctx.func = func; + ctx.priv_data = priv_data; + ctx.flags = flags; + ctx.bcount = 0; + if (block_buf) { + ctx.ind_buf = block_buf; + } else { + retval = ext2fs_get_mem(fs->blocksize * 3, &ctx.ind_buf); + if (retval) + return retval; + } + ctx.dind_buf = ctx.ind_buf + fs->blocksize; + ctx.tind_buf = ctx.dind_buf + fs->blocksize; + + /* + * Iterate over the HURD translator block (if present) + */ + if ((fs->super->s_creator_os == EXT2_OS_HURD) && + !(flags & BLOCK_FLAG_DATA_ONLY)) { + ctx.errcode = ext2fs_read_inode(fs, ino, &inode); + if (ctx.errcode) + goto abort_exit; + got_inode = 1; + if (inode.osd1.hurd1.h_i_translator) { + ret |= (*ctx.func)(fs, + &inode.osd1.hurd1.h_i_translator, + BLOCK_COUNT_TRANSLATOR, + 0, 0, priv_data); + if (ret & BLOCK_ABORT) + goto abort_exit; + } + } + + /* + * Iterate over normal data blocks + */ + for (i = 0; i < EXT2_NDIR_BLOCKS ; i++, ctx.bcount++) { + if (blocks[i] || (flags & BLOCK_FLAG_APPEND)) { + ret |= (*ctx.func)(fs, &blocks[i], + ctx.bcount, 0, i, priv_data); + if (ret & BLOCK_ABORT) + goto abort_exit; + } + } + if (*(blocks + EXT2_IND_BLOCK) || (flags & BLOCK_FLAG_APPEND)) { + ret |= block_iterate_ind(blocks + EXT2_IND_BLOCK, + 0, EXT2_IND_BLOCK, &ctx); + if (ret & BLOCK_ABORT) + goto abort_exit; + } else + ctx.bcount += limit; + if (*(blocks + EXT2_DIND_BLOCK) || (flags & BLOCK_FLAG_APPEND)) { + ret |= block_iterate_dind(blocks + EXT2_DIND_BLOCK, + 0, EXT2_DIND_BLOCK, &ctx); + if (ret & BLOCK_ABORT) + goto abort_exit; + } else + ctx.bcount += limit * limit; + if (*(blocks + EXT2_TIND_BLOCK) || (flags & BLOCK_FLAG_APPEND)) { + ret |= block_iterate_tind(blocks + EXT2_TIND_BLOCK, + 0, EXT2_TIND_BLOCK, &ctx); + if (ret & BLOCK_ABORT) + goto abort_exit; + } + +abort_exit: + if (ret & BLOCK_CHANGED) { + if (!got_inode) { + retval = ext2fs_read_inode(fs, ino, &inode); + if (retval) + return retval; + } + for (i=0; i < EXT2_N_BLOCKS; i++) + inode.i_block[i] = blocks[i]; + retval = ext2fs_write_inode(fs, ino, &inode); + if (retval) + return retval; + } + + if (!block_buf) + ext2fs_free_mem(&ctx.ind_buf); + + return (ret & BLOCK_ERROR) ? ctx.errcode : 0; +} + +/* + * Emulate the old ext2fs_block_iterate function! + */ + +struct xlate { + int (*func)(ext2_filsys fs, + blk_t *blocknr, + int bcount, + void *priv_data); + void *real_private; +}; + +#ifdef __TURBOC__ +# pragma argsused +#endif +static int xlate_func(ext2_filsys fs, blk_t *blocknr, e2_blkcnt_t blockcnt, + blk_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct xlate *xl = (struct xlate *) priv_data; + + return (*xl->func)(fs, blocknr, (int) blockcnt, xl->real_private); +} + +errcode_t ext2fs_block_iterate(ext2_filsys fs, + ext2_ino_t ino, + int flags, + char *block_buf, + int (*func)(ext2_filsys fs, + blk_t *blocknr, + int blockcnt, + void *priv_data), + void *priv_data) +{ + struct xlate xl; + + xl.real_private = priv_data; + xl.func = func; + + return ext2fs_block_iterate2(fs, ino, BLOCK_FLAG_NO_LARGE | flags, + block_buf, xlate_func, &xl); +} + diff --git a/e2fsprogs/ext2fs/bmap.c b/e2fsprogs/ext2fs/bmap.c new file mode 100644 index 000000000..b22fe3dbf --- /dev/null +++ b/e2fsprogs/ext2fs/bmap.c @@ -0,0 +1,264 @@ +/* vi: set sw=4 ts=4: */ +/* + * bmap.c --- logical to physical block mapping + * + * Copyright (C) 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +extern errcode_t ext2fs_bmap(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode, + char *block_buf, int bmap_flags, + blk_t block, blk_t *phys_blk); + +#define inode_bmap(inode, nr) ((inode)->i_block[(nr)]) + +static errcode_t block_ind_bmap(ext2_filsys fs, int flags, + blk_t ind, char *block_buf, + int *blocks_alloc, + blk_t nr, blk_t *ret_blk) +{ + errcode_t retval; + blk_t b; + + if (!ind) { + if (flags & BMAP_SET) + return EXT2_ET_SET_BMAP_NO_IND; + *ret_blk = 0; + return 0; + } + retval = io_channel_read_blk(fs->io, ind, 1, block_buf); + if (retval) + return retval; + + if (flags & BMAP_SET) { + b = *ret_blk; +#if BB_BIG_ENDIAN + if ((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_WRITE)) + b = ext2fs_swab32(b); +#endif + ((blk_t *) block_buf)[nr] = b; + return io_channel_write_blk(fs->io, ind, 1, block_buf); + } + + b = ((blk_t *) block_buf)[nr]; + +#if BB_BIG_ENDIAN + if ((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_READ)) + b = ext2fs_swab32(b); +#endif + + if (!b && (flags & BMAP_ALLOC)) { + b = nr ? ((blk_t *) block_buf)[nr-1] : 0; + retval = ext2fs_alloc_block(fs, b, + block_buf + fs->blocksize, &b); + if (retval) + return retval; + +#if BB_BIG_ENDIAN + if ((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_WRITE)) + ((blk_t *) block_buf)[nr] = ext2fs_swab32(b); + else +#endif + ((blk_t *) block_buf)[nr] = b; + + retval = io_channel_write_blk(fs->io, ind, 1, block_buf); + if (retval) + return retval; + + (*blocks_alloc)++; + } + + *ret_blk = b; + return 0; +} + +static errcode_t block_dind_bmap(ext2_filsys fs, int flags, + blk_t dind, char *block_buf, + int *blocks_alloc, + blk_t nr, blk_t *ret_blk) +{ + blk_t b; + errcode_t retval; + blk_t addr_per_block; + + addr_per_block = (blk_t) fs->blocksize >> 2; + + retval = block_ind_bmap(fs, flags & ~BMAP_SET, dind, block_buf, + blocks_alloc, nr / addr_per_block, &b); + if (retval) + return retval; + retval = block_ind_bmap(fs, flags, b, block_buf, blocks_alloc, + nr % addr_per_block, ret_blk); + return retval; +} + +static errcode_t block_tind_bmap(ext2_filsys fs, int flags, + blk_t tind, char *block_buf, + int *blocks_alloc, + blk_t nr, blk_t *ret_blk) +{ + blk_t b; + errcode_t retval; + blk_t addr_per_block; + + addr_per_block = (blk_t) fs->blocksize >> 2; + + retval = block_dind_bmap(fs, flags & ~BMAP_SET, tind, block_buf, + blocks_alloc, nr / addr_per_block, &b); + if (retval) + return retval; + retval = block_ind_bmap(fs, flags, b, block_buf, blocks_alloc, + nr % addr_per_block, ret_blk); + return retval; +} + +errcode_t ext2fs_bmap(ext2_filsys fs, ext2_ino_t ino, struct ext2_inode *inode, + char *block_buf, int bmap_flags, blk_t block, + blk_t *phys_blk) +{ + struct ext2_inode inode_buf; + blk_t addr_per_block; + blk_t b; + char *buf = 0; + errcode_t retval = 0; + int blocks_alloc = 0, inode_dirty = 0; + + if (!(bmap_flags & BMAP_SET)) + *phys_blk = 0; + + /* Read inode structure if necessary */ + if (!inode) { + retval = ext2fs_read_inode(fs, ino, &inode_buf); + if (retval) + return retval; + inode = &inode_buf; + } + addr_per_block = (blk_t) fs->blocksize >> 2; + + if (!block_buf) { + retval = ext2fs_get_mem(fs->blocksize * 2, &buf); + if (retval) + return retval; + block_buf = buf; + } + + if (block < EXT2_NDIR_BLOCKS) { + if (bmap_flags & BMAP_SET) { + b = *phys_blk; +#if BB_BIG_ENDIAN + if ((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_READ)) + b = ext2fs_swab32(b); +#endif + inode_bmap(inode, block) = b; + inode_dirty++; + goto done; + } + + *phys_blk = inode_bmap(inode, block); + b = block ? inode_bmap(inode, block-1) : 0; + + if ((*phys_blk == 0) && (bmap_flags & BMAP_ALLOC)) { + retval = ext2fs_alloc_block(fs, b, block_buf, &b); + if (retval) + goto done; + inode_bmap(inode, block) = b; + blocks_alloc++; + *phys_blk = b; + } + goto done; + } + + /* Indirect block */ + block -= EXT2_NDIR_BLOCKS; + if (block < addr_per_block) { + b = inode_bmap(inode, EXT2_IND_BLOCK); + if (!b) { + if (!(bmap_flags & BMAP_ALLOC)) { + if (bmap_flags & BMAP_SET) + retval = EXT2_ET_SET_BMAP_NO_IND; + goto done; + } + + b = inode_bmap(inode, EXT2_IND_BLOCK-1); + retval = ext2fs_alloc_block(fs, b, block_buf, &b); + if (retval) + goto done; + inode_bmap(inode, EXT2_IND_BLOCK) = b; + blocks_alloc++; + } + retval = block_ind_bmap(fs, bmap_flags, b, block_buf, + &blocks_alloc, block, phys_blk); + goto done; + } + + /* Doubly indirect block */ + block -= addr_per_block; + if (block < addr_per_block * addr_per_block) { + b = inode_bmap(inode, EXT2_DIND_BLOCK); + if (!b) { + if (!(bmap_flags & BMAP_ALLOC)) { + if (bmap_flags & BMAP_SET) + retval = EXT2_ET_SET_BMAP_NO_IND; + goto done; + } + + b = inode_bmap(inode, EXT2_IND_BLOCK); + retval = ext2fs_alloc_block(fs, b, block_buf, &b); + if (retval) + goto done; + inode_bmap(inode, EXT2_DIND_BLOCK) = b; + blocks_alloc++; + } + retval = block_dind_bmap(fs, bmap_flags, b, block_buf, + &blocks_alloc, block, phys_blk); + goto done; + } + + /* Triply indirect block */ + block -= addr_per_block * addr_per_block; + b = inode_bmap(inode, EXT2_TIND_BLOCK); + if (!b) { + if (!(bmap_flags & BMAP_ALLOC)) { + if (bmap_flags & BMAP_SET) + retval = EXT2_ET_SET_BMAP_NO_IND; + goto done; + } + + b = inode_bmap(inode, EXT2_DIND_BLOCK); + retval = ext2fs_alloc_block(fs, b, block_buf, &b); + if (retval) + goto done; + inode_bmap(inode, EXT2_TIND_BLOCK) = b; + blocks_alloc++; + } + retval = block_tind_bmap(fs, bmap_flags, b, block_buf, + &blocks_alloc, block, phys_blk); +done: + ext2fs_free_mem(&buf); + if ((retval == 0) && (blocks_alloc || inode_dirty)) { + inode->i_blocks += (blocks_alloc * fs->blocksize) / 512; + retval = ext2fs_write_inode(fs, ino, inode); + } + return retval; +} + + + diff --git a/e2fsprogs/ext2fs/bmove.c b/e2fsprogs/ext2fs/bmove.c new file mode 100644 index 000000000..635410da5 --- /dev/null +++ b/e2fsprogs/ext2fs/bmove.c @@ -0,0 +1,156 @@ +/* vi: set sw=4 ts=4: */ +/* + * bmove.c --- Move blocks around to make way for a particular + * filesystem structure. + * + * Copyright (C) 1997 Theodore Ts'o. This file may be redistributed + * under the terms of the GNU Public License. + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fsP.h" + +struct process_block_struct { + ext2_ino_t ino; + struct ext2_inode * inode; + ext2fs_block_bitmap reserve; + ext2fs_block_bitmap alloc_map; + errcode_t error; + char *buf; + int add_dir; + int flags; +}; + +static int process_block(ext2_filsys fs, blk_t *block_nr, + e2_blkcnt_t blockcnt, blk_t ref_block, + int ref_offset, void *priv_data) +{ + struct process_block_struct *pb; + errcode_t retval; + int ret; + blk_t block, orig; + + pb = (struct process_block_struct *) priv_data; + block = orig = *block_nr; + ret = 0; + + /* + * Let's see if this is one which we need to relocate + */ + if (ext2fs_test_block_bitmap(pb->reserve, block)) { + do { + if (++block >= fs->super->s_blocks_count) + block = fs->super->s_first_data_block; + if (block == orig) { + pb->error = EXT2_ET_BLOCK_ALLOC_FAIL; + return BLOCK_ABORT; + } + } while (ext2fs_test_block_bitmap(pb->reserve, block) || + ext2fs_test_block_bitmap(pb->alloc_map, block)); + + retval = io_channel_read_blk(fs->io, orig, 1, pb->buf); + if (retval) { + pb->error = retval; + return BLOCK_ABORT; + } + retval = io_channel_write_blk(fs->io, block, 1, pb->buf); + if (retval) { + pb->error = retval; + return BLOCK_ABORT; + } + *block_nr = block; + ext2fs_mark_block_bitmap(pb->alloc_map, block); + ret = BLOCK_CHANGED; + if (pb->flags & EXT2_BMOVE_DEBUG) + printf("ino=%ld, blockcnt=%lld, %d->%d\n", pb->ino, + blockcnt, orig, block); + } + if (pb->add_dir) { + retval = ext2fs_add_dir_block(fs->dblist, pb->ino, + block, (int) blockcnt); + if (retval) { + pb->error = retval; + ret |= BLOCK_ABORT; + } + } + return ret; +} + +errcode_t ext2fs_move_blocks(ext2_filsys fs, + ext2fs_block_bitmap reserve, + ext2fs_block_bitmap alloc_map, + int flags) +{ + ext2_ino_t ino; + struct ext2_inode inode; + errcode_t retval; + struct process_block_struct pb; + ext2_inode_scan scan; + char *block_buf; + + retval = ext2fs_open_inode_scan(fs, 0, &scan); + if (retval) + return retval; + + pb.reserve = reserve; + pb.error = 0; + pb.alloc_map = alloc_map ? alloc_map : fs->block_map; + pb.flags = flags; + + retval = ext2fs_get_mem(fs->blocksize * 4, &block_buf); + if (retval) + return retval; + pb.buf = block_buf + fs->blocksize * 3; + + /* + * If GET_DBLIST is set in the flags field, then we should + * gather directory block information while we're doing the + * block move. + */ + if (flags & EXT2_BMOVE_GET_DBLIST) { + ext2fs_free_dblist(fs->dblist); + fs->dblist = NULL; + retval = ext2fs_init_dblist(fs, 0); + if (retval) + return retval; + } + + retval = ext2fs_get_next_inode(scan, &ino, &inode); + if (retval) + return retval; + + while (ino) { + if ((inode.i_links_count == 0) || + !ext2fs_inode_has_valid_blocks(&inode)) + goto next; + + pb.ino = ino; + pb.inode = &inode; + + pb.add_dir = (LINUX_S_ISDIR(inode.i_mode) && + flags & EXT2_BMOVE_GET_DBLIST); + + retval = ext2fs_block_iterate2(fs, ino, 0, block_buf, + process_block, &pb); + if (retval) + return retval; + if (pb.error) + return pb.error; + + next: + retval = ext2fs_get_next_inode(scan, &ino, &inode); + if (retval == EXT2_ET_BAD_BLOCK_IN_INODE_TABLE) + goto next; + } + return 0; +} + diff --git a/e2fsprogs/ext2fs/brel.h b/e2fsprogs/ext2fs/brel.h new file mode 100644 index 000000000..216fd132c --- /dev/null +++ b/e2fsprogs/ext2fs/brel.h @@ -0,0 +1,87 @@ +/* vi: set sw=4 ts=4: */ +/* + * brel.h + * + * Copyright (C) 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +struct ext2_block_relocate_entry { + blk_t new; + __s16 offset; + __u16 flags; + union { + blk_t block_ref; + ext2_ino_t inode_ref; + } owner; +}; + +#define RELOCATE_TYPE_REF 0x0007 +#define RELOCATE_BLOCK_REF 0x0001 +#define RELOCATE_INODE_REF 0x0002 + +typedef struct ext2_block_relocation_table *ext2_brel; + +struct ext2_block_relocation_table { + __u32 magic; + char *name; + blk_t current; + void *priv_data; + + /* + * Add a block relocation entry. + */ + errcode_t (*put)(ext2_brel brel, blk_t old, + struct ext2_block_relocate_entry *ent); + + /* + * Get a block relocation entry. + */ + errcode_t (*get)(ext2_brel brel, blk_t old, + struct ext2_block_relocate_entry *ent); + + /* + * Initialize for iterating over the block relocation entries. + */ + errcode_t (*start_iter)(ext2_brel brel); + + /* + * The iterator function for the inode relocation entries. + * Returns an inode number of 0 when out of entries. + */ + errcode_t (*next)(ext2_brel brel, blk_t *old, + struct ext2_block_relocate_entry *ent); + + /* + * Move the inode relocation table from one block number to + * another. + */ + errcode_t (*move)(ext2_brel brel, blk_t old, blk_t new); + + /* + * Remove a block relocation entry. + */ + errcode_t (*delete)(ext2_brel brel, blk_t old); + + + /* + * Free the block relocation table. + */ + errcode_t (*free)(ext2_brel brel); +}; + +errcode_t ext2fs_brel_memarray_create(char *name, blk_t max_block, + ext2_brel *brel); + +#define ext2fs_brel_put(brel, old, ent) ((brel)->put((brel), old, ent)) +#define ext2fs_brel_get(brel, old, ent) ((brel)->get((brel), old, ent)) +#define ext2fs_brel_start_iter(brel) ((brel)->start_iter((brel))) +#define ext2fs_brel_next(brel, old, ent) ((brel)->next((brel), old, ent)) +#define ext2fs_brel_move(brel, old, new) ((brel)->move((brel), old, new)) +#define ext2fs_brel_delete(brel, old) ((brel)->delete((brel), old)) +#define ext2fs_brel_free(brel) ((brel)->free((brel))) + diff --git a/e2fsprogs/ext2fs/brel_ma.c b/e2fsprogs/ext2fs/brel_ma.c new file mode 100644 index 000000000..652a3509c --- /dev/null +++ b/e2fsprogs/ext2fs/brel_ma.c @@ -0,0 +1,196 @@ +/* vi: set sw=4 ts=4: */ +/* + * brel_ma.c + * + * Copyright (C) 1996, 1997 Theodore Ts'o. + * + * TODO: rewrite to not use a direct array!!! (Fortunately this + * module isn't really used yet.) + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" +#include "brel.h" + +static errcode_t bma_put(ext2_brel brel, blk_t old, + struct ext2_block_relocate_entry *ent); +static errcode_t bma_get(ext2_brel brel, blk_t old, + struct ext2_block_relocate_entry *ent); +static errcode_t bma_start_iter(ext2_brel brel); +static errcode_t bma_next(ext2_brel brel, blk_t *old, + struct ext2_block_relocate_entry *ent); +static errcode_t bma_move(ext2_brel brel, blk_t old, blk_t new); +static errcode_t bma_delete(ext2_brel brel, blk_t old); +static errcode_t bma_free(ext2_brel brel); + +struct brel_ma { + __u32 magic; + blk_t max_block; + struct ext2_block_relocate_entry *entries; +}; + +errcode_t ext2fs_brel_memarray_create(char *name, blk_t max_block, + ext2_brel *new_brel) +{ + ext2_brel brel = 0; + errcode_t retval; + struct brel_ma *ma = 0; + size_t size; + + *new_brel = 0; + + /* + * Allocate memory structures + */ + retval = ext2fs_get_mem(sizeof(struct ext2_block_relocation_table), + &brel); + if (retval) + goto errout; + memset(brel, 0, sizeof(struct ext2_block_relocation_table)); + + retval = ext2fs_get_mem(strlen(name)+1, &brel->name); + if (retval) + goto errout; + strcpy(brel->name, name); + + retval = ext2fs_get_mem(sizeof(struct brel_ma), &ma); + if (retval) + goto errout; + memset(ma, 0, sizeof(struct brel_ma)); + brel->priv_data = ma; + + size = (size_t) (sizeof(struct ext2_block_relocate_entry) * + (max_block+1)); + retval = ext2fs_get_mem(size, &ma->entries); + if (retval) + goto errout; + memset(ma->entries, 0, size); + ma->max_block = max_block; + + /* + * Fill in the brel data structure + */ + brel->put = bma_put; + brel->get = bma_get; + brel->start_iter = bma_start_iter; + brel->next = bma_next; + brel->move = bma_move; + brel->delete = bma_delete; + brel->free = bma_free; + + *new_brel = brel; + return 0; + +errout: + bma_free(brel); + return retval; +} + +static errcode_t bma_put(ext2_brel brel, blk_t old, + struct ext2_block_relocate_entry *ent) +{ + struct brel_ma *ma; + + ma = brel->priv_data; + if (old > ma->max_block) + return EXT2_ET_INVALID_ARGUMENT; + ma->entries[(unsigned)old] = *ent; + return 0; +} + +static errcode_t bma_get(ext2_brel brel, blk_t old, + struct ext2_block_relocate_entry *ent) +{ + struct brel_ma *ma; + + ma = brel->priv_data; + if (old > ma->max_block) + return EXT2_ET_INVALID_ARGUMENT; + if (ma->entries[(unsigned)old].new == 0) + return ENOENT; + *ent = ma->entries[old]; + return 0; +} + +static errcode_t bma_start_iter(ext2_brel brel) +{ + brel->current = 0; + return 0; +} + +static errcode_t bma_next(ext2_brel brel, blk_t *old, + struct ext2_block_relocate_entry *ent) +{ + struct brel_ma *ma; + + ma = brel->priv_data; + while (++brel->current < ma->max_block) { + if (ma->entries[(unsigned)brel->current].new == 0) + continue; + *old = brel->current; + *ent = ma->entries[(unsigned)brel->current]; + return 0; + } + *old = 0; + return 0; +} + +static errcode_t bma_move(ext2_brel brel, blk_t old, blk_t new) +{ + struct brel_ma *ma; + + ma = brel->priv_data; + if ((old > ma->max_block) || (new > ma->max_block)) + return EXT2_ET_INVALID_ARGUMENT; + if (ma->entries[(unsigned)old].new == 0) + return ENOENT; + ma->entries[(unsigned)new] = ma->entries[old]; + ma->entries[(unsigned)old].new = 0; + return 0; +} + +static errcode_t bma_delete(ext2_brel brel, blk_t old) +{ + struct brel_ma *ma; + + ma = brel->priv_data; + if (old > ma->max_block) + return EXT2_ET_INVALID_ARGUMENT; + if (ma->entries[(unsigned)old].new == 0) + return ENOENT; + ma->entries[(unsigned)old].new = 0; + return 0; +} + +static errcode_t bma_free(ext2_brel brel) +{ + struct brel_ma *ma; + + if (!brel) + return 0; + + ma = brel->priv_data; + + if (ma) { + ext2fs_free_mem(&ma->entries); + ext2fs_free_mem(&ma); + } + ext2fs_free_mem(&brel->name); + ext2fs_free_mem(&brel); + return 0; +} diff --git a/e2fsprogs/ext2fs/check_desc.c b/e2fsprogs/ext2fs/check_desc.c new file mode 100644 index 000000000..dd4b0e9cf --- /dev/null +++ b/e2fsprogs/ext2fs/check_desc.c @@ -0,0 +1,69 @@ +/* vi: set sw=4 ts=4: */ +/* + * check_desc.c --- Check the group descriptors of an ext2 filesystem + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * This routine sanity checks the group descriptors + */ +errcode_t ext2fs_check_desc(ext2_filsys fs) +{ + dgrp_t i; + blk_t block = fs->super->s_first_data_block; + blk_t next; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + for (i = 0; i < fs->group_desc_count; i++) { + next = block + fs->super->s_blocks_per_group; + /* + * Check to make sure block bitmap for group is + * located within the group. + */ + if (fs->group_desc[i].bg_block_bitmap < block || + fs->group_desc[i].bg_block_bitmap >= next) + return EXT2_ET_GDESC_BAD_BLOCK_MAP; + /* + * Check to make sure inode bitmap for group is + * located within the group + */ + if (fs->group_desc[i].bg_inode_bitmap < block || + fs->group_desc[i].bg_inode_bitmap >= next) + return EXT2_ET_GDESC_BAD_INODE_MAP; + /* + * Check to make sure inode table for group is located + * within the group + */ + if (fs->group_desc[i].bg_inode_table < block || + ((fs->group_desc[i].bg_inode_table + + fs->inode_blocks_per_group) >= next)) + return EXT2_ET_GDESC_BAD_INODE_TABLE; + + block = next; + } + return 0; +} diff --git a/e2fsprogs/ext2fs/closefs.c b/e2fsprogs/ext2fs/closefs.c new file mode 100644 index 000000000..008d5f36e --- /dev/null +++ b/e2fsprogs/ext2fs/closefs.c @@ -0,0 +1,381 @@ +/* vi: set sw=4 ts=4: */ +/* + * closefs.c --- close an ext2 filesystem + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include + +#include "ext2_fs.h" +#include "ext2fsP.h" + +static int test_root(int a, int b) +{ + if (a == 0) + return 1; + while (1) { + if (a == 1) + return 1; + if (a % b) + return 0; + a = a / b; + } +} + +int ext2fs_bg_has_super(ext2_filsys fs, int group_block) +{ + if (!(fs->super->s_feature_ro_compat & + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER)) + return 1; + + if (test_root(group_block, 3) || (test_root(group_block, 5)) || + test_root(group_block, 7)) + return 1; + + return 0; +} + +int ext2fs_super_and_bgd_loc(ext2_filsys fs, + dgrp_t group, + blk_t *ret_super_blk, + blk_t *ret_old_desc_blk, + blk_t *ret_new_desc_blk, + int *ret_meta_bg) +{ + blk_t group_block, super_blk = 0, old_desc_blk = 0, new_desc_blk = 0; + unsigned int meta_bg, meta_bg_size; + int numblocks, has_super; + int old_desc_blocks; + + group_block = fs->super->s_first_data_block + + (group * fs->super->s_blocks_per_group); + + if (fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG) + old_desc_blocks = fs->super->s_first_meta_bg; + else + old_desc_blocks = + fs->desc_blocks + fs->super->s_reserved_gdt_blocks; + + if (group == fs->group_desc_count-1) { + numblocks = (fs->super->s_blocks_count - + fs->super->s_first_data_block) % + fs->super->s_blocks_per_group; + if (!numblocks) + numblocks = fs->super->s_blocks_per_group; + } else + numblocks = fs->super->s_blocks_per_group; + + has_super = ext2fs_bg_has_super(fs, group); + + if (has_super) { + super_blk = group_block; + numblocks--; + } + meta_bg_size = (fs->blocksize / sizeof (struct ext2_group_desc)); + meta_bg = group / meta_bg_size; + + if (!(fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG) || + (meta_bg < fs->super->s_first_meta_bg)) { + if (has_super) { + old_desc_blk = group_block + 1; + numblocks -= old_desc_blocks; + } + } else { + if (((group % meta_bg_size) == 0) || + ((group % meta_bg_size) == 1) || + ((group % meta_bg_size) == (meta_bg_size-1))) { + if (has_super) + has_super = 1; + new_desc_blk = group_block + has_super; + numblocks--; + } + } + + numblocks -= 2 + fs->inode_blocks_per_group; + + if (ret_super_blk) + *ret_super_blk = super_blk; + if (ret_old_desc_blk) + *ret_old_desc_blk = old_desc_blk; + if (ret_new_desc_blk) + *ret_new_desc_blk = new_desc_blk; + if (ret_meta_bg) + *ret_meta_bg = meta_bg; + return numblocks; +} + + +/* + * This function forces out the primary superblock. We need to only + * write out those fields which we have changed, since if the + * filesystem is mounted, it may have changed some of the other + * fields. + * + * It takes as input a superblock which has already been byte swapped + * (if necessary). + * + */ +static errcode_t write_primary_superblock(ext2_filsys fs, + struct ext2_super_block *super) +{ + __u16 *old_super, *new_super; + int check_idx, write_idx, size; + errcode_t retval; + + if (!fs->io->manager->write_byte || !fs->orig_super) { + io_channel_set_blksize(fs->io, SUPERBLOCK_OFFSET); + retval = io_channel_write_blk(fs->io, 1, -SUPERBLOCK_SIZE, + super); + io_channel_set_blksize(fs->io, fs->blocksize); + return retval; + } + + old_super = (__u16 *) fs->orig_super; + new_super = (__u16 *) super; + + for (check_idx = 0; check_idx < SUPERBLOCK_SIZE/2; check_idx++) { + if (old_super[check_idx] == new_super[check_idx]) + continue; + write_idx = check_idx; + for (check_idx++; check_idx < SUPERBLOCK_SIZE/2; check_idx++) + if (old_super[check_idx] == new_super[check_idx]) + break; + size = 2 * (check_idx - write_idx); + retval = io_channel_write_byte(fs->io, + SUPERBLOCK_OFFSET + (2 * write_idx), size, + new_super + write_idx); + if (retval) + return retval; + } + memcpy(fs->orig_super, super, SUPERBLOCK_SIZE); + return 0; +} + + +/* + * Updates the revision to EXT2_DYNAMIC_REV + */ +void ext2fs_update_dynamic_rev(ext2_filsys fs) +{ + struct ext2_super_block *sb = fs->super; + + if (sb->s_rev_level > EXT2_GOOD_OLD_REV) + return; + + sb->s_rev_level = EXT2_DYNAMIC_REV; + sb->s_first_ino = EXT2_GOOD_OLD_FIRST_INO; + sb->s_inode_size = EXT2_GOOD_OLD_INODE_SIZE; + /* s_uuid is handled by e2fsck already */ + /* other fields should be left alone */ +} + +static errcode_t write_backup_super(ext2_filsys fs, dgrp_t group, + blk_t group_block, + struct ext2_super_block *super_shadow) +{ + dgrp_t sgrp = group; + + if (sgrp > ((1 << 16) - 1)) + sgrp = (1 << 16) - 1; +#if BB_BIG_ENDIAN + if (fs->flags & EXT2_FLAG_SWAP_BYTES) + super_shadow->s_block_group_nr = ext2fs_swab16(sgrp); + else +#endif + fs->super->s_block_group_nr = sgrp; + + return io_channel_write_blk(fs->io, group_block, -SUPERBLOCK_SIZE, + super_shadow); +} + + +errcode_t ext2fs_flush(ext2_filsys fs) +{ + dgrp_t i; + blk_t group_block; + errcode_t retval; + unsigned long fs_state; + struct ext2_super_block *super_shadow = 0; + struct ext2_group_desc *group_shadow = 0; + char *group_ptr; + int old_desc_blocks; +#if BB_BIG_ENDIAN + dgrp_t j; + struct ext2_group_desc *s, *t; +#endif + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + fs_state = fs->super->s_state; + + fs->super->s_wtime = time(NULL); + fs->super->s_block_group_nr = 0; +#if BB_BIG_ENDIAN + if (fs->flags & EXT2_FLAG_SWAP_BYTES) { + retval = EXT2_ET_NO_MEMORY; + retval = ext2fs_get_mem(SUPERBLOCK_SIZE, &super_shadow); + if (retval) + goto errout; + retval = ext2fs_get_mem((size_t)(fs->blocksize * + fs->desc_blocks), + &group_shadow); + if (retval) + goto errout; + memset(group_shadow, 0, (size_t) fs->blocksize * + fs->desc_blocks); + + /* swap the group descriptors */ + for (j=0, s=fs->group_desc, t=group_shadow; + j < fs->group_desc_count; j++, t++, s++) { + *t = *s; + ext2fs_swap_group_desc(t); + } + } else { + super_shadow = fs->super; + group_shadow = fs->group_desc; + } +#else + super_shadow = fs->super; + group_shadow = fs->group_desc; +#endif + + /* + * If this is an external journal device, don't write out the + * block group descriptors or any of the backup superblocks + */ + if (fs->super->s_feature_incompat & + EXT3_FEATURE_INCOMPAT_JOURNAL_DEV) + goto write_primary_superblock_only; + + /* + * Set the state of the FS to be non-valid. (The state has + * already been backed up earlier, and will be restored after + * we write out the backup superblocks.) + */ + fs->super->s_state &= ~EXT2_VALID_FS; +#if BB_BIG_ENDIAN + if (fs->flags & EXT2_FLAG_SWAP_BYTES) { + *super_shadow = *fs->super; + ext2fs_swap_super(super_shadow); + } +#endif + + /* + * Write out the master group descriptors, and the backup + * superblocks and group descriptors. + */ + group_block = fs->super->s_first_data_block; + group_ptr = (char *) group_shadow; + if (fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG) + old_desc_blocks = fs->super->s_first_meta_bg; + else + old_desc_blocks = fs->desc_blocks; + + for (i = 0; i < fs->group_desc_count; i++) { + blk_t super_blk, old_desc_blk, new_desc_blk; + int meta_bg; + + ext2fs_super_and_bgd_loc(fs, i, &super_blk, &old_desc_blk, + &new_desc_blk, &meta_bg); + + if (!(fs->flags & EXT2_FLAG_MASTER_SB_ONLY) &&i && super_blk) { + retval = write_backup_super(fs, i, super_blk, + super_shadow); + if (retval) + goto errout; + } + if (fs->flags & EXT2_FLAG_SUPER_ONLY) + continue; + if ((old_desc_blk) && + (!(fs->flags & EXT2_FLAG_MASTER_SB_ONLY) || (i == 0))) { + retval = io_channel_write_blk(fs->io, + old_desc_blk, old_desc_blocks, group_ptr); + if (retval) + goto errout; + } + if (new_desc_blk) { + retval = io_channel_write_blk(fs->io, new_desc_blk, + 1, group_ptr + (meta_bg*fs->blocksize)); + if (retval) + goto errout; + } + } + fs->super->s_block_group_nr = 0; + fs->super->s_state = fs_state; +#if BB_BIG_ENDIAN + if (fs->flags & EXT2_FLAG_SWAP_BYTES) { + *super_shadow = *fs->super; + ext2fs_swap_super(super_shadow); + } +#endif + + /* + * If the write_bitmaps() function is present, call it to + * flush the bitmaps. This is done this way so that a simple + * program that doesn't mess with the bitmaps doesn't need to + * drag in the bitmaps.c code. + */ + if (fs->write_bitmaps) { + retval = fs->write_bitmaps(fs); + if (retval) + goto errout; + } + +write_primary_superblock_only: + /* + * Write out master superblock. This has to be done + * separately, since it is located at a fixed location + * (SUPERBLOCK_OFFSET). We flush all other pending changes + * out to disk first, just to avoid a race condition with an + * insy-tinsy window.... + */ + retval = io_channel_flush(fs->io); + retval = write_primary_superblock(fs, super_shadow); + if (retval) + goto errout; + + fs->flags &= ~EXT2_FLAG_DIRTY; + + retval = io_channel_flush(fs->io); +errout: + fs->super->s_state = fs_state; + if (fs->flags & EXT2_FLAG_SWAP_BYTES) { + if (super_shadow) + ext2fs_free_mem(&super_shadow); + if (group_shadow) + ext2fs_free_mem(&group_shadow); + } + return retval; +} + +errcode_t ext2fs_close(ext2_filsys fs) +{ + errcode_t retval; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (fs->flags & EXT2_FLAG_DIRTY) { + retval = ext2fs_flush(fs); + if (retval) + return retval; + } + if (fs->write_bitmaps) { + retval = fs->write_bitmaps(fs); + if (retval) + return retval; + } + ext2fs_free(fs); + return 0; +} + diff --git a/e2fsprogs/ext2fs/cmp_bitmaps.c b/e2fsprogs/ext2fs/cmp_bitmaps.c new file mode 100644 index 000000000..05b8eb8d7 --- /dev/null +++ b/e2fsprogs/ext2fs/cmp_bitmaps.c @@ -0,0 +1,73 @@ +/* vi: set sw=4 ts=4: */ +/* + * cmp_bitmaps.c --- routines to compare inode and block bitmaps. + * + * Copyright (C) 1995 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +errcode_t ext2fs_compare_block_bitmap(ext2fs_block_bitmap bm1, + ext2fs_block_bitmap bm2) +{ + blk_t i; + + EXT2_CHECK_MAGIC(bm1, EXT2_ET_MAGIC_BLOCK_BITMAP); + EXT2_CHECK_MAGIC(bm2, EXT2_ET_MAGIC_BLOCK_BITMAP); + + if ((bm1->start != bm2->start) || + (bm1->end != bm2->end) || + (memcmp(bm1->bitmap, bm2->bitmap, + (size_t) (bm1->end - bm1->start)/8))) + return EXT2_ET_NEQ_BLOCK_BITMAP; + + for (i = bm1->end - ((bm1->end - bm1->start) % 8); i <= bm1->end; i++) + if (ext2fs_fast_test_block_bitmap(bm1, i) != + ext2fs_fast_test_block_bitmap(bm2, i)) + return EXT2_ET_NEQ_BLOCK_BITMAP; + + return 0; +} + +errcode_t ext2fs_compare_inode_bitmap(ext2fs_inode_bitmap bm1, + ext2fs_inode_bitmap bm2) +{ + ext2_ino_t i; + + EXT2_CHECK_MAGIC(bm1, EXT2_ET_MAGIC_INODE_BITMAP); + EXT2_CHECK_MAGIC(bm2, EXT2_ET_MAGIC_INODE_BITMAP); + + if ((bm1->start != bm2->start) || + (bm1->end != bm2->end) || + (memcmp(bm1->bitmap, bm2->bitmap, + (size_t) (bm1->end - bm1->start)/8))) + return EXT2_ET_NEQ_INODE_BITMAP; + + for (i = bm1->end - ((bm1->end - bm1->start) % 8); i <= bm1->end; i++) + if (ext2fs_fast_test_inode_bitmap(bm1, i) != + ext2fs_fast_test_inode_bitmap(bm2, i)) + return EXT2_ET_NEQ_INODE_BITMAP; + + return 0; +} + diff --git a/e2fsprogs/ext2fs/dblist.c b/e2fsprogs/ext2fs/dblist.c new file mode 100644 index 000000000..06ff6d807 --- /dev/null +++ b/e2fsprogs/ext2fs/dblist.c @@ -0,0 +1,260 @@ +/* vi: set sw=4 ts=4: */ +/* + * dblist.c -- directory block list functions + * + * Copyright 1997 by Theodore Ts'o + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + * + */ + +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include + +#include "ext2_fs.h" +#include "ext2fsP.h" + +static int dir_block_cmp(const void *a, const void *b); + +/* + * Returns the number of directories in the filesystem as reported by + * the group descriptors. Of course, the group descriptors could be + * wrong! + */ +errcode_t ext2fs_get_num_dirs(ext2_filsys fs, ext2_ino_t *ret_num_dirs) +{ + dgrp_t i; + ext2_ino_t num_dirs, max_dirs; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + num_dirs = 0; + max_dirs = fs->super->s_inodes_per_group; + for (i = 0; i < fs->group_desc_count; i++) { + if (fs->group_desc[i].bg_used_dirs_count > max_dirs) + num_dirs += max_dirs / 8; + else + num_dirs += fs->group_desc[i].bg_used_dirs_count; + } + if (num_dirs > fs->super->s_inodes_count) + num_dirs = fs->super->s_inodes_count; + + *ret_num_dirs = num_dirs; + + return 0; +} + +/* + * helper function for making a new directory block list (for + * initialize and copy). + */ +static errcode_t make_dblist(ext2_filsys fs, ext2_ino_t size, ext2_ino_t count, + struct ext2_db_entry *list, + ext2_dblist *ret_dblist) +{ + ext2_dblist dblist; + errcode_t retval; + size_t len; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if ((ret_dblist == 0) && fs->dblist && + (fs->dblist->magic == EXT2_ET_MAGIC_DBLIST)) + return 0; + + retval = ext2fs_get_mem(sizeof(struct ext2_struct_dblist), &dblist); + if (retval) + return retval; + memset(dblist, 0, sizeof(struct ext2_struct_dblist)); + + dblist->magic = EXT2_ET_MAGIC_DBLIST; + dblist->fs = fs; + if (size) + dblist->size = size; + else { + retval = ext2fs_get_num_dirs(fs, &dblist->size); + if (retval) + goto cleanup; + dblist->size = (dblist->size * 2) + 12; + } + len = (size_t) sizeof(struct ext2_db_entry) * dblist->size; + dblist->count = count; + retval = ext2fs_get_mem(len, &dblist->list); + if (retval) + goto cleanup; + + if (list) + memcpy(dblist->list, list, len); + else + memset(dblist->list, 0, len); + if (ret_dblist) + *ret_dblist = dblist; + else + fs->dblist = dblist; + return 0; +cleanup: + ext2fs_free_mem(&dblist); + return retval; +} + +/* + * Initialize a directory block list + */ +errcode_t ext2fs_init_dblist(ext2_filsys fs, ext2_dblist *ret_dblist) +{ + ext2_dblist dblist; + errcode_t retval; + + retval = make_dblist(fs, 0, 0, 0, &dblist); + if (retval) + return retval; + + dblist->sorted = 1; + if (ret_dblist) + *ret_dblist = dblist; + else + fs->dblist = dblist; + + return 0; +} + +/* + * Copy a directory block list + */ +errcode_t ext2fs_copy_dblist(ext2_dblist src, ext2_dblist *dest) +{ + ext2_dblist dblist; + errcode_t retval; + + retval = make_dblist(src->fs, src->size, src->count, src->list, + &dblist); + if (retval) + return retval; + dblist->sorted = src->sorted; + *dest = dblist; + return 0; +} + +/* + * Close a directory block list + * + * (moved to closefs.c) + */ + + +/* + * Add a directory block to the directory block list + */ +errcode_t ext2fs_add_dir_block(ext2_dblist dblist, ext2_ino_t ino, blk_t blk, + int blockcnt) +{ + struct ext2_db_entry *new_entry; + errcode_t retval; + unsigned long old_size; + + EXT2_CHECK_MAGIC(dblist, EXT2_ET_MAGIC_DBLIST); + + if (dblist->count >= dblist->size) { + old_size = dblist->size * sizeof(struct ext2_db_entry); + dblist->size += 100; + retval = ext2fs_resize_mem(old_size, (size_t) dblist->size * + sizeof(struct ext2_db_entry), + &dblist->list); + if (retval) { + dblist->size -= 100; + return retval; + } + } + new_entry = dblist->list + ( (int) dblist->count++); + new_entry->blk = blk; + new_entry->ino = ino; + new_entry->blockcnt = blockcnt; + + dblist->sorted = 0; + + return 0; +} + +/* + * Change the directory block to the directory block list + */ +errcode_t ext2fs_set_dir_block(ext2_dblist dblist, ext2_ino_t ino, blk_t blk, + int blockcnt) +{ + dgrp_t i; + + EXT2_CHECK_MAGIC(dblist, EXT2_ET_MAGIC_DBLIST); + + for (i=0; i < dblist->count; i++) { + if ((dblist->list[i].ino != ino) || + (dblist->list[i].blockcnt != blockcnt)) + continue; + dblist->list[i].blk = blk; + dblist->sorted = 0; + return 0; + } + return EXT2_ET_DB_NOT_FOUND; +} + +void ext2fs_dblist_sort(ext2_dblist dblist, + int (*sortfunc)(const void *, + const void *)) +{ + if (!sortfunc) + sortfunc = dir_block_cmp; + qsort(dblist->list, (size_t) dblist->count, + sizeof(struct ext2_db_entry), sortfunc); + dblist->sorted = 1; +} + +/* + * This function iterates over the directory block list + */ +errcode_t ext2fs_dblist_iterate(ext2_dblist dblist, + int (*func)(ext2_filsys fs, + struct ext2_db_entry *db_info, + void *priv_data), + void *priv_data) +{ + ext2_ino_t i; + int ret; + + EXT2_CHECK_MAGIC(dblist, EXT2_ET_MAGIC_DBLIST); + + if (!dblist->sorted) + ext2fs_dblist_sort(dblist, 0); + for (i=0; i < dblist->count; i++) { + ret = (*func)(dblist->fs, &dblist->list[(int)i], priv_data); + if (ret & DBLIST_ABORT) + return 0; + } + return 0; +} + +static int dir_block_cmp(const void *a, const void *b) +{ + const struct ext2_db_entry *db_a = + (const struct ext2_db_entry *) a; + const struct ext2_db_entry *db_b = + (const struct ext2_db_entry *) b; + + if (db_a->blk != db_b->blk) + return (int) (db_a->blk - db_b->blk); + + if (db_a->ino != db_b->ino) + return (int) (db_a->ino - db_b->ino); + + return (int) (db_a->blockcnt - db_b->blockcnt); +} + +int ext2fs_dblist_count(ext2_dblist dblist) +{ + return (int) dblist->count; +} diff --git a/e2fsprogs/ext2fs/dblist_dir.c b/e2fsprogs/ext2fs/dblist_dir.c new file mode 100644 index 000000000..b23920466 --- /dev/null +++ b/e2fsprogs/ext2fs/dblist_dir.c @@ -0,0 +1,76 @@ +/* vi: set sw=4 ts=4: */ +/* + * dblist_dir.c --- iterate by directory entry + * + * Copyright 1997 by Theodore Ts'o + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + * + */ + +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include + +#include "ext2_fs.h" +#include "ext2fsP.h" + +static int db_dir_proc(ext2_filsys fs, struct ext2_db_entry *db_info, + void *priv_data); + +errcode_t ext2fs_dblist_dir_iterate(ext2_dblist dblist, + int flags, + char *block_buf, + int (*func)(ext2_ino_t dir, + int entry, + struct ext2_dir_entry *dirent, + int offset, + int blocksize, + char *buf, + void *priv_data), + void *priv_data) +{ + errcode_t retval; + struct dir_context ctx; + + EXT2_CHECK_MAGIC(dblist, EXT2_ET_MAGIC_DBLIST); + + ctx.dir = 0; + ctx.flags = flags; + if (block_buf) + ctx.buf = block_buf; + else { + retval = ext2fs_get_mem(dblist->fs->blocksize, &ctx.buf); + if (retval) + return retval; + } + ctx.func = func; + ctx.priv_data = priv_data; + ctx.errcode = 0; + + retval = ext2fs_dblist_iterate(dblist, db_dir_proc, &ctx); + + if (!block_buf) + ext2fs_free_mem(&ctx.buf); + if (retval) + return retval; + return ctx.errcode; +} + +static int db_dir_proc(ext2_filsys fs, struct ext2_db_entry *db_info, + void *priv_data) +{ + struct dir_context *ctx; + + ctx = (struct dir_context *) priv_data; + ctx->dir = db_info->ino; + + return ext2fs_process_dir_block(fs, &db_info->blk, + db_info->blockcnt, 0, 0, priv_data); +} diff --git a/e2fsprogs/ext2fs/dir_iterate.c b/e2fsprogs/ext2fs/dir_iterate.c new file mode 100644 index 000000000..b7d873595 --- /dev/null +++ b/e2fsprogs/ext2fs/dir_iterate.c @@ -0,0 +1,220 @@ +/* vi: set sw=4 ts=4: */ +/* + * dir_iterate.c --- ext2fs directory iteration operations + * + * Copyright (C) 1993, 1994, 1994, 1995, 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fsP.h" + +/* + * This function checks to see whether or not a potential deleted + * directory entry looks valid. What we do is check the deleted entry + * and each successive entry to make sure that they all look valid and + * that the last deleted entry ends at the beginning of the next + * undeleted entry. Returns 1 if the deleted entry looks valid, zero + * if not valid. + */ +static int ext2fs_validate_entry(char *buf, int offset, int final_offset) +{ + struct ext2_dir_entry *dirent; + + while (offset < final_offset) { + dirent = (struct ext2_dir_entry *)(buf + offset); + offset += dirent->rec_len; + if ((dirent->rec_len < 8) || + ((dirent->rec_len % 4) != 0) || + (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) + return 0; + } + return (offset == final_offset); +} + +errcode_t ext2fs_dir_iterate2(ext2_filsys fs, + ext2_ino_t dir, + int flags, + char *block_buf, + int (*func)(ext2_ino_t dir, + int entry, + struct ext2_dir_entry *dirent, + int offset, + int blocksize, + char *buf, + void *priv_data), + void *priv_data) +{ + struct dir_context ctx; + errcode_t retval; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + retval = ext2fs_check_directory(fs, dir); + if (retval) + return retval; + + ctx.dir = dir; + ctx.flags = flags; + if (block_buf) + ctx.buf = block_buf; + else { + retval = ext2fs_get_mem(fs->blocksize, &ctx.buf); + if (retval) + return retval; + } + ctx.func = func; + ctx.priv_data = priv_data; + ctx.errcode = 0; + retval = ext2fs_block_iterate2(fs, dir, 0, 0, + ext2fs_process_dir_block, &ctx); + if (!block_buf) + ext2fs_free_mem(&ctx.buf); + if (retval) + return retval; + return ctx.errcode; +} + +struct xlate { + int (*func)(struct ext2_dir_entry *dirent, + int offset, + int blocksize, + char *buf, + void *priv_data); + void *real_private; +}; + +static int xlate_func(ext2_ino_t dir EXT2FS_ATTR((unused)), + int entry EXT2FS_ATTR((unused)), + struct ext2_dir_entry *dirent, int offset, + int blocksize, char *buf, void *priv_data) +{ + struct xlate *xl = (struct xlate *) priv_data; + + return (*xl->func)(dirent, offset, blocksize, buf, xl->real_private); +} + +extern errcode_t ext2fs_dir_iterate(ext2_filsys fs, + ext2_ino_t dir, + int flags, + char *block_buf, + int (*func)(struct ext2_dir_entry *dirent, + int offset, + int blocksize, + char *buf, + void *priv_data), + void *priv_data) +{ + struct xlate xl; + + xl.real_private = priv_data; + xl.func = func; + + return ext2fs_dir_iterate2(fs, dir, flags, block_buf, + xlate_func, &xl); +} + + +/* + * Helper function which is private to this module. Used by + * ext2fs_dir_iterate() and ext2fs_dblist_dir_iterate() + */ +int ext2fs_process_dir_block(ext2_filsys fs, + blk_t *blocknr, + e2_blkcnt_t blockcnt, + blk_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct dir_context *ctx = (struct dir_context *) priv_data; + unsigned int offset = 0; + unsigned int next_real_entry = 0; + int ret = 0; + int changed = 0; + int do_abort = 0; + int entry, size; + struct ext2_dir_entry *dirent; + + if (blockcnt < 0) + return 0; + + entry = blockcnt ? DIRENT_OTHER_FILE : DIRENT_DOT_FILE; + + ctx->errcode = ext2fs_read_dir_block(fs, *blocknr, ctx->buf); + if (ctx->errcode) + return BLOCK_ABORT; + + while (offset < fs->blocksize) { + dirent = (struct ext2_dir_entry *) (ctx->buf + offset); + if (((offset + dirent->rec_len) > fs->blocksize) || + (dirent->rec_len < 8) || + ((dirent->rec_len % 4) != 0) || + (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) { + ctx->errcode = EXT2_ET_DIR_CORRUPTED; + return BLOCK_ABORT; + } + if (!dirent->inode && + !(ctx->flags & DIRENT_FLAG_INCLUDE_EMPTY)) + goto next; + + ret = (ctx->func)(ctx->dir, + (next_real_entry > offset) ? + DIRENT_DELETED_FILE : entry, + dirent, offset, + fs->blocksize, ctx->buf, + ctx->priv_data); + if (entry < DIRENT_OTHER_FILE) + entry++; + + if (ret & DIRENT_CHANGED) + changed++; + if (ret & DIRENT_ABORT) { + do_abort++; + break; + } +next: + if (next_real_entry == offset) + next_real_entry += dirent->rec_len; + + if (ctx->flags & DIRENT_FLAG_INCLUDE_REMOVED) { + size = ((dirent->name_len & 0xFF) + 11) & ~3; + + if (dirent->rec_len != size) { + unsigned int final_offset; + + final_offset = offset + dirent->rec_len; + offset += size; + while (offset < final_offset && + !ext2fs_validate_entry(ctx->buf, + offset, + final_offset)) + offset += 4; + continue; + } + } + offset += dirent->rec_len; + } + + if (changed) { + ctx->errcode = ext2fs_write_dir_block(fs, *blocknr, ctx->buf); + if (ctx->errcode) + return BLOCK_ABORT; + } + if (do_abort) + return BLOCK_ABORT; + return 0; +} + diff --git a/e2fsprogs/ext2fs/dirblock.c b/e2fsprogs/ext2fs/dirblock.c new file mode 100644 index 000000000..5d3f6a1bc --- /dev/null +++ b/e2fsprogs/ext2fs/dirblock.c @@ -0,0 +1,133 @@ +/* vi: set sw=4 ts=4: */ +/* + * dirblock.c --- directory block routines. + * + * Copyright (C) 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include + +#include "ext2_fs.h" +#include "ext2fs.h" + +errcode_t ext2fs_read_dir_block2(ext2_filsys fs, blk_t block, + void *buf, int flags EXT2FS_ATTR((unused))) +{ + errcode_t retval; + char *p, *end; + struct ext2_dir_entry *dirent; + unsigned int name_len, rec_len; +#if BB_BIG_ENDIAN + unsigned int do_swap; +#endif + + retval = io_channel_read_blk(fs->io, block, 1, buf); + if (retval) + return retval; +#if BB_BIG_ENDIAN + do_swap = (fs->flags & (EXT2_FLAG_SWAP_BYTES| + EXT2_FLAG_SWAP_BYTES_READ)) != 0; +#endif + p = (char *) buf; + end = (char *) buf + fs->blocksize; + while (p < end-8) { + dirent = (struct ext2_dir_entry *) p; +#if BB_BIG_ENDIAN + if (do_swap) { + dirent->inode = ext2fs_swab32(dirent->inode); + dirent->rec_len = ext2fs_swab16(dirent->rec_len); + dirent->name_len = ext2fs_swab16(dirent->name_len); + } +#endif + name_len = dirent->name_len; +#ifdef WORDS_BIGENDIAN + if (flags & EXT2_DIRBLOCK_V2_STRUCT) + dirent->name_len = ext2fs_swab16(dirent->name_len); +#endif + rec_len = dirent->rec_len; + if ((rec_len < 8) || (rec_len % 4)) { + rec_len = 8; + retval = EXT2_ET_DIR_CORRUPTED; + } + if (((name_len & 0xFF) + 8) > dirent->rec_len) + retval = EXT2_ET_DIR_CORRUPTED; + p += rec_len; + } + return retval; +} + +errcode_t ext2fs_read_dir_block(ext2_filsys fs, blk_t block, + void *buf) +{ + return ext2fs_read_dir_block2(fs, block, buf, 0); +} + + +errcode_t ext2fs_write_dir_block2(ext2_filsys fs, blk_t block, + void *inbuf, int flags EXT2FS_ATTR((unused))) +{ +#if BB_BIG_ENDIAN + int do_swap = 0; + errcode_t retval; + char *p, *end; + char *buf = 0; + struct ext2_dir_entry *dirent; + + if ((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_WRITE)) + do_swap = 1; + +#ifndef WORDS_BIGENDIAN + if (!do_swap) + return io_channel_write_blk(fs->io, block, 1, (char *) inbuf); +#endif + + retval = ext2fs_get_mem(fs->blocksize, &buf); + if (retval) + return retval; + memcpy(buf, inbuf, fs->blocksize); + p = buf; + end = buf + fs->blocksize; + while (p < end) { + dirent = (struct ext2_dir_entry *) p; + if ((dirent->rec_len < 8) || + (dirent->rec_len % 4)) { + ext2fs_free_mem(&buf); + return EXT2_ET_DIR_CORRUPTED; + } + p += dirent->rec_len; + if (do_swap) { + dirent->inode = ext2fs_swab32(dirent->inode); + dirent->rec_len = ext2fs_swab16(dirent->rec_len); + dirent->name_len = ext2fs_swab16(dirent->name_len); + } +#ifdef WORDS_BIGENDIAN + if (flags & EXT2_DIRBLOCK_V2_STRUCT) + dirent->name_len = ext2fs_swab16(dirent->name_len); +#endif + } + retval = io_channel_write_blk(fs->io, block, 1, buf); + ext2fs_free_mem(&buf); + return retval; +#else + return io_channel_write_blk(fs->io, block, 1, (char *) inbuf); +#endif +} + + +errcode_t ext2fs_write_dir_block(ext2_filsys fs, blk_t block, + void *inbuf) +{ + return ext2fs_write_dir_block2(fs, block, inbuf, 0); +} + diff --git a/e2fsprogs/ext2fs/dirhash.c b/e2fsprogs/ext2fs/dirhash.c new file mode 100644 index 000000000..ab3243fd7 --- /dev/null +++ b/e2fsprogs/ext2fs/dirhash.c @@ -0,0 +1,234 @@ +/* vi: set sw=4 ts=4: */ +/* + * dirhash.c -- Calculate the hash of a directory entry + * + * Copyright (c) 2001 Daniel Phillips + * + * Copyright (c) 2002 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include + +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * Keyed 32-bit hash function using TEA in a Davis-Meyer function + * H0 = Key + * Hi = E Mi(Hi-1) + Hi-1 + * + * (see Applied Cryptography, 2nd edition, p448). + * + * Jeremy Fitzhardinge 1998 + * + * This code is made available under the terms of the GPL + */ +#define DELTA 0x9E3779B9 + +static void TEA_transform(__u32 buf[4], __u32 const in[]) +{ + __u32 sum = 0; + __u32 b0 = buf[0], b1 = buf[1]; + __u32 a = in[0], b = in[1], c = in[2], d = in[3]; + int n = 16; + + do { + sum += DELTA; + b0 += ((b1 << 4)+a) ^ (b1+sum) ^ ((b1 >> 5)+b); + b1 += ((b0 << 4)+c) ^ (b0+sum) ^ ((b0 >> 5)+d); + } while(--n); + + buf[0] += b0; + buf[1] += b1; +} + +/* F, G and H are basic MD4 functions: selection, majority, parity */ +#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define G(x, y, z) (((x) & (y)) + (((x) ^ (y)) & (z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) + +/* + * The generic round function. The application is so specific that + * we don't bother protecting all the arguments with parens, as is generally + * good macro practice, in favor of extra legibility. + * Rotation is separate from addition to prevent recomputation + */ +#define ROUND(f, a, b, c, d, x, s) \ + (a += f(b, c, d) + x, a = (a << s) | (a >> (32-s))) +#define K1 0 +#define K2 013240474631UL +#define K3 015666365641UL + +/* + * Basic cut-down MD4 transform. Returns only 32 bits of result. + */ +static void halfMD4Transform (__u32 buf[4], __u32 const in[]) +{ + __u32 a = buf[0], b = buf[1], c = buf[2], d = buf[3]; + + /* Round 1 */ + ROUND(F, a, b, c, d, in[0] + K1, 3); + ROUND(F, d, a, b, c, in[1] + K1, 7); + ROUND(F, c, d, a, b, in[2] + K1, 11); + ROUND(F, b, c, d, a, in[3] + K1, 19); + ROUND(F, a, b, c, d, in[4] + K1, 3); + ROUND(F, d, a, b, c, in[5] + K1, 7); + ROUND(F, c, d, a, b, in[6] + K1, 11); + ROUND(F, b, c, d, a, in[7] + K1, 19); + + /* Round 2 */ + ROUND(G, a, b, c, d, in[1] + K2, 3); + ROUND(G, d, a, b, c, in[3] + K2, 5); + ROUND(G, c, d, a, b, in[5] + K2, 9); + ROUND(G, b, c, d, a, in[7] + K2, 13); + ROUND(G, a, b, c, d, in[0] + K2, 3); + ROUND(G, d, a, b, c, in[2] + K2, 5); + ROUND(G, c, d, a, b, in[4] + K2, 9); + ROUND(G, b, c, d, a, in[6] + K2, 13); + + /* Round 3 */ + ROUND(H, a, b, c, d, in[3] + K3, 3); + ROUND(H, d, a, b, c, in[7] + K3, 9); + ROUND(H, c, d, a, b, in[2] + K3, 11); + ROUND(H, b, c, d, a, in[6] + K3, 15); + ROUND(H, a, b, c, d, in[1] + K3, 3); + ROUND(H, d, a, b, c, in[5] + K3, 9); + ROUND(H, c, d, a, b, in[0] + K3, 11); + ROUND(H, b, c, d, a, in[4] + K3, 15); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +#undef ROUND +#undef F +#undef G +#undef H +#undef K1 +#undef K2 +#undef K3 + +/* The old legacy hash */ +static ext2_dirhash_t dx_hack_hash (const char *name, int len) +{ + __u32 hash0 = 0x12a3fe2d, hash1 = 0x37abe8f9; + while (len--) { + __u32 hash = hash1 + (hash0 ^ (*name++ * 7152373)); + + if (hash & 0x80000000) hash -= 0x7fffffff; + hash1 = hash0; + hash0 = hash; + } + return (hash0 << 1); +} + +static void str2hashbuf(const char *msg, int len, __u32 *buf, int num) +{ + __u32 pad, val; + int i; + + pad = (__u32)len | ((__u32)len << 8); + pad |= pad << 16; + + val = pad; + if (len > num*4) + len = num * 4; + for (i=0; i < len; i++) { + if ((i % 4) == 0) + val = pad; + val = msg[i] + (val << 8); + if ((i % 4) == 3) { + *buf++ = val; + val = pad; + num--; + } + } + if (--num >= 0) + *buf++ = val; + while (--num >= 0) + *buf++ = pad; +} + +/* + * Returns the hash of a filename. If len is 0 and name is NULL, then + * this function can be used to test whether or not a hash version is + * supported. + * + * The seed is an 4 longword (32 bits) "secret" which can be used to + * uniquify a hash. If the seed is all zero's, then some default seed + * may be used. + * + * A particular hash version specifies whether or not the seed is + * represented, and whether or not the returned hash is 32 bits or 64 + * bits. 32 bit hashes will return 0 for the minor hash. + */ +errcode_t ext2fs_dirhash(int version, const char *name, int len, + const __u32 *seed, + ext2_dirhash_t *ret_hash, + ext2_dirhash_t *ret_minor_hash) +{ + __u32 hash; + __u32 minor_hash = 0; + const char *p; + int i; + __u32 in[8], buf[4]; + + /* Initialize the default seed for the hash checksum functions */ + buf[0] = 0x67452301; + buf[1] = 0xefcdab89; + buf[2] = 0x98badcfe; + buf[3] = 0x10325476; + + /* Check to see if the seed is all zero's */ + if (seed) { + for (i=0; i < 4; i++) { + if (seed[i]) + break; + } + if (i < 4) + memcpy(buf, seed, sizeof(buf)); + } + + switch (version) { + case EXT2_HASH_LEGACY: + hash = dx_hack_hash(name, len); + break; + case EXT2_HASH_HALF_MD4: + p = name; + while (len > 0) { + str2hashbuf(p, len, in, 8); + halfMD4Transform(buf, in); + len -= 32; + p += 32; + } + minor_hash = buf[2]; + hash = buf[1]; + break; + case EXT2_HASH_TEA: + p = name; + while (len > 0) { + str2hashbuf(p, len, in, 4); + TEA_transform(buf, in); + len -= 16; + p += 16; + } + hash = buf[0]; + minor_hash = buf[1]; + break; + default: + *ret_hash = 0; + return EXT2_ET_DIRHASH_UNSUPP; + } + *ret_hash = hash & ~1; + if (ret_minor_hash) + *ret_minor_hash = minor_hash; + return 0; +} diff --git a/e2fsprogs/ext2fs/dupfs.c b/e2fsprogs/ext2fs/dupfs.c new file mode 100644 index 000000000..203c29fe3 --- /dev/null +++ b/e2fsprogs/ext2fs/dupfs.c @@ -0,0 +1,97 @@ +/* vi: set sw=4 ts=4: */ +/* + * dupfs.c --- duplicate a ext2 filesystem handle + * + * Copyright (C) 1997, 1998, 2001, 2003, 2005 by Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include + +#include "ext2_fs.h" +#include "ext2fsP.h" + +errcode_t ext2fs_dup_handle(ext2_filsys src, ext2_filsys *dest) +{ + ext2_filsys fs; + errcode_t retval; + + EXT2_CHECK_MAGIC(src, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + retval = ext2fs_get_mem(sizeof(struct struct_ext2_filsys), &fs); + if (retval) + return retval; + + *fs = *src; + fs->device_name = 0; + fs->super = 0; + fs->orig_super = 0; + fs->group_desc = 0; + fs->inode_map = 0; + fs->block_map = 0; + fs->badblocks = 0; + fs->dblist = 0; + + io_channel_bumpcount(fs->io); + if (fs->icache) + fs->icache->refcount++; + + retval = ext2fs_get_mem(strlen(src->device_name)+1, &fs->device_name); + if (retval) + goto errout; + strcpy(fs->device_name, src->device_name); + + retval = ext2fs_get_mem(SUPERBLOCK_SIZE, &fs->super); + if (retval) + goto errout; + memcpy(fs->super, src->super, SUPERBLOCK_SIZE); + + retval = ext2fs_get_mem(SUPERBLOCK_SIZE, &fs->orig_super); + if (retval) + goto errout; + memcpy(fs->orig_super, src->orig_super, SUPERBLOCK_SIZE); + + retval = ext2fs_get_mem((size_t) fs->desc_blocks * fs->blocksize, + &fs->group_desc); + if (retval) + goto errout; + memcpy(fs->group_desc, src->group_desc, + (size_t) fs->desc_blocks * fs->blocksize); + + if (src->inode_map) { + retval = ext2fs_copy_bitmap(src->inode_map, &fs->inode_map); + if (retval) + goto errout; + } + if (src->block_map) { + retval = ext2fs_copy_bitmap(src->block_map, &fs->block_map); + if (retval) + goto errout; + } + if (src->badblocks) { + retval = ext2fs_badblocks_copy(src->badblocks, &fs->badblocks); + if (retval) + goto errout; + } + if (src->dblist) { + retval = ext2fs_copy_dblist(src->dblist, &fs->dblist); + if (retval) + goto errout; + } + *dest = fs; + return 0; +errout: + ext2fs_free(fs); + return retval; + +} + diff --git a/e2fsprogs/ext2fs/e2image.h b/e2fsprogs/ext2fs/e2image.h new file mode 100644 index 000000000..8d38ecc13 --- /dev/null +++ b/e2fsprogs/ext2fs/e2image.h @@ -0,0 +1,52 @@ +/* vi: set sw=4 ts=4: */ +/* + * e2image.h --- header file describing the ext2 image format + * + * Copyright (C) 2000 Theodore Ts'o. + * + * Note: this uses the POSIX IO interfaces, unlike most of the other + * functions in this library. So sue me. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + + +struct ext2_image_hdr { + __u32 magic_number; /* This must be EXT2_ET_MAGIC_E2IMAGE */ + char magic_descriptor[16]; /* "Ext2 Image 1.0", w/ null padding */ + char fs_hostname[64];/* Hostname of machine of image */ + char fs_netaddr[32]; /* Network address */ + __u32 fs_netaddr_type;/* 0 = IPV4, 1 = IPV6, etc. */ + __u32 fs_device; /* Device number of image */ + char fs_device_name[64]; /* Device name */ + char fs_uuid[16]; /* UUID of filesystem */ + __u32 fs_blocksize; /* Block size of the filesystem */ + __u32 fs_reserved[8]; + + __u32 image_device; /* Device number of image file */ + __u32 image_inode; /* Inode number of image file */ + __u32 image_time; /* Time of image creation */ + __u32 image_reserved[8]; + + __u32 offset_super; /* Byte offset of the sb and descriptors */ + __u32 offset_inode; /* Byte offset of the inode table */ + __u32 offset_inodemap; /* Byte offset of the inode bitmaps */ + __u32 offset_blockmap; /* Byte offset of the inode bitmaps */ + __u32 offset_reserved[8]; +}; + + + + + + + + + + + + + diff --git a/e2fsprogs/ext2fs/expanddir.c b/e2fsprogs/ext2fs/expanddir.c new file mode 100644 index 000000000..8a29ae584 --- /dev/null +++ b/e2fsprogs/ext2fs/expanddir.c @@ -0,0 +1,127 @@ +/* vi: set sw=4 ts=4: */ +/* + * expand.c --- expand an ext2fs directory + * + * Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +struct expand_dir_struct { + int done; + int newblocks; + errcode_t err; +}; + +static int expand_dir_proc(ext2_filsys fs, + blk_t *blocknr, + e2_blkcnt_t blockcnt, + blk_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct expand_dir_struct *es = (struct expand_dir_struct *) priv_data; + blk_t new_blk; + static blk_t last_blk = 0; + char *block; + errcode_t retval; + + if (*blocknr) { + last_blk = *blocknr; + return 0; + } + retval = ext2fs_new_block(fs, last_blk, 0, &new_blk); + if (retval) { + es->err = retval; + return BLOCK_ABORT; + } + if (blockcnt > 0) { + retval = ext2fs_new_dir_block(fs, 0, 0, &block); + if (retval) { + es->err = retval; + return BLOCK_ABORT; + } + es->done = 1; + retval = ext2fs_write_dir_block(fs, new_blk, block); + } else { + retval = ext2fs_get_mem(fs->blocksize, &block); + if (retval) { + es->err = retval; + return BLOCK_ABORT; + } + memset(block, 0, fs->blocksize); + retval = io_channel_write_blk(fs->io, new_blk, 1, block); + } + if (retval) { + es->err = retval; + return BLOCK_ABORT; + } + ext2fs_free_mem(&block); + *blocknr = new_blk; + ext2fs_block_alloc_stats(fs, new_blk, +1); + es->newblocks++; + + if (es->done) + return (BLOCK_CHANGED | BLOCK_ABORT); + else + return BLOCK_CHANGED; +} + +errcode_t ext2fs_expand_dir(ext2_filsys fs, ext2_ino_t dir) +{ + errcode_t retval; + struct expand_dir_struct es; + struct ext2_inode inode; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (!(fs->flags & EXT2_FLAG_RW)) + return EXT2_ET_RO_FILSYS; + + if (!fs->block_map) + return EXT2_ET_NO_BLOCK_BITMAP; + + retval = ext2fs_check_directory(fs, dir); + if (retval) + return retval; + + es.done = 0; + es.err = 0; + es.newblocks = 0; + + retval = ext2fs_block_iterate2(fs, dir, BLOCK_FLAG_APPEND, + 0, expand_dir_proc, &es); + + if (es.err) + return es.err; + if (!es.done) + return EXT2_ET_EXPAND_DIR_ERR; + + /* + * Update the size and block count fields in the inode. + */ + retval = ext2fs_read_inode(fs, dir, &inode); + if (retval) + return retval; + + inode.i_size += fs->blocksize; + inode.i_blocks += (fs->blocksize / 512) * es.newblocks; + + retval = ext2fs_write_inode(fs, dir, &inode); + if (retval) + return retval; + + return 0; +} diff --git a/e2fsprogs/ext2fs/ext2_err.h b/e2fsprogs/ext2fs/ext2_err.h new file mode 100644 index 000000000..ead352810 --- /dev/null +++ b/e2fsprogs/ext2fs/ext2_err.h @@ -0,0 +1,116 @@ +/* vi: set sw=4 ts=4: */ +/* + * ext2_err.h: + * This file is automatically generated; please do not edit it. + */ + +#define EXT2_ET_BASE (2133571328L) +#define EXT2_ET_MAGIC_EXT2FS_FILSYS (2133571329L) +#define EXT2_ET_MAGIC_BADBLOCKS_LIST (2133571330L) +#define EXT2_ET_MAGIC_BADBLOCKS_ITERATE (2133571331L) +#define EXT2_ET_MAGIC_INODE_SCAN (2133571332L) +#define EXT2_ET_MAGIC_IO_CHANNEL (2133571333L) +#define EXT2_ET_MAGIC_UNIX_IO_CHANNEL (2133571334L) +#define EXT2_ET_MAGIC_IO_MANAGER (2133571335L) +#define EXT2_ET_MAGIC_BLOCK_BITMAP (2133571336L) +#define EXT2_ET_MAGIC_INODE_BITMAP (2133571337L) +#define EXT2_ET_MAGIC_GENERIC_BITMAP (2133571338L) +#define EXT2_ET_MAGIC_TEST_IO_CHANNEL (2133571339L) +#define EXT2_ET_MAGIC_DBLIST (2133571340L) +#define EXT2_ET_MAGIC_ICOUNT (2133571341L) +#define EXT2_ET_MAGIC_PQ_IO_CHANNEL (2133571342L) +#define EXT2_ET_MAGIC_EXT2_FILE (2133571343L) +#define EXT2_ET_MAGIC_E2IMAGE (2133571344L) +#define EXT2_ET_MAGIC_INODE_IO_CHANNEL (2133571345L) +#define EXT2_ET_MAGIC_RESERVED_9 (2133571346L) +#define EXT2_ET_BAD_MAGIC (2133571347L) +#define EXT2_ET_REV_TOO_HIGH (2133571348L) +#define EXT2_ET_RO_FILSYS (2133571349L) +#define EXT2_ET_GDESC_READ (2133571350L) +#define EXT2_ET_GDESC_WRITE (2133571351L) +#define EXT2_ET_GDESC_BAD_BLOCK_MAP (2133571352L) +#define EXT2_ET_GDESC_BAD_INODE_MAP (2133571353L) +#define EXT2_ET_GDESC_BAD_INODE_TABLE (2133571354L) +#define EXT2_ET_INODE_BITMAP_WRITE (2133571355L) +#define EXT2_ET_INODE_BITMAP_READ (2133571356L) +#define EXT2_ET_BLOCK_BITMAP_WRITE (2133571357L) +#define EXT2_ET_BLOCK_BITMAP_READ (2133571358L) +#define EXT2_ET_INODE_TABLE_WRITE (2133571359L) +#define EXT2_ET_INODE_TABLE_READ (2133571360L) +#define EXT2_ET_NEXT_INODE_READ (2133571361L) +#define EXT2_ET_UNEXPECTED_BLOCK_SIZE (2133571362L) +#define EXT2_ET_DIR_CORRUPTED (2133571363L) +#define EXT2_ET_SHORT_READ (2133571364L) +#define EXT2_ET_SHORT_WRITE (2133571365L) +#define EXT2_ET_DIR_NO_SPACE (2133571366L) +#define EXT2_ET_NO_INODE_BITMAP (2133571367L) +#define EXT2_ET_NO_BLOCK_BITMAP (2133571368L) +#define EXT2_ET_BAD_INODE_NUM (2133571369L) +#define EXT2_ET_BAD_BLOCK_NUM (2133571370L) +#define EXT2_ET_EXPAND_DIR_ERR (2133571371L) +#define EXT2_ET_TOOSMALL (2133571372L) +#define EXT2_ET_BAD_BLOCK_MARK (2133571373L) +#define EXT2_ET_BAD_BLOCK_UNMARK (2133571374L) +#define EXT2_ET_BAD_BLOCK_TEST (2133571375L) +#define EXT2_ET_BAD_INODE_MARK (2133571376L) +#define EXT2_ET_BAD_INODE_UNMARK (2133571377L) +#define EXT2_ET_BAD_INODE_TEST (2133571378L) +#define EXT2_ET_FUDGE_BLOCK_BITMAP_END (2133571379L) +#define EXT2_ET_FUDGE_INODE_BITMAP_END (2133571380L) +#define EXT2_ET_BAD_IND_BLOCK (2133571381L) +#define EXT2_ET_BAD_DIND_BLOCK (2133571382L) +#define EXT2_ET_BAD_TIND_BLOCK (2133571383L) +#define EXT2_ET_NEQ_BLOCK_BITMAP (2133571384L) +#define EXT2_ET_NEQ_INODE_BITMAP (2133571385L) +#define EXT2_ET_BAD_DEVICE_NAME (2133571386L) +#define EXT2_ET_MISSING_INODE_TABLE (2133571387L) +#define EXT2_ET_CORRUPT_SUPERBLOCK (2133571388L) +#define EXT2_ET_BAD_GENERIC_MARK (2133571389L) +#define EXT2_ET_BAD_GENERIC_UNMARK (2133571390L) +#define EXT2_ET_BAD_GENERIC_TEST (2133571391L) +#define EXT2_ET_SYMLINK_LOOP (2133571392L) +#define EXT2_ET_CALLBACK_NOTHANDLED (2133571393L) +#define EXT2_ET_BAD_BLOCK_IN_INODE_TABLE (2133571394L) +#define EXT2_ET_UNSUPP_FEATURE (2133571395L) +#define EXT2_ET_RO_UNSUPP_FEATURE (2133571396L) +#define EXT2_ET_LLSEEK_FAILED (2133571397L) +#define EXT2_ET_NO_MEMORY (2133571398L) +#define EXT2_ET_INVALID_ARGUMENT (2133571399L) +#define EXT2_ET_BLOCK_ALLOC_FAIL (2133571400L) +#define EXT2_ET_INODE_ALLOC_FAIL (2133571401L) +#define EXT2_ET_NO_DIRECTORY (2133571402L) +#define EXT2_ET_TOO_MANY_REFS (2133571403L) +#define EXT2_ET_FILE_NOT_FOUND (2133571404L) +#define EXT2_ET_FILE_RO (2133571405L) +#define EXT2_ET_DB_NOT_FOUND (2133571406L) +#define EXT2_ET_DIR_EXISTS (2133571407L) +#define EXT2_ET_UNIMPLEMENTED (2133571408L) +#define EXT2_ET_CANCEL_REQUESTED (2133571409L) +#define EXT2_ET_FILE_TOO_BIG (2133571410L) +#define EXT2_ET_JOURNAL_NOT_BLOCK (2133571411L) +#define EXT2_ET_NO_JOURNAL_SB (2133571412L) +#define EXT2_ET_JOURNAL_TOO_SMALL (2133571413L) +#define EXT2_ET_JOURNAL_UNSUPP_VERSION (2133571414L) +#define EXT2_ET_LOAD_EXT_JOURNAL (2133571415L) +#define EXT2_ET_NO_JOURNAL (2133571416L) +#define EXT2_ET_DIRHASH_UNSUPP (2133571417L) +#define EXT2_ET_BAD_EA_BLOCK_NUM (2133571418L) +#define EXT2_ET_TOO_MANY_INODES (2133571419L) +#define EXT2_ET_NOT_IMAGE_FILE (2133571420L) +#define EXT2_ET_RES_GDT_BLOCKS (2133571421L) +#define EXT2_ET_RESIZE_INODE_CORRUPT (2133571422L) +#define EXT2_ET_SET_BMAP_NO_IND (2133571423L) + +#if 0 +extern const struct error_table et_ext2_error_table; +extern void initialize_ext2_error_table(void); + +/* For compatibility with Heimdal */ +extern void initialize_ext2_error_table_r(struct et_list **list); + +#define ERROR_TABLE_BASE_ext2 (2133571328L) + +/* for compatibility with older versions... */ +#define init_ext2_err_tbl initialize_ext2_error_table +#define ext2_err_base ERROR_TABLE_BASE_ext2 +#endif diff --git a/e2fsprogs/ext2fs/ext2_ext_attr.h b/e2fsprogs/ext2fs/ext2_ext_attr.h new file mode 100644 index 000000000..cc91bb8d6 --- /dev/null +++ b/e2fsprogs/ext2fs/ext2_ext_attr.h @@ -0,0 +1,53 @@ +/* vi: set sw=4 ts=4: */ +/* + File: linux/ext2_ext_attr.h + + On-disk format of extended attributes for the ext2 filesystem. + + (C) 2000 Andreas Gruenbacher, +*/ + +/* Magic value in attribute blocks */ +#define EXT2_EXT_ATTR_MAGIC_v1 0xEA010000 +#define EXT2_EXT_ATTR_MAGIC 0xEA020000 + +/* Maximum number of references to one attribute block */ +#define EXT2_EXT_ATTR_REFCOUNT_MAX 1024 + +struct ext2_ext_attr_header { + __u32 h_magic; /* magic number for identification */ + __u32 h_refcount; /* reference count */ + __u32 h_blocks; /* number of disk blocks used */ + __u32 h_hash; /* hash value of all attributes */ + __u32 h_reserved[4]; /* zero right now */ +}; + +struct ext2_ext_attr_entry { + __u8 e_name_len; /* length of name */ + __u8 e_name_index; /* attribute name index */ + __u16 e_value_offs; /* offset in disk block of value */ + __u32 e_value_block; /* disk block attribute is stored on (n/i) */ + __u32 e_value_size; /* size of attribute value */ + __u32 e_hash; /* hash value of name and value */ +}; + +#define EXT2_EXT_ATTR_PAD_BITS 2 +#define EXT2_EXT_ATTR_PAD (1<e_name_len)) ) +#define EXT2_EXT_ATTR_SIZE(size) \ + (((size) + EXT2_EXT_ATTR_ROUND) & ~EXT2_EXT_ATTR_ROUND) +#define EXT2_EXT_IS_LAST_ENTRY(entry) (*((__u32 *)(entry)) == 0UL) +#define EXT2_EXT_ATTR_NAME(entry) \ + (((char *) (entry)) + sizeof(struct ext2_ext_attr_entry)) +#define EXT2_XATTR_LEN(name_len) \ + (((name_len) + EXT2_EXT_ATTR_ROUND + \ + sizeof(struct ext2_xattr_entry)) & ~EXT2_EXT_ATTR_ROUND) +#define EXT2_XATTR_SIZE(size) \ + (((size) + EXT2_EXT_ATTR_ROUND) & ~EXT2_EXT_ATTR_ROUND) + diff --git a/e2fsprogs/ext2fs/ext2_fs.h b/e2fsprogs/ext2fs/ext2_fs.h new file mode 100644 index 000000000..cb49d7a60 --- /dev/null +++ b/e2fsprogs/ext2fs/ext2_fs.h @@ -0,0 +1,570 @@ +/* vi: set sw=4 ts=4: */ +/* + * linux/include/linux/ext2_fs.h + * + * Copyright (C) 1992, 1993, 1994, 1995 + * Remy Card (card@masi.ibp.fr) + * Laboratoire MASI - Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * from + * + * linux/include/linux/minix_fs.h + * + * Copyright (C) 1991, 1992 Linus Torvalds + */ + +#ifndef _LINUX_EXT2_FS_H +#define _LINUX_EXT2_FS_H + +#include "ext2_types.h" /* Changed from linux/types.h */ + +/* + * Special inode numbers + */ +#define EXT2_BAD_INO 1 /* Bad blocks inode */ +#define EXT2_ROOT_INO 2 /* Root inode */ +#define EXT2_ACL_IDX_INO 3 /* ACL inode */ +#define EXT2_ACL_DATA_INO 4 /* ACL inode */ +#define EXT2_BOOT_LOADER_INO 5 /* Boot loader inode */ +#define EXT2_UNDEL_DIR_INO 6 /* Undelete directory inode */ +#define EXT2_RESIZE_INO 7 /* Reserved group descriptors inode */ +#define EXT2_JOURNAL_INO 8 /* Journal inode */ + +/* First non-reserved inode for old ext2 filesystems */ +#define EXT2_GOOD_OLD_FIRST_INO 11 + +/* + * The second extended file system magic number + */ +#define EXT2_SUPER_MAGIC 0xEF53 + +/* Assume that user mode programs are passing in an ext2fs superblock, not + * a kernel struct super_block. This will allow us to call the feature-test + * macros from user land. */ +#define EXT2_SB(sb) (sb) + +/* + * Maximal count of links to a file + */ +#define EXT2_LINK_MAX 32000 + +/* + * Macro-instructions used to manage several block sizes + */ +#define EXT2_MIN_BLOCK_LOG_SIZE 10 /* 1024 */ +#define EXT2_MAX_BLOCK_LOG_SIZE 16 /* 65536 */ +#define EXT2_MIN_BLOCK_SIZE (1 << EXT2_MIN_BLOCK_LOG_SIZE) +#define EXT2_MAX_BLOCK_SIZE (1 << EXT2_MAX_BLOCK_LOG_SIZE) +#define EXT2_BLOCK_SIZE(s) (EXT2_MIN_BLOCK_SIZE << (s)->s_log_block_size) +#define EXT2_BLOCK_SIZE_BITS(s) ((s)->s_log_block_size + 10) +#define EXT2_INODE_SIZE(s) (((s)->s_rev_level == EXT2_GOOD_OLD_REV) ? \ + EXT2_GOOD_OLD_INODE_SIZE : (s)->s_inode_size) +#define EXT2_FIRST_INO(s) (((s)->s_rev_level == EXT2_GOOD_OLD_REV) ? \ + EXT2_GOOD_OLD_FIRST_INO : (s)->s_first_ino) +#define EXT2_ADDR_PER_BLOCK(s) (EXT2_BLOCK_SIZE(s) / sizeof(__u32)) + +/* + * Macro-instructions used to manage fragments + */ +#define EXT2_MIN_FRAG_SIZE EXT2_MIN_BLOCK_SIZE +#define EXT2_MAX_FRAG_SIZE EXT2_MAX_BLOCK_SIZE +#define EXT2_MIN_FRAG_LOG_SIZE EXT2_MIN_BLOCK_LOG_SIZE +# define EXT2_FRAG_SIZE(s) (EXT2_MIN_FRAG_SIZE << (s)->s_log_frag_size) +# define EXT2_FRAGS_PER_BLOCK(s) (EXT2_BLOCK_SIZE(s) / EXT2_FRAG_SIZE(s)) + +/* + * ACL structures + */ +struct ext2_acl_header /* Header of Access Control Lists */ +{ + __u32 aclh_size; + __u32 aclh_file_count; + __u32 aclh_acle_count; + __u32 aclh_first_acle; +}; + +struct ext2_acl_entry /* Access Control List Entry */ +{ + __u32 acle_size; + __u16 acle_perms; /* Access permissions */ + __u16 acle_type; /* Type of entry */ + __u16 acle_tag; /* User or group identity */ + __u16 acle_pad1; + __u32 acle_next; /* Pointer on next entry for the */ + /* same inode or on next free entry */ +}; + +/* + * Structure of a blocks group descriptor + */ +struct ext2_group_desc +{ + __u32 bg_block_bitmap; /* Blocks bitmap block */ + __u32 bg_inode_bitmap; /* Inodes bitmap block */ + __u32 bg_inode_table; /* Inodes table block */ + __u16 bg_free_blocks_count; /* Free blocks count */ + __u16 bg_free_inodes_count; /* Free inodes count */ + __u16 bg_used_dirs_count; /* Directories count */ + __u16 bg_pad; + __u32 bg_reserved[3]; +}; + +/* + * Data structures used by the directory indexing feature + * + * Note: all of the multibyte integer fields are little endian. + */ + +/* + * Note: dx_root_info is laid out so that if it should somehow get + * overlaid by a dirent the two low bits of the hash version will be + * zero. Therefore, the hash version mod 4 should never be 0. + * Sincerely, the paranoia department. + */ +struct ext2_dx_root_info { + __u32 reserved_zero; + __u8 hash_version; /* 0 now, 1 at release */ + __u8 info_length; /* 8 */ + __u8 indirect_levels; + __u8 unused_flags; +}; + +#define EXT2_HASH_LEGACY 0 +#define EXT2_HASH_HALF_MD4 1 +#define EXT2_HASH_TEA 2 + +#define EXT2_HASH_FLAG_INCOMPAT 0x1 + +struct ext2_dx_entry { + __u32 hash; + __u32 block; +}; + +struct ext2_dx_countlimit { + __u16 limit; + __u16 count; +}; + + +/* + * Macro-instructions used to manage group descriptors + */ +#define EXT2_BLOCKS_PER_GROUP(s) (EXT2_SB(s)->s_blocks_per_group) +#define EXT2_INODES_PER_GROUP(s) (EXT2_SB(s)->s_inodes_per_group) +#define EXT2_INODES_PER_BLOCK(s) (EXT2_BLOCK_SIZE(s)/EXT2_INODE_SIZE(s)) +/* limits imposed by 16-bit value gd_free_{blocks,inode}_count */ +#define EXT2_MAX_BLOCKS_PER_GROUP(s) ((1 << 16) - 8) +#define EXT2_MAX_INODES_PER_GROUP(s) ((1 << 16) - EXT2_INODES_PER_BLOCK(s)) +#define EXT2_DESC_PER_BLOCK(s) (EXT2_BLOCK_SIZE(s) / sizeof (struct ext2_group_desc)) + +/* + * Constants relative to the data blocks + */ +#define EXT2_NDIR_BLOCKS 12 +#define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS +#define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1) +#define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1) +#define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1) + +/* + * Inode flags + */ +#define EXT2_SECRM_FL 0x00000001 /* Secure deletion */ +#define EXT2_UNRM_FL 0x00000002 /* Undelete */ +#define EXT2_COMPR_FL 0x00000004 /* Compress file */ +#define EXT2_SYNC_FL 0x00000008 /* Synchronous updates */ +#define EXT2_IMMUTABLE_FL 0x00000010 /* Immutable file */ +#define EXT2_APPEND_FL 0x00000020 /* writes to file may only append */ +#define EXT2_NODUMP_FL 0x00000040 /* do not dump file */ +#define EXT2_NOATIME_FL 0x00000080 /* do not update atime */ +/* Reserved for compression usage... */ +#define EXT2_DIRTY_FL 0x00000100 +#define EXT2_COMPRBLK_FL 0x00000200 /* One or more compressed clusters */ +#define EXT2_NOCOMPR_FL 0x00000400 /* Access raw compressed data */ +#define EXT2_ECOMPR_FL 0x00000800 /* Compression error */ +/* End compression flags --- maybe not all used */ +#define EXT2_BTREE_FL 0x00001000 /* btree format dir */ +#define EXT2_INDEX_FL 0x00001000 /* hash-indexed directory */ +#define EXT2_IMAGIC_FL 0x00002000 +#define EXT3_JOURNAL_DATA_FL 0x00004000 /* file data should be journaled */ +#define EXT2_NOTAIL_FL 0x00008000 /* file tail should not be merged */ +#define EXT2_DIRSYNC_FL 0x00010000 /* Synchronous directory modifications */ +#define EXT2_TOPDIR_FL 0x00020000 /* Top of directory hierarchies*/ +#define EXT3_EXTENTS_FL 0x00080000 /* Inode uses extents */ +#define EXT2_RESERVED_FL 0x80000000 /* reserved for ext2 lib */ + +#define EXT2_FL_USER_VISIBLE 0x0003DFFF /* User visible flags */ +#define EXT2_FL_USER_MODIFIABLE 0x000080FF /* User modifiable flags */ + +/* + * ioctl commands + */ +#define EXT2_IOC_GETFLAGS _IOR('f', 1, long) +#define EXT2_IOC_SETFLAGS _IOW('f', 2, long) +#define EXT2_IOC_GETVERSION _IOR('v', 1, long) +#define EXT2_IOC_SETVERSION _IOW('v', 2, long) + +/* + * Structure of an inode on the disk + */ +struct ext2_inode { + __u16 i_mode; /* File mode */ + __u16 i_uid; /* Low 16 bits of Owner Uid */ + __u32 i_size; /* Size in bytes */ + __u32 i_atime; /* Access time */ + __u32 i_ctime; /* Creation time */ + __u32 i_mtime; /* Modification time */ + __u32 i_dtime; /* Deletion Time */ + __u16 i_gid; /* Low 16 bits of Group Id */ + __u16 i_links_count; /* Links count */ + __u32 i_blocks; /* Blocks count */ + __u32 i_flags; /* File flags */ + union { + struct { + __u32 l_i_reserved1; + } linux1; + struct { + __u32 h_i_translator; + } hurd1; + struct { + __u32 m_i_reserved1; + } masix1; + } osd1; /* OS dependent 1 */ + __u32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */ + __u32 i_generation; /* File version (for NFS) */ + __u32 i_file_acl; /* File ACL */ + __u32 i_dir_acl; /* Directory ACL */ + __u32 i_faddr; /* Fragment address */ + union { + struct { + __u8 l_i_frag; /* Fragment number */ + __u8 l_i_fsize; /* Fragment size */ + __u16 i_pad1; + __u16 l_i_uid_high; /* these 2 fields */ + __u16 l_i_gid_high; /* were reserved2[0] */ + __u32 l_i_reserved2; + } linux2; + struct { + __u8 h_i_frag; /* Fragment number */ + __u8 h_i_fsize; /* Fragment size */ + __u16 h_i_mode_high; + __u16 h_i_uid_high; + __u16 h_i_gid_high; + __u32 h_i_author; + } hurd2; + struct { + __u8 m_i_frag; /* Fragment number */ + __u8 m_i_fsize; /* Fragment size */ + __u16 m_pad1; + __u32 m_i_reserved2[2]; + } masix2; + } osd2; /* OS dependent 2 */ +}; + +/* + * Permanent part of an large inode on the disk + */ +struct ext2_inode_large { + __u16 i_mode; /* File mode */ + __u16 i_uid; /* Low 16 bits of Owner Uid */ + __u32 i_size; /* Size in bytes */ + __u32 i_atime; /* Access time */ + __u32 i_ctime; /* Creation time */ + __u32 i_mtime; /* Modification time */ + __u32 i_dtime; /* Deletion Time */ + __u16 i_gid; /* Low 16 bits of Group Id */ + __u16 i_links_count; /* Links count */ + __u32 i_blocks; /* Blocks count */ + __u32 i_flags; /* File flags */ + union { + struct { + __u32 l_i_reserved1; + } linux1; + struct { + __u32 h_i_translator; + } hurd1; + struct { + __u32 m_i_reserved1; + } masix1; + } osd1; /* OS dependent 1 */ + __u32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */ + __u32 i_generation; /* File version (for NFS) */ + __u32 i_file_acl; /* File ACL */ + __u32 i_dir_acl; /* Directory ACL */ + __u32 i_faddr; /* Fragment address */ + union { + struct { + __u8 l_i_frag; /* Fragment number */ + __u8 l_i_fsize; /* Fragment size */ + __u16 i_pad1; + __u16 l_i_uid_high; /* these 2 fields */ + __u16 l_i_gid_high; /* were reserved2[0] */ + __u32 l_i_reserved2; + } linux2; + struct { + __u8 h_i_frag; /* Fragment number */ + __u8 h_i_fsize; /* Fragment size */ + __u16 h_i_mode_high; + __u16 h_i_uid_high; + __u16 h_i_gid_high; + __u32 h_i_author; + } hurd2; + struct { + __u8 m_i_frag; /* Fragment number */ + __u8 m_i_fsize; /* Fragment size */ + __u16 m_pad1; + __u32 m_i_reserved2[2]; + } masix2; + } osd2; /* OS dependent 2 */ + __u16 i_extra_isize; + __u16 i_pad1; +}; + +#define i_size_high i_dir_acl + +/* + * File system states + */ +#define EXT2_VALID_FS 0x0001 /* Unmounted cleanly */ +#define EXT2_ERROR_FS 0x0002 /* Errors detected */ + +/* + * Mount flags + */ +#define EXT2_MOUNT_CHECK 0x0001 /* Do mount-time checks */ +#define EXT2_MOUNT_GRPID 0x0004 /* Create files with directory's group */ +#define EXT2_MOUNT_DEBUG 0x0008 /* Some debugging messages */ +#define EXT2_MOUNT_ERRORS_CONT 0x0010 /* Continue on errors */ +#define EXT2_MOUNT_ERRORS_RO 0x0020 /* Remount fs ro on errors */ +#define EXT2_MOUNT_ERRORS_PANIC 0x0040 /* Panic on errors */ +#define EXT2_MOUNT_MINIX_DF 0x0080 /* Mimics the Minix statfs */ +#define EXT2_MOUNT_NO_UID32 0x0200 /* Disable 32-bit UIDs */ + +#define clear_opt(o, opt) o &= ~EXT2_MOUNT_##opt +#define set_opt(o, opt) o |= EXT2_MOUNT_##opt +#define test_opt(sb, opt) (EXT2_SB(sb)->s_mount_opt & \ + EXT2_MOUNT_##opt) +/* + * Maximal mount counts between two filesystem checks + */ +#define EXT2_DFL_MAX_MNT_COUNT 20 /* Allow 20 mounts */ +#define EXT2_DFL_CHECKINTERVAL 0 /* Don't use interval check */ + +/* + * Behaviour when detecting errors + */ +#define EXT2_ERRORS_CONTINUE 1 /* Continue execution */ +#define EXT2_ERRORS_RO 2 /* Remount fs read-only */ +#define EXT2_ERRORS_PANIC 3 /* Panic */ +#define EXT2_ERRORS_DEFAULT EXT2_ERRORS_CONTINUE + +/* + * Structure of the super block + */ +struct ext2_super_block { + __u32 s_inodes_count; /* Inodes count */ + __u32 s_blocks_count; /* Blocks count */ + __u32 s_r_blocks_count; /* Reserved blocks count */ + __u32 s_free_blocks_count; /* Free blocks count */ + __u32 s_free_inodes_count; /* Free inodes count */ + __u32 s_first_data_block; /* First Data Block */ + __u32 s_log_block_size; /* Block size */ + __s32 s_log_frag_size; /* Fragment size */ + __u32 s_blocks_per_group; /* # Blocks per group */ + __u32 s_frags_per_group; /* # Fragments per group */ + __u32 s_inodes_per_group; /* # Inodes per group */ + __u32 s_mtime; /* Mount time */ + __u32 s_wtime; /* Write time */ + __u16 s_mnt_count; /* Mount count */ + __s16 s_max_mnt_count; /* Maximal mount count */ + __u16 s_magic; /* Magic signature */ + __u16 s_state; /* File system state */ + __u16 s_errors; /* Behaviour when detecting errors */ + __u16 s_minor_rev_level; /* minor revision level */ + __u32 s_lastcheck; /* time of last check */ + __u32 s_checkinterval; /* max. time between checks */ + __u32 s_creator_os; /* OS */ + __u32 s_rev_level; /* Revision level */ + __u16 s_def_resuid; /* Default uid for reserved blocks */ + __u16 s_def_resgid; /* Default gid for reserved blocks */ + /* + * These fields are for EXT2_DYNAMIC_REV superblocks only. + * + * Note: the difference between the compatible feature set and + * the incompatible feature set is that if there is a bit set + * in the incompatible feature set that the kernel doesn't + * know about, it should refuse to mount the filesystem. + * + * e2fsck's requirements are more strict; if it doesn't know + * about a feature in either the compatible or incompatible + * feature set, it must abort and not try to meddle with + * things it doesn't understand... + */ + __u32 s_first_ino; /* First non-reserved inode */ + __u16 s_inode_size; /* size of inode structure */ + __u16 s_block_group_nr; /* block group # of this superblock */ + __u32 s_feature_compat; /* compatible feature set */ + __u32 s_feature_incompat; /* incompatible feature set */ + __u32 s_feature_ro_compat; /* readonly-compatible feature set */ + __u8 s_uuid[16]; /* 128-bit uuid for volume */ + char s_volume_name[16]; /* volume name */ + char s_last_mounted[64]; /* directory where last mounted */ + __u32 s_algorithm_usage_bitmap; /* For compression */ + /* + * Performance hints. Directory preallocation should only + * happen if the EXT2_FEATURE_COMPAT_DIR_PREALLOC flag is on. + */ + __u8 s_prealloc_blocks; /* Nr of blocks to try to preallocate*/ + __u8 s_prealloc_dir_blocks; /* Nr to preallocate for dirs */ + __u16 s_reserved_gdt_blocks; /* Per group table for online growth */ + /* + * Journaling support valid if EXT2_FEATURE_COMPAT_HAS_JOURNAL set. + */ + __u8 s_journal_uuid[16]; /* uuid of journal superblock */ + __u32 s_journal_inum; /* inode number of journal file */ + __u32 s_journal_dev; /* device number of journal file */ + __u32 s_last_orphan; /* start of list of inodes to delete */ + __u32 s_hash_seed[4]; /* HTREE hash seed */ + __u8 s_def_hash_version; /* Default hash version to use */ + __u8 s_jnl_backup_type; /* Default type of journal backup */ + __u16 s_reserved_word_pad; + __u32 s_default_mount_opts; + __u32 s_first_meta_bg; /* First metablock group */ + __u32 s_mkfs_time; /* When the filesystem was created */ + __u32 s_jnl_blocks[17]; /* Backup of the journal inode */ + __u32 s_reserved[172]; /* Padding to the end of the block */ +}; + +/* + * Codes for operating systems + */ +#define EXT2_OS_LINUX 0 +#define EXT2_OS_HURD 1 +#define EXT2_OS_MASIX 2 +#define EXT2_OS_FREEBSD 3 +#define EXT2_OS_LITES 4 + +/* + * Revision levels + */ +#define EXT2_GOOD_OLD_REV 0 /* The good old (original) format */ +#define EXT2_DYNAMIC_REV 1 /* V2 format w/ dynamic inode sizes */ + +#define EXT2_CURRENT_REV EXT2_GOOD_OLD_REV +#define EXT2_MAX_SUPP_REV EXT2_DYNAMIC_REV + +#define EXT2_GOOD_OLD_INODE_SIZE 128 + +/* + * Journal inode backup types + */ +#define EXT3_JNL_BACKUP_BLOCKS 1 + +/* + * Feature set definitions + */ + +#define EXT2_HAS_COMPAT_FEATURE(sb,mask) \ + ( EXT2_SB(sb)->s_feature_compat & (mask) ) +#define EXT2_HAS_RO_COMPAT_FEATURE(sb,mask) \ + ( EXT2_SB(sb)->s_feature_ro_compat & (mask) ) +#define EXT2_HAS_INCOMPAT_FEATURE(sb,mask) \ + ( EXT2_SB(sb)->s_feature_incompat & (mask) ) + +#define EXT2_FEATURE_COMPAT_DIR_PREALLOC 0x0001 +#define EXT2_FEATURE_COMPAT_IMAGIC_INODES 0x0002 +#define EXT3_FEATURE_COMPAT_HAS_JOURNAL 0x0004 +#define EXT2_FEATURE_COMPAT_EXT_ATTR 0x0008 +#define EXT2_FEATURE_COMPAT_RESIZE_INODE 0x0010 +#define EXT2_FEATURE_COMPAT_DIR_INDEX 0x0020 + +#define EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001 +#define EXT2_FEATURE_RO_COMPAT_LARGE_FILE 0x0002 +/* #define EXT2_FEATURE_RO_COMPAT_BTREE_DIR 0x0004 not used */ + +#define EXT2_FEATURE_INCOMPAT_COMPRESSION 0x0001 +#define EXT2_FEATURE_INCOMPAT_FILETYPE 0x0002 +#define EXT3_FEATURE_INCOMPAT_RECOVER 0x0004 /* Needs recovery */ +#define EXT3_FEATURE_INCOMPAT_JOURNAL_DEV 0x0008 /* Journal device */ +#define EXT2_FEATURE_INCOMPAT_META_BG 0x0010 +#define EXT3_FEATURE_INCOMPAT_EXTENTS 0x0040 + + +#define EXT2_FEATURE_COMPAT_SUPP 0 +#define EXT2_FEATURE_INCOMPAT_SUPP (EXT2_FEATURE_INCOMPAT_FILETYPE) +#define EXT2_FEATURE_RO_COMPAT_SUPP (EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER| \ + EXT2_FEATURE_RO_COMPAT_LARGE_FILE| \ + EXT2_FEATURE_RO_COMPAT_BTREE_DIR) + +/* + * Default values for user and/or group using reserved blocks + */ +#define EXT2_DEF_RESUID 0 +#define EXT2_DEF_RESGID 0 + +/* + * Default mount options + */ +#define EXT2_DEFM_DEBUG 0x0001 +#define EXT2_DEFM_BSDGROUPS 0x0002 +#define EXT2_DEFM_XATTR_USER 0x0004 +#define EXT2_DEFM_ACL 0x0008 +#define EXT2_DEFM_UID16 0x0010 +#define EXT3_DEFM_JMODE 0x0060 +#define EXT3_DEFM_JMODE_DATA 0x0020 +#define EXT3_DEFM_JMODE_ORDERED 0x0040 +#define EXT3_DEFM_JMODE_WBACK 0x0060 + +/* + * Structure of a directory entry + */ +#define EXT2_NAME_LEN 255 + +struct ext2_dir_entry { + __u32 inode; /* Inode number */ + __u16 rec_len; /* Directory entry length */ + __u16 name_len; /* Name length */ + char name[EXT2_NAME_LEN]; /* File name */ +}; + +/* + * The new version of the directory entry. Since EXT2 structures are + * stored in intel byte order, and the name_len field could never be + * bigger than 255 chars, it's safe to reclaim the extra byte for the + * file_type field. + */ +struct ext2_dir_entry_2 { + __u32 inode; /* Inode number */ + __u16 rec_len; /* Directory entry length */ + __u8 name_len; /* Name length */ + __u8 file_type; + char name[EXT2_NAME_LEN]; /* File name */ +}; + +/* + * Ext2 directory file types. Only the low 3 bits are used. The + * other bits are reserved for now. + */ +#define EXT2_FT_UNKNOWN 0 +#define EXT2_FT_REG_FILE 1 +#define EXT2_FT_DIR 2 +#define EXT2_FT_CHRDEV 3 +#define EXT2_FT_BLKDEV 4 +#define EXT2_FT_FIFO 5 +#define EXT2_FT_SOCK 6 +#define EXT2_FT_SYMLINK 7 + +#define EXT2_FT_MAX 8 + +/* + * EXT2_DIR_PAD defines the directory entries boundaries + * + * NOTE: It must be a multiple of 4 + */ +#define EXT2_DIR_PAD 4 +#define EXT2_DIR_ROUND (EXT2_DIR_PAD - 1) +#define EXT2_DIR_REC_LEN(name_len) (((name_len) + 8 + EXT2_DIR_ROUND) & \ + ~EXT2_DIR_ROUND) + +#endif /* _LINUX_EXT2_FS_H */ diff --git a/e2fsprogs/ext2fs/ext2_io.h b/e2fsprogs/ext2fs/ext2_io.h new file mode 100644 index 000000000..e6c9630e2 --- /dev/null +++ b/e2fsprogs/ext2fs/ext2_io.h @@ -0,0 +1,114 @@ +/* vi: set sw=4 ts=4: */ +/* + * io.h --- the I/O manager abstraction + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#ifndef _EXT2FS_EXT2_IO_H +#define _EXT2FS_EXT2_IO_H + +/* + * ext2_loff_t is defined here since unix_io.c needs it. + */ +#if defined(__GNUC__) || defined(HAS_LONG_LONG) +typedef long long ext2_loff_t; +#else +typedef long ext2_loff_t; +#endif + +/* llseek.c */ +/* ext2_loff_t ext2fs_llseek (int, ext2_loff_t, int); */ +#ifdef CONFIG_LFS +# define ext2fs_llseek lseek64 +#else +# define ext2fs_llseek lseek +#endif + +typedef struct struct_io_manager *io_manager; +typedef struct struct_io_channel *io_channel; + +#define CHANNEL_FLAGS_WRITETHROUGH 0x01 + +struct struct_io_channel { + errcode_t magic; + io_manager manager; + char *name; + int block_size; + errcode_t (*read_error)(io_channel channel, + unsigned long block, + int count, + void *data, + size_t size, + int actual_bytes_read, + errcode_t error); + errcode_t (*write_error)(io_channel channel, + unsigned long block, + int count, + const void *data, + size_t size, + int actual_bytes_written, + errcode_t error); + int refcount; + int flags; + int reserved[14]; + void *private_data; + void *app_data; +}; + +struct struct_io_manager { + errcode_t magic; + const char *name; + errcode_t (*open)(const char *name, int flags, io_channel *channel); + errcode_t (*close)(io_channel channel); + errcode_t (*set_blksize)(io_channel channel, int blksize); + errcode_t (*read_blk)(io_channel channel, unsigned long block, + int count, void *data); + errcode_t (*write_blk)(io_channel channel, unsigned long block, + int count, const void *data); + errcode_t (*flush)(io_channel channel); + errcode_t (*write_byte)(io_channel channel, unsigned long offset, + int count, const void *data); + errcode_t (*set_option)(io_channel channel, const char *option, + const char *arg); + int reserved[14]; +}; + +#define IO_FLAG_RW 1 + +/* + * Convenience functions.... + */ +#define io_channel_close(c) ((c)->manager->close((c))) +#define io_channel_set_blksize(c,s) ((c)->manager->set_blksize((c),s)) +#define io_channel_read_blk(c,b,n,d) ((c)->manager->read_blk((c),b,n,d)) +#define io_channel_write_blk(c,b,n,d) ((c)->manager->write_blk((c),b,n,d)) +#define io_channel_flush(c) ((c)->manager->flush((c))) +#define io_channel_bumpcount(c) ((c)->refcount++) + +/* io_manager.c */ +extern errcode_t io_channel_set_options(io_channel channel, + const char *options); +extern errcode_t io_channel_write_byte(io_channel channel, + unsigned long offset, + int count, const void *data); + +/* unix_io.c */ +extern io_manager unix_io_manager; + +/* test_io.c */ +extern io_manager test_io_manager, test_io_backing_manager; +extern void (*test_io_cb_read_blk) + (unsigned long block, int count, errcode_t err); +extern void (*test_io_cb_write_blk) + (unsigned long block, int count, errcode_t err); +extern void (*test_io_cb_set_blksize) + (int blksize, errcode_t err); + +#endif /* _EXT2FS_EXT2_IO_H */ + diff --git a/e2fsprogs/ext2fs/ext2_types.h b/e2fsprogs/ext2fs/ext2_types.h new file mode 100644 index 000000000..2c1196b8b --- /dev/null +++ b/e2fsprogs/ext2fs/ext2_types.h @@ -0,0 +1,2 @@ +/* vi: set sw=4 ts=4: */ +#include diff --git a/e2fsprogs/ext2fs/ext2fs.h b/e2fsprogs/ext2fs/ext2fs.h new file mode 100644 index 000000000..133fb1f1b --- /dev/null +++ b/e2fsprogs/ext2fs/ext2fs.h @@ -0,0 +1,923 @@ +/* vi: set sw=4 ts=4: */ +/* + * ext2fs.h --- ext2fs + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#ifndef _EXT2FS_EXT2FS_H +#define _EXT2FS_EXT2FS_H + + +#define EXT2FS_ATTR(x) + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Where the master copy of the superblock is located, and how big + * superblocks are supposed to be. We define SUPERBLOCK_SIZE because + * the size of the superblock structure is not necessarily trustworthy + * (some versions have the padding set up so that the superblock is + * 1032 bytes long). + */ +#define SUPERBLOCK_OFFSET 1024 +#define SUPERBLOCK_SIZE 1024 + +/* + * The last ext2fs revision level that this version of the library is + * able to support. + */ +#define EXT2_LIB_CURRENT_REV EXT2_DYNAMIC_REV + +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#include +#include + +#include "ext2_types.h" +#include "ext2_fs.h" + +typedef __u32 ext2_ino_t; +typedef __u32 blk_t; +typedef __u32 dgrp_t; +typedef __u32 ext2_off_t; +typedef __s64 e2_blkcnt_t; +typedef __u32 ext2_dirhash_t; + +#include "ext2_io.h" +#include "ext2_err.h" + +typedef struct struct_ext2_filsys *ext2_filsys; + +struct ext2fs_struct_generic_bitmap { + errcode_t magic; + ext2_filsys fs; + __u32 start, end; + __u32 real_end; + char * description; + char * bitmap; + errcode_t base_error_code; + __u32 reserved[7]; +}; + +#define EXT2FS_MARK_ERROR 0 +#define EXT2FS_UNMARK_ERROR 1 +#define EXT2FS_TEST_ERROR 2 + +typedef struct ext2fs_struct_generic_bitmap *ext2fs_generic_bitmap; +typedef struct ext2fs_struct_generic_bitmap *ext2fs_inode_bitmap; +typedef struct ext2fs_struct_generic_bitmap *ext2fs_block_bitmap; + +#define EXT2_FIRST_INODE(s) EXT2_FIRST_INO(s) + +/* + * badblocks list definitions + */ + +typedef struct ext2_struct_u32_list *ext2_badblocks_list; +typedef struct ext2_struct_u32_iterate *ext2_badblocks_iterate; + +typedef struct ext2_struct_u32_list *ext2_u32_list; +typedef struct ext2_struct_u32_iterate *ext2_u32_iterate; + +/* old */ +typedef struct ext2_struct_u32_list *badblocks_list; +typedef struct ext2_struct_u32_iterate *badblocks_iterate; + +#define BADBLOCKS_FLAG_DIRTY 1 + +/* + * ext2_dblist structure and abstractions (see dblist.c) + */ +struct ext2_db_entry { + ext2_ino_t ino; + blk_t blk; + int blockcnt; +}; + +typedef struct ext2_struct_dblist *ext2_dblist; + +#define DBLIST_ABORT 1 + +/* + * ext2_fileio definitions + */ + +#define EXT2_FILE_WRITE 0x0001 +#define EXT2_FILE_CREATE 0x0002 + +#define EXT2_FILE_MASK 0x00FF + +#define EXT2_FILE_BUF_DIRTY 0x4000 +#define EXT2_FILE_BUF_VALID 0x2000 + +typedef struct ext2_file *ext2_file_t; + +#define EXT2_SEEK_SET 0 +#define EXT2_SEEK_CUR 1 +#define EXT2_SEEK_END 2 + +/* + * Flags for the ext2_filsys structure and for ext2fs_open() + */ +#define EXT2_FLAG_RW 0x01 +#define EXT2_FLAG_CHANGED 0x02 +#define EXT2_FLAG_DIRTY 0x04 +#define EXT2_FLAG_VALID 0x08 +#define EXT2_FLAG_IB_DIRTY 0x10 +#define EXT2_FLAG_BB_DIRTY 0x20 +#define EXT2_FLAG_SWAP_BYTES 0x40 +#define EXT2_FLAG_SWAP_BYTES_READ 0x80 +#define EXT2_FLAG_SWAP_BYTES_WRITE 0x100 +#define EXT2_FLAG_MASTER_SB_ONLY 0x200 +#define EXT2_FLAG_FORCE 0x400 +#define EXT2_FLAG_SUPER_ONLY 0x800 +#define EXT2_FLAG_JOURNAL_DEV_OK 0x1000 +#define EXT2_FLAG_IMAGE_FILE 0x2000 + +/* + * Special flag in the ext2 inode i_flag field that means that this is + * a new inode. (So that ext2_write_inode() can clear extra fields.) + */ +#define EXT2_NEW_INODE_FL 0x80000000 + +/* + * Flags for mkjournal + * + * EXT2_MKJOURNAL_V1_SUPER Make a (deprecated) V1 journal superblock + */ +#define EXT2_MKJOURNAL_V1_SUPER 0x0000001 + +struct struct_ext2_filsys { + errcode_t magic; + io_channel io; + int flags; + char * device_name; + struct ext2_super_block * super; + unsigned int blocksize; + int fragsize; + dgrp_t group_desc_count; + unsigned long desc_blocks; + struct ext2_group_desc * group_desc; + int inode_blocks_per_group; + ext2fs_inode_bitmap inode_map; + ext2fs_block_bitmap block_map; + errcode_t (*get_blocks)(ext2_filsys fs, ext2_ino_t ino, blk_t *blocks); + errcode_t (*check_directory)(ext2_filsys fs, ext2_ino_t ino); + errcode_t (*write_bitmaps)(ext2_filsys fs); + errcode_t (*read_inode)(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode); + errcode_t (*write_inode)(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode); + ext2_badblocks_list badblocks; + ext2_dblist dblist; + __u32 stride; /* for mke2fs */ + struct ext2_super_block * orig_super; + struct ext2_image_hdr * image_header; + __u32 umask; + /* + * Reserved for future expansion + */ + __u32 reserved[8]; + + /* + * Reserved for the use of the calling application. + */ + void * priv_data; + + /* + * Inode cache + */ + struct ext2_inode_cache *icache; + io_channel image_io; +}; + +#include "bitops.h" + +/* + * Return flags for the block iterator functions + */ +#define BLOCK_CHANGED 1 +#define BLOCK_ABORT 2 +#define BLOCK_ERROR 4 + +/* + * Block interate flags + * + * BLOCK_FLAG_APPEND, or BLOCK_FLAG_HOLE, indicates that the interator + * function should be called on blocks where the block number is zero. + * This is used by ext2fs_expand_dir() to be able to add a new block + * to an inode. It can also be used for programs that want to be able + * to deal with files that contain "holes". + * + * BLOCK_FLAG_TRAVERSE indicates that the iterator function for the + * indirect, doubly indirect, etc. blocks should be called after all + * of the blocks containined in the indirect blocks are processed. + * This is useful if you are going to be deallocating blocks from an + * inode. + * + * BLOCK_FLAG_DATA_ONLY indicates that the iterator function should be + * called for data blocks only. + * + * BLOCK_FLAG_NO_LARGE is for internal use only. It informs + * ext2fs_block_iterate2 that large files won't be accepted. + */ +#define BLOCK_FLAG_APPEND 1 +#define BLOCK_FLAG_HOLE 1 +#define BLOCK_FLAG_DEPTH_TRAVERSE 2 +#define BLOCK_FLAG_DATA_ONLY 4 + +#define BLOCK_FLAG_NO_LARGE 0x1000 + +/* + * Magic "block count" return values for the block iterator function. + */ +#define BLOCK_COUNT_IND (-1) +#define BLOCK_COUNT_DIND (-2) +#define BLOCK_COUNT_TIND (-3) +#define BLOCK_COUNT_TRANSLATOR (-4) + +#if 0 +/* + * Flags for ext2fs_move_blocks + */ +#define EXT2_BMOVE_GET_DBLIST 0x0001 +#define EXT2_BMOVE_DEBUG 0x0002 +#endif + +/* + * Flags for directory block reading and writing functions + */ +#define EXT2_DIRBLOCK_V2_STRUCT 0x0001 + +/* + * Return flags for the directory iterator functions + */ +#define DIRENT_CHANGED 1 +#define DIRENT_ABORT 2 +#define DIRENT_ERROR 3 + +/* + * Directory iterator flags + */ + +#define DIRENT_FLAG_INCLUDE_EMPTY 1 +#define DIRENT_FLAG_INCLUDE_REMOVED 2 + +#define DIRENT_DOT_FILE 1 +#define DIRENT_DOT_DOT_FILE 2 +#define DIRENT_OTHER_FILE 3 +#define DIRENT_DELETED_FILE 4 + +/* + * Inode scan definitions + */ +typedef struct ext2_struct_inode_scan *ext2_inode_scan; + +/* + * ext2fs_scan flags + */ +#define EXT2_SF_CHK_BADBLOCKS 0x0001 +#define EXT2_SF_BAD_INODE_BLK 0x0002 +#define EXT2_SF_BAD_EXTRA_BYTES 0x0004 +#define EXT2_SF_SKIP_MISSING_ITABLE 0x0008 + +/* + * ext2fs_check_if_mounted flags + */ +#define EXT2_MF_MOUNTED 1 +#define EXT2_MF_ISROOT 2 +#define EXT2_MF_READONLY 4 +#define EXT2_MF_SWAP 8 +#define EXT2_MF_BUSY 16 + +/* + * Ext2/linux mode flags. We define them here so that we don't need + * to depend on the OS's sys/stat.h, since we may be compiling on a + * non-Linux system. + */ +#define LINUX_S_IFMT 00170000 +#define LINUX_S_IFSOCK 0140000 +#define LINUX_S_IFLNK 0120000 +#define LINUX_S_IFREG 0100000 +#define LINUX_S_IFBLK 0060000 +#define LINUX_S_IFDIR 0040000 +#define LINUX_S_IFCHR 0020000 +#define LINUX_S_IFIFO 0010000 +#define LINUX_S_ISUID 0004000 +#define LINUX_S_ISGID 0002000 +#define LINUX_S_ISVTX 0001000 + +#define LINUX_S_IRWXU 00700 +#define LINUX_S_IRUSR 00400 +#define LINUX_S_IWUSR 00200 +#define LINUX_S_IXUSR 00100 + +#define LINUX_S_IRWXG 00070 +#define LINUX_S_IRGRP 00040 +#define LINUX_S_IWGRP 00020 +#define LINUX_S_IXGRP 00010 + +#define LINUX_S_IRWXO 00007 +#define LINUX_S_IROTH 00004 +#define LINUX_S_IWOTH 00002 +#define LINUX_S_IXOTH 00001 + +#define LINUX_S_ISLNK(m) (((m) & LINUX_S_IFMT) == LINUX_S_IFLNK) +#define LINUX_S_ISREG(m) (((m) & LINUX_S_IFMT) == LINUX_S_IFREG) +#define LINUX_S_ISDIR(m) (((m) & LINUX_S_IFMT) == LINUX_S_IFDIR) +#define LINUX_S_ISCHR(m) (((m) & LINUX_S_IFMT) == LINUX_S_IFCHR) +#define LINUX_S_ISBLK(m) (((m) & LINUX_S_IFMT) == LINUX_S_IFBLK) +#define LINUX_S_ISFIFO(m) (((m) & LINUX_S_IFMT) == LINUX_S_IFIFO) +#define LINUX_S_ISSOCK(m) (((m) & LINUX_S_IFMT) == LINUX_S_IFSOCK) + +/* + * ext2 size of an inode + */ +#define EXT2_I_SIZE(i) ((i)->i_size | ((__u64) (i)->i_size_high << 32)) + +/* + * ext2_icount_t abstraction + */ +#define EXT2_ICOUNT_OPT_INCREMENT 0x01 + +typedef struct ext2_icount *ext2_icount_t; + +/* + * Flags for ext2fs_bmap + */ +#define BMAP_ALLOC 0x0001 +#define BMAP_SET 0x0002 + +/* + * Flags for imager.c functions + */ +#define IMAGER_FLAG_INODEMAP 1 +#define IMAGER_FLAG_SPARSEWRITE 2 + +/* + * For checking structure magic numbers... + */ + +#define EXT2_CHECK_MAGIC(struct, code) \ + if ((struct)->magic != (code)) return (code) + + +/* + * For ext2 compression support + */ +#define EXT2FS_COMPRESSED_BLKADDR ((blk_t) 0xffffffff) +#define HOLE_BLKADDR(_b) ((_b) == 0 || (_b) == EXT2FS_COMPRESSED_BLKADDR) + +/* + * Features supported by this version of the library + */ +#define EXT2_LIB_FEATURE_COMPAT_SUPP (EXT2_FEATURE_COMPAT_DIR_PREALLOC|\ + EXT2_FEATURE_COMPAT_IMAGIC_INODES|\ + EXT3_FEATURE_COMPAT_HAS_JOURNAL|\ + EXT2_FEATURE_COMPAT_RESIZE_INODE|\ + EXT2_FEATURE_COMPAT_DIR_INDEX|\ + EXT2_FEATURE_COMPAT_EXT_ATTR) + +/* This #ifdef is temporary until compression is fully supported */ +#ifdef ENABLE_COMPRESSION +#ifndef I_KNOW_THAT_COMPRESSION_IS_EXPERIMENTAL +/* If the below warning bugs you, then have + `CPPFLAGS=-DI_KNOW_THAT_COMPRESSION_IS_EXPERIMENTAL' in your + environment at configure time. */ + #warning "Compression support is experimental" +#endif +#define EXT2_LIB_FEATURE_INCOMPAT_SUPP (EXT2_FEATURE_INCOMPAT_FILETYPE|\ + EXT2_FEATURE_INCOMPAT_COMPRESSION|\ + EXT3_FEATURE_INCOMPAT_JOURNAL_DEV|\ + EXT2_FEATURE_INCOMPAT_META_BG|\ + EXT3_FEATURE_INCOMPAT_RECOVER) +#else +#define EXT2_LIB_FEATURE_INCOMPAT_SUPP (EXT2_FEATURE_INCOMPAT_FILETYPE|\ + EXT3_FEATURE_INCOMPAT_JOURNAL_DEV|\ + EXT2_FEATURE_INCOMPAT_META_BG|\ + EXT3_FEATURE_INCOMPAT_RECOVER) +#endif +#define EXT2_LIB_FEATURE_RO_COMPAT_SUPP (EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER|\ + EXT2_FEATURE_RO_COMPAT_LARGE_FILE) +/* + * function prototypes + */ + +/* alloc.c */ +extern errcode_t ext2fs_new_inode(ext2_filsys fs, ext2_ino_t dir, int mode, + ext2fs_inode_bitmap map, ext2_ino_t *ret); +extern errcode_t ext2fs_new_block(ext2_filsys fs, blk_t goal, + ext2fs_block_bitmap map, blk_t *ret); +extern errcode_t ext2fs_get_free_blocks(ext2_filsys fs, blk_t start, + blk_t finish, int num, + ext2fs_block_bitmap map, + blk_t *ret); +extern errcode_t ext2fs_alloc_block(ext2_filsys fs, blk_t goal, + char *block_buf, blk_t *ret); + +/* alloc_sb.c */ +extern int ext2fs_reserve_super_and_bgd(ext2_filsys fs, + dgrp_t group, + ext2fs_block_bitmap bmap); + +/* alloc_stats.c */ +void ext2fs_inode_alloc_stats(ext2_filsys fs, ext2_ino_t ino, int inuse); +void ext2fs_inode_alloc_stats2(ext2_filsys fs, ext2_ino_t ino, + int inuse, int isdir); +void ext2fs_block_alloc_stats(ext2_filsys fs, blk_t blk, int inuse); + +/* alloc_tables.c */ +extern errcode_t ext2fs_allocate_tables(ext2_filsys fs); +extern errcode_t ext2fs_allocate_group_table(ext2_filsys fs, dgrp_t group, + ext2fs_block_bitmap bmap); + +/* badblocks.c */ +extern errcode_t ext2fs_u32_list_create(ext2_u32_list *ret, int size); +extern errcode_t ext2fs_u32_list_add(ext2_u32_list bb, __u32 blk); +extern int ext2fs_u32_list_find(ext2_u32_list bb, __u32 blk); +extern int ext2fs_u32_list_test(ext2_u32_list bb, blk_t blk); +extern errcode_t ext2fs_u32_list_iterate_begin(ext2_u32_list bb, + ext2_u32_iterate *ret); +extern int ext2fs_u32_list_iterate(ext2_u32_iterate iter, blk_t *blk); +extern void ext2fs_u32_list_iterate_end(ext2_u32_iterate iter); +extern errcode_t ext2fs_u32_copy(ext2_u32_list src, ext2_u32_list *dest); +extern int ext2fs_u32_list_equal(ext2_u32_list bb1, ext2_u32_list bb2); + +extern errcode_t ext2fs_badblocks_list_create(ext2_badblocks_list *ret, + int size); +extern errcode_t ext2fs_badblocks_list_add(ext2_badblocks_list bb, + blk_t blk); +extern int ext2fs_badblocks_list_test(ext2_badblocks_list bb, + blk_t blk); +extern int ext2fs_u32_list_del(ext2_u32_list bb, __u32 blk); +extern void ext2fs_badblocks_list_del(ext2_u32_list bb, __u32 blk); +extern errcode_t + ext2fs_badblocks_list_iterate_begin(ext2_badblocks_list bb, + ext2_badblocks_iterate *ret); +extern int ext2fs_badblocks_list_iterate(ext2_badblocks_iterate iter, + blk_t *blk); +extern void ext2fs_badblocks_list_iterate_end(ext2_badblocks_iterate iter); +extern errcode_t ext2fs_badblocks_copy(ext2_badblocks_list src, + ext2_badblocks_list *dest); +extern int ext2fs_badblocks_equal(ext2_badblocks_list bb1, + ext2_badblocks_list bb2); +extern int ext2fs_u32_list_count(ext2_u32_list bb); + +/* bb_compat */ +extern errcode_t badblocks_list_create(badblocks_list *ret, int size); +extern errcode_t badblocks_list_add(badblocks_list bb, blk_t blk); +extern int badblocks_list_test(badblocks_list bb, blk_t blk); +extern errcode_t badblocks_list_iterate_begin(badblocks_list bb, + badblocks_iterate *ret); +extern int badblocks_list_iterate(badblocks_iterate iter, blk_t *blk); +extern void badblocks_list_iterate_end(badblocks_iterate iter); +extern void badblocks_list_free(badblocks_list bb); + +/* bb_inode.c */ +extern errcode_t ext2fs_update_bb_inode(ext2_filsys fs, + ext2_badblocks_list bb_list); + +/* bitmaps.c */ +extern errcode_t ext2fs_write_inode_bitmap(ext2_filsys fs); +extern errcode_t ext2fs_write_block_bitmap (ext2_filsys fs); +extern errcode_t ext2fs_read_inode_bitmap (ext2_filsys fs); +extern errcode_t ext2fs_read_block_bitmap(ext2_filsys fs); +extern errcode_t ext2fs_allocate_generic_bitmap(__u32 start, + __u32 end, + __u32 real_end, + const char *descr, + ext2fs_generic_bitmap *ret); +extern errcode_t ext2fs_allocate_block_bitmap(ext2_filsys fs, + const char *descr, + ext2fs_block_bitmap *ret); +extern errcode_t ext2fs_allocate_inode_bitmap(ext2_filsys fs, + const char *descr, + ext2fs_inode_bitmap *ret); +extern errcode_t ext2fs_fudge_inode_bitmap_end(ext2fs_inode_bitmap bitmap, + ext2_ino_t end, ext2_ino_t *oend); +extern errcode_t ext2fs_fudge_block_bitmap_end(ext2fs_block_bitmap bitmap, + blk_t end, blk_t *oend); +extern void ext2fs_clear_inode_bitmap(ext2fs_inode_bitmap bitmap); +extern void ext2fs_clear_block_bitmap(ext2fs_block_bitmap bitmap); +extern errcode_t ext2fs_read_bitmaps(ext2_filsys fs); +extern errcode_t ext2fs_write_bitmaps(ext2_filsys fs); + +/* block.c */ +extern errcode_t ext2fs_block_iterate(ext2_filsys fs, + ext2_ino_t ino, + int flags, + char *block_buf, + int (*func)(ext2_filsys fs, + blk_t *blocknr, + int blockcnt, + void *priv_data), + void *priv_data); +errcode_t ext2fs_block_iterate2(ext2_filsys fs, + ext2_ino_t ino, + int flags, + char *block_buf, + int (*func)(ext2_filsys fs, + blk_t *blocknr, + e2_blkcnt_t blockcnt, + blk_t ref_blk, + int ref_offset, + void *priv_data), + void *priv_data); + +/* bmap.c */ +extern errcode_t ext2fs_bmap(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode, + char *block_buf, int bmap_flags, + blk_t block, blk_t *phys_blk); + + +#if 0 +/* bmove.c */ +extern errcode_t ext2fs_move_blocks(ext2_filsys fs, + ext2fs_block_bitmap reserve, + ext2fs_block_bitmap alloc_map, + int flags); +#endif + +/* check_desc.c */ +extern errcode_t ext2fs_check_desc(ext2_filsys fs); + +/* closefs.c */ +extern errcode_t ext2fs_close(ext2_filsys fs); +extern errcode_t ext2fs_flush(ext2_filsys fs); +extern int ext2fs_bg_has_super(ext2_filsys fs, int group_block); +extern int ext2fs_super_and_bgd_loc(ext2_filsys fs, + dgrp_t group, + blk_t *ret_super_blk, + blk_t *ret_old_desc_blk, + blk_t *ret_new_desc_blk, + int *ret_meta_bg); +extern void ext2fs_update_dynamic_rev(ext2_filsys fs); + +/* cmp_bitmaps.c */ +extern errcode_t ext2fs_compare_block_bitmap(ext2fs_block_bitmap bm1, + ext2fs_block_bitmap bm2); +extern errcode_t ext2fs_compare_inode_bitmap(ext2fs_inode_bitmap bm1, + ext2fs_inode_bitmap bm2); + +/* dblist.c */ + +extern errcode_t ext2fs_get_num_dirs(ext2_filsys fs, ext2_ino_t *ret_num_dirs); +extern errcode_t ext2fs_init_dblist(ext2_filsys fs, ext2_dblist *ret_dblist); +extern errcode_t ext2fs_add_dir_block(ext2_dblist dblist, ext2_ino_t ino, + blk_t blk, int blockcnt); +extern void ext2fs_dblist_sort(ext2_dblist dblist, + int (*sortfunc)(const void *, + const void *)); +extern errcode_t ext2fs_dblist_iterate(ext2_dblist dblist, + int (*func)(ext2_filsys fs, struct ext2_db_entry *db_info, + void *priv_data), + void *priv_data); +extern errcode_t ext2fs_set_dir_block(ext2_dblist dblist, ext2_ino_t ino, + blk_t blk, int blockcnt); +extern errcode_t ext2fs_copy_dblist(ext2_dblist src, + ext2_dblist *dest); +extern int ext2fs_dblist_count(ext2_dblist dblist); + +/* dblist_dir.c */ +extern errcode_t + ext2fs_dblist_dir_iterate(ext2_dblist dblist, + int flags, + char *block_buf, + int (*func)(ext2_ino_t dir, + int entry, + struct ext2_dir_entry *dirent, + int offset, + int blocksize, + char *buf, + void *priv_data), + void *priv_data); + +/* dirblock.c */ +extern errcode_t ext2fs_read_dir_block(ext2_filsys fs, blk_t block, + void *buf); +extern errcode_t ext2fs_read_dir_block2(ext2_filsys fs, blk_t block, + void *buf, int flags); +extern errcode_t ext2fs_write_dir_block(ext2_filsys fs, blk_t block, + void *buf); +extern errcode_t ext2fs_write_dir_block2(ext2_filsys fs, blk_t block, + void *buf, int flags); + +/* dirhash.c */ +extern errcode_t ext2fs_dirhash(int version, const char *name, int len, + const __u32 *seed, + ext2_dirhash_t *ret_hash, + ext2_dirhash_t *ret_minor_hash); + + +/* dir_iterate.c */ +extern errcode_t ext2fs_dir_iterate(ext2_filsys fs, + ext2_ino_t dir, + int flags, + char *block_buf, + int (*func)(struct ext2_dir_entry *dirent, + int offset, + int blocksize, + char *buf, + void *priv_data), + void *priv_data); +extern errcode_t ext2fs_dir_iterate2(ext2_filsys fs, + ext2_ino_t dir, + int flags, + char *block_buf, + int (*func)(ext2_ino_t dir, + int entry, + struct ext2_dir_entry *dirent, + int offset, + int blocksize, + char *buf, + void *priv_data), + void *priv_data); + +/* dupfs.c */ +extern errcode_t ext2fs_dup_handle(ext2_filsys src, ext2_filsys *dest); + +/* expanddir.c */ +extern errcode_t ext2fs_expand_dir(ext2_filsys fs, ext2_ino_t dir); + +/* ext_attr.c */ +extern errcode_t ext2fs_read_ext_attr(ext2_filsys fs, blk_t block, void *buf); +extern errcode_t ext2fs_write_ext_attr(ext2_filsys fs, blk_t block, + void *buf); +extern errcode_t ext2fs_adjust_ea_refcount(ext2_filsys fs, blk_t blk, + char *block_buf, + int adjust, __u32 *newcount); + +/* fileio.c */ +extern errcode_t ext2fs_file_open2(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode, + int flags, ext2_file_t *ret); +extern errcode_t ext2fs_file_open(ext2_filsys fs, ext2_ino_t ino, + int flags, ext2_file_t *ret); +extern ext2_filsys ext2fs_file_get_fs(ext2_file_t file); +extern errcode_t ext2fs_file_close(ext2_file_t file); +extern errcode_t ext2fs_file_flush(ext2_file_t file); +extern errcode_t ext2fs_file_read(ext2_file_t file, void *buf, + unsigned int wanted, unsigned int *got); +extern errcode_t ext2fs_file_write(ext2_file_t file, const void *buf, + unsigned int nbytes, unsigned int *written); +extern errcode_t ext2fs_file_llseek(ext2_file_t file, __u64 offset, + int whence, __u64 *ret_pos); +extern errcode_t ext2fs_file_lseek(ext2_file_t file, ext2_off_t offset, + int whence, ext2_off_t *ret_pos); +errcode_t ext2fs_file_get_lsize(ext2_file_t file, __u64 *ret_size); +extern ext2_off_t ext2fs_file_get_size(ext2_file_t file); +extern errcode_t ext2fs_file_set_size(ext2_file_t file, ext2_off_t size); + +/* finddev.c */ +extern char *ext2fs_find_block_device(dev_t device); + +/* flushb.c */ +extern errcode_t ext2fs_sync_device(int fd, int flushb); + +/* freefs.c */ +extern void ext2fs_free(ext2_filsys fs); +extern void ext2fs_free_generic_bitmap(ext2fs_inode_bitmap bitmap); +extern void ext2fs_free_block_bitmap(ext2fs_block_bitmap bitmap); +extern void ext2fs_free_inode_bitmap(ext2fs_inode_bitmap bitmap); +extern void ext2fs_free_dblist(ext2_dblist dblist); +extern void ext2fs_badblocks_list_free(ext2_badblocks_list bb); +extern void ext2fs_u32_list_free(ext2_u32_list bb); + +/* getsize.c */ +extern errcode_t ext2fs_get_device_size(const char *file, int blocksize, + blk_t *retblocks); + +/* getsectsize.c */ +errcode_t ext2fs_get_device_sectsize(const char *file, int *sectsize); + +/* imager.c */ +extern errcode_t ext2fs_image_inode_write(ext2_filsys fs, int fd, int flags); +extern errcode_t ext2fs_image_inode_read(ext2_filsys fs, int fd, int flags); +extern errcode_t ext2fs_image_super_write(ext2_filsys fs, int fd, int flags); +extern errcode_t ext2fs_image_super_read(ext2_filsys fs, int fd, int flags); +extern errcode_t ext2fs_image_bitmap_write(ext2_filsys fs, int fd, int flags); +extern errcode_t ext2fs_image_bitmap_read(ext2_filsys fs, int fd, int flags); + +/* ind_block.c */ +errcode_t ext2fs_read_ind_block(ext2_filsys fs, blk_t blk, void *buf); +errcode_t ext2fs_write_ind_block(ext2_filsys fs, blk_t blk, void *buf); + +/* initialize.c */ +extern errcode_t ext2fs_initialize(const char *name, int flags, + struct ext2_super_block *param, + io_manager manager, ext2_filsys *ret_fs); + +/* icount.c */ +extern void ext2fs_free_icount(ext2_icount_t icount); +extern errcode_t ext2fs_create_icount2(ext2_filsys fs, int flags, + unsigned int size, + ext2_icount_t hint, ext2_icount_t *ret); +extern errcode_t ext2fs_create_icount(ext2_filsys fs, int flags, + unsigned int size, + ext2_icount_t *ret); +extern errcode_t ext2fs_icount_fetch(ext2_icount_t icount, ext2_ino_t ino, + __u16 *ret); +extern errcode_t ext2fs_icount_increment(ext2_icount_t icount, ext2_ino_t ino, + __u16 *ret); +extern errcode_t ext2fs_icount_decrement(ext2_icount_t icount, ext2_ino_t ino, + __u16 *ret); +extern errcode_t ext2fs_icount_store(ext2_icount_t icount, ext2_ino_t ino, + __u16 count); +extern ext2_ino_t ext2fs_get_icount_size(ext2_icount_t icount); +errcode_t ext2fs_icount_validate(ext2_icount_t icount, FILE *); + +/* inode.c */ +extern errcode_t ext2fs_flush_icache(ext2_filsys fs); +extern errcode_t ext2fs_get_next_inode_full(ext2_inode_scan scan, + ext2_ino_t *ino, + struct ext2_inode *inode, + int bufsize); +extern errcode_t ext2fs_open_inode_scan(ext2_filsys fs, int buffer_blocks, + ext2_inode_scan *ret_scan); +extern void ext2fs_close_inode_scan(ext2_inode_scan scan); +extern errcode_t ext2fs_get_next_inode(ext2_inode_scan scan, ext2_ino_t *ino, + struct ext2_inode *inode); +extern errcode_t ext2fs_inode_scan_goto_blockgroup(ext2_inode_scan scan, + int group); +extern void ext2fs_set_inode_callback + (ext2_inode_scan scan, + errcode_t (*done_group)(ext2_filsys fs, + dgrp_t group, + void * priv_data), + void *done_group_data); +extern int ext2fs_inode_scan_flags(ext2_inode_scan scan, int set_flags, + int clear_flags); +extern errcode_t ext2fs_read_inode_full(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode * inode, + int bufsize); +extern errcode_t ext2fs_read_inode (ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode * inode); +extern errcode_t ext2fs_write_inode_full(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode * inode, + int bufsize); +extern errcode_t ext2fs_write_inode(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode * inode); +extern errcode_t ext2fs_write_new_inode(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode * inode); +extern errcode_t ext2fs_get_blocks(ext2_filsys fs, ext2_ino_t ino, blk_t *blocks); +extern errcode_t ext2fs_check_directory(ext2_filsys fs, ext2_ino_t ino); + +/* inode_io.c */ +extern io_manager inode_io_manager; +extern errcode_t ext2fs_inode_io_intern(ext2_filsys fs, ext2_ino_t ino, + char **name); +extern errcode_t ext2fs_inode_io_intern2(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode, + char **name); + +/* ismounted.c */ +extern errcode_t ext2fs_check_if_mounted(const char *file, int *mount_flags); +extern errcode_t ext2fs_check_mount_point(const char *device, int *mount_flags, + char *mtpt, int mtlen); + +/* namei.c */ +extern errcode_t ext2fs_lookup(ext2_filsys fs, ext2_ino_t dir, const char *name, + int namelen, char *buf, ext2_ino_t *inode); +extern errcode_t ext2fs_namei(ext2_filsys fs, ext2_ino_t root, ext2_ino_t cwd, + const char *name, ext2_ino_t *inode); +errcode_t ext2fs_namei_follow(ext2_filsys fs, ext2_ino_t root, ext2_ino_t cwd, + const char *name, ext2_ino_t *inode); +extern errcode_t ext2fs_follow_link(ext2_filsys fs, ext2_ino_t root, ext2_ino_t cwd, + ext2_ino_t inode, ext2_ino_t *res_inode); + +/* native.c */ +int ext2fs_native_flag(void); + +/* newdir.c */ +extern errcode_t ext2fs_new_dir_block(ext2_filsys fs, ext2_ino_t dir_ino, + ext2_ino_t parent_ino, char **block); + +/* mkdir.c */ +extern errcode_t ext2fs_mkdir(ext2_filsys fs, ext2_ino_t parent, ext2_ino_t inum, + const char *name); + +/* mkjournal.c */ +extern errcode_t ext2fs_create_journal_superblock(ext2_filsys fs, + __u32 size, int flags, + char **ret_jsb); +extern errcode_t ext2fs_add_journal_device(ext2_filsys fs, + ext2_filsys journal_dev); +extern errcode_t ext2fs_add_journal_inode(ext2_filsys fs, blk_t size, + int flags); + +/* openfs.c */ +extern errcode_t ext2fs_open(const char *name, int flags, int superblock, + unsigned int block_size, io_manager manager, + ext2_filsys *ret_fs); +extern errcode_t ext2fs_open2(const char *name, const char *io_options, + int flags, int superblock, + unsigned int block_size, io_manager manager, + ext2_filsys *ret_fs); +extern blk_t ext2fs_descriptor_block_loc(ext2_filsys fs, blk_t group_block, + dgrp_t i); +errcode_t ext2fs_get_data_io(ext2_filsys fs, io_channel *old_io); +errcode_t ext2fs_set_data_io(ext2_filsys fs, io_channel new_io); +errcode_t ext2fs_rewrite_to_io(ext2_filsys fs, io_channel new_io); + +/* get_pathname.c */ +extern errcode_t ext2fs_get_pathname(ext2_filsys fs, ext2_ino_t dir, ext2_ino_t ino, + char **name); + +/* link.c */ +errcode_t ext2fs_link(ext2_filsys fs, ext2_ino_t dir, const char *name, + ext2_ino_t ino, int flags); +errcode_t ext2fs_unlink(ext2_filsys fs, ext2_ino_t dir, const char *name, + ext2_ino_t ino, int flags); + +/* read_bb.c */ +extern errcode_t ext2fs_read_bb_inode(ext2_filsys fs, + ext2_badblocks_list *bb_list); + +/* read_bb_file.c */ +extern errcode_t ext2fs_read_bb_FILE2(ext2_filsys fs, FILE *f, + ext2_badblocks_list *bb_list, + void *priv_data, + void (*invalid)(ext2_filsys fs, + blk_t blk, + char *badstr, + void *priv_data)); +extern errcode_t ext2fs_read_bb_FILE(ext2_filsys fs, FILE *f, + ext2_badblocks_list *bb_list, + void (*invalid)(ext2_filsys fs, + blk_t blk)); + +/* res_gdt.c */ +extern errcode_t ext2fs_create_resize_inode(ext2_filsys fs); + +/* rs_bitmap.c */ +extern errcode_t ext2fs_resize_generic_bitmap(__u32 new_end, + __u32 new_real_end, + ext2fs_generic_bitmap bmap); +extern errcode_t ext2fs_resize_inode_bitmap(__u32 new_end, __u32 new_real_end, + ext2fs_inode_bitmap bmap); +extern errcode_t ext2fs_resize_block_bitmap(__u32 new_end, __u32 new_real_end, + ext2fs_block_bitmap bmap); +extern errcode_t ext2fs_copy_bitmap(ext2fs_generic_bitmap src, + ext2fs_generic_bitmap *dest); + +/* swapfs.c */ +extern void ext2fs_swap_ext_attr(char *to, char *from, int bufsize, + int has_header); +extern void ext2fs_swap_super(struct ext2_super_block * super); +extern void ext2fs_swap_group_desc(struct ext2_group_desc *gdp); +extern void ext2fs_swap_inode_full(ext2_filsys fs, struct ext2_inode_large *t, + struct ext2_inode_large *f, int hostorder, + int bufsize); +extern void ext2fs_swap_inode(ext2_filsys fs,struct ext2_inode *t, + struct ext2_inode *f, int hostorder); + +/* valid_blk.c */ +extern int ext2fs_inode_has_valid_blocks(struct ext2_inode *inode); + +/* version.c */ +extern int ext2fs_parse_version_string(const char *ver_string); +extern int ext2fs_get_library_version(const char **ver_string, + const char **date_string); + +/* write_bb_file.c */ +extern errcode_t ext2fs_write_bb_FILE(ext2_badblocks_list bb_list, + unsigned int flags, + FILE *f); + + +/* inline functions */ +extern errcode_t ext2fs_get_mem(unsigned long size, void *ptr); +extern errcode_t ext2fs_free_mem(void *ptr); +extern errcode_t ext2fs_resize_mem(unsigned long old_size, + unsigned long size, void *ptr); +extern void ext2fs_mark_super_dirty(ext2_filsys fs); +extern void ext2fs_mark_changed(ext2_filsys fs); +extern int ext2fs_test_changed(ext2_filsys fs); +extern void ext2fs_mark_valid(ext2_filsys fs); +extern void ext2fs_unmark_valid(ext2_filsys fs); +extern int ext2fs_test_valid(ext2_filsys fs); +extern void ext2fs_mark_ib_dirty(ext2_filsys fs); +extern void ext2fs_mark_bb_dirty(ext2_filsys fs); +extern int ext2fs_test_ib_dirty(ext2_filsys fs); +extern int ext2fs_test_bb_dirty(ext2_filsys fs); +extern int ext2fs_group_of_blk(ext2_filsys fs, blk_t blk); +extern int ext2fs_group_of_ino(ext2_filsys fs, ext2_ino_t ino); +extern blk_t ext2fs_inode_data_blocks(ext2_filsys fs, + struct ext2_inode *inode); + +#ifdef __cplusplus +} +#endif + +#endif /* _EXT2FS_EXT2FS_H */ diff --git a/e2fsprogs/ext2fs/ext2fsP.h b/e2fsprogs/ext2fs/ext2fsP.h new file mode 100644 index 000000000..908b5d9a4 --- /dev/null +++ b/e2fsprogs/ext2fs/ext2fsP.h @@ -0,0 +1,89 @@ +/* vi: set sw=4 ts=4: */ +/* + * ext2fsP.h --- private header file for ext2 library + * + * Copyright (C) 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include "ext2fs.h" + +/* + * Badblocks list + */ +struct ext2_struct_u32_list { + int magic; + int num; + int size; + __u32 *list; + int badblocks_flags; +}; + +struct ext2_struct_u32_iterate { + int magic; + ext2_u32_list bb; + int ptr; +}; + + +/* + * Directory block iterator definition + */ +struct ext2_struct_dblist { + int magic; + ext2_filsys fs; + ext2_ino_t size; + ext2_ino_t count; + int sorted; + struct ext2_db_entry * list; +}; + +/* + * For directory iterators + */ +struct dir_context { + ext2_ino_t dir; + int flags; + char *buf; + int (*func)(ext2_ino_t dir, + int entry, + struct ext2_dir_entry *dirent, + int offset, + int blocksize, + char *buf, + void *priv_data); + void *priv_data; + errcode_t errcode; +}; + +/* + * Inode cache structure + */ +struct ext2_inode_cache { + void * buffer; + blk_t buffer_blk; + int cache_last; + int cache_size; + int refcount; + struct ext2_inode_cache_ent *cache; +}; + +struct ext2_inode_cache_ent { + ext2_ino_t ino; + struct ext2_inode inode; +}; + +/* Function prototypes */ + +extern int ext2fs_process_dir_block(ext2_filsys fs, + blk_t *blocknr, + e2_blkcnt_t blockcnt, + blk_t ref_block, + int ref_offset, + void *priv_data); + + diff --git a/e2fsprogs/ext2fs/ext2fs_inline.c b/e2fsprogs/ext2fs/ext2fs_inline.c new file mode 100644 index 000000000..da1cf5be5 --- /dev/null +++ b/e2fsprogs/ext2fs/ext2fs_inline.c @@ -0,0 +1,367 @@ +/* vi: set sw=4 ts=4: */ +/* + * ext2fs.h --- ext2fs + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include "ext2fs.h" +#include "bitops.h" +#include + +/* + * Allocate memory + */ +errcode_t ext2fs_get_mem(unsigned long size, void *ptr) +{ + void **pp = (void **)ptr; + + *pp = malloc(size); + if (!*pp) + return EXT2_ET_NO_MEMORY; + return 0; +} + +/* + * Free memory + */ +errcode_t ext2fs_free_mem(void *ptr) +{ + void **pp = (void **)ptr; + + free(*pp); + *pp = 0; + return 0; +} + +/* + * Resize memory + */ +errcode_t ext2fs_resize_mem(unsigned long EXT2FS_ATTR((unused)) old_size, + unsigned long size, void *ptr) +{ + void *p; + + /* Use "memcpy" for pointer assignments here to avoid problems + * with C99 strict type aliasing rules. */ + memcpy(&p, ptr, sizeof (p)); + p = realloc(p, size); + if (!p) + return EXT2_ET_NO_MEMORY; + memcpy(ptr, &p, sizeof (p)); + return 0; +} + +/* + * Mark a filesystem superblock as dirty + */ +void ext2fs_mark_super_dirty(ext2_filsys fs) +{ + fs->flags |= EXT2_FLAG_DIRTY | EXT2_FLAG_CHANGED; +} + +/* + * Mark a filesystem as changed + */ +void ext2fs_mark_changed(ext2_filsys fs) +{ + fs->flags |= EXT2_FLAG_CHANGED; +} + +/* + * Check to see if a filesystem has changed + */ +int ext2fs_test_changed(ext2_filsys fs) +{ + return (fs->flags & EXT2_FLAG_CHANGED); +} + +/* + * Mark a filesystem as valid + */ +void ext2fs_mark_valid(ext2_filsys fs) +{ + fs->flags |= EXT2_FLAG_VALID; +} + +/* + * Mark a filesystem as NOT valid + */ +void ext2fs_unmark_valid(ext2_filsys fs) +{ + fs->flags &= ~EXT2_FLAG_VALID; +} + +/* + * Check to see if a filesystem is valid + */ +int ext2fs_test_valid(ext2_filsys fs) +{ + return (fs->flags & EXT2_FLAG_VALID); +} + +/* + * Mark the inode bitmap as dirty + */ +void ext2fs_mark_ib_dirty(ext2_filsys fs) +{ + fs->flags |= EXT2_FLAG_IB_DIRTY | EXT2_FLAG_CHANGED; +} + +/* + * Mark the block bitmap as dirty + */ +void ext2fs_mark_bb_dirty(ext2_filsys fs) +{ + fs->flags |= EXT2_FLAG_BB_DIRTY | EXT2_FLAG_CHANGED; +} + +/* + * Check to see if a filesystem's inode bitmap is dirty + */ +int ext2fs_test_ib_dirty(ext2_filsys fs) +{ + return (fs->flags & EXT2_FLAG_IB_DIRTY); +} + +/* + * Check to see if a filesystem's block bitmap is dirty + */ +int ext2fs_test_bb_dirty(ext2_filsys fs) +{ + return (fs->flags & EXT2_FLAG_BB_DIRTY); +} + +/* + * Return the group # of a block + */ +int ext2fs_group_of_blk(ext2_filsys fs, blk_t blk) +{ + return (blk - fs->super->s_first_data_block) / + fs->super->s_blocks_per_group; +} + +/* + * Return the group # of an inode number + */ +int ext2fs_group_of_ino(ext2_filsys fs, ext2_ino_t ino) +{ + return (ino - 1) / fs->super->s_inodes_per_group; +} + +blk_t ext2fs_inode_data_blocks(ext2_filsys fs, + struct ext2_inode *inode) +{ + return inode->i_blocks - + (inode->i_file_acl ? fs->blocksize >> 9 : 0); +} + + + + + + + + + +__u16 ext2fs_swab16(__u16 val) +{ + return (val >> 8) | (val << 8); +} + +__u32 ext2fs_swab32(__u32 val) +{ + return ((val>>24) | ((val>>8)&0xFF00) | + ((val<<8)&0xFF0000) | (val<<24)); +} + +int ext2fs_test_generic_bitmap(ext2fs_generic_bitmap bitmap, + blk_t bitno); + +int ext2fs_test_generic_bitmap(ext2fs_generic_bitmap bitmap, + blk_t bitno) +{ + if ((bitno < bitmap->start) || (bitno > bitmap->end)) { + ext2fs_warn_bitmap2(bitmap, EXT2FS_TEST_ERROR, bitno); + return 0; + } + return ext2fs_test_bit(bitno - bitmap->start, bitmap->bitmap); +} + +int ext2fs_mark_block_bitmap(ext2fs_block_bitmap bitmap, + blk_t block) +{ + return ext2fs_mark_generic_bitmap((ext2fs_generic_bitmap) + bitmap, + block); +} + +int ext2fs_unmark_block_bitmap(ext2fs_block_bitmap bitmap, + blk_t block) +{ + return ext2fs_unmark_generic_bitmap((ext2fs_generic_bitmap) bitmap, + block); +} + +int ext2fs_test_block_bitmap(ext2fs_block_bitmap bitmap, + blk_t block) +{ + return ext2fs_test_generic_bitmap((ext2fs_generic_bitmap) bitmap, + block); +} + +int ext2fs_mark_inode_bitmap(ext2fs_inode_bitmap bitmap, + ext2_ino_t inode) +{ + return ext2fs_mark_generic_bitmap((ext2fs_generic_bitmap) bitmap, + inode); +} + +int ext2fs_unmark_inode_bitmap(ext2fs_inode_bitmap bitmap, + ext2_ino_t inode) +{ + return ext2fs_unmark_generic_bitmap((ext2fs_generic_bitmap) bitmap, + inode); +} + +int ext2fs_test_inode_bitmap(ext2fs_inode_bitmap bitmap, + ext2_ino_t inode) +{ + return ext2fs_test_generic_bitmap((ext2fs_generic_bitmap) bitmap, + inode); +} + +void ext2fs_fast_mark_block_bitmap(ext2fs_block_bitmap bitmap, + blk_t block) +{ + ext2fs_set_bit(block - bitmap->start, bitmap->bitmap); +} + +void ext2fs_fast_unmark_block_bitmap(ext2fs_block_bitmap bitmap, + blk_t block) +{ + ext2fs_clear_bit(block - bitmap->start, bitmap->bitmap); +} + +int ext2fs_fast_test_block_bitmap(ext2fs_block_bitmap bitmap, + blk_t block) +{ + return ext2fs_test_bit(block - bitmap->start, bitmap->bitmap); +} + +void ext2fs_fast_mark_inode_bitmap(ext2fs_inode_bitmap bitmap, + ext2_ino_t inode) +{ + ext2fs_set_bit(inode - bitmap->start, bitmap->bitmap); +} + +void ext2fs_fast_unmark_inode_bitmap(ext2fs_inode_bitmap bitmap, + ext2_ino_t inode) +{ + ext2fs_clear_bit(inode - bitmap->start, bitmap->bitmap); +} + +int ext2fs_fast_test_inode_bitmap(ext2fs_inode_bitmap bitmap, + ext2_ino_t inode) +{ + return ext2fs_test_bit(inode - bitmap->start, bitmap->bitmap); +} + +blk_t ext2fs_get_block_bitmap_start(ext2fs_block_bitmap bitmap) +{ + return bitmap->start; +} + +ext2_ino_t ext2fs_get_inode_bitmap_start(ext2fs_inode_bitmap bitmap) +{ + return bitmap->start; +} + +blk_t ext2fs_get_block_bitmap_end(ext2fs_block_bitmap bitmap) +{ + return bitmap->end; +} + +ext2_ino_t ext2fs_get_inode_bitmap_end(ext2fs_inode_bitmap bitmap) +{ + return bitmap->end; +} + +int ext2fs_test_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num) +{ + int i; + + if ((block < bitmap->start) || (block+num-1 > bitmap->end)) { + ext2fs_warn_bitmap(EXT2_ET_BAD_BLOCK_TEST, + block, bitmap->description); + return 0; + } + for (i=0; i < num; i++) { + if (ext2fs_fast_test_block_bitmap(bitmap, block+i)) + return 0; + } + return 1; +} + +int ext2fs_fast_test_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num) +{ + int i; + + for (i=0; i < num; i++) { + if (ext2fs_fast_test_block_bitmap(bitmap, block+i)) + return 0; + } + return 1; +} + +void ext2fs_mark_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num) +{ + int i; + + if ((block < bitmap->start) || (block+num-1 > bitmap->end)) { + ext2fs_warn_bitmap(EXT2_ET_BAD_BLOCK_MARK, block, + bitmap->description); + return; + } + for (i=0; i < num; i++) + ext2fs_set_bit(block + i - bitmap->start, bitmap->bitmap); +} + +void ext2fs_fast_mark_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num) +{ + int i; + + for (i=0; i < num; i++) + ext2fs_set_bit(block + i - bitmap->start, bitmap->bitmap); +} + +void ext2fs_unmark_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num) +{ + int i; + + if ((block < bitmap->start) || (block+num-1 > bitmap->end)) { + ext2fs_warn_bitmap(EXT2_ET_BAD_BLOCK_UNMARK, block, + bitmap->description); + return; + } + for (i=0; i < num; i++) + ext2fs_clear_bit(block + i - bitmap->start, bitmap->bitmap); +} + +void ext2fs_fast_unmark_block_bitmap_range(ext2fs_block_bitmap bitmap, + blk_t block, int num) +{ + int i; + for (i=0; i < num; i++) + ext2fs_clear_bit(block + i - bitmap->start, bitmap->bitmap); +} diff --git a/e2fsprogs/ext2fs/ext_attr.c b/e2fsprogs/ext2fs/ext_attr.c new file mode 100644 index 000000000..7ee41f234 --- /dev/null +++ b/e2fsprogs/ext2fs/ext_attr.c @@ -0,0 +1,101 @@ +/* vi: set sw=4 ts=4: */ +/* + * ext_attr.c --- extended attribute blocks + * + * Copyright (C) 2001 Andreas Gruenbacher, + * + * Copyright (C) 2002 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#include +#include + +#include "ext2_fs.h" +#include "ext2_ext_attr.h" +#include "ext2fs.h" + +errcode_t ext2fs_read_ext_attr(ext2_filsys fs, blk_t block, void *buf) +{ + errcode_t retval; + + retval = io_channel_read_blk(fs->io, block, 1, buf); + if (retval) + return retval; +#if BB_BIG_ENDIAN + if ((fs->flags & (EXT2_FLAG_SWAP_BYTES| + EXT2_FLAG_SWAP_BYTES_READ)) != 0) + ext2fs_swap_ext_attr(buf, buf, fs->blocksize, 1); +#endif + return 0; +} + +errcode_t ext2fs_write_ext_attr(ext2_filsys fs, blk_t block, void *inbuf) +{ + errcode_t retval; + char *write_buf; + char *buf = NULL; + + if (BB_BIG_ENDIAN && ((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_WRITE))) { + retval = ext2fs_get_mem(fs->blocksize, &buf); + if (retval) + return retval; + write_buf = buf; + ext2fs_swap_ext_attr(buf, inbuf, fs->blocksize, 1); + } else + write_buf = (char *) inbuf; + retval = io_channel_write_blk(fs->io, block, 1, write_buf); + if (buf) + ext2fs_free_mem(&buf); + if (!retval) + ext2fs_mark_changed(fs); + return retval; +} + +/* + * This function adjusts the reference count of the EA block. + */ +errcode_t ext2fs_adjust_ea_refcount(ext2_filsys fs, blk_t blk, + char *block_buf, int adjust, + __u32 *newcount) +{ + errcode_t retval; + struct ext2_ext_attr_header *header; + char *buf = 0; + + if ((blk >= fs->super->s_blocks_count) || + (blk < fs->super->s_first_data_block)) + return EXT2_ET_BAD_EA_BLOCK_NUM; + + if (!block_buf) { + retval = ext2fs_get_mem(fs->blocksize, &buf); + if (retval) + return retval; + block_buf = buf; + } + + retval = ext2fs_read_ext_attr(fs, blk, block_buf); + if (retval) + goto errout; + + header = (struct ext2_ext_attr_header *) block_buf; + header->h_refcount += adjust; + if (newcount) + *newcount = header->h_refcount; + + retval = ext2fs_write_ext_attr(fs, blk, block_buf); + if (retval) + goto errout; + +errout: + if (buf) + ext2fs_free_mem(&buf); + return retval; +} diff --git a/e2fsprogs/ext2fs/fileio.c b/e2fsprogs/ext2fs/fileio.c new file mode 100644 index 000000000..c56a21aa8 --- /dev/null +++ b/e2fsprogs/ext2fs/fileio.c @@ -0,0 +1,377 @@ +/* vi: set sw=4 ts=4: */ +/* + * fileio.c --- Simple file I/O routines + * + * Copyright (C) 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +struct ext2_file { + errcode_t magic; + ext2_filsys fs; + ext2_ino_t ino; + struct ext2_inode inode; + int flags; + __u64 pos; + blk_t blockno; + blk_t physblock; + char *buf; +}; + +#define BMAP_BUFFER (file->buf + fs->blocksize) + +errcode_t ext2fs_file_open2(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode, + int flags, ext2_file_t *ret) +{ + ext2_file_t file; + errcode_t retval; + + /* + * Don't let caller create or open a file for writing if the + * filesystem is read-only. + */ + if ((flags & (EXT2_FILE_WRITE | EXT2_FILE_CREATE)) && + !(fs->flags & EXT2_FLAG_RW)) + return EXT2_ET_RO_FILSYS; + + retval = ext2fs_get_mem(sizeof(struct ext2_file), &file); + if (retval) + return retval; + + memset(file, 0, sizeof(struct ext2_file)); + file->magic = EXT2_ET_MAGIC_EXT2_FILE; + file->fs = fs; + file->ino = ino; + file->flags = flags & EXT2_FILE_MASK; + + if (inode) { + memcpy(&file->inode, inode, sizeof(struct ext2_inode)); + } else { + retval = ext2fs_read_inode(fs, ino, &file->inode); + if (retval) + goto fail; + } + + retval = ext2fs_get_mem(fs->blocksize * 3, &file->buf); + if (retval) + goto fail; + + *ret = file; + return 0; + +fail: + ext2fs_free_mem(&file->buf); + ext2fs_free_mem(&file); + return retval; +} + +errcode_t ext2fs_file_open(ext2_filsys fs, ext2_ino_t ino, + int flags, ext2_file_t *ret) +{ + return ext2fs_file_open2(fs, ino, NULL, flags, ret); +} + +/* + * This function returns the filesystem handle of a file from the structure + */ +ext2_filsys ext2fs_file_get_fs(ext2_file_t file) +{ + if (file->magic != EXT2_ET_MAGIC_EXT2_FILE) + return 0; + return file->fs; +} + +/* + * This function flushes the dirty block buffer out to disk if + * necessary. + */ +errcode_t ext2fs_file_flush(ext2_file_t file) +{ + errcode_t retval; + ext2_filsys fs; + + EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE); + fs = file->fs; + + if (!(file->flags & EXT2_FILE_BUF_VALID) || + !(file->flags & EXT2_FILE_BUF_DIRTY)) + return 0; + + /* + * OK, the physical block hasn't been allocated yet. + * Allocate it. + */ + if (!file->physblock) { + retval = ext2fs_bmap(fs, file->ino, &file->inode, + BMAP_BUFFER, file->ino ? BMAP_ALLOC : 0, + file->blockno, &file->physblock); + if (retval) + return retval; + } + + retval = io_channel_write_blk(fs->io, file->physblock, + 1, file->buf); + if (retval) + return retval; + + file->flags &= ~EXT2_FILE_BUF_DIRTY; + + return retval; +} + +/* + * This function synchronizes the file's block buffer and the current + * file position, possibly invalidating block buffer if necessary + */ +static errcode_t sync_buffer_position(ext2_file_t file) +{ + blk_t b; + errcode_t retval; + + b = file->pos / file->fs->blocksize; + if (b != file->blockno) { + retval = ext2fs_file_flush(file); + if (retval) + return retval; + file->flags &= ~EXT2_FILE_BUF_VALID; + } + file->blockno = b; + return 0; +} + +/* + * This function loads the file's block buffer with valid data from + * the disk as necessary. + * + * If dontfill is true, then skip initializing the buffer since we're + * going to be replacing its entire contents anyway. If set, then the + * function basically only sets file->physblock and EXT2_FILE_BUF_VALID + */ +#define DONTFILL 1 +static errcode_t load_buffer(ext2_file_t file, int dontfill) +{ + ext2_filsys fs = file->fs; + errcode_t retval; + + if (!(file->flags & EXT2_FILE_BUF_VALID)) { + retval = ext2fs_bmap(fs, file->ino, &file->inode, + BMAP_BUFFER, 0, file->blockno, + &file->physblock); + if (retval) + return retval; + if (!dontfill) { + if (file->physblock) { + retval = io_channel_read_blk(fs->io, + file->physblock, + 1, file->buf); + if (retval) + return retval; + } else + memset(file->buf, 0, fs->blocksize); + } + file->flags |= EXT2_FILE_BUF_VALID; + } + return 0; +} + + +errcode_t ext2fs_file_close(ext2_file_t file) +{ + errcode_t retval; + + EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE); + + retval = ext2fs_file_flush(file); + + ext2fs_free_mem(&file->buf); + ext2fs_free_mem(&file); + + return retval; +} + + +errcode_t ext2fs_file_read(ext2_file_t file, void *buf, + unsigned int wanted, unsigned int *got) +{ + ext2_filsys fs; + errcode_t retval = 0; + unsigned int start, c, count = 0; + __u64 left; + char *ptr = (char *) buf; + + EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE); + fs = file->fs; + + while ((file->pos < EXT2_I_SIZE(&file->inode)) && (wanted > 0)) { + retval = sync_buffer_position(file); + if (retval) + goto fail; + retval = load_buffer(file, 0); + if (retval) + goto fail; + + start = file->pos % fs->blocksize; + c = fs->blocksize - start; + if (c > wanted) + c = wanted; + left = EXT2_I_SIZE(&file->inode) - file->pos ; + if (c > left) + c = left; + + memcpy(ptr, file->buf+start, c); + file->pos += c; + ptr += c; + count += c; + wanted -= c; + } + +fail: + if (got) + *got = count; + return retval; +} + + +errcode_t ext2fs_file_write(ext2_file_t file, const void *buf, + unsigned int nbytes, unsigned int *written) +{ + ext2_filsys fs; + errcode_t retval = 0; + unsigned int start, c, count = 0; + const char *ptr = (const char *) buf; + + EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE); + fs = file->fs; + + if (!(file->flags & EXT2_FILE_WRITE)) + return EXT2_ET_FILE_RO; + + while (nbytes > 0) { + retval = sync_buffer_position(file); + if (retval) + goto fail; + + start = file->pos % fs->blocksize; + c = fs->blocksize - start; + if (c > nbytes) + c = nbytes; + + /* + * We only need to do a read-modify-update cycle if + * we're doing a partial write. + */ + retval = load_buffer(file, (c == fs->blocksize)); + if (retval) + goto fail; + + file->flags |= EXT2_FILE_BUF_DIRTY; + memcpy(file->buf+start, ptr, c); + file->pos += c; + ptr += c; + count += c; + nbytes -= c; + } + +fail: + if (written) + *written = count; + return retval; +} + +errcode_t ext2fs_file_llseek(ext2_file_t file, __u64 offset, + int whence, __u64 *ret_pos) +{ + EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE); + + if (whence == EXT2_SEEK_SET) + file->pos = offset; + else if (whence == EXT2_SEEK_CUR) + file->pos += offset; + else if (whence == EXT2_SEEK_END) + file->pos = EXT2_I_SIZE(&file->inode) + offset; + else + return EXT2_ET_INVALID_ARGUMENT; + + if (ret_pos) + *ret_pos = file->pos; + + return 0; +} + +errcode_t ext2fs_file_lseek(ext2_file_t file, ext2_off_t offset, + int whence, ext2_off_t *ret_pos) +{ + __u64 loffset, ret_loffset; + errcode_t retval; + + loffset = offset; + retval = ext2fs_file_llseek(file, loffset, whence, &ret_loffset); + if (ret_pos) + *ret_pos = (ext2_off_t) ret_loffset; + return retval; +} + + +/* + * This function returns the size of the file, according to the inode + */ +errcode_t ext2fs_file_get_lsize(ext2_file_t file, __u64 *ret_size) +{ + if (file->magic != EXT2_ET_MAGIC_EXT2_FILE) + return EXT2_ET_MAGIC_EXT2_FILE; + *ret_size = EXT2_I_SIZE(&file->inode); + return 0; +} + +/* + * This function returns the size of the file, according to the inode + */ +ext2_off_t ext2fs_file_get_size(ext2_file_t file) +{ + __u64 size; + + if (ext2fs_file_get_lsize(file, &size)) + return 0; + if ((size >> 32) != 0) + return 0; + return size; +} + +/* + * This function sets the size of the file, truncating it if necessary + * + * XXX still need to call truncate + */ +errcode_t ext2fs_file_set_size(ext2_file_t file, ext2_off_t size) +{ + errcode_t retval; + EXT2_CHECK_MAGIC(file, EXT2_ET_MAGIC_EXT2_FILE); + + file->inode.i_size = size; + file->inode.i_size_high = 0; + if (file->ino) { + retval = ext2fs_write_inode(file->fs, file->ino, &file->inode); + if (retval) + return retval; + } + + /* + * XXX truncate inode if necessary + */ + + return 0; +} diff --git a/e2fsprogs/ext2fs/finddev.c b/e2fsprogs/ext2fs/finddev.c new file mode 100644 index 000000000..5e2cce940 --- /dev/null +++ b/e2fsprogs/ext2fs/finddev.c @@ -0,0 +1,199 @@ +/* vi: set sw=4 ts=4: */ +/* + * finddev.c -- this routine attempts to find a particular device in + * /dev + * + * Copyright (C) 2000 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#include +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_SYS_MKDEV_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +struct dir_list { + char *name; + struct dir_list *next; +}; + +/* + * This function adds an entry to the directory list + */ +static void add_to_dirlist(const char *name, struct dir_list **list) +{ + struct dir_list *dp; + + dp = xmalloc(sizeof(struct dir_list)); + dp->name = xmalloc(strlen(name)+1); + strcpy(dp->name, name); + dp->next = *list; + *list = dp; +} + +/* + * This function frees a directory list + */ +static void free_dirlist(struct dir_list **list) +{ + struct dir_list *dp, *next; + + for (dp = *list; dp; dp = next) { + next = dp->next; + free(dp->name); + free(dp); + } + *list = 0; +} + +static int scan_dir(char *dir_name, dev_t device, struct dir_list **list, + char **ret_path) +{ + DIR *dir; + struct dirent *dp; + char path[1024], *cp; + int dirlen; + struct stat st; + + dirlen = strlen(dir_name); + if ((dir = opendir(dir_name)) == NULL) + return errno; + dp = readdir(dir); + while (dp) { + if (dirlen + strlen(dp->d_name) + 2 >= sizeof(path)) + goto skip_to_next; + if (dp->d_name[0] == '.' && + ((dp->d_name[1] == 0) || + ((dp->d_name[1] == '.') && (dp->d_name[2] == 0)))) + goto skip_to_next; + sprintf(path, "%s/%s", dir_name, dp->d_name); + if (stat(path, &st) < 0) + goto skip_to_next; + if (S_ISDIR(st.st_mode)) + add_to_dirlist(path, list); + if (S_ISBLK(st.st_mode) && st.st_rdev == device) { + cp = xmalloc(strlen(path)+1); + strcpy(cp, path); + *ret_path = cp; + goto success; + } + skip_to_next: + dp = readdir(dir); + } +success: + closedir(dir); + return 0; +} + +/* + * This function finds the pathname to a block device with a given + * device number. It returns a pointer to allocated memory to the + * pathname on success, and NULL on failure. + */ +char *ext2fs_find_block_device(dev_t device) +{ + struct dir_list *list = 0, *new_list = 0; + struct dir_list *current; + char *ret_path = 0; + + /* + * Add the starting directories to search... + */ + add_to_dirlist("/devices", &list); + add_to_dirlist("/devfs", &list); + add_to_dirlist("/dev", &list); + + while (list) { + current = list; + list = list->next; +#ifdef DEBUG + printf("Scanning directory %s\n", current->name); +#endif + scan_dir(current->name, device, &new_list, &ret_path); + free(current->name); + free(current); + if (ret_path) + break; + /* + * If we're done checking at this level, descend to + * the next level of subdirectories. (breadth-first) + */ + if (list == 0) { + list = new_list; + new_list = 0; + } + } + free_dirlist(&list); + free_dirlist(&new_list); + return ret_path; +} + + +#ifdef DEBUG +int main(int argc, char** argv) +{ + char *devname, *tmp; + int major, minor; + dev_t device; + const char *errmsg = "Cannot parse %s: %s\n"; + + if ((argc != 2) && (argc != 3)) { + fprintf(stderr, "Usage: %s device_number\n", argv[0]); + fprintf(stderr, "\t: %s major minor\n", argv[0]); + exit(1); + } + if (argc == 2) { + device = strtoul(argv[1], &tmp, 0); + if (*tmp) { + fprintf(stderr, errmsg, "device number", argv[1]); + exit(1); + } + } else { + major = strtoul(argv[1], &tmp, 0); + if (*tmp) { + fprintf(stderr, errmsg, "major number", argv[1]); + exit(1); + } + minor = strtoul(argv[2], &tmp, 0); + if (*tmp) { + fprintf(stderr, errmsg, "minor number", argv[2]); + exit(1); + } + device = makedev(major, minor); + printf("Looking for device 0x%04x (%d:%d)\n", device, + major, minor); + } + devname = ext2fs_find_block_device(device); + if (devname) { + printf("Found device! %s\n", devname); + free(devname); + } else { + printf("Cannot find device.\n"); + } + return 0; +} + +#endif diff --git a/e2fsprogs/ext2fs/flushb.c b/e2fsprogs/ext2fs/flushb.c new file mode 100644 index 000000000..e42982653 --- /dev/null +++ b/e2fsprogs/ext2fs/flushb.c @@ -0,0 +1,83 @@ +/* vi: set sw=4 ts=4: */ +/* + * flushb.c --- Hides system-dependent information for both syncing a + * device to disk and to flush any buffers from disk cache. + * + * Copyright (C) 2000 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#if HAVE_ERRNO_H +#include +#endif +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_SYS_IOCTL_H +#include +#endif +#if HAVE_SYS_MOUNT_H +#include +#include /* This may define BLKFLSBUF */ +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * For Linux, define BLKFLSBUF and FDFLUSH if necessary, since + * not all portable header file does so for us. This really should be + * fixed in the glibc header files. (Recent glibcs appear to define + * BLKFLSBUF in sys/mount.h, but FDFLUSH still doesn't seem to be + * defined anywhere portable.) Until then.... + */ +#ifdef __linux__ +#ifndef BLKFLSBUF +#define BLKFLSBUF _IO(0x12,97) /* flush buffer cache */ +#endif +#ifndef FDFLUSH +#define FDFLUSH _IO(2,0x4b) /* flush floppy disk */ +#endif +#endif + +/* + * This function will sync a device/file, and optionally attempt to + * flush the buffer cache. The latter is basically only useful for + * system benchmarks and for torturing systems in burn-in tests. :) + */ +errcode_t ext2fs_sync_device(int fd, int flushb) +{ + /* + * We always sync the device in case we're running on old + * kernels for which we can lose data if we don't. (There + * still is a race condition for those kernels, but this + * reduces it greatly.) + */ + if (fsync (fd) == -1) + return errno; + + if (flushb) { + +#ifdef BLKFLSBUF + if (ioctl (fd, BLKFLSBUF, 0) == 0) + return 0; +#else +#ifdef __GNUC__ +# warning BLKFLSBUF not defined +#endif /* __GNUC__ */ +#endif +#ifdef FDFLUSH + ioctl (fd, FDFLUSH, 0); /* In case this is a floppy */ +#else +#ifdef __GNUC__ +# warning FDFLUSH not defined +#endif /* __GNUC__ */ +#endif + } + return 0; +} diff --git a/e2fsprogs/ext2fs/freefs.c b/e2fsprogs/ext2fs/freefs.c new file mode 100644 index 000000000..65c4ee794 --- /dev/null +++ b/e2fsprogs/ext2fs/freefs.c @@ -0,0 +1,128 @@ +/* vi: set sw=4 ts=4: */ +/* + * freefs.c --- free an ext2 filesystem + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#if HAVE_UNISTD_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fsP.h" + +static void ext2fs_free_inode_cache(struct ext2_inode_cache *icache); + +void ext2fs_free(ext2_filsys fs) +{ + if (!fs || (fs->magic != EXT2_ET_MAGIC_EXT2FS_FILSYS)) + return; + if (fs->image_io != fs->io) { + if (fs->image_io) + io_channel_close(fs->image_io); + } + if (fs->io) { + io_channel_close(fs->io); + } + ext2fs_free_mem(&fs->device_name); + ext2fs_free_mem(&fs->super); + ext2fs_free_mem(&fs->orig_super); + ext2fs_free_mem(&fs->group_desc); + ext2fs_free_block_bitmap(fs->block_map); + ext2fs_free_inode_bitmap(fs->inode_map); + + ext2fs_badblocks_list_free(fs->badblocks); + fs->badblocks = 0; + + ext2fs_free_dblist(fs->dblist); + + if (fs->icache) + ext2fs_free_inode_cache(fs->icache); + + fs->magic = 0; + + ext2fs_free_mem(&fs); +} + +void ext2fs_free_generic_bitmap(ext2fs_inode_bitmap bitmap) +{ + if (!bitmap || (bitmap->magic != EXT2_ET_MAGIC_GENERIC_BITMAP)) + return; + + bitmap->magic = 0; + ext2fs_free_mem(&bitmap->description); + ext2fs_free_mem(&bitmap->bitmap); + ext2fs_free_mem(&bitmap); +} + +void ext2fs_free_inode_bitmap(ext2fs_inode_bitmap bitmap) +{ + if (!bitmap || (bitmap->magic != EXT2_ET_MAGIC_INODE_BITMAP)) + return; + + bitmap->magic = EXT2_ET_MAGIC_GENERIC_BITMAP; + ext2fs_free_generic_bitmap(bitmap); +} + +void ext2fs_free_block_bitmap(ext2fs_block_bitmap bitmap) +{ + if (!bitmap || (bitmap->magic != EXT2_ET_MAGIC_BLOCK_BITMAP)) + return; + + bitmap->magic = EXT2_ET_MAGIC_GENERIC_BITMAP; + ext2fs_free_generic_bitmap(bitmap); +} + +/* + * Free the inode cache structure + */ +static void ext2fs_free_inode_cache(struct ext2_inode_cache *icache) +{ + if (--icache->refcount) + return; + ext2fs_free_mem(&icache->buffer); + ext2fs_free_mem(&icache->cache); + icache->buffer_blk = 0; + ext2fs_free_mem(&icache); +} + +/* + * This procedure frees a badblocks list. + */ +void ext2fs_u32_list_free(ext2_u32_list bb) +{ + if (!bb || bb->magic != EXT2_ET_MAGIC_BADBLOCKS_LIST) + return; + + ext2fs_free_mem(&bb->list); + ext2fs_free_mem(&bb); +} + +void ext2fs_badblocks_list_free(ext2_badblocks_list bb) +{ + ext2fs_u32_list_free((ext2_u32_list) bb); +} + + +/* + * Free a directory block list + */ +void ext2fs_free_dblist(ext2_dblist dblist) +{ + if (!dblist || (dblist->magic != EXT2_ET_MAGIC_DBLIST)) + return; + + ext2fs_free_mem(&dblist->list); + if (dblist->fs && dblist->fs->dblist == dblist) + dblist->fs->dblist = 0; + dblist->magic = 0; + ext2fs_free_mem(&dblist); +} + diff --git a/e2fsprogs/ext2fs/gen_bitmap.c b/e2fsprogs/ext2fs/gen_bitmap.c new file mode 100644 index 000000000..d0869c919 --- /dev/null +++ b/e2fsprogs/ext2fs/gen_bitmap.c @@ -0,0 +1,49 @@ +/* vi: set sw=4 ts=4: */ +/* + * gen_bitmap.c --- Generic bitmap routines that used to be inlined. + * + * Copyright (C) 2001 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +int ext2fs_mark_generic_bitmap(ext2fs_generic_bitmap bitmap, + __u32 bitno) +{ + if ((bitno < bitmap->start) || (bitno > bitmap->end)) { + ext2fs_warn_bitmap2(bitmap, EXT2FS_MARK_ERROR, bitno); + return 0; + } + return ext2fs_set_bit(bitno - bitmap->start, bitmap->bitmap); +} + +int ext2fs_unmark_generic_bitmap(ext2fs_generic_bitmap bitmap, + blk_t bitno) +{ + if ((bitno < bitmap->start) || (bitno > bitmap->end)) { + ext2fs_warn_bitmap2(bitmap, EXT2FS_UNMARK_ERROR, bitno); + return 0; + } + return ext2fs_clear_bit(bitno - bitmap->start, bitmap->bitmap); +} diff --git a/e2fsprogs/ext2fs/get_pathname.c b/e2fsprogs/ext2fs/get_pathname.c new file mode 100644 index 000000000..a98b2b9e5 --- /dev/null +++ b/e2fsprogs/ext2fs/get_pathname.c @@ -0,0 +1,157 @@ +/* vi: set sw=4 ts=4: */ +/* + * get_pathname.c --- do directry/inode -> name translation + * + * Copyright (C) 1993, 1994, 1995 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + * + * ext2fs_get_pathname(fs, dir, ino, name) + * + * This function translates takes two inode numbers into a + * string, placing the result in . is the containing + * directory inode, and is the inode number itself. If + * is zero, then ext2fs_get_pathname will return pathname + * of the the directory . + * + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +struct get_pathname_struct { + ext2_ino_t search_ino; + ext2_ino_t parent; + char *name; + errcode_t errcode; +}; + +#ifdef __TURBOC__ +# pragma argsused +#endif +static int get_pathname_proc(struct ext2_dir_entry *dirent, + int offset EXT2FS_ATTR((unused)), + int blocksize EXT2FS_ATTR((unused)), + char *buf EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct get_pathname_struct *gp; + errcode_t retval; + + gp = (struct get_pathname_struct *) priv_data; + + if (((dirent->name_len & 0xFF) == 2) && + !strncmp(dirent->name, "..", 2)) + gp->parent = dirent->inode; + if (dirent->inode == gp->search_ino) { + retval = ext2fs_get_mem((dirent->name_len & 0xFF) + 1, + &gp->name); + if (retval) { + gp->errcode = retval; + return DIRENT_ABORT; + } + strncpy(gp->name, dirent->name, (dirent->name_len & 0xFF)); + gp->name[dirent->name_len & 0xFF] = '\0'; + return DIRENT_ABORT; + } + return 0; +} + +static errcode_t ext2fs_get_pathname_int(ext2_filsys fs, ext2_ino_t dir, + ext2_ino_t ino, int maxdepth, + char *buf, char **name) +{ + struct get_pathname_struct gp; + char *parent_name, *ret; + errcode_t retval; + + if (dir == ino) { + retval = ext2fs_get_mem(2, name); + if (retval) + return retval; + strcpy(*name, (dir == EXT2_ROOT_INO) ? "/" : "."); + return 0; + } + + if (!dir || (maxdepth < 0)) { + retval = ext2fs_get_mem(4, name); + if (retval) + return retval; + strcpy(*name, "..."); + return 0; + } + + gp.search_ino = ino; + gp.parent = 0; + gp.name = 0; + gp.errcode = 0; + + retval = ext2fs_dir_iterate(fs, dir, 0, buf, get_pathname_proc, &gp); + if (retval) + goto cleanup; + if (gp.errcode) { + retval = gp.errcode; + goto cleanup; + } + + retval = ext2fs_get_pathname_int(fs, gp.parent, dir, maxdepth-1, + buf, &parent_name); + if (retval) + goto cleanup; + if (!ino) { + *name = parent_name; + return 0; + } + + if (gp.name) + retval = ext2fs_get_mem(strlen(parent_name)+strlen(gp.name)+2, + &ret); + else + retval = ext2fs_get_mem(strlen(parent_name)+5, &ret); + if (retval) + goto cleanup; + + ret[0] = 0; + if (parent_name[1]) + strcat(ret, parent_name); + strcat(ret, "/"); + if (gp.name) + strcat(ret, gp.name); + else + strcat(ret, "???"); + *name = ret; + ext2fs_free_mem(&parent_name); + retval = 0; + +cleanup: + ext2fs_free_mem(&gp.name); + return retval; +} + +errcode_t ext2fs_get_pathname(ext2_filsys fs, ext2_ino_t dir, ext2_ino_t ino, + char **name) +{ + char *buf; + errcode_t retval; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + retval = ext2fs_get_mem(fs->blocksize, &buf); + if (retval) + return retval; + if (dir == ino) + ino = 0; + retval = ext2fs_get_pathname_int(fs, dir, ino, 32, buf, name); + ext2fs_free_mem(&buf); + return retval; + +} diff --git a/e2fsprogs/ext2fs/getsectsize.c b/e2fsprogs/ext2fs/getsectsize.c new file mode 100644 index 000000000..163ec65e5 --- /dev/null +++ b/e2fsprogs/ext2fs/getsectsize.c @@ -0,0 +1,58 @@ +/* vi: set sw=4 ts=4: */ +/* + * getsectsize.c --- get the sector size of a device. + * + * Copyright (C) 1995, 1995 Theodore Ts'o. + * Copyright (C) 2003 VMware, Inc. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif +#include +#ifdef HAVE_LINUX_FD_H +#include +#include +#endif + +#if defined(__linux__) && defined(_IO) && !defined(BLKSSZGET) +#define BLKSSZGET _IO(0x12,104)/* get block device sector size */ +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * Returns the number of blocks in a partition + */ +errcode_t ext2fs_get_device_sectsize(const char *file, int *sectsize) +{ + int fd; + +#ifdef CONFIG_LFS + fd = open64(file, O_RDONLY); +#else + fd = open(file, O_RDONLY); +#endif + if (fd < 0) + return errno; + +#ifdef BLKSSZGET + if (ioctl(fd, BLKSSZGET, sectsize) >= 0) { + close(fd); + return 0; + } +#endif + *sectsize = 0; + close(fd); + return 0; +} diff --git a/e2fsprogs/ext2fs/getsize.c b/e2fsprogs/ext2fs/getsize.c new file mode 100644 index 000000000..516886c1f --- /dev/null +++ b/e2fsprogs/ext2fs/getsize.c @@ -0,0 +1,291 @@ +/* vi: set sw=4 ts=4: */ +/* + * getsize.c --- get the size of a partition. + * + * Copyright (C) 1995, 1995 Theodore Ts'o. + * Copyright (C) 2003 VMware, Inc. + * + * Windows version of ext2fs_get_device_size by Chris Li, VMware. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif +#include +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#ifdef HAVE_LINUX_FD_H +#include +#endif +#ifdef HAVE_SYS_DISKLABEL_H +#include +#endif +#ifdef HAVE_SYS_DISK_H +#ifdef HAVE_SYS_QUEUE_H +#include /* for LIST_HEAD */ +#endif +#include +#endif +#ifdef __linux__ +#include +#endif + +#if defined(__linux__) && defined(_IO) && !defined(BLKGETSIZE) +#define BLKGETSIZE _IO(0x12,96) /* return device size */ +#endif + +#if defined(__linux__) && defined(_IOR) && !defined(BLKGETSIZE64) +#define BLKGETSIZE64 _IOR(0x12,114,size_t) /* return device size in bytes (u64 *arg) */ +#endif + +#ifdef APPLE_DARWIN +#define BLKGETSIZE DKIOCGETBLOCKCOUNT32 +#endif /* APPLE_DARWIN */ + +#include "ext2_fs.h" +#include "ext2fs.h" + +#if defined(__CYGWIN__) || defined (WIN32) +#include +#include + +#if (_WIN32_WINNT >= 0x0500) +#define HAVE_GET_FILE_SIZE_EX 1 +#endif + +errcode_t ext2fs_get_device_size(const char *file, int blocksize, + blk_t *retblocks) +{ + HANDLE dev; + PARTITION_INFORMATION pi; + DISK_GEOMETRY gi; + DWORD retbytes; +#ifdef HAVE_GET_FILE_SIZE_EX + LARGE_INTEGER filesize; +#else + DWORD filesize; +#endif /* HAVE_GET_FILE_SIZE_EX */ + + dev = CreateFile(file, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE , + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + if (dev == INVALID_HANDLE_VALUE) + return EBADF; + if (DeviceIoControl(dev, IOCTL_DISK_GET_PARTITION_INFO, + &pi, sizeof(PARTITION_INFORMATION), + &pi, sizeof(PARTITION_INFORMATION), + &retbytes, NULL)) { + + *retblocks = pi.PartitionLength.QuadPart / blocksize; + + } else if (DeviceIoControl(dev, IOCTL_DISK_GET_DRIVE_GEOMETRY, + &gi, sizeof(DISK_GEOMETRY), + &gi, sizeof(DISK_GEOMETRY), + &retbytes, NULL)) { + + *retblocks = gi.BytesPerSector * + gi.SectorsPerTrack * + gi.TracksPerCylinder * + gi.Cylinders.QuadPart / blocksize; + +#ifdef HAVE_GET_FILE_SIZE_EX + } else if (GetFileSizeEx(dev, &filesize)) { + *retblocks = filesize.QuadPart / blocksize; + } +#else + } else { + filesize = GetFileSize(dev, NULL); + if (INVALID_FILE_SIZE != filesize) { + *retblocks = filesize / blocksize; + } + } +#endif /* HAVE_GET_FILE_SIZE_EX */ + + CloseHandle(dev); + return 0; +} + +#else + +static int valid_offset (int fd, ext2_loff_t offset) +{ + char ch; + + if (ext2fs_llseek (fd, offset, 0) < 0) + return 0; + if (read (fd, &ch, 1) < 1) + return 0; + return 1; +} + +/* + * Returns the number of blocks in a partition + */ +errcode_t ext2fs_get_device_size(const char *file, int blocksize, + blk_t *retblocks) +{ + int fd; + int valid_blkgetsize64 = 1; +#ifdef __linux__ + struct utsname ut; +#endif + unsigned long long size64; + unsigned long size; + ext2_loff_t high, low; +#ifdef FDGETPRM + struct floppy_struct this_floppy; +#endif +#ifdef HAVE_SYS_DISKLABEL_H + int part; + struct disklabel lab; + struct partition *pp; + char ch; +#endif /* HAVE_SYS_DISKLABEL_H */ + +#ifdef CONFIG_LFS + fd = open64(file, O_RDONLY); +#else + fd = open(file, O_RDONLY); +#endif + if (fd < 0) + return errno; + +#ifdef DKIOCGETBLOCKCOUNT /* For Apple Darwin */ + if (ioctl(fd, DKIOCGETBLOCKCOUNT, &size64) >= 0) { + if ((sizeof(*retblocks) < sizeof(unsigned long long)) + && ((size64 / (blocksize / 512)) > 0xFFFFFFFF)) + return EFBIG; + close(fd); + *retblocks = size64 / (blocksize / 512); + return 0; + } +#endif + +#ifdef BLKGETSIZE64 +#ifdef __linux__ + if ((uname(&ut) == 0) && + ((ut.release[0] == '2') && (ut.release[1] == '.') && + (ut.release[2] < '6') && (ut.release[3] == '.'))) + valid_blkgetsize64 = 0; +#endif + if (valid_blkgetsize64 && + ioctl(fd, BLKGETSIZE64, &size64) >= 0) { + if ((sizeof(*retblocks) < sizeof(unsigned long long)) + && ((size64 / blocksize) > 0xFFFFFFFF)) + return EFBIG; + close(fd); + *retblocks = size64 / blocksize; + return 0; + } +#endif + +#ifdef BLKGETSIZE + if (ioctl(fd, BLKGETSIZE, &size) >= 0) { + close(fd); + *retblocks = size / (blocksize / 512); + return 0; + } +#endif + +#ifdef FDGETPRM + if (ioctl(fd, FDGETPRM, &this_floppy) >= 0) { + close(fd); + *retblocks = this_floppy.size / (blocksize / 512); + return 0; + } +#endif + +#ifdef HAVE_SYS_DISKLABEL_H +#if defined(DIOCGMEDIASIZE) + { + off_t ms; + u_int bs; + if (ioctl(fd, DIOCGMEDIASIZE, &ms) >= 0) { + *retblocks = ms / blocksize; + return 0; + } + } +#elif defined(DIOCGDINFO) + /* old disklabel interface */ + part = strlen(file) - 1; + if (part >= 0) { + ch = file[part]; + if (isdigit(ch)) + part = 0; + else if (ch >= 'a' && ch <= 'h') + part = ch - 'a'; + else + part = -1; + } + if (part >= 0 && (ioctl(fd, DIOCGDINFO, (char *)&lab) >= 0)) { + pp = &lab.d_partitions[part]; + if (pp->p_size) { + close(fd); + *retblocks = pp->p_size / (blocksize / 512); + return 0; + } + } +#endif /* defined(DIOCG*) */ +#endif /* HAVE_SYS_DISKLABEL_H */ + + /* + * OK, we couldn't figure it out by using a specialized ioctl, + * which is generally the best way. So do binary search to + * find the size of the partition. + */ + low = 0; + for (high = 1024; valid_offset (fd, high); high *= 2) + low = high; + while (low < high - 1) + { + const ext2_loff_t mid = (low + high) / 2; + + if (valid_offset (fd, mid)) + low = mid; + else + high = mid; + } + valid_offset (fd, 0); + close(fd); + size64 = low + 1; + if ((sizeof(*retblocks) < sizeof(unsigned long long)) + && ((size64 / blocksize) > 0xFFFFFFFF)) + return EFBIG; + *retblocks = size64 / blocksize; + return 0; +} + +#endif /* WIN32 */ + +#ifdef DEBUG +int main(int argc, char **argv) +{ + blk_t blocks; + int retval; + + if (argc < 2) { + fprintf(stderr, "Usage: %s device\n", argv[0]); + exit(1); + } + + retval = ext2fs_get_device_size(argv[1], 1024, &blocks); + if (retval) { + com_err(argv[0], retval, + "while calling ext2fs_get_device_size"); + exit(1); + } + printf("Device %s has %d 1k blocks.\n", argv[1], blocks); + exit(0); +} +#endif diff --git a/e2fsprogs/ext2fs/icount.c b/e2fsprogs/ext2fs/icount.c new file mode 100644 index 000000000..7ab5f51f4 --- /dev/null +++ b/e2fsprogs/ext2fs/icount.c @@ -0,0 +1,467 @@ +/* vi: set sw=4 ts=4: */ +/* + * icount.c --- an efficient inode count abstraction + * + * Copyright (C) 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#if HAVE_UNISTD_H +#include +#endif +#include +#include + +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * The data storage strategy used by icount relies on the observation + * that most inode counts are either zero (for non-allocated inodes), + * one (for most files), and only a few that are two or more + * (directories and files that are linked to more than one directory). + * + * Also, e2fsck tends to load the icount data sequentially. + * + * So, we use an inode bitmap to indicate which inodes have a count of + * one, and then use a sorted list to store the counts for inodes + * which are greater than one. + * + * We also use an optional bitmap to indicate which inodes are already + * in the sorted list, to speed up the use of this abstraction by + * e2fsck's pass 2. Pass 2 increments inode counts as it finds them, + * so this extra bitmap avoids searching the sorted list to see if a + * particular inode is on the sorted list already. + */ + +struct ext2_icount_el { + ext2_ino_t ino; + __u16 count; +}; + +struct ext2_icount { + errcode_t magic; + ext2fs_inode_bitmap single; + ext2fs_inode_bitmap multiple; + ext2_ino_t count; + ext2_ino_t size; + ext2_ino_t num_inodes; + ext2_ino_t cursor; + struct ext2_icount_el *list; +}; + +void ext2fs_free_icount(ext2_icount_t icount) +{ + if (!icount) + return; + + icount->magic = 0; + ext2fs_free_mem(&icount->list); + ext2fs_free_inode_bitmap(icount->single); + ext2fs_free_inode_bitmap(icount->multiple); + ext2fs_free_mem(&icount); +} + +errcode_t ext2fs_create_icount2(ext2_filsys fs, int flags, unsigned int size, + ext2_icount_t hint, ext2_icount_t *ret) +{ + ext2_icount_t icount; + errcode_t retval; + size_t bytes; + ext2_ino_t i; + + if (hint) { + EXT2_CHECK_MAGIC(hint, EXT2_ET_MAGIC_ICOUNT); + if (hint->size > size) + size = (size_t) hint->size; + } + + retval = ext2fs_get_mem(sizeof(struct ext2_icount), &icount); + if (retval) + return retval; + memset(icount, 0, sizeof(struct ext2_icount)); + + retval = ext2fs_allocate_inode_bitmap(fs, 0, + &icount->single); + if (retval) + goto errout; + + if (flags & EXT2_ICOUNT_OPT_INCREMENT) { + retval = ext2fs_allocate_inode_bitmap(fs, 0, + &icount->multiple); + if (retval) + goto errout; + } else + icount->multiple = 0; + + if (size) { + icount->size = size; + } else { + /* + * Figure out how many special case inode counts we will + * have. We know we will need one for each directory; + * we also need to reserve some extra room for file links + */ + retval = ext2fs_get_num_dirs(fs, &icount->size); + if (retval) + goto errout; + icount->size += fs->super->s_inodes_count / 50; + } + + bytes = (size_t) (icount->size * sizeof(struct ext2_icount_el)); + retval = ext2fs_get_mem(bytes, &icount->list); + if (retval) + goto errout; + memset(icount->list, 0, bytes); + + icount->magic = EXT2_ET_MAGIC_ICOUNT; + icount->count = 0; + icount->cursor = 0; + icount->num_inodes = fs->super->s_inodes_count; + + /* + * Populate the sorted list with those entries which were + * found in the hint icount (since those are ones which will + * likely need to be in the sorted list this time around). + */ + if (hint) { + for (i=0; i < hint->count; i++) + icount->list[i].ino = hint->list[i].ino; + icount->count = hint->count; + } + + *ret = icount; + return 0; + +errout: + ext2fs_free_icount(icount); + return retval; +} + +errcode_t ext2fs_create_icount(ext2_filsys fs, int flags, + unsigned int size, + ext2_icount_t *ret) +{ + return ext2fs_create_icount2(fs, flags, size, 0, ret); +} + +/* + * insert_icount_el() --- Insert a new entry into the sorted list at a + * specified position. + */ +static struct ext2_icount_el *insert_icount_el(ext2_icount_t icount, + ext2_ino_t ino, int pos) +{ + struct ext2_icount_el *el; + errcode_t retval; + ext2_ino_t new_size = 0; + int num; + + if (icount->count >= icount->size) { + if (icount->count) { + new_size = icount->list[(unsigned)icount->count-1].ino; + new_size = (ext2_ino_t) (icount->count * + ((float) icount->num_inodes / new_size)); + } + if (new_size < (icount->size + 100)) + new_size = icount->size + 100; + retval = ext2fs_resize_mem((size_t) icount->size * + sizeof(struct ext2_icount_el), + (size_t) new_size * + sizeof(struct ext2_icount_el), + &icount->list); + if (retval) + return 0; + icount->size = new_size; + } + num = (int) icount->count - pos; + if (num < 0) + return 0; /* should never happen */ + if (num) { + memmove(&icount->list[pos+1], &icount->list[pos], + sizeof(struct ext2_icount_el) * num); + } + icount->count++; + el = &icount->list[pos]; + el->count = 0; + el->ino = ino; + return el; +} + +/* + * get_icount_el() --- given an inode number, try to find icount + * information in the sorted list. If the create flag is set, + * and we can't find an entry, create one in the sorted list. + */ +static struct ext2_icount_el *get_icount_el(ext2_icount_t icount, + ext2_ino_t ino, int create) +{ + float range; + int low, high, mid; + ext2_ino_t lowval, highval; + + if (!icount || !icount->list) + return 0; + + if (create && ((icount->count == 0) || + (ino > icount->list[(unsigned)icount->count-1].ino))) { + return insert_icount_el(icount, ino, (unsigned) icount->count); + } + if (icount->count == 0) + return 0; + + if (icount->cursor >= icount->count) + icount->cursor = 0; + if (ino == icount->list[icount->cursor].ino) + return &icount->list[icount->cursor++]; + low = 0; + high = (int) icount->count-1; + while (low <= high) { + if (low == high) + mid = low; + else { + /* Interpolate for efficiency */ + lowval = icount->list[low].ino; + highval = icount->list[high].ino; + + if (ino < lowval) + range = 0; + else if (ino > highval) + range = 1; + else + range = ((float) (ino - lowval)) / + (highval - lowval); + mid = low + ((int) (range * (high-low))); + } + if (ino == icount->list[mid].ino) { + icount->cursor = mid+1; + return &icount->list[mid]; + } + if (ino < icount->list[mid].ino) + high = mid-1; + else + low = mid+1; + } + /* + * If we need to create a new entry, it should be right at + * low (where high will be left at low-1). + */ + if (create) + return insert_icount_el(icount, ino, low); + return 0; +} + +errcode_t ext2fs_icount_validate(ext2_icount_t icount, FILE *out) +{ + errcode_t ret = 0; + unsigned int i; + const char *bad = "bad icount"; + + EXT2_CHECK_MAGIC(icount, EXT2_ET_MAGIC_ICOUNT); + + if (icount->count > icount->size) { + fprintf(out, "%s: count > size\n", bad); + return EXT2_ET_INVALID_ARGUMENT; + } + for (i=1; i < icount->count; i++) { + if (icount->list[i-1].ino >= icount->list[i].ino) { + fprintf(out, "%s: list[%d].ino=%u, list[%d].ino=%u\n", + bad, i-1, icount->list[i-1].ino, + i, icount->list[i].ino); + ret = EXT2_ET_INVALID_ARGUMENT; + } + } + return ret; +} + +errcode_t ext2fs_icount_fetch(ext2_icount_t icount, ext2_ino_t ino, __u16 *ret) +{ + struct ext2_icount_el *el; + + EXT2_CHECK_MAGIC(icount, EXT2_ET_MAGIC_ICOUNT); + + if (!ino || (ino > icount->num_inodes)) + return EXT2_ET_INVALID_ARGUMENT; + + if (ext2fs_test_inode_bitmap(icount->single, ino)) { + *ret = 1; + return 0; + } + if (icount->multiple && + !ext2fs_test_inode_bitmap(icount->multiple, ino)) { + *ret = 0; + return 0; + } + el = get_icount_el(icount, ino, 0); + if (!el) { + *ret = 0; + return 0; + } + *ret = el->count; + return 0; +} + +errcode_t ext2fs_icount_increment(ext2_icount_t icount, ext2_ino_t ino, + __u16 *ret) +{ + struct ext2_icount_el *el; + + EXT2_CHECK_MAGIC(icount, EXT2_ET_MAGIC_ICOUNT); + + if (!ino || (ino > icount->num_inodes)) + return EXT2_ET_INVALID_ARGUMENT; + + if (ext2fs_test_inode_bitmap(icount->single, ino)) { + /* + * If the existing count is 1, then we know there is + * no entry in the list. + */ + el = get_icount_el(icount, ino, 1); + if (!el) + return EXT2_ET_NO_MEMORY; + ext2fs_unmark_inode_bitmap(icount->single, ino); + el->count = 2; + } else if (icount->multiple) { + /* + * The count is either zero or greater than 1; if the + * inode is set in icount->multiple, then there should + * be an entry in the list, so find it using + * get_icount_el(). + */ + if (ext2fs_test_inode_bitmap(icount->multiple, ino)) { + el = get_icount_el(icount, ino, 1); + if (!el) + return EXT2_ET_NO_MEMORY; + el->count++; + } else { + /* + * The count was zero; mark the single bitmap + * and return. + */ + zero_count: + ext2fs_mark_inode_bitmap(icount->single, ino); + if (ret) + *ret = 1; + return 0; + } + } else { + /* + * The count is either zero or greater than 1; try to + * find an entry in the list to determine which. + */ + el = get_icount_el(icount, ino, 0); + if (!el) { + /* No entry means the count was zero */ + goto zero_count; + } + el = get_icount_el(icount, ino, 1); + if (!el) + return EXT2_ET_NO_MEMORY; + el->count++; + } + if (icount->multiple) + ext2fs_mark_inode_bitmap(icount->multiple, ino); + if (ret) + *ret = el->count; + return 0; +} + +errcode_t ext2fs_icount_decrement(ext2_icount_t icount, ext2_ino_t ino, + __u16 *ret) +{ + struct ext2_icount_el *el; + + if (!ino || (ino > icount->num_inodes)) + return EXT2_ET_INVALID_ARGUMENT; + + EXT2_CHECK_MAGIC(icount, EXT2_ET_MAGIC_ICOUNT); + + if (ext2fs_test_inode_bitmap(icount->single, ino)) { + ext2fs_unmark_inode_bitmap(icount->single, ino); + if (icount->multiple) + ext2fs_unmark_inode_bitmap(icount->multiple, ino); + else { + el = get_icount_el(icount, ino, 0); + if (el) + el->count = 0; + } + if (ret) + *ret = 0; + return 0; + } + + if (icount->multiple && + !ext2fs_test_inode_bitmap(icount->multiple, ino)) + return EXT2_ET_INVALID_ARGUMENT; + + el = get_icount_el(icount, ino, 0); + if (!el || el->count == 0) + return EXT2_ET_INVALID_ARGUMENT; + + el->count--; + if (el->count == 1) + ext2fs_mark_inode_bitmap(icount->single, ino); + if ((el->count == 0) && icount->multiple) + ext2fs_unmark_inode_bitmap(icount->multiple, ino); + + if (ret) + *ret = el->count; + return 0; +} + +errcode_t ext2fs_icount_store(ext2_icount_t icount, ext2_ino_t ino, + __u16 count) +{ + struct ext2_icount_el *el; + + if (!ino || (ino > icount->num_inodes)) + return EXT2_ET_INVALID_ARGUMENT; + + EXT2_CHECK_MAGIC(icount, EXT2_ET_MAGIC_ICOUNT); + + if (count == 1) { + ext2fs_mark_inode_bitmap(icount->single, ino); + if (icount->multiple) + ext2fs_unmark_inode_bitmap(icount->multiple, ino); + return 0; + } + if (count == 0) { + ext2fs_unmark_inode_bitmap(icount->single, ino); + if (icount->multiple) { + /* + * If the icount->multiple bitmap is enabled, + * we can just clear both bitmaps and we're done + */ + ext2fs_unmark_inode_bitmap(icount->multiple, ino); + } else { + el = get_icount_el(icount, ino, 0); + if (el) + el->count = 0; + } + return 0; + } + + /* + * Get the icount element + */ + el = get_icount_el(icount, ino, 1); + if (!el) + return EXT2_ET_NO_MEMORY; + el->count = count; + ext2fs_unmark_inode_bitmap(icount->single, ino); + if (icount->multiple) + ext2fs_mark_inode_bitmap(icount->multiple, ino); + return 0; +} + +ext2_ino_t ext2fs_get_icount_size(ext2_icount_t icount) +{ + if (!icount || icount->magic != EXT2_ET_MAGIC_ICOUNT) + return 0; + + return icount->size; +} diff --git a/e2fsprogs/ext2fs/imager.c b/e2fsprogs/ext2fs/imager.c new file mode 100644 index 000000000..e82321efa --- /dev/null +++ b/e2fsprogs/ext2fs/imager.c @@ -0,0 +1,377 @@ +/* vi: set sw=4 ts=4: */ +/* + * image.c --- writes out the critical parts of the filesystem as a + * flat file. + * + * Copyright (C) 2000 Theodore Ts'o. + * + * Note: this uses the POSIX IO interfaces, unlike most of the other + * functions in this library. So sue me. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +#ifndef HAVE_TYPE_SSIZE_T +typedef int ssize_t; +#endif + +/* + * This function returns 1 if the specified block is all zeros + */ +static int check_zero_block(char *buf, int blocksize) +{ + char *cp = buf; + int left = blocksize; + + while (left > 0) { + if (*cp++) + return 0; + left--; + } + return 1; +} + +/* + * Write the inode table out as a single block. + */ +#define BUF_BLOCKS 32 + +errcode_t ext2fs_image_inode_write(ext2_filsys fs, int fd, int flags) +{ + unsigned int group, left, c, d; + char *buf, *cp; + blk_t blk; + ssize_t actual; + errcode_t retval; + + buf = xmalloc(fs->blocksize * BUF_BLOCKS); + + for (group = 0; group < fs->group_desc_count; group++) { + blk = fs->group_desc[(unsigned)group].bg_inode_table; + if (!blk) + return EXT2_ET_MISSING_INODE_TABLE; + left = fs->inode_blocks_per_group; + while (left) { + c = BUF_BLOCKS; + if (c > left) + c = left; + retval = io_channel_read_blk(fs->io, blk, c, buf); + if (retval) + goto errout; + cp = buf; + while (c) { + if (!(flags & IMAGER_FLAG_SPARSEWRITE)) { + d = c; + goto skip_sparse; + } + /* Skip zero blocks */ + if (check_zero_block(cp, fs->blocksize)) { + c--; + blk++; + left--; + cp += fs->blocksize; + lseek(fd, fs->blocksize, SEEK_CUR); + continue; + } + /* Find non-zero blocks */ + for (d=1; d < c; d++) { + if (check_zero_block(cp + d*fs->blocksize, fs->blocksize)) + break; + } + skip_sparse: + actual = write(fd, cp, fs->blocksize * d); + if (actual == -1) { + retval = errno; + goto errout; + } + if (actual != (ssize_t) (fs->blocksize * d)) { + retval = EXT2_ET_SHORT_WRITE; + goto errout; + } + blk += d; + left -= d; + cp += fs->blocksize * d; + c -= d; + } + } + } + retval = 0; + +errout: + free(buf); + return retval; +} + +/* + * Read in the inode table and stuff it into place + */ +errcode_t ext2fs_image_inode_read(ext2_filsys fs, int fd, + int flags EXT2FS_ATTR((unused))) +{ + unsigned int group, c, left; + char *buf; + blk_t blk; + ssize_t actual; + errcode_t retval; + + buf = xmalloc(fs->blocksize * BUF_BLOCKS); + + for (group = 0; group < fs->group_desc_count; group++) { + blk = fs->group_desc[(unsigned)group].bg_inode_table; + if (!blk) { + retval = EXT2_ET_MISSING_INODE_TABLE; + goto errout; + } + left = fs->inode_blocks_per_group; + while (left) { + c = BUF_BLOCKS; + if (c > left) + c = left; + actual = read(fd, buf, fs->blocksize * c); + if (actual == -1) { + retval = errno; + goto errout; + } + if (actual != (ssize_t) (fs->blocksize * c)) { + retval = EXT2_ET_SHORT_READ; + goto errout; + } + retval = io_channel_write_blk(fs->io, blk, c, buf); + if (retval) + goto errout; + + blk += c; + left -= c; + } + } + retval = ext2fs_flush_icache(fs); + +errout: + free(buf); + return retval; +} + +/* + * Write out superblock and group descriptors + */ +errcode_t ext2fs_image_super_write(ext2_filsys fs, int fd, + int flags EXT2FS_ATTR((unused))) +{ + char *buf, *cp; + ssize_t actual; + errcode_t retval; + + buf = xmalloc(fs->blocksize); + + /* + * Write out the superblock + */ + memset(buf, 0, fs->blocksize); + memcpy(buf, fs->super, SUPERBLOCK_SIZE); + actual = write(fd, buf, fs->blocksize); + if (actual == -1) { + retval = errno; + goto errout; + } + if (actual != (ssize_t) fs->blocksize) { + retval = EXT2_ET_SHORT_WRITE; + goto errout; + } + + /* + * Now write out the block group descriptors + */ + cp = (char *) fs->group_desc; + actual = write(fd, cp, fs->blocksize * fs->desc_blocks); + if (actual == -1) { + retval = errno; + goto errout; + } + if (actual != (ssize_t) (fs->blocksize * fs->desc_blocks)) { + retval = EXT2_ET_SHORT_WRITE; + goto errout; + } + + retval = 0; + +errout: + free(buf); + return retval; +} + +/* + * Read the superblock and group descriptors and overwrite them. + */ +errcode_t ext2fs_image_super_read(ext2_filsys fs, int fd, + int flags EXT2FS_ATTR((unused))) +{ + char *buf; + ssize_t actual, size; + errcode_t retval; + + size = fs->blocksize * (fs->group_desc_count + 1); + buf = xmalloc(size); + + /* + * Read it all in. + */ + actual = read(fd, buf, size); + if (actual == -1) { + retval = errno; + goto errout; + } + if (actual != size) { + retval = EXT2_ET_SHORT_READ; + goto errout; + } + + /* + * Now copy in the superblock and group descriptors + */ + memcpy(fs->super, buf, SUPERBLOCK_SIZE); + + memcpy(fs->group_desc, buf + fs->blocksize, + fs->blocksize * fs->group_desc_count); + + retval = 0; + +errout: + free(buf); + return retval; +} + +/* + * Write the block/inode bitmaps. + */ +errcode_t ext2fs_image_bitmap_write(ext2_filsys fs, int fd, int flags) +{ + char *ptr; + int c, size; + char zero_buf[1024]; + ssize_t actual; + errcode_t retval; + + if (flags & IMAGER_FLAG_INODEMAP) { + if (!fs->inode_map) { + retval = ext2fs_read_inode_bitmap(fs); + if (retval) + return retval; + } + ptr = fs->inode_map->bitmap; + size = (EXT2_INODES_PER_GROUP(fs->super) / 8); + } else { + if (!fs->block_map) { + retval = ext2fs_read_block_bitmap(fs); + if (retval) + return retval; + } + ptr = fs->block_map->bitmap; + size = EXT2_BLOCKS_PER_GROUP(fs->super) / 8; + } + size = size * fs->group_desc_count; + + actual = write(fd, ptr, size); + if (actual == -1) { + retval = errno; + goto errout; + } + if (actual != size) { + retval = EXT2_ET_SHORT_WRITE; + goto errout; + } + size = size % fs->blocksize; + memset(zero_buf, 0, sizeof(zero_buf)); + if (size) { + size = fs->blocksize - size; + while (size) { + c = size; + if (c > (int) sizeof(zero_buf)) + c = sizeof(zero_buf); + actual = write(fd, zero_buf, c); + if (actual == -1) { + retval = errno; + goto errout; + } + if (actual != c) { + retval = EXT2_ET_SHORT_WRITE; + goto errout; + } + size -= c; + } + } + retval = 0; +errout: + return retval; +} + + +/* + * Read the block/inode bitmaps. + */ +errcode_t ext2fs_image_bitmap_read(ext2_filsys fs, int fd, int flags) +{ + char *ptr, *buf = 0; + int size; + ssize_t actual; + errcode_t retval; + + if (flags & IMAGER_FLAG_INODEMAP) { + if (!fs->inode_map) { + retval = ext2fs_read_inode_bitmap(fs); + if (retval) + return retval; + } + ptr = fs->inode_map->bitmap; + size = (EXT2_INODES_PER_GROUP(fs->super) / 8); + } else { + if (!fs->block_map) { + retval = ext2fs_read_block_bitmap(fs); + if (retval) + return retval; + } + ptr = fs->block_map->bitmap; + size = EXT2_BLOCKS_PER_GROUP(fs->super) / 8; + } + size = size * fs->group_desc_count; + + buf = xmalloc(size); + + actual = read(fd, buf, size); + if (actual == -1) { + retval = errno; + goto errout; + } + if (actual != size) { + retval = EXT2_ET_SHORT_WRITE; + goto errout; + } + memcpy(ptr, buf, size); + + retval = 0; +errout: + free(buf); + return retval; +} diff --git a/e2fsprogs/ext2fs/ind_block.c b/e2fsprogs/ext2fs/ind_block.c new file mode 100644 index 000000000..c86a1c59a --- /dev/null +++ b/e2fsprogs/ext2fs/ind_block.c @@ -0,0 +1,71 @@ +/* vi: set sw=4 ts=4: */ +/* + * ind_block.c --- indirect block I/O routines + * + * Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, + * 2001, 2002, 2003, 2004, 2005 by Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +errcode_t ext2fs_read_ind_block(ext2_filsys fs, blk_t blk, void *buf) +{ + errcode_t retval; +#if BB_BIG_ENDIAN + blk_t *block_nr; + int i; + int limit = fs->blocksize >> 2; +#endif + + if ((fs->flags & EXT2_FLAG_IMAGE_FILE) && + (fs->io != fs->image_io)) + memset(buf, 0, fs->blocksize); + else { + retval = io_channel_read_blk(fs->io, blk, 1, buf); + if (retval) + return retval; + } +#if BB_BIG_ENDIAN + if (fs->flags & (EXT2_FLAG_SWAP_BYTES | EXT2_FLAG_SWAP_BYTES_READ)) { + block_nr = (blk_t *) buf; + for (i = 0; i < limit; i++, block_nr++) + *block_nr = ext2fs_swab32(*block_nr); + } +#endif + return 0; +} + +errcode_t ext2fs_write_ind_block(ext2_filsys fs, blk_t blk, void *buf) +{ +#if BB_BIG_ENDIAN + blk_t *block_nr; + int i; + int limit = fs->blocksize >> 2; +#endif + + if (fs->flags & EXT2_FLAG_IMAGE_FILE) + return 0; + +#if BB_BIG_ENDIAN + if (fs->flags & (EXT2_FLAG_SWAP_BYTES | EXT2_FLAG_SWAP_BYTES_WRITE)) { + block_nr = (blk_t *) buf; + for (i = 0; i < limit; i++, block_nr++) + *block_nr = ext2fs_swab32(*block_nr); + } +#endif + return io_channel_write_blk(fs->io, blk, 1, buf); +} + + diff --git a/e2fsprogs/ext2fs/initialize.c b/e2fsprogs/ext2fs/initialize.c new file mode 100644 index 000000000..ef1d34379 --- /dev/null +++ b/e2fsprogs/ext2fs/initialize.c @@ -0,0 +1,388 @@ +/* vi: set sw=4 ts=4: */ +/* + * initialize.c --- initialize a filesystem handle given superblock + * parameters. Used by mke2fs when initializing a filesystem. + * + * Copyright (C) 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +#if defined(__linux__) && defined(EXT2_OS_LINUX) +#define CREATOR_OS EXT2_OS_LINUX +#else +#if defined(__GNU__) && defined(EXT2_OS_HURD) +#define CREATOR_OS EXT2_OS_HURD +#else +#if defined(__FreeBSD__) && defined(EXT2_OS_FREEBSD) +#define CREATOR_OS EXT2_OS_FREEBSD +#else +#if defined(LITES) && defined(EXT2_OS_LITES) +#define CREATOR_OS EXT2_OS_LITES +#else +#define CREATOR_OS EXT2_OS_LINUX /* by default */ +#endif /* defined(LITES) && defined(EXT2_OS_LITES) */ +#endif /* defined(__FreeBSD__) && defined(EXT2_OS_FREEBSD) */ +#endif /* defined(__GNU__) && defined(EXT2_OS_HURD) */ +#endif /* defined(__linux__) && defined(EXT2_OS_LINUX) */ + +/* + * Note we override the kernel include file's idea of what the default + * check interval (never) should be. It's a good idea to check at + * least *occasionally*, specially since servers will never rarely get + * to reboot, since Linux is so robust these days. :-) + * + * 180 days (six months) seems like a good value. + */ +#ifdef EXT2_DFL_CHECKINTERVAL +#undef EXT2_DFL_CHECKINTERVAL +#endif +#define EXT2_DFL_CHECKINTERVAL (86400L * 180L) + +/* + * Calculate the number of GDT blocks to reserve for online filesystem growth. + * The absolute maximum number of GDT blocks we can reserve is determined by + * the number of block pointers that can fit into a single block. + */ +static int calc_reserved_gdt_blocks(ext2_filsys fs) +{ + struct ext2_super_block *sb = fs->super; + unsigned long bpg = sb->s_blocks_per_group; + unsigned int gdpb = fs->blocksize / sizeof(struct ext2_group_desc); + unsigned long max_blocks = 0xffffffff; + unsigned long rsv_groups; + int rsv_gdb; + + /* We set it at 1024x the current filesystem size, or + * the upper block count limit (2^32), whichever is lower. + */ + if (sb->s_blocks_count < max_blocks / 1024) + max_blocks = sb->s_blocks_count * 1024; + rsv_groups = (max_blocks - sb->s_first_data_block + bpg - 1) / bpg; + rsv_gdb = (rsv_groups + gdpb - 1) / gdpb - fs->desc_blocks; + if (rsv_gdb > EXT2_ADDR_PER_BLOCK(sb)) + rsv_gdb = EXT2_ADDR_PER_BLOCK(sb); +#ifdef RES_GDT_DEBUG + printf("max_blocks %lu, rsv_groups = %lu, rsv_gdb = %lu\n", + max_blocks, rsv_groups, rsv_gdb); +#endif + + return rsv_gdb; +} + +errcode_t ext2fs_initialize(const char *name, int flags, + struct ext2_super_block *param, + io_manager manager, ext2_filsys *ret_fs) +{ + ext2_filsys fs; + errcode_t retval; + struct ext2_super_block *super; + int frags_per_block; + unsigned int rem; + unsigned int overhead = 0; + blk_t group_block; + unsigned int ipg; + dgrp_t i; + blk_t numblocks; + int rsv_gdt; + char *buf; + + if (!param || !param->s_blocks_count) + return EXT2_ET_INVALID_ARGUMENT; + + retval = ext2fs_get_mem(sizeof(struct struct_ext2_filsys), &fs); + if (retval) + return retval; + + memset(fs, 0, sizeof(struct struct_ext2_filsys)); + fs->magic = EXT2_ET_MAGIC_EXT2FS_FILSYS; + fs->flags = flags | EXT2_FLAG_RW; + fs->umask = 022; +#ifdef WORDS_BIGENDIAN + fs->flags |= EXT2_FLAG_SWAP_BYTES; +#endif + retval = manager->open(name, IO_FLAG_RW, &fs->io); + if (retval) + goto cleanup; + fs->image_io = fs->io; + fs->io->app_data = fs; + retval = ext2fs_get_mem(strlen(name)+1, &fs->device_name); + if (retval) + goto cleanup; + + strcpy(fs->device_name, name); + retval = ext2fs_get_mem(SUPERBLOCK_SIZE, &super); + if (retval) + goto cleanup; + fs->super = super; + + memset(super, 0, SUPERBLOCK_SIZE); + +#define set_field(field, default) (super->field = param->field ? \ + param->field : (default)) + + super->s_magic = EXT2_SUPER_MAGIC; + super->s_state = EXT2_VALID_FS; + + set_field(s_log_block_size, 0); /* default blocksize: 1024 bytes */ + set_field(s_log_frag_size, 0); /* default fragsize: 1024 bytes */ + set_field(s_first_data_block, super->s_log_block_size ? 0 : 1); + set_field(s_max_mnt_count, EXT2_DFL_MAX_MNT_COUNT); + set_field(s_errors, EXT2_ERRORS_DEFAULT); + set_field(s_feature_compat, 0); + set_field(s_feature_incompat, 0); + set_field(s_feature_ro_compat, 0); + set_field(s_first_meta_bg, 0); + if (super->s_feature_incompat & ~EXT2_LIB_FEATURE_INCOMPAT_SUPP) { + retval = EXT2_ET_UNSUPP_FEATURE; + goto cleanup; + } + if (super->s_feature_ro_compat & ~EXT2_LIB_FEATURE_RO_COMPAT_SUPP) { + retval = EXT2_ET_RO_UNSUPP_FEATURE; + goto cleanup; + } + + set_field(s_rev_level, EXT2_GOOD_OLD_REV); + if (super->s_rev_level >= EXT2_DYNAMIC_REV) { + set_field(s_first_ino, EXT2_GOOD_OLD_FIRST_INO); + set_field(s_inode_size, EXT2_GOOD_OLD_INODE_SIZE); + } + + set_field(s_checkinterval, EXT2_DFL_CHECKINTERVAL); + super->s_mkfs_time = super->s_lastcheck = time(NULL); + + super->s_creator_os = CREATOR_OS; + + fs->blocksize = EXT2_BLOCK_SIZE(super); + fs->fragsize = EXT2_FRAG_SIZE(super); + frags_per_block = fs->blocksize / fs->fragsize; + + /* default: (fs->blocksize*8) blocks/group, up to 2^16 (GDT limit) */ + set_field(s_blocks_per_group, fs->blocksize * 8); + if (super->s_blocks_per_group > EXT2_MAX_BLOCKS_PER_GROUP(super)) + super->s_blocks_per_group = EXT2_MAX_BLOCKS_PER_GROUP(super); + super->s_frags_per_group = super->s_blocks_per_group * frags_per_block; + + super->s_blocks_count = param->s_blocks_count; + super->s_r_blocks_count = param->s_r_blocks_count; + if (super->s_r_blocks_count >= param->s_blocks_count) { + retval = EXT2_ET_INVALID_ARGUMENT; + goto cleanup; + } + + /* + * If we're creating an external journal device, we don't need + * to bother with the rest. + */ + if (super->s_feature_incompat & EXT3_FEATURE_INCOMPAT_JOURNAL_DEV) { + fs->group_desc_count = 0; + ext2fs_mark_super_dirty(fs); + *ret_fs = fs; + return 0; + } + +retry: + fs->group_desc_count = (super->s_blocks_count - + super->s_first_data_block + + EXT2_BLOCKS_PER_GROUP(super) - 1) + / EXT2_BLOCKS_PER_GROUP(super); + if (fs->group_desc_count == 0) { + retval = EXT2_ET_TOOSMALL; + goto cleanup; + } + fs->desc_blocks = (fs->group_desc_count + + EXT2_DESC_PER_BLOCK(super) - 1) + / EXT2_DESC_PER_BLOCK(super); + + i = fs->blocksize >= 4096 ? 1 : 4096 / fs->blocksize; + set_field(s_inodes_count, super->s_blocks_count / i); + + /* + * Make sure we have at least EXT2_FIRST_INO + 1 inodes, so + * that we have enough inodes for the filesystem(!) + */ + if (super->s_inodes_count < EXT2_FIRST_INODE(super)+1) + super->s_inodes_count = EXT2_FIRST_INODE(super)+1; + + /* + * There should be at least as many inodes as the user + * requested. Figure out how many inodes per group that + * should be. But make sure that we don't allocate more than + * one bitmap's worth of inodes each group. + */ + ipg = (super->s_inodes_count + fs->group_desc_count - 1) / + fs->group_desc_count; + if (ipg > fs->blocksize * 8) { + if (super->s_blocks_per_group >= 256) { + /* Try again with slightly different parameters */ + super->s_blocks_per_group -= 8; + super->s_blocks_count = param->s_blocks_count; + super->s_frags_per_group = super->s_blocks_per_group * + frags_per_block; + goto retry; + } else + return EXT2_ET_TOO_MANY_INODES; + } + + if (ipg > (unsigned) EXT2_MAX_INODES_PER_GROUP(super)) + ipg = EXT2_MAX_INODES_PER_GROUP(super); + + super->s_inodes_per_group = ipg; + if (super->s_inodes_count > ipg * fs->group_desc_count) + super->s_inodes_count = ipg * fs->group_desc_count; + + /* + * Make sure the number of inodes per group completely fills + * the inode table blocks in the descriptor. If not, add some + * additional inodes/group. Waste not, want not... + */ + fs->inode_blocks_per_group = (((super->s_inodes_per_group * + EXT2_INODE_SIZE(super)) + + EXT2_BLOCK_SIZE(super) - 1) / + EXT2_BLOCK_SIZE(super)); + super->s_inodes_per_group = ((fs->inode_blocks_per_group * + EXT2_BLOCK_SIZE(super)) / + EXT2_INODE_SIZE(super)); + /* + * Finally, make sure the number of inodes per group is a + * multiple of 8. This is needed to simplify the bitmap + * splicing code. + */ + super->s_inodes_per_group &= ~7; + fs->inode_blocks_per_group = (((super->s_inodes_per_group * + EXT2_INODE_SIZE(super)) + + EXT2_BLOCK_SIZE(super) - 1) / + EXT2_BLOCK_SIZE(super)); + + /* + * adjust inode count to reflect the adjusted inodes_per_group + */ + super->s_inodes_count = super->s_inodes_per_group * + fs->group_desc_count; + super->s_free_inodes_count = super->s_inodes_count; + + /* + * check the number of reserved group descriptor table blocks + */ + if (super->s_feature_compat & EXT2_FEATURE_COMPAT_RESIZE_INODE) + rsv_gdt = calc_reserved_gdt_blocks(fs); + else + rsv_gdt = 0; + set_field(s_reserved_gdt_blocks, rsv_gdt); + if (super->s_reserved_gdt_blocks > EXT2_ADDR_PER_BLOCK(super)) { + retval = EXT2_ET_RES_GDT_BLOCKS; + goto cleanup; + } + + /* + * Overhead is the number of bookkeeping blocks per group. It + * includes the superblock backup, the group descriptor + * backups, the inode bitmap, the block bitmap, and the inode + * table. + */ + + overhead = (int) (2 + fs->inode_blocks_per_group); + + if (ext2fs_bg_has_super(fs, fs->group_desc_count - 1)) + overhead += 1 + fs->desc_blocks + super->s_reserved_gdt_blocks; + + /* This can only happen if the user requested too many inodes */ + if (overhead > super->s_blocks_per_group) + return EXT2_ET_TOO_MANY_INODES; + + /* + * See if the last group is big enough to support the + * necessary data structures. If not, we need to get rid of + * it. + */ + rem = ((super->s_blocks_count - super->s_first_data_block) % + super->s_blocks_per_group); + if ((fs->group_desc_count == 1) && rem && (rem < overhead)) + return EXT2_ET_TOOSMALL; + if (rem && (rem < overhead+50)) { + super->s_blocks_count -= rem; + goto retry; + } + + /* + * At this point we know how big the filesystem will be. So + * we can do any and all allocations that depend on the block + * count. + */ + + retval = ext2fs_get_mem(strlen(fs->device_name) + 80, &buf); + if (retval) + goto cleanup; + + sprintf(buf, "block bitmap for %s", fs->device_name); + retval = ext2fs_allocate_block_bitmap(fs, buf, &fs->block_map); + if (retval) + goto cleanup; + + sprintf(buf, "inode bitmap for %s", fs->device_name); + retval = ext2fs_allocate_inode_bitmap(fs, buf, &fs->inode_map); + if (retval) + goto cleanup; + + ext2fs_free_mem(&buf); + + retval = ext2fs_get_mem((size_t) fs->desc_blocks * fs->blocksize, + &fs->group_desc); + if (retval) + goto cleanup; + + memset(fs->group_desc, 0, (size_t) fs->desc_blocks * fs->blocksize); + + /* + * Reserve the superblock and group descriptors for each + * group, and fill in the correct group statistics for group. + * Note that although the block bitmap, inode bitmap, and + * inode table have not been allocated (and in fact won't be + * by this routine), they are accounted for nevertheless. + */ + group_block = super->s_first_data_block; + super->s_free_blocks_count = 0; + for (i = 0; i < fs->group_desc_count; i++) { + numblocks = ext2fs_reserve_super_and_bgd(fs, i, fs->block_map); + + super->s_free_blocks_count += numblocks; + fs->group_desc[i].bg_free_blocks_count = numblocks; + fs->group_desc[i].bg_free_inodes_count = + fs->super->s_inodes_per_group; + fs->group_desc[i].bg_used_dirs_count = 0; + + group_block += super->s_blocks_per_group; + } + + ext2fs_mark_super_dirty(fs); + ext2fs_mark_bb_dirty(fs); + ext2fs_mark_ib_dirty(fs); + + io_channel_set_blksize(fs->io, fs->blocksize); + + *ret_fs = fs; + return 0; +cleanup: + ext2fs_free(fs); + return retval; +} diff --git a/e2fsprogs/ext2fs/inline.c b/e2fsprogs/ext2fs/inline.c new file mode 100644 index 000000000..9b620a772 --- /dev/null +++ b/e2fsprogs/ext2fs/inline.c @@ -0,0 +1,33 @@ +/* vi: set sw=4 ts=4: */ +/* + * inline.c --- Includes the inlined functions defined in the header + * files as standalone functions, in case the application program + * is compiled with inlining turned off. + * + * Copyright (C) 1993, 1994 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#define INCLUDE_INLINE_FUNCS +#include "ext2fs.h" + diff --git a/e2fsprogs/ext2fs/inode.c b/e2fsprogs/ext2fs/inode.c new file mode 100644 index 000000000..2ff9fe6ca --- /dev/null +++ b/e2fsprogs/ext2fs/inode.c @@ -0,0 +1,768 @@ +/* vi: set sw=4 ts=4: */ +/* + * inode.c --- utility routines to read and write inodes + * + * Copyright (C) 1993, 1994, 1995, 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fsP.h" +#include "e2image.h" + +struct ext2_struct_inode_scan { + errcode_t magic; + ext2_filsys fs; + ext2_ino_t current_inode; + blk_t current_block; + dgrp_t current_group; + ext2_ino_t inodes_left; + blk_t blocks_left; + dgrp_t groups_left; + blk_t inode_buffer_blocks; + char * inode_buffer; + int inode_size; + char * ptr; + int bytes_left; + char *temp_buffer; + errcode_t (*done_group)(ext2_filsys fs, + dgrp_t group, + void * priv_data); + void * done_group_data; + int bad_block_ptr; + int scan_flags; + int reserved[6]; +}; + +/* + * This routine flushes the icache, if it exists. + */ +errcode_t ext2fs_flush_icache(ext2_filsys fs) +{ + int i; + + if (!fs->icache) + return 0; + + for (i=0; i < fs->icache->cache_size; i++) + fs->icache->cache[i].ino = 0; + + fs->icache->buffer_blk = 0; + return 0; +} + +static errcode_t create_icache(ext2_filsys fs) +{ + errcode_t retval; + + if (fs->icache) + return 0; + retval = ext2fs_get_mem(sizeof(struct ext2_inode_cache), &fs->icache); + if (retval) + return retval; + + memset(fs->icache, 0, sizeof(struct ext2_inode_cache)); + retval = ext2fs_get_mem(fs->blocksize, &fs->icache->buffer); + if (retval) { + ext2fs_free_mem(&fs->icache); + return retval; + } + fs->icache->buffer_blk = 0; + fs->icache->cache_last = -1; + fs->icache->cache_size = 4; + fs->icache->refcount = 1; + retval = ext2fs_get_mem(sizeof(struct ext2_inode_cache_ent) + * fs->icache->cache_size, + &fs->icache->cache); + if (retval) { + ext2fs_free_mem(&fs->icache->buffer); + ext2fs_free_mem(&fs->icache); + return retval; + } + ext2fs_flush_icache(fs); + return 0; +} + +errcode_t ext2fs_open_inode_scan(ext2_filsys fs, int buffer_blocks, + ext2_inode_scan *ret_scan) +{ + ext2_inode_scan scan; + errcode_t retval; + errcode_t (*save_get_blocks)(ext2_filsys f, ext2_ino_t ino, blk_t *blocks); + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + /* + * If fs->badblocks isn't set, then set it --- since the inode + * scanning functions require it. + */ + if (fs->badblocks == 0) { + /* + * Temporarly save fs->get_blocks and set it to zero, + * for compatibility with old e2fsck's. + */ + save_get_blocks = fs->get_blocks; + fs->get_blocks = 0; + retval = ext2fs_read_bb_inode(fs, &fs->badblocks); + if (retval) { + ext2fs_badblocks_list_free(fs->badblocks); + fs->badblocks = 0; + } + fs->get_blocks = save_get_blocks; + } + + retval = ext2fs_get_mem(sizeof(struct ext2_struct_inode_scan), &scan); + if (retval) + return retval; + memset(scan, 0, sizeof(struct ext2_struct_inode_scan)); + + scan->magic = EXT2_ET_MAGIC_INODE_SCAN; + scan->fs = fs; + scan->inode_size = EXT2_INODE_SIZE(fs->super); + scan->bytes_left = 0; + scan->current_group = 0; + scan->groups_left = fs->group_desc_count - 1; + scan->inode_buffer_blocks = buffer_blocks ? buffer_blocks : 8; + scan->current_block = scan->fs-> + group_desc[scan->current_group].bg_inode_table; + scan->inodes_left = EXT2_INODES_PER_GROUP(scan->fs->super); + scan->blocks_left = scan->fs->inode_blocks_per_group; + retval = ext2fs_get_mem((size_t) (scan->inode_buffer_blocks * + fs->blocksize), + &scan->inode_buffer); + scan->done_group = 0; + scan->done_group_data = 0; + scan->bad_block_ptr = 0; + if (retval) { + ext2fs_free_mem(&scan); + return retval; + } + retval = ext2fs_get_mem(scan->inode_size, &scan->temp_buffer); + if (retval) { + ext2fs_free_mem(&scan->inode_buffer); + ext2fs_free_mem(&scan); + return retval; + } + if (scan->fs->badblocks && scan->fs->badblocks->num) + scan->scan_flags |= EXT2_SF_CHK_BADBLOCKS; + *ret_scan = scan; + return 0; +} + +void ext2fs_close_inode_scan(ext2_inode_scan scan) +{ + if (!scan || (scan->magic != EXT2_ET_MAGIC_INODE_SCAN)) + return; + + ext2fs_free_mem(&scan->inode_buffer); + scan->inode_buffer = NULL; + ext2fs_free_mem(&scan->temp_buffer); + scan->temp_buffer = NULL; + ext2fs_free_mem(&scan); + return; +} + +void ext2fs_set_inode_callback(ext2_inode_scan scan, + errcode_t (*done_group)(ext2_filsys fs, + dgrp_t group, + void * priv_data), + void *done_group_data) +{ + if (!scan || (scan->magic != EXT2_ET_MAGIC_INODE_SCAN)) + return; + + scan->done_group = done_group; + scan->done_group_data = done_group_data; +} + +int ext2fs_inode_scan_flags(ext2_inode_scan scan, int set_flags, + int clear_flags) +{ + int old_flags; + + if (!scan || (scan->magic != EXT2_ET_MAGIC_INODE_SCAN)) + return 0; + + old_flags = scan->scan_flags; + scan->scan_flags &= ~clear_flags; + scan->scan_flags |= set_flags; + return old_flags; +} + +/* + * This function is called by ext2fs_get_next_inode when it needs to + * get ready to read in a new blockgroup. + */ +static errcode_t get_next_blockgroup(ext2_inode_scan scan) +{ + scan->current_group++; + scan->groups_left--; + + scan->current_block = scan->fs-> + group_desc[scan->current_group].bg_inode_table; + + scan->current_inode = scan->current_group * + EXT2_INODES_PER_GROUP(scan->fs->super); + + scan->bytes_left = 0; + scan->inodes_left = EXT2_INODES_PER_GROUP(scan->fs->super); + scan->blocks_left = scan->fs->inode_blocks_per_group; + return 0; +} + +errcode_t ext2fs_inode_scan_goto_blockgroup(ext2_inode_scan scan, + int group) +{ + scan->current_group = group - 1; + scan->groups_left = scan->fs->group_desc_count - group; + return get_next_blockgroup(scan); +} + +/* + * This function is called by get_next_blocks() to check for bad + * blocks in the inode table. + * + * This function assumes that badblocks_list->list is sorted in + * increasing order. + */ +static errcode_t check_for_inode_bad_blocks(ext2_inode_scan scan, + blk_t *num_blocks) +{ + blk_t blk = scan->current_block; + badblocks_list bb = scan->fs->badblocks; + + /* + * If the inode table is missing, then obviously there are no + * bad blocks. :-) + */ + if (blk == 0) + return 0; + + /* + * If the current block is greater than the bad block listed + * in the bad block list, then advance the pointer until this + * is no longer the case. If we run out of bad blocks, then + * we don't need to do any more checking! + */ + while (blk > bb->list[scan->bad_block_ptr]) { + if (++scan->bad_block_ptr >= bb->num) { + scan->scan_flags &= ~EXT2_SF_CHK_BADBLOCKS; + return 0; + } + } + + /* + * If the current block is equal to the bad block listed in + * the bad block list, then handle that one block specially. + * (We could try to handle runs of bad blocks, but that + * only increases CPU efficiency by a small amount, at the + * expense of a huge expense of code complexity, and for an + * uncommon case at that.) + */ + if (blk == bb->list[scan->bad_block_ptr]) { + scan->scan_flags |= EXT2_SF_BAD_INODE_BLK; + *num_blocks = 1; + if (++scan->bad_block_ptr >= bb->num) + scan->scan_flags &= ~EXT2_SF_CHK_BADBLOCKS; + return 0; + } + + /* + * If there is a bad block in the range that we're about to + * read in, adjust the number of blocks to read so that we we + * don't read in the bad block. (Then the next block to read + * will be the bad block, which is handled in the above case.) + */ + if ((blk + *num_blocks) > bb->list[scan->bad_block_ptr]) + *num_blocks = (int) (bb->list[scan->bad_block_ptr] - blk); + + return 0; +} + +/* + * This function is called by ext2fs_get_next_inode when it needs to + * read in more blocks from the current blockgroup's inode table. + */ +static errcode_t get_next_blocks(ext2_inode_scan scan) +{ + blk_t num_blocks; + errcode_t retval; + + /* + * Figure out how many blocks to read; we read at most + * inode_buffer_blocks, and perhaps less if there aren't that + * many blocks left to read. + */ + num_blocks = scan->inode_buffer_blocks; + if (num_blocks > scan->blocks_left) + num_blocks = scan->blocks_left; + + /* + * If the past block "read" was a bad block, then mark the + * left-over extra bytes as also being bad. + */ + if (scan->scan_flags & EXT2_SF_BAD_INODE_BLK) { + if (scan->bytes_left) + scan->scan_flags |= EXT2_SF_BAD_EXTRA_BYTES; + scan->scan_flags &= ~EXT2_SF_BAD_INODE_BLK; + } + + /* + * Do inode bad block processing, if necessary. + */ + if (scan->scan_flags & EXT2_SF_CHK_BADBLOCKS) { + retval = check_for_inode_bad_blocks(scan, &num_blocks); + if (retval) + return retval; + } + + if ((scan->scan_flags & EXT2_SF_BAD_INODE_BLK) || + (scan->current_block == 0)) { + memset(scan->inode_buffer, 0, + (size_t) num_blocks * scan->fs->blocksize); + } else { + retval = io_channel_read_blk(scan->fs->io, + scan->current_block, + (int) num_blocks, + scan->inode_buffer); + if (retval) + return EXT2_ET_NEXT_INODE_READ; + } + scan->ptr = scan->inode_buffer; + scan->bytes_left = num_blocks * scan->fs->blocksize; + + scan->blocks_left -= num_blocks; + if (scan->current_block) + scan->current_block += num_blocks; + return 0; +} + +errcode_t ext2fs_get_next_inode_full(ext2_inode_scan scan, ext2_ino_t *ino, + struct ext2_inode *inode, int bufsize) +{ + errcode_t retval; + int extra_bytes = 0; + + EXT2_CHECK_MAGIC(scan, EXT2_ET_MAGIC_INODE_SCAN); + + /* + * Do we need to start reading a new block group? + */ + if (scan->inodes_left <= 0) { + force_new_group: + if (scan->done_group) { + retval = (scan->done_group) + (scan->fs, scan->current_group, + scan->done_group_data); + if (retval) + return retval; + } + if (scan->groups_left <= 0) { + *ino = 0; + return 0; + } + retval = get_next_blockgroup(scan); + if (retval) + return retval; + } + /* + * This is done outside the above if statement so that the + * check can be done for block group #0. + */ + if (scan->current_block == 0) { + if (scan->scan_flags & EXT2_SF_SKIP_MISSING_ITABLE) { + goto force_new_group; + } else + return EXT2_ET_MISSING_INODE_TABLE; + } + + + /* + * Have we run out of space in the inode buffer? If so, we + * need to read in more blocks. + */ + if (scan->bytes_left < scan->inode_size) { + memcpy(scan->temp_buffer, scan->ptr, scan->bytes_left); + extra_bytes = scan->bytes_left; + + retval = get_next_blocks(scan); + if (retval) + return retval; +#if 0 + /* + * XXX test Need check for used inode somehow. + * (Note: this is hard.) + */ + if (is_empty_scan(scan)) + goto force_new_group; +#endif + } + + retval = 0; + if (extra_bytes) { + memcpy(scan->temp_buffer+extra_bytes, scan->ptr, + scan->inode_size - extra_bytes); + scan->ptr += scan->inode_size - extra_bytes; + scan->bytes_left -= scan->inode_size - extra_bytes; + +#if BB_BIG_ENDIAN + if ((scan->fs->flags & EXT2_FLAG_SWAP_BYTES) || + (scan->fs->flags & EXT2_FLAG_SWAP_BYTES_READ)) + ext2fs_swap_inode_full(scan->fs, + (struct ext2_inode_large *) inode, + (struct ext2_inode_large *) scan->temp_buffer, + 0, bufsize); + else +#endif + *inode = *((struct ext2_inode *) scan->temp_buffer); + if (scan->scan_flags & EXT2_SF_BAD_EXTRA_BYTES) + retval = EXT2_ET_BAD_BLOCK_IN_INODE_TABLE; + scan->scan_flags &= ~EXT2_SF_BAD_EXTRA_BYTES; + } else { +#if BB_BIG_ENDIAN + if ((scan->fs->flags & EXT2_FLAG_SWAP_BYTES) || + (scan->fs->flags & EXT2_FLAG_SWAP_BYTES_READ)) + ext2fs_swap_inode_full(scan->fs, + (struct ext2_inode_large *) inode, + (struct ext2_inode_large *) scan->ptr, + 0, bufsize); + else +#endif + memcpy(inode, scan->ptr, bufsize); + scan->ptr += scan->inode_size; + scan->bytes_left -= scan->inode_size; + if (scan->scan_flags & EXT2_SF_BAD_INODE_BLK) + retval = EXT2_ET_BAD_BLOCK_IN_INODE_TABLE; + } + + scan->inodes_left--; + scan->current_inode++; + *ino = scan->current_inode; + return retval; +} + +errcode_t ext2fs_get_next_inode(ext2_inode_scan scan, ext2_ino_t *ino, + struct ext2_inode *inode) +{ + return ext2fs_get_next_inode_full(scan, ino, inode, + sizeof(struct ext2_inode)); +} + +/* + * Functions to read and write a single inode. + */ +errcode_t ext2fs_read_inode_full(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode * inode, int bufsize) +{ + unsigned long group, block, block_nr, offset; + char *ptr; + errcode_t retval; + int clen, i, inodes_per_block, length; + io_channel io; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + /* Check to see if user has an override function */ + if (fs->read_inode) { + retval = (fs->read_inode)(fs, ino, inode); + if (retval != EXT2_ET_CALLBACK_NOTHANDLED) + return retval; + } + /* Create inode cache if not present */ + if (!fs->icache) { + retval = create_icache(fs); + if (retval) + return retval; + } + /* Check to see if it's in the inode cache */ + if (bufsize == sizeof(struct ext2_inode)) { + /* only old good inode can be retrieve from the cache */ + for (i=0; i < fs->icache->cache_size; i++) { + if (fs->icache->cache[i].ino == ino) { + *inode = fs->icache->cache[i].inode; + return 0; + } + } + } + if ((ino == 0) || (ino > fs->super->s_inodes_count)) + return EXT2_ET_BAD_INODE_NUM; + if (fs->flags & EXT2_FLAG_IMAGE_FILE) { + inodes_per_block = fs->blocksize / EXT2_INODE_SIZE(fs->super); + block_nr = fs->image_header->offset_inode / fs->blocksize; + block_nr += (ino - 1) / inodes_per_block; + offset = ((ino - 1) % inodes_per_block) * + EXT2_INODE_SIZE(fs->super); + io = fs->image_io; + } else { + group = (ino - 1) / EXT2_INODES_PER_GROUP(fs->super); + offset = ((ino - 1) % EXT2_INODES_PER_GROUP(fs->super)) * + EXT2_INODE_SIZE(fs->super); + block = offset >> EXT2_BLOCK_SIZE_BITS(fs->super); + if (!fs->group_desc[(unsigned)group].bg_inode_table) + return EXT2_ET_MISSING_INODE_TABLE; + block_nr = fs->group_desc[(unsigned)group].bg_inode_table + + block; + io = fs->io; + } + offset &= (EXT2_BLOCK_SIZE(fs->super) - 1); + + length = EXT2_INODE_SIZE(fs->super); + if (bufsize < length) + length = bufsize; + + ptr = (char *) inode; + while (length) { + clen = length; + if ((offset + length) > fs->blocksize) + clen = fs->blocksize - offset; + + if (block_nr != fs->icache->buffer_blk) { + retval = io_channel_read_blk(io, block_nr, 1, + fs->icache->buffer); + if (retval) + return retval; + fs->icache->buffer_blk = block_nr; + } + + memcpy(ptr, ((char *) fs->icache->buffer) + (unsigned) offset, + clen); + + offset = 0; + length -= clen; + ptr += clen; + block_nr++; + } + +#if BB_BIG_ENDIAN + if ((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_READ)) + ext2fs_swap_inode_full(fs, (struct ext2_inode_large *) inode, + (struct ext2_inode_large *) inode, + 0, length); +#endif + + /* Update the inode cache */ + fs->icache->cache_last = (fs->icache->cache_last + 1) % + fs->icache->cache_size; + fs->icache->cache[fs->icache->cache_last].ino = ino; + fs->icache->cache[fs->icache->cache_last].inode = *inode; + + return 0; +} + +errcode_t ext2fs_read_inode(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode * inode) +{ + return ext2fs_read_inode_full(fs, ino, inode, + sizeof(struct ext2_inode)); +} + +errcode_t ext2fs_write_inode_full(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode * inode, int bufsize) +{ + unsigned long group, block, block_nr, offset; + errcode_t retval = 0; + struct ext2_inode_large temp_inode, *w_inode; + char *ptr; + int clen, i, length; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + /* Check to see if user provided an override function */ + if (fs->write_inode) { + retval = (fs->write_inode)(fs, ino, inode); + if (retval != EXT2_ET_CALLBACK_NOTHANDLED) + return retval; + } + + /* Check to see if the inode cache needs to be updated */ + if (fs->icache) { + for (i=0; i < fs->icache->cache_size; i++) { + if (fs->icache->cache[i].ino == ino) { + fs->icache->cache[i].inode = *inode; + break; + } + } + } else { + retval = create_icache(fs); + if (retval) + return retval; + } + + if (!(fs->flags & EXT2_FLAG_RW)) + return EXT2_ET_RO_FILSYS; + + if ((ino == 0) || (ino > fs->super->s_inodes_count)) + return EXT2_ET_BAD_INODE_NUM; + + length = bufsize; + if (length < EXT2_INODE_SIZE(fs->super)) + length = EXT2_INODE_SIZE(fs->super); + + if (length > (int) sizeof(struct ext2_inode_large)) { + w_inode = xmalloc(length); + } else + w_inode = &temp_inode; + memset(w_inode, 0, length); + +#if BB_BIG_ENDIAN + if ((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_WRITE)) + ext2fs_swap_inode_full(fs, w_inode, + (struct ext2_inode_large *) inode, + 1, bufsize); + else +#endif + memcpy(w_inode, inode, bufsize); + + group = (ino - 1) / EXT2_INODES_PER_GROUP(fs->super); + offset = ((ino - 1) % EXT2_INODES_PER_GROUP(fs->super)) * + EXT2_INODE_SIZE(fs->super); + block = offset >> EXT2_BLOCK_SIZE_BITS(fs->super); + if (!fs->group_desc[(unsigned) group].bg_inode_table) + return EXT2_ET_MISSING_INODE_TABLE; + block_nr = fs->group_desc[(unsigned) group].bg_inode_table + block; + + offset &= (EXT2_BLOCK_SIZE(fs->super) - 1); + + length = EXT2_INODE_SIZE(fs->super); + if (length > bufsize) + length = bufsize; + + ptr = (char *) w_inode; + + while (length) { + clen = length; + if ((offset + length) > fs->blocksize) + clen = fs->blocksize - offset; + + if (fs->icache->buffer_blk != block_nr) { + retval = io_channel_read_blk(fs->io, block_nr, 1, + fs->icache->buffer); + if (retval) + goto errout; + fs->icache->buffer_blk = block_nr; + } + + + memcpy((char *) fs->icache->buffer + (unsigned) offset, + ptr, clen); + + retval = io_channel_write_blk(fs->io, block_nr, 1, + fs->icache->buffer); + if (retval) + goto errout; + + offset = 0; + ptr += clen; + length -= clen; + block_nr++; + } + + fs->flags |= EXT2_FLAG_CHANGED; +errout: + if (w_inode && w_inode != &temp_inode) + free(w_inode); + return retval; +} + +errcode_t ext2fs_write_inode(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode) +{ + return ext2fs_write_inode_full(fs, ino, inode, + sizeof(struct ext2_inode)); +} + +/* + * This function should be called when writing a new inode. It makes + * sure that extra part of large inodes is initialized properly. + */ +errcode_t ext2fs_write_new_inode(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode) +{ + struct ext2_inode *buf; + int size = EXT2_INODE_SIZE(fs->super); + struct ext2_inode_large *large_inode; + + if (size == sizeof(struct ext2_inode)) + return ext2fs_write_inode_full(fs, ino, inode, + sizeof(struct ext2_inode)); + + buf = xmalloc(size); + + memset(buf, 0, size); + *buf = *inode; + + large_inode = (struct ext2_inode_large *) buf; + large_inode->i_extra_isize = sizeof(struct ext2_inode_large) - + EXT2_GOOD_OLD_INODE_SIZE; + + return ext2fs_write_inode_full(fs, ino, buf, size); +} + + +errcode_t ext2fs_get_blocks(ext2_filsys fs, ext2_ino_t ino, blk_t *blocks) +{ + struct ext2_inode inode; + int i; + errcode_t retval; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (ino > fs->super->s_inodes_count) + return EXT2_ET_BAD_INODE_NUM; + + if (fs->get_blocks) { + if (!(*fs->get_blocks)(fs, ino, blocks)) + return 0; + } + retval = ext2fs_read_inode(fs, ino, &inode); + if (retval) + return retval; + for (i=0; i < EXT2_N_BLOCKS; i++) + blocks[i] = inode.i_block[i]; + return 0; +} + +errcode_t ext2fs_check_directory(ext2_filsys fs, ext2_ino_t ino) +{ + struct ext2_inode inode; + errcode_t retval; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (ino > fs->super->s_inodes_count) + return EXT2_ET_BAD_INODE_NUM; + + if (fs->check_directory) { + retval = (fs->check_directory)(fs, ino); + if (retval != EXT2_ET_CALLBACK_NOTHANDLED) + return retval; + } + retval = ext2fs_read_inode(fs, ino, &inode); + if (retval) + return retval; + if (!LINUX_S_ISDIR(inode.i_mode)) + return EXT2_ET_NO_DIRECTORY; + return 0; +} + diff --git a/e2fsprogs/ext2fs/inode_io.c b/e2fsprogs/ext2fs/inode_io.c new file mode 100644 index 000000000..4bfa93aef --- /dev/null +++ b/e2fsprogs/ext2fs/inode_io.c @@ -0,0 +1,271 @@ +/* vi: set sw=4 ts=4: */ +/* + * inode_io.c --- This is allows an inode in an ext2 filesystem image + * to be accessed via the I/O manager interface. + * + * Copyright (C) 2002 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif +#include + +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * For checking structure magic numbers... + */ + +#define EXT2_CHECK_MAGIC(struct, code) \ + if ((struct)->magic != (code)) return (code) + +struct inode_private_data { + int magic; + char name[32]; + ext2_file_t file; + ext2_filsys fs; + ext2_ino_t ino; + struct ext2_inode inode; + int flags; + struct inode_private_data *next; +}; + +#define CHANNEL_HAS_INODE 0x8000 + +static struct inode_private_data *top_intern; +static int ino_unique = 0; + +static errcode_t inode_open(const char *name, int flags, io_channel *channel); +static errcode_t inode_close(io_channel channel); +static errcode_t inode_set_blksize(io_channel channel, int blksize); +static errcode_t inode_read_blk(io_channel channel, unsigned long block, + int count, void *data); +static errcode_t inode_write_blk(io_channel channel, unsigned long block, + int count, const void *data); +static errcode_t inode_flush(io_channel channel); +static errcode_t inode_write_byte(io_channel channel, unsigned long offset, + int size, const void *data); + +static struct struct_io_manager struct_inode_manager = { + EXT2_ET_MAGIC_IO_MANAGER, + "Inode I/O Manager", + inode_open, + inode_close, + inode_set_blksize, + inode_read_blk, + inode_write_blk, + inode_flush, + inode_write_byte +}; + +io_manager inode_io_manager = &struct_inode_manager; + +errcode_t ext2fs_inode_io_intern2(ext2_filsys fs, ext2_ino_t ino, + struct ext2_inode *inode, + char **name) +{ + struct inode_private_data *data; + errcode_t retval; + + if ((retval = ext2fs_get_mem(sizeof(struct inode_private_data), + &data))) + return retval; + data->magic = EXT2_ET_MAGIC_INODE_IO_CHANNEL; + sprintf(data->name, "%u:%d", ino, ino_unique++); + data->file = 0; + data->fs = fs; + data->ino = ino; + data->flags = 0; + if (inode) { + memcpy(&data->inode, inode, sizeof(struct ext2_inode)); + data->flags |= CHANNEL_HAS_INODE; + } + data->next = top_intern; + top_intern = data; + *name = data->name; + return 0; +} + +errcode_t ext2fs_inode_io_intern(ext2_filsys fs, ext2_ino_t ino, + char **name) +{ + return ext2fs_inode_io_intern2(fs, ino, NULL, name); +} + + +static errcode_t inode_open(const char *name, int flags, io_channel *channel) +{ + io_channel io = NULL; + struct inode_private_data *prev, *data = NULL; + errcode_t retval; + int open_flags; + + if (name == 0) + return EXT2_ET_BAD_DEVICE_NAME; + + for (data = top_intern, prev = NULL; data; + prev = data, data = data->next) + if (strcmp(name, data->name) == 0) + break; + if (!data) + return ENOENT; + if (prev) + prev->next = data->next; + else + top_intern = data->next; + + retval = ext2fs_get_mem(sizeof(struct struct_io_channel), &io); + if (retval) + goto cleanup; + memset(io, 0, sizeof(struct struct_io_channel)); + + io->magic = EXT2_ET_MAGIC_IO_CHANNEL; + io->manager = inode_io_manager; + retval = ext2fs_get_mem(strlen(name)+1, &io->name); + if (retval) + goto cleanup; + + strcpy(io->name, name); + io->private_data = data; + io->block_size = 1024; + io->read_error = 0; + io->write_error = 0; + io->refcount = 1; + + open_flags = (flags & IO_FLAG_RW) ? EXT2_FILE_WRITE : 0; + retval = ext2fs_file_open2(data->fs, data->ino, + (data->flags & CHANNEL_HAS_INODE) ? + &data->inode : 0, open_flags, + &data->file); + if (retval) + goto cleanup; + + *channel = io; + return 0; + +cleanup: + if (data) { + ext2fs_free_mem(&data); + } + if (io) + ext2fs_free_mem(&io); + return retval; +} + +static errcode_t inode_close(io_channel channel) +{ + struct inode_private_data *data; + errcode_t retval = 0; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct inode_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_INODE_IO_CHANNEL); + + if (--channel->refcount > 0) + return 0; + + retval = ext2fs_file_close(data->file); + + ext2fs_free_mem(&channel->private_data); + if (channel->name) + ext2fs_free_mem(&channel->name); + ext2fs_free_mem(&channel); + return retval; +} + +static errcode_t inode_set_blksize(io_channel channel, int blksize) +{ + struct inode_private_data *data; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct inode_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_INODE_IO_CHANNEL); + + channel->block_size = blksize; + return 0; +} + + +static errcode_t inode_read_blk(io_channel channel, unsigned long block, + int count, void *buf) +{ + struct inode_private_data *data; + errcode_t retval; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct inode_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_INODE_IO_CHANNEL); + + if ((retval = ext2fs_file_lseek(data->file, + block * channel->block_size, + EXT2_SEEK_SET, 0))) + return retval; + + count = (count < 0) ? -count : (count * channel->block_size); + + return ext2fs_file_read(data->file, buf, count, 0); +} + +static errcode_t inode_write_blk(io_channel channel, unsigned long block, + int count, const void *buf) +{ + struct inode_private_data *data; + errcode_t retval; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct inode_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_INODE_IO_CHANNEL); + + if ((retval = ext2fs_file_lseek(data->file, + block * channel->block_size, + EXT2_SEEK_SET, 0))) + return retval; + + count = (count < 0) ? -count : (count * channel->block_size); + + return ext2fs_file_write(data->file, buf, count, 0); +} + +static errcode_t inode_write_byte(io_channel channel, unsigned long offset, + int size, const void *buf) +{ + struct inode_private_data *data; + errcode_t retval = 0; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct inode_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_INODE_IO_CHANNEL); + + if ((retval = ext2fs_file_lseek(data->file, offset, + EXT2_SEEK_SET, 0))) + return retval; + + return ext2fs_file_write(data->file, buf, size, 0); +} + +/* + * Flush data buffers to disk. + */ +static errcode_t inode_flush(io_channel channel) +{ + struct inode_private_data *data; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct inode_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_INODE_IO_CHANNEL); + + return ext2fs_file_flush(data->file); +} + diff --git a/e2fsprogs/ext2fs/io_manager.c b/e2fsprogs/ext2fs/io_manager.c new file mode 100644 index 000000000..b47038602 --- /dev/null +++ b/e2fsprogs/ext2fs/io_manager.c @@ -0,0 +1,70 @@ +/* vi: set sw=4 ts=4: */ +/* + * io_manager.c --- the I/O manager abstraction + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +errcode_t io_channel_set_options(io_channel channel, const char *opts) +{ + errcode_t retval = 0; + char *next, *ptr, *options, *arg; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + + if (!opts) + return 0; + + if (!channel->manager->set_option) + return EXT2_ET_INVALID_ARGUMENT; + + options = malloc(strlen(opts)+1); + if (!options) + return EXT2_ET_NO_MEMORY; + strcpy(options, opts); + ptr = options; + + while (ptr && *ptr) { + next = strchr(ptr, '&'); + if (next) + *next++ = 0; + + arg = strchr(ptr, '='); + if (arg) + *arg++ = 0; + + retval = (channel->manager->set_option)(channel, ptr, arg); + if (retval) + break; + ptr = next; + } + free(options); + return retval; +} + +errcode_t io_channel_write_byte(io_channel channel, unsigned long offset, + int count, const void *data) +{ + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + + if (channel->manager->write_byte) + return channel->manager->write_byte(channel, offset, + count, data); + + return EXT2_ET_UNIMPLEMENTED; +} diff --git a/e2fsprogs/ext2fs/irel.h b/e2fsprogs/ext2fs/irel.h new file mode 100644 index 000000000..91d1d89d5 --- /dev/null +++ b/e2fsprogs/ext2fs/irel.h @@ -0,0 +1,115 @@ +/* vi: set sw=4 ts=4: */ +/* + * irel.h + * + * Copyright (C) 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +struct ext2_inode_reference { + blk_t block; + __u16 offset; +}; + +struct ext2_inode_relocate_entry { + ext2_ino_t new; + ext2_ino_t orig; + __u16 flags; + __u16 max_refs; +}; + +typedef struct ext2_inode_relocation_table *ext2_irel; + +struct ext2_inode_relocation_table { + __u32 magic; + char *name; + ext2_ino_t current; + void *priv_data; + + /* + * Add an inode relocation entry. + */ + errcode_t (*put)(ext2_irel irel, ext2_ino_t old, + struct ext2_inode_relocate_entry *ent); + /* + * Get an inode relocation entry. + */ + errcode_t (*get)(ext2_irel irel, ext2_ino_t old, + struct ext2_inode_relocate_entry *ent); + + /* + * Get an inode relocation entry by its original inode number + */ + errcode_t (*get_by_orig)(ext2_irel irel, ext2_ino_t orig, ext2_ino_t *old, + struct ext2_inode_relocate_entry *ent); + + /* + * Initialize for iterating over the inode relocation entries. + */ + errcode_t (*start_iter)(ext2_irel irel); + + /* + * The iterator function for the inode relocation entries. + * Returns an inode number of 0 when out of entries. + */ + errcode_t (*next)(ext2_irel irel, ext2_ino_t *old, + struct ext2_inode_relocate_entry *ent); + + /* + * Add an inode reference (i.e., note the fact that a + * particular block/offset contains a reference to an inode) + */ + errcode_t (*add_ref)(ext2_irel irel, ext2_ino_t ino, + struct ext2_inode_reference *ref); + + /* + * Initialize for iterating over the inode references for a + * particular inode. + */ + errcode_t (*start_iter_ref)(ext2_irel irel, ext2_ino_t ino); + + /* + * The iterator function for the inode references for an + * inode. The references for only one inode can be interator + * over at a time, as the iterator state is stored in ext2_irel. + */ + errcode_t (*next_ref)(ext2_irel irel, + struct ext2_inode_reference *ref); + + /* + * Move the inode relocation table from one inode number to + * another. Note that the inode references also must move. + */ + errcode_t (*move)(ext2_irel irel, ext2_ino_t old, ext2_ino_t new); + + /* + * Remove an inode relocation entry, along with all of the + * inode references. + */ + errcode_t (*delete)(ext2_irel irel, ext2_ino_t old); + + /* + * Free the inode relocation table. + */ + errcode_t (*free)(ext2_irel irel); +}; + +errcode_t ext2fs_irel_memarray_create(char *name, ext2_ino_t max_inode, + ext2_irel *irel); + +#define ext2fs_irel_put(irel, old, ent) ((irel)->put((irel), old, ent)) +#define ext2fs_irel_get(irel, old, ent) ((irel)->get((irel), old, ent)) +#define ext2fs_irel_get_by_orig(irel, orig, old, ent) \ + ((irel)->get_by_orig((irel), orig, old, ent)) +#define ext2fs_irel_start_iter(irel) ((irel)->start_iter((irel))) +#define ext2fs_irel_next(irel, old, ent) ((irel)->next((irel), old, ent)) +#define ext2fs_irel_add_ref(irel, ino, ref) ((irel)->add_ref((irel), ino, ref)) +#define ext2fs_irel_start_iter_ref(irel, ino) ((irel)->start_iter_ref((irel), ino)) +#define ext2fs_irel_next_ref(irel, ref) ((irel)->next_ref((irel), ref)) +#define ext2fs_irel_move(irel, old, new) ((irel)->move((irel), old, new)) +#define ext2fs_irel_delete(irel, old) ((irel)->delete((irel), old)) +#define ext2fs_irel_free(irel) ((irel)->free((irel))) diff --git a/e2fsprogs/ext2fs/irel_ma.c b/e2fsprogs/ext2fs/irel_ma.c new file mode 100644 index 000000000..c871b1891 --- /dev/null +++ b/e2fsprogs/ext2fs/irel_ma.c @@ -0,0 +1,367 @@ +/* vi: set sw=4 ts=4: */ +/* + * irel_ma.c + * + * Copyright (C) 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" +#include "irel.h" + +static errcode_t ima_put(ext2_irel irel, ext2_ino_t old, + struct ext2_inode_relocate_entry *ent); +static errcode_t ima_get(ext2_irel irel, ext2_ino_t old, + struct ext2_inode_relocate_entry *ent); +static errcode_t ima_get_by_orig(ext2_irel irel, ext2_ino_t orig, ext2_ino_t *old, + struct ext2_inode_relocate_entry *ent); +static errcode_t ima_start_iter(ext2_irel irel); +static errcode_t ima_next(ext2_irel irel, ext2_ino_t *old, + struct ext2_inode_relocate_entry *ent); +static errcode_t ima_add_ref(ext2_irel irel, ext2_ino_t ino, + struct ext2_inode_reference *ref); +static errcode_t ima_start_iter_ref(ext2_irel irel, ext2_ino_t ino); +static errcode_t ima_next_ref(ext2_irel irel, struct ext2_inode_reference *ref); +static errcode_t ima_move(ext2_irel irel, ext2_ino_t old, ext2_ino_t new); +static errcode_t ima_delete(ext2_irel irel, ext2_ino_t old); +static errcode_t ima_free(ext2_irel irel); + +/* + * This data structure stores the array of inode references; there is + * a structure for each inode. + */ +struct inode_reference_entry { + __u16 num; + struct ext2_inode_reference *refs; +}; + +struct irel_ma { + __u32 magic; + ext2_ino_t max_inode; + ext2_ino_t ref_current; + int ref_iter; + ext2_ino_t *orig_map; + struct ext2_inode_relocate_entry *entries; + struct inode_reference_entry *ref_entries; +}; + +errcode_t ext2fs_irel_memarray_create(char *name, ext2_ino_t max_inode, + ext2_irel *new_irel) +{ + ext2_irel irel = 0; + errcode_t retval; + struct irel_ma *ma = 0; + size_t size; + + *new_irel = 0; + + /* + * Allocate memory structures + */ + retval = ext2fs_get_mem(sizeof(struct ext2_inode_relocation_table), + &irel); + if (retval) + goto errout; + memset(irel, 0, sizeof(struct ext2_inode_relocation_table)); + + retval = ext2fs_get_mem(strlen(name)+1, &irel->name); + if (retval) + goto errout; + strcpy(irel->name, name); + + retval = ext2fs_get_mem(sizeof(struct irel_ma), &ma); + if (retval) + goto errout; + memset(ma, 0, sizeof(struct irel_ma)); + irel->priv_data = ma; + + size = (size_t) (sizeof(ext2_ino_t) * (max_inode+1)); + retval = ext2fs_get_mem(size, &ma->orig_map); + if (retval) + goto errout; + memset(ma->orig_map, 0, size); + + size = (size_t) (sizeof(struct ext2_inode_relocate_entry) * + (max_inode+1)); + retval = ext2fs_get_mem(size, &ma->entries); + if (retval) + goto errout; + memset(ma->entries, 0, size); + + size = (size_t) (sizeof(struct inode_reference_entry) * + (max_inode+1)); + retval = ext2fs_get_mem(size, &ma->ref_entries); + if (retval) + goto errout; + memset(ma->ref_entries, 0, size); + ma->max_inode = max_inode; + + /* + * Fill in the irel data structure + */ + irel->put = ima_put; + irel->get = ima_get; + irel->get_by_orig = ima_get_by_orig; + irel->start_iter = ima_start_iter; + irel->next = ima_next; + irel->add_ref = ima_add_ref; + irel->start_iter_ref = ima_start_iter_ref; + irel->next_ref = ima_next_ref; + irel->move = ima_move; + irel->delete = ima_delete; + irel->free = ima_free; + + *new_irel = irel; + return 0; + +errout: + ima_free(irel); + return retval; +} + +static errcode_t ima_put(ext2_irel irel, ext2_ino_t old, + struct ext2_inode_relocate_entry *ent) +{ + struct inode_reference_entry *ref_ent; + struct irel_ma *ma; + errcode_t retval; + size_t size, old_size; + + ma = irel->priv_data; + if (old > ma->max_inode) + return EXT2_ET_INVALID_ARGUMENT; + + /* + * Force the orig field to the correct value; the application + * program shouldn't be messing with this field. + */ + if (ma->entries[(unsigned) old].new == 0) + ent->orig = old; + else + ent->orig = ma->entries[(unsigned) old].orig; + + /* + * If max_refs has changed, reallocate the refs array + */ + ref_ent = ma->ref_entries + (unsigned) old; + if (ref_ent->refs && ent->max_refs != + ma->entries[(unsigned) old].max_refs) { + size = (sizeof(struct ext2_inode_reference) * ent->max_refs); + old_size = (sizeof(struct ext2_inode_reference) * + ma->entries[(unsigned) old].max_refs); + retval = ext2fs_resize_mem(old_size, size, &ref_ent->refs); + if (retval) + return retval; + } + + ma->entries[(unsigned) old] = *ent; + ma->orig_map[(unsigned) ent->orig] = old; + return 0; +} + +static errcode_t ima_get(ext2_irel irel, ext2_ino_t old, + struct ext2_inode_relocate_entry *ent) +{ + struct irel_ma *ma; + + ma = irel->priv_data; + if (old > ma->max_inode) + return EXT2_ET_INVALID_ARGUMENT; + if (ma->entries[(unsigned) old].new == 0) + return ENOENT; + *ent = ma->entries[(unsigned) old]; + return 0; +} + +static errcode_t ima_get_by_orig(ext2_irel irel, ext2_ino_t orig, ext2_ino_t *old, + struct ext2_inode_relocate_entry *ent) +{ + struct irel_ma *ma; + ext2_ino_t ino; + + ma = irel->priv_data; + if (orig > ma->max_inode) + return EXT2_ET_INVALID_ARGUMENT; + ino = ma->orig_map[(unsigned) orig]; + if (ino == 0) + return ENOENT; + *old = ino; + *ent = ma->entries[(unsigned) ino]; + return 0; +} + +static errcode_t ima_start_iter(ext2_irel irel) +{ + irel->current = 0; + return 0; +} + +static errcode_t ima_next(ext2_irel irel, ext2_ino_t *old, + struct ext2_inode_relocate_entry *ent) +{ + struct irel_ma *ma; + + ma = irel->priv_data; + while (++irel->current < ma->max_inode) { + if (ma->entries[(unsigned) irel->current].new == 0) + continue; + *old = irel->current; + *ent = ma->entries[(unsigned) irel->current]; + return 0; + } + *old = 0; + return 0; +} + +static errcode_t ima_add_ref(ext2_irel irel, ext2_ino_t ino, + struct ext2_inode_reference *ref) +{ + struct irel_ma *ma; + size_t size; + struct inode_reference_entry *ref_ent; + struct ext2_inode_relocate_entry *ent; + errcode_t retval; + + ma = irel->priv_data; + if (ino > ma->max_inode) + return EXT2_ET_INVALID_ARGUMENT; + + ref_ent = ma->ref_entries + (unsigned) ino; + ent = ma->entries + (unsigned) ino; + + /* + * If the inode reference array doesn't exist, create it. + */ + if (ref_ent->refs == 0) { + size = (size_t) ((sizeof(struct ext2_inode_reference) * + ent->max_refs)); + retval = ext2fs_get_mem(size, &ref_ent->refs); + if (retval) + return retval; + memset(ref_ent->refs, 0, size); + ref_ent->num = 0; + } + + if (ref_ent->num >= ent->max_refs) + return EXT2_ET_TOO_MANY_REFS; + + ref_ent->refs[(unsigned) ref_ent->num++] = *ref; + return 0; +} + +static errcode_t ima_start_iter_ref(ext2_irel irel, ext2_ino_t ino) +{ + struct irel_ma *ma; + + ma = irel->priv_data; + if (ino > ma->max_inode) + return EXT2_ET_INVALID_ARGUMENT; + if (ma->entries[(unsigned) ino].new == 0) + return ENOENT; + ma->ref_current = ino; + ma->ref_iter = 0; + return 0; +} + +static errcode_t ima_next_ref(ext2_irel irel, + struct ext2_inode_reference *ref) +{ + struct irel_ma *ma; + struct inode_reference_entry *ref_ent; + + ma = irel->priv_data; + + ref_ent = ma->ref_entries + ma->ref_current; + + if ((ref_ent->refs == NULL) || + (ma->ref_iter >= ref_ent->num)) { + ref->block = 0; + ref->offset = 0; + return 0; + } + *ref = ref_ent->refs[ma->ref_iter++]; + return 0; +} + + +static errcode_t ima_move(ext2_irel irel, ext2_ino_t old, ext2_ino_t new) +{ + struct irel_ma *ma; + + ma = irel->priv_data; + if ((old > ma->max_inode) || (new > ma->max_inode)) + return EXT2_ET_INVALID_ARGUMENT; + if (ma->entries[(unsigned) old].new == 0) + return ENOENT; + + ma->entries[(unsigned) new] = ma->entries[(unsigned) old]; + ext2fs_free_mem(&ma->ref_entries[(unsigned) new].refs); + ma->ref_entries[(unsigned) new] = ma->ref_entries[(unsigned) old]; + + ma->entries[(unsigned) old].new = 0; + ma->ref_entries[(unsigned) old].num = 0; + ma->ref_entries[(unsigned) old].refs = 0; + + ma->orig_map[ma->entries[new].orig] = new; + return 0; +} + +static errcode_t ima_delete(ext2_irel irel, ext2_ino_t old) +{ + struct irel_ma *ma; + + ma = irel->priv_data; + if (old > ma->max_inode) + return EXT2_ET_INVALID_ARGUMENT; + if (ma->entries[(unsigned) old].new == 0) + return ENOENT; + + ma->entries[old].new = 0; + ext2fs_free_mem(&ma->ref_entries[(unsigned) old].refs); + ma->orig_map[ma->entries[(unsigned) old].orig] = 0; + + ma->ref_entries[(unsigned) old].num = 0; + ma->ref_entries[(unsigned) old].refs = 0; + return 0; +} + +static errcode_t ima_free(ext2_irel irel) +{ + struct irel_ma *ma; + ext2_ino_t ino; + + if (!irel) + return 0; + + ma = irel->priv_data; + + if (ma) { + ext2fs_free_mem(&ma->orig_map); + ext2fs_free_mem(&ma->entries); + if (ma->ref_entries) { + for (ino = 0; ino <= ma->max_inode; ino++) { + ext2fs_free_mem(&ma->ref_entries[(unsigned) ino].refs); + } + ext2fs_free_mem(&ma->ref_entries); + } + ext2fs_free_mem(&ma); + } + ext2fs_free_mem(&irel->name); + ext2fs_free_mem(&irel); + return 0; +} diff --git a/e2fsprogs/ext2fs/ismounted.c b/e2fsprogs/ext2fs/ismounted.c new file mode 100644 index 000000000..cace7715c --- /dev/null +++ b/e2fsprogs/ext2fs/ismounted.c @@ -0,0 +1,357 @@ +/* vi: set sw=4 ts=4: */ +/* + * ismounted.c --- Check to see if the filesystem was mounted + * + * Copyright (C) 1995,1996,1997,1998,1999,2000 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif +#include +#ifdef HAVE_LINUX_FD_H +#include +#endif +#ifdef HAVE_MNTENT_H +#include +#endif +#ifdef HAVE_GETMNTINFO +#include +#include +#include +#endif /* HAVE_GETMNTINFO */ +#include +#include + +#include "ext2_fs.h" +#include "ext2fs.h" + +#ifdef HAVE_MNTENT_H +/* + * Helper function which checks a file in /etc/mtab format to see if a + * filesystem is mounted. Returns an error if the file doesn't exist + * or can't be opened. + */ +static errcode_t check_mntent_file(const char *mtab_file, const char *file, + int *mount_flags, char *mtpt, int mtlen) +{ + struct mntent *mnt; + struct stat st_buf; + errcode_t retval = 0; + dev_t file_dev=0, file_rdev=0; + ino_t file_ino=0; + FILE *f; + int fd; + + *mount_flags = 0; + if ((f = setmntent (mtab_file, "r")) == NULL) + return errno; + if (stat(file, &st_buf) == 0) { + if (S_ISBLK(st_buf.st_mode)) { +#ifndef __GNU__ /* The GNU hurd is broken with respect to stat devices */ + file_rdev = st_buf.st_rdev; +#endif /* __GNU__ */ + } else { + file_dev = st_buf.st_dev; + file_ino = st_buf.st_ino; + } + } + while ((mnt = getmntent (f)) != NULL) { + if (strcmp(file, mnt->mnt_fsname) == 0) + break; + if (stat(mnt->mnt_fsname, &st_buf) == 0) { + if (S_ISBLK(st_buf.st_mode)) { +#ifndef __GNU__ + if (file_rdev && (file_rdev == st_buf.st_rdev)) + break; +#endif /* __GNU__ */ + } else { + if (file_dev && ((file_dev == st_buf.st_dev) && + (file_ino == st_buf.st_ino))) + break; + } + } + } + + if (mnt == 0) { +#ifndef __GNU__ /* The GNU hurd is broken with respect to stat devices */ + /* + * Do an extra check to see if this is the root device. We + * can't trust /etc/mtab, and /proc/mounts will only list + * /dev/root for the root filesystem. Argh. Instead we + * check if the given device has the same major/minor number + * as the device that the root directory is on. + */ + if (file_rdev && stat("/", &st_buf) == 0) { + if (st_buf.st_dev == file_rdev) { + *mount_flags = EXT2_MF_MOUNTED; + if (mtpt) + strncpy(mtpt, "/", mtlen); + goto is_root; + } + } +#endif /* __GNU__ */ + goto errout; + } +#ifndef __GNU__ /* The GNU hurd is deficient; what else is new? */ + /* Validate the entry in case /etc/mtab is out of date */ + /* + * We need to be paranoid, because some broken distributions + * (read: Slackware) don't initialize /etc/mtab before checking + * all of the non-root filesystems on the disk. + */ + if (stat(mnt->mnt_dir, &st_buf) < 0) { + retval = errno; + if (retval == ENOENT) { +#ifdef DEBUG + printf("Bogus entry in %s! (%s does not exist)\n", + mtab_file, mnt->mnt_dir); +#endif /* DEBUG */ + retval = 0; + } + goto errout; + } + if (file_rdev && (st_buf.st_dev != file_rdev)) { +#ifdef DEBUG + printf("Bogus entry in %s! (%s not mounted on %s)\n", + mtab_file, file, mnt->mnt_dir); +#endif /* DEBUG */ + goto errout; + } +#endif /* __GNU__ */ + *mount_flags = EXT2_MF_MOUNTED; + +#ifdef MNTOPT_RO + /* Check to see if the ro option is set */ + if (hasmntopt(mnt, MNTOPT_RO)) + *mount_flags |= EXT2_MF_READONLY; +#endif + + if (mtpt) + strncpy(mtpt, mnt->mnt_dir, mtlen); + /* + * Check to see if we're referring to the root filesystem. + * If so, do a manual check to see if we can open /etc/mtab + * read/write, since if the root is mounted read/only, the + * contents of /etc/mtab may not be accurate. + */ + if (!strcmp(mnt->mnt_dir, "/")) { +is_root: +#define TEST_FILE "/.ismount-test-file" + *mount_flags |= EXT2_MF_ISROOT; + fd = open(TEST_FILE, O_RDWR|O_CREAT); + if (fd < 0) { + if (errno == EROFS) + *mount_flags |= EXT2_MF_READONLY; + } else + close(fd); + (void) unlink(TEST_FILE); + } + retval = 0; +errout: + endmntent (f); + return retval; +} + +static errcode_t check_mntent(const char *file, int *mount_flags, + char *mtpt, int mtlen) +{ + errcode_t retval; + +#ifdef DEBUG + retval = check_mntent_file("/tmp/mtab", file, mount_flags, + mtpt, mtlen); + if (retval == 0) + return 0; +#endif /* DEBUG */ +#ifdef __linux__ + retval = check_mntent_file("/proc/mounts", file, mount_flags, + mtpt, mtlen); + if (retval == 0 && (*mount_flags != 0)) + return 0; +#endif /* __linux__ */ +#if defined(MOUNTED) || defined(_PATH_MOUNTED) +#ifndef MOUNTED +#define MOUNTED _PATH_MOUNTED +#endif /* MOUNTED */ + retval = check_mntent_file(MOUNTED, file, mount_flags, mtpt, mtlen); + return retval; +#else + *mount_flags = 0; + return 0; +#endif /* defined(MOUNTED) || defined(_PATH_MOUNTED) */ +} + +#else +#if defined(HAVE_GETMNTINFO) + +static errcode_t check_getmntinfo(const char *file, int *mount_flags, + char *mtpt, int mtlen) +{ + struct statfs *mp; + int len, n; + const char *s1; + char *s2; + + n = getmntinfo(&mp, MNT_NOWAIT); + if (n == 0) + return errno; + + len = sizeof(_PATH_DEV) - 1; + s1 = file; + if (strncmp(_PATH_DEV, s1, len) == 0) + s1 += len; + + *mount_flags = 0; + while (--n >= 0) { + s2 = mp->f_mntfromname; + if (strncmp(_PATH_DEV, s2, len) == 0) { + s2 += len - 1; + *s2 = 'r'; + } + if (strcmp(s1, s2) == 0 || strcmp(s1, &s2[1]) == 0) { + *mount_flags = EXT2_MF_MOUNTED; + break; + } + ++mp; + } + if (mtpt) + strncpy(mtpt, mp->f_mntonname, mtlen); + return 0; +} +#endif /* HAVE_GETMNTINFO */ +#endif /* HAVE_MNTENT_H */ + +/* + * Check to see if we're dealing with the swap device. + */ +static int is_swap_device(const char *file) +{ + FILE *f; + char buf[1024], *cp; + dev_t file_dev; + struct stat st_buf; + int ret = 0; + + file_dev = 0; +#ifndef __GNU__ /* The GNU hurd is broken with respect to stat devices */ + if ((stat(file, &st_buf) == 0) && + S_ISBLK(st_buf.st_mode)) + file_dev = st_buf.st_rdev; +#endif /* __GNU__ */ + + if (!(f = fopen("/proc/swaps", "r"))) + return 0; + /* Skip the first line */ + fgets(buf, sizeof(buf), f); + while (!feof(f)) { + if (!fgets(buf, sizeof(buf), f)) + break; + if ((cp = strchr(buf, ' ')) != NULL) + *cp = 0; + if ((cp = strchr(buf, '\t')) != NULL) + *cp = 0; + if (strcmp(buf, file) == 0) { + ret++; + break; + } +#ifndef __GNU__ + if (file_dev && (stat(buf, &st_buf) == 0) && + S_ISBLK(st_buf.st_mode) && + file_dev == st_buf.st_rdev) { + ret++; + break; + } +#endif /* __GNU__ */ + } + fclose(f); + return ret; +} + + +/* + * ext2fs_check_mount_point() returns 1 if the device is mounted, 0 + * otherwise. If mtpt is non-NULL, the directory where the device is + * mounted is copied to where mtpt is pointing, up to mtlen + * characters. + */ +#ifdef __TURBOC__ +# pragma argsused +#endif +errcode_t ext2fs_check_mount_point(const char *device, int *mount_flags, + char *mtpt, int mtlen) +{ + if (is_swap_device(device)) { + *mount_flags = EXT2_MF_MOUNTED | EXT2_MF_SWAP; + strncpy(mtpt, "", mtlen); + return 0; + } +#ifdef HAVE_MNTENT_H + return check_mntent(device, mount_flags, mtpt, mtlen); +#else +#ifdef HAVE_GETMNTINFO + return check_getmntinfo(device, mount_flags, mtpt, mtlen); +#else +#ifdef __GNUC__ + #warning "Can't use getmntent or getmntinfo to check for mounted filesystems!" +#endif + *mount_flags = 0; + return 0; +#endif /* HAVE_GETMNTINFO */ +#endif /* HAVE_MNTENT_H */ +} + +/* + * ext2fs_check_if_mounted() sets the mount_flags EXT2_MF_MOUNTED, + * EXT2_MF_READONLY, and EXT2_MF_ROOT + * + */ +errcode_t ext2fs_check_if_mounted(const char *file, int *mount_flags) +{ + return ext2fs_check_mount_point(file, mount_flags, NULL, 0); +} + +#ifdef DEBUG +int main(int argc, char **argv) +{ + int retval, mount_flags; + char mntpt[80]; + + if (argc < 2) { + fprintf(stderr, "Usage: %s device\n", argv[0]); + exit(1); + } + + mntpt[0] = 0; + retval = ext2fs_check_mount_point(argv[1], &mount_flags, + mntpt, sizeof(mntpt)); + if (retval) { + com_err(argv[0], retval, + "while calling ext2fs_check_if_mounted"); + exit(1); + } + printf("Device %s reports flags %02x\n", argv[1], mount_flags); + if (mount_flags & EXT2_MF_BUSY) + printf("\t%s is apparently in use.\n", argv[1]); + if (mount_flags & EXT2_MF_MOUNTED) + printf("\t%s is mounted.\n", argv[1]); + if (mount_flags & EXT2_MF_SWAP) + printf("\t%s is a swap device.\n", argv[1]); + if (mount_flags & EXT2_MF_READONLY) + printf("\t%s is read-only.\n", argv[1]); + if (mount_flags & EXT2_MF_ISROOT) + printf("\t%s is the root filesystem.\n", argv[1]); + if (mntpt[0]) + printf("\t%s is mounted on %s.\n", argv[1], mntpt); + exit(0); +} +#endif /* DEBUG */ diff --git a/e2fsprogs/ext2fs/jfs_dat.h b/e2fsprogs/ext2fs/jfs_dat.h new file mode 100644 index 000000000..136635de0 --- /dev/null +++ b/e2fsprogs/ext2fs/jfs_dat.h @@ -0,0 +1,65 @@ +/* vi: set sw=4 ts=4: */ +/* + * jfs_dat.h --- stripped down header file which only contains the JFS + * on-disk data structures + */ + +#define JFS_MAGIC_NUMBER 0xc03b3998U /* The first 4 bytes of /dev/random! */ + +/* + * On-disk structures + */ + +/* + * Descriptor block types: + */ + +#define JFS_DESCRIPTOR_BLOCK 1 +#define JFS_COMMIT_BLOCK 2 +#define JFS_SUPERBLOCK 3 + +/* + * Standard header for all descriptor blocks: + */ +typedef struct journal_header_s +{ + __u32 h_magic; + __u32 h_blocktype; + __u32 h_sequence; +} journal_header_t; + + +/* + * The block tag: used to describe a single buffer in the journal + */ +typedef struct journal_block_tag_s +{ + __u32 t_blocknr; /* The on-disk block number */ + __u32 t_flags; /* See below */ +} journal_block_tag_t; + +/* Definitions for the journal tag flags word: */ +#define JFS_FLAG_ESCAPE 1 /* on-disk block is escaped */ +#define JFS_FLAG_SAME_UUID 2 /* block has same uuid as previous */ +#define JFS_FLAG_DELETED 4 /* block deleted by this transaction */ +#define JFS_FLAG_LAST_TAG 8 /* last tag in this descriptor block */ + + +/* + * The journal superblock + */ +typedef struct journal_superblock_s +{ + journal_header_t s_header; + + /* Static information describing the journal */ + __u32 s_blocksize; /* journal device blocksize */ + __u32 s_maxlen; /* total blocks in journal file */ + __u32 s_first; /* first block of log information */ + + /* Dynamic information describing the current state of the log */ + __u32 s_sequence; /* first commit ID expected in log */ + __u32 s_start; /* blocknr of start of log */ + +} journal_superblock_t; + diff --git a/e2fsprogs/ext2fs/kernel-jbd.h b/e2fsprogs/ext2fs/kernel-jbd.h new file mode 100644 index 000000000..4c6c7dedd --- /dev/null +++ b/e2fsprogs/ext2fs/kernel-jbd.h @@ -0,0 +1,236 @@ +/* vi: set sw=4 ts=4: */ +/* + * linux/include/linux/jbd.h + * + * Written by Stephen C. Tweedie + * + * Copyright 1998-2000 Red Hat, Inc --- All Rights Reserved + * + * This file is part of the Linux kernel and is made available under + * the terms of the GNU General Public License, version 2, or at your + * option, any later version, incorporated herein by reference. + * + * Definitions for transaction data structures for the buffer cache + * filesystem journaling support. + */ + +#ifndef _LINUX_JBD_H +#define _LINUX_JBD_H + +#include +#include +#include "ext2fs.h" + +/* + * Standard header for all descriptor blocks: + */ + +typedef struct journal_header_s +{ + __u32 h_magic; + __u32 h_blocktype; + __u32 h_sequence; +} journal_header_t; + +/* + * This is the global e2fsck structure. + */ +typedef struct e2fsck_struct *e2fsck_t; + + +struct inode { + e2fsck_t i_ctx; + ext2_ino_t i_ino; + struct ext2_inode i_ext2; +}; + + +/* + * The journal superblock. All fields are in big-endian byte order. + */ +typedef struct journal_superblock_s +{ +/* 0x0000 */ + journal_header_t s_header; + +/* 0x000C */ + /* Static information describing the journal */ + __u32 s_blocksize; /* journal device blocksize */ + __u32 s_maxlen; /* total blocks in journal file */ + __u32 s_first; /* first block of log information */ + +/* 0x0018 */ + /* Dynamic information describing the current state of the log */ + __u32 s_sequence; /* first commit ID expected in log */ + __u32 s_start; /* blocknr of start of log */ + +/* 0x0020 */ + /* Error value, as set by journal_abort(). */ + __s32 s_errno; + +/* 0x0024 */ + /* Remaining fields are only valid in a version-2 superblock */ + __u32 s_feature_compat; /* compatible feature set */ + __u32 s_feature_incompat; /* incompatible feature set */ + __u32 s_feature_ro_compat; /* readonly-compatible feature set */ +/* 0x0030 */ + __u8 s_uuid[16]; /* 128-bit uuid for journal */ + +/* 0x0040 */ + __u32 s_nr_users; /* Nr of filesystems sharing log */ + + __u32 s_dynsuper; /* Blocknr of dynamic superblock copy*/ + +/* 0x0048 */ + __u32 s_max_transaction; /* Limit of journal blocks per trans.*/ + __u32 s_max_trans_data; /* Limit of data blocks per trans. */ + +/* 0x0050 */ + __u32 s_padding[44]; + +/* 0x0100 */ + __u8 s_users[16*48]; /* ids of all fs'es sharing the log */ +/* 0x0400 */ +} journal_superblock_t; + + +extern int journal_blocks_per_page(struct inode *inode); +extern int jbd_blocks_per_page(struct inode *inode); + +#define JFS_MIN_JOURNAL_BLOCKS 1024 + + +/* + * Internal structures used by the logging mechanism: + */ + +#define JFS_MAGIC_NUMBER 0xc03b3998U /* The first 4 bytes of /dev/random! */ + +/* + * Descriptor block types: + */ + +#define JFS_DESCRIPTOR_BLOCK 1 +#define JFS_COMMIT_BLOCK 2 +#define JFS_SUPERBLOCK_V1 3 +#define JFS_SUPERBLOCK_V2 4 +#define JFS_REVOKE_BLOCK 5 + +/* + * The block tag: used to describe a single buffer in the journal + */ +typedef struct journal_block_tag_s +{ + __u32 t_blocknr; /* The on-disk block number */ + __u32 t_flags; /* See below */ +} journal_block_tag_t; + +/* + * The revoke descriptor: used on disk to describe a series of blocks to + * be revoked from the log + */ +typedef struct journal_revoke_header_s +{ + journal_header_t r_header; + int r_count; /* Count of bytes used in the block */ +} journal_revoke_header_t; + + +/* Definitions for the journal tag flags word: */ +#define JFS_FLAG_ESCAPE 1 /* on-disk block is escaped */ +#define JFS_FLAG_SAME_UUID 2 /* block has same uuid as previous */ +#define JFS_FLAG_DELETED 4 /* block deleted by this transaction */ +#define JFS_FLAG_LAST_TAG 8 /* last tag in this descriptor block */ + + + + +#define JFS_HAS_COMPAT_FEATURE(j,mask) \ + ((j)->j_format_version >= 2 && \ + ((j)->j_superblock->s_feature_compat & cpu_to_be32((mask)))) +#define JFS_HAS_RO_COMPAT_FEATURE(j,mask) \ + ((j)->j_format_version >= 2 && \ + ((j)->j_superblock->s_feature_ro_compat & cpu_to_be32((mask)))) +#define JFS_HAS_INCOMPAT_FEATURE(j,mask) \ + ((j)->j_format_version >= 2 && \ + ((j)->j_superblock->s_feature_incompat & cpu_to_be32((mask)))) + +#define JFS_FEATURE_INCOMPAT_REVOKE 0x00000001 + +/* Features known to this kernel version: */ +#define JFS_KNOWN_COMPAT_FEATURES 0 +#define JFS_KNOWN_ROCOMPAT_FEATURES 0 +#define JFS_KNOWN_INCOMPAT_FEATURES JFS_FEATURE_INCOMPAT_REVOKE + +/* Comparison functions for transaction IDs: perform comparisons using + * modulo arithmetic so that they work over sequence number wraps. */ + + +/* + * Definitions which augment the buffer_head layer + */ + +/* journaling buffer types */ +#define BJ_None 0 /* Not journaled */ +#define BJ_SyncData 1 /* Normal data: flush before commit */ +#define BJ_AsyncData 2 /* writepage data: wait on it before commit */ +#define BJ_Metadata 3 /* Normal journaled metadata */ +#define BJ_Forget 4 /* Buffer superceded by this transaction */ +#define BJ_IO 5 /* Buffer is for temporary IO use */ +#define BJ_Shadow 6 /* Buffer contents being shadowed to the log */ +#define BJ_LogCtl 7 /* Buffer contains log descriptors */ +#define BJ_Reserved 8 /* Buffer is reserved for access by journal */ +#define BJ_Types 9 + + +struct kdev_s { + e2fsck_t k_ctx; + int k_dev; +}; + +typedef struct kdev_s *kdev_t; +typedef unsigned int tid_t; + +struct journal_s +{ + unsigned long j_flags; + int j_errno; + struct buffer_head * j_sb_buffer; + struct journal_superblock_s *j_superblock; + int j_format_version; + unsigned long j_head; + unsigned long j_tail; + unsigned long j_free; + unsigned long j_first, j_last; + kdev_t j_dev; + kdev_t j_fs_dev; + int j_blocksize; + unsigned int j_blk_offset; + unsigned int j_maxlen; + struct inode * j_inode; + tid_t j_tail_sequence; + tid_t j_transaction_sequence; + __u8 j_uuid[16]; + struct jbd_revoke_table_s *j_revoke; +}; + +typedef struct journal_s journal_t; + +extern int journal_recover (journal_t *journal); +extern int journal_skip_recovery (journal_t *); + +/* Primary revoke support */ +extern int journal_init_revoke(journal_t *, int); +extern void journal_destroy_revoke_caches(void); +extern int journal_init_revoke_caches(void); + +/* Recovery revoke support */ +extern int journal_set_revoke(journal_t *, unsigned long, tid_t); +extern int journal_test_revoke(journal_t *, unsigned long, tid_t); +extern void journal_clear_revoke(journal_t *); +extern void journal_brelse_array(struct buffer_head *b[], int n); + +extern void journal_destroy_revoke(journal_t *); + + +#endif /* _LINUX_JBD_H */ diff --git a/e2fsprogs/ext2fs/kernel-list.h b/e2fsprogs/ext2fs/kernel-list.h new file mode 100644 index 000000000..3392596ca --- /dev/null +++ b/e2fsprogs/ext2fs/kernel-list.h @@ -0,0 +1,113 @@ +/* vi: set sw=4 ts=4: */ +#ifndef _LINUX_LIST_H +#define _LINUX_LIST_H + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = { &name, &name } + +#define INIT_LIST_HEAD(ptr) do { \ + (ptr)->next = (ptr); (ptr)->prev = (ptr); \ +} while (0) + +#if (!defined(__GNUC__) && !defined(__WATCOMC__)) +#define __inline__ +#endif + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static __inline__ void __list_add(struct list_head * new, + struct list_head * prev, + struct list_head * next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/* + * Insert a new entry after the specified head.. + */ +static __inline__ void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +/* + * Insert a new entry at the tail + */ +static __inline__ void list_add_tail(struct list_head *new, struct list_head *head) +{ + __list_add(new, head->prev, head); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static __inline__ void __list_del(struct list_head * prev, + struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +static __inline__ void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); +} + +static __inline__ int list_empty(struct list_head *head) +{ + return head->next == head; +} + +/* + * Splice in "list" into "head" + */ +static __inline__ void list_splice(struct list_head *list, struct list_head *head) +{ + struct list_head *first = list->next; + + if (first != list) { + struct list_head *last = list->prev; + struct list_head *at = head->next; + + first->prev = head; + head->next = first; + + last->next = at; + at->prev = last; + } +} + +#define list_entry(ptr, type, member) \ + ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) + +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +#endif diff --git a/e2fsprogs/ext2fs/link.c b/e2fsprogs/ext2fs/link.c new file mode 100644 index 000000000..08b2e9673 --- /dev/null +++ b/e2fsprogs/ext2fs/link.c @@ -0,0 +1,135 @@ +/* vi: set sw=4 ts=4: */ +/* + * link.c --- create links in a ext2fs directory + * + * Copyright (C) 1993, 1994 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +struct link_struct { + const char *name; + int namelen; + ext2_ino_t inode; + int flags; + int done; + struct ext2_super_block *sb; +}; + +static int link_proc(struct ext2_dir_entry *dirent, + int offset, + int blocksize, + char *buf, + void *priv_data) +{ + struct link_struct *ls = (struct link_struct *) priv_data; + struct ext2_dir_entry *next; + int rec_len, min_rec_len; + int ret = 0; + + rec_len = EXT2_DIR_REC_LEN(ls->namelen); + + /* + * See if the following directory entry (if any) is unused; + * if so, absorb it into this one. + */ + next = (struct ext2_dir_entry *) (buf + offset + dirent->rec_len); + if ((offset + dirent->rec_len < blocksize - 8) && + (next->inode == 0) && + (offset + dirent->rec_len + next->rec_len <= blocksize)) { + dirent->rec_len += next->rec_len; + ret = DIRENT_CHANGED; + } + + /* + * If the directory entry is used, see if we can split the + * directory entry to make room for the new name. If so, + * truncate it and return. + */ + if (dirent->inode) { + min_rec_len = EXT2_DIR_REC_LEN(dirent->name_len & 0xFF); + if (dirent->rec_len < (min_rec_len + rec_len)) + return ret; + rec_len = dirent->rec_len - min_rec_len; + dirent->rec_len = min_rec_len; + next = (struct ext2_dir_entry *) (buf + offset + + dirent->rec_len); + next->inode = 0; + next->name_len = 0; + next->rec_len = rec_len; + return DIRENT_CHANGED; + } + + /* + * If we get this far, then the directory entry is not used. + * See if we can fit the request entry in. If so, do it. + */ + if (dirent->rec_len < rec_len) + return ret; + dirent->inode = ls->inode; + dirent->name_len = ls->namelen; + strncpy(dirent->name, ls->name, ls->namelen); + if (ls->sb->s_feature_incompat & EXT2_FEATURE_INCOMPAT_FILETYPE) + dirent->name_len |= (ls->flags & 0x7) << 8; + + ls->done++; + return DIRENT_ABORT|DIRENT_CHANGED; +} + +/* + * Note: the low 3 bits of the flags field are used as the directory + * entry filetype. + */ +#ifdef __TURBOC__ +# pragma argsused +#endif +errcode_t ext2fs_link(ext2_filsys fs, ext2_ino_t dir, const char *name, + ext2_ino_t ino, int flags) +{ + errcode_t retval; + struct link_struct ls; + struct ext2_inode inode; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (!(fs->flags & EXT2_FLAG_RW)) + return EXT2_ET_RO_FILSYS; + + ls.name = name; + ls.namelen = name ? strlen(name) : 0; + ls.inode = ino; + ls.flags = flags; + ls.done = 0; + ls.sb = fs->super; + + retval = ext2fs_dir_iterate(fs, dir, DIRENT_FLAG_INCLUDE_EMPTY, + 0, link_proc, &ls); + if (retval) + return retval; + + if (!ls.done) + return EXT2_ET_DIR_NO_SPACE; + + if ((retval = ext2fs_read_inode(fs, dir, &inode)) != 0) + return retval; + + if (inode.i_flags & EXT2_INDEX_FL) { + inode.i_flags &= ~EXT2_INDEX_FL; + if ((retval = ext2fs_write_inode(fs, dir, &inode)) != 0) + return retval; + } + + return 0; +} diff --git a/e2fsprogs/ext2fs/lookup.c b/e2fsprogs/ext2fs/lookup.c new file mode 100644 index 000000000..31b30a182 --- /dev/null +++ b/e2fsprogs/ext2fs/lookup.c @@ -0,0 +1,70 @@ +/* vi: set sw=4 ts=4: */ +/* + * lookup.c --- ext2fs directory lookup operations + * + * Copyright (C) 1993, 1994, 1994, 1995 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +struct lookup_struct { + const char *name; + int len; + ext2_ino_t *inode; + int found; +}; + +#ifdef __TURBOC__ +# pragma argsused +#endif +static int lookup_proc(struct ext2_dir_entry *dirent, + int offset EXT2FS_ATTR((unused)), + int blocksize EXT2FS_ATTR((unused)), + char *buf EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct lookup_struct *ls = (struct lookup_struct *) priv_data; + + if (ls->len != (dirent->name_len & 0xFF)) + return 0; + if (strncmp(ls->name, dirent->name, (dirent->name_len & 0xFF))) + return 0; + *ls->inode = dirent->inode; + ls->found++; + return DIRENT_ABORT; +} + + +errcode_t ext2fs_lookup(ext2_filsys fs, ext2_ino_t dir, const char *name, + int namelen, char *buf, ext2_ino_t *inode) +{ + errcode_t retval; + struct lookup_struct ls; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + ls.name = name; + ls.len = namelen; + ls.inode = inode; + ls.found = 0; + + retval = ext2fs_dir_iterate(fs, dir, 0, buf, lookup_proc, &ls); + if (retval) + return retval; + + return (ls.found) ? 0 : EXT2_ET_FILE_NOT_FOUND; +} + + diff --git a/e2fsprogs/ext2fs/mkdir.c b/e2fsprogs/ext2fs/mkdir.c new file mode 100644 index 000000000..b63c5d7a6 --- /dev/null +++ b/e2fsprogs/ext2fs/mkdir.c @@ -0,0 +1,142 @@ +/* vi: set sw=4 ts=4: */ +/* + * mkdir.c --- make a directory in the filesystem + * + * Copyright (C) 1994, 1995 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +#ifndef EXT2_FT_DIR +#define EXT2_FT_DIR 2 +#endif + +errcode_t ext2fs_mkdir(ext2_filsys fs, ext2_ino_t parent, ext2_ino_t inum, + const char *name) +{ + errcode_t retval; + struct ext2_inode parent_inode, inode; + ext2_ino_t ino = inum; + ext2_ino_t scratch_ino; + blk_t blk; + char *block = 0; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + /* + * Allocate an inode, if necessary + */ + if (!ino) { + retval = ext2fs_new_inode(fs, parent, LINUX_S_IFDIR | 0755, + 0, &ino); + if (retval) + goto cleanup; + } + + /* + * Allocate a data block for the directory + */ + retval = ext2fs_new_block(fs, 0, 0, &blk); + if (retval) + goto cleanup; + + /* + * Create a scratch template for the directory + */ + retval = ext2fs_new_dir_block(fs, ino, parent, &block); + if (retval) + goto cleanup; + + /* + * Get the parent's inode, if necessary + */ + if (parent != ino) { + retval = ext2fs_read_inode(fs, parent, &parent_inode); + if (retval) + goto cleanup; + } else + memset(&parent_inode, 0, sizeof(parent_inode)); + + /* + * Create the inode structure.... + */ + memset(&inode, 0, sizeof(struct ext2_inode)); + inode.i_mode = LINUX_S_IFDIR | (0777 & ~fs->umask); + inode.i_uid = inode.i_gid = 0; + inode.i_blocks = fs->blocksize / 512; + inode.i_block[0] = blk; + inode.i_links_count = 2; + inode.i_ctime = inode.i_atime = inode.i_mtime = time(NULL); + inode.i_size = fs->blocksize; + + /* + * Write out the inode and inode data block + */ + retval = ext2fs_write_dir_block(fs, blk, block); + if (retval) + goto cleanup; + retval = ext2fs_write_new_inode(fs, ino, &inode); + if (retval) + goto cleanup; + + /* + * Link the directory into the filesystem hierarchy + */ + if (name) { + retval = ext2fs_lookup(fs, parent, name, strlen(name), 0, + &scratch_ino); + if (!retval) { + retval = EXT2_ET_DIR_EXISTS; + name = 0; + goto cleanup; + } + if (retval != EXT2_ET_FILE_NOT_FOUND) + goto cleanup; + retval = ext2fs_link(fs, parent, name, ino, EXT2_FT_DIR); + if (retval) + goto cleanup; + } + + /* + * Update parent inode's counts + */ + if (parent != ino) { + parent_inode.i_links_count++; + retval = ext2fs_write_inode(fs, parent, &parent_inode); + if (retval) + goto cleanup; + } + + /* + * Update accounting.... + */ + ext2fs_block_alloc_stats(fs, blk, +1); + ext2fs_inode_alloc_stats2(fs, ino, +1, 1); + +cleanup: + ext2fs_free_mem(&block); + return retval; + +} + + diff --git a/e2fsprogs/ext2fs/mkjournal.c b/e2fsprogs/ext2fs/mkjournal.c new file mode 100644 index 000000000..5bdd34682 --- /dev/null +++ b/e2fsprogs/ext2fs/mkjournal.c @@ -0,0 +1,428 @@ +/* vi: set sw=4 ts=4: */ +/* + * mkjournal.c --- make a journal for a filesystem + * + * Copyright (C) 2000 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif +#if HAVE_SYS_IOCTL_H +#include +#endif +#if HAVE_NETINET_IN_H +#include +#endif + +#include "ext2_fs.h" +#include "../e2p/e2p.h" +#include "../e2fsck.h" +#include "ext2fs.h" +#include "kernel-jbd.h" + +/* + * This function automatically sets up the journal superblock and + * returns it as an allocated block. + */ +errcode_t ext2fs_create_journal_superblock(ext2_filsys fs, + __u32 size, int flags, + char **ret_jsb) +{ + errcode_t retval; + journal_superblock_t *jsb; + + if (size < 1024) + return EXT2_ET_JOURNAL_TOO_SMALL; + + if ((retval = ext2fs_get_mem(fs->blocksize, &jsb))) + return retval; + + memset (jsb, 0, fs->blocksize); + + jsb->s_header.h_magic = htonl(JFS_MAGIC_NUMBER); + if (flags & EXT2_MKJOURNAL_V1_SUPER) + jsb->s_header.h_blocktype = htonl(JFS_SUPERBLOCK_V1); + else + jsb->s_header.h_blocktype = htonl(JFS_SUPERBLOCK_V2); + jsb->s_blocksize = htonl(fs->blocksize); + jsb->s_maxlen = htonl(size); + jsb->s_nr_users = htonl(1); + jsb->s_first = htonl(1); + jsb->s_sequence = htonl(1); + memcpy(jsb->s_uuid, fs->super->s_uuid, sizeof(fs->super->s_uuid)); + /* + * If we're creating an external journal device, we need to + * adjust these fields. + */ + if (fs->super->s_feature_incompat & + EXT3_FEATURE_INCOMPAT_JOURNAL_DEV) { + jsb->s_nr_users = 0; + if (fs->blocksize == 1024) + jsb->s_first = htonl(3); + else + jsb->s_first = htonl(2); + } + + *ret_jsb = (char *) jsb; + return 0; +} + +/* + * This function writes a journal using POSIX routines. It is used + * for creating external journals and creating journals on live + * filesystems. + */ +static errcode_t write_journal_file(ext2_filsys fs, char *filename, + blk_t size, int flags) +{ + errcode_t retval; + char *buf = 0; + int fd, ret_size; + blk_t i; + + if ((retval = ext2fs_create_journal_superblock(fs, size, flags, &buf))) + return retval; + + /* Open the device or journal file */ + if ((fd = open(filename, O_WRONLY)) < 0) { + retval = errno; + goto errout; + } + + /* Write the superblock out */ + retval = EXT2_ET_SHORT_WRITE; + ret_size = write(fd, buf, fs->blocksize); + if (ret_size < 0) { + retval = errno; + goto errout; + } + if (ret_size != (int) fs->blocksize) + goto errout; + memset(buf, 0, fs->blocksize); + + for (i = 1; i < size; i++) { + ret_size = write(fd, buf, fs->blocksize); + if (ret_size < 0) { + retval = errno; + goto errout; + } + if (ret_size != (int) fs->blocksize) + goto errout; + } + close(fd); + + retval = 0; +errout: + ext2fs_free_mem(&buf); + return retval; +} + +/* + * Helper function for creating the journal using direct I/O routines + */ +struct mkjournal_struct { + int num_blocks; + int newblocks; + char *buf; + errcode_t err; +}; + +static int mkjournal_proc(ext2_filsys fs, + blk_t *blocknr, + e2_blkcnt_t blockcnt, + blk_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct mkjournal_struct *es = (struct mkjournal_struct *) priv_data; + blk_t new_blk; + static blk_t last_blk = 0; + errcode_t retval; + + if (*blocknr) { + last_blk = *blocknr; + return 0; + } + retval = ext2fs_new_block(fs, last_blk, 0, &new_blk); + if (retval) { + es->err = retval; + return BLOCK_ABORT; + } + if (blockcnt > 0) + es->num_blocks--; + + es->newblocks++; + retval = io_channel_write_blk(fs->io, new_blk, 1, es->buf); + + if (blockcnt == 0) + memset(es->buf, 0, fs->blocksize); + + if (retval) { + es->err = retval; + return BLOCK_ABORT; + } + *blocknr = new_blk; + last_blk = new_blk; + ext2fs_block_alloc_stats(fs, new_blk, +1); + + if (es->num_blocks == 0) + return (BLOCK_CHANGED | BLOCK_ABORT); + else + return BLOCK_CHANGED; + +} + +/* + * This function creates a journal using direct I/O routines. + */ +static errcode_t write_journal_inode(ext2_filsys fs, ext2_ino_t journal_ino, + blk_t size, int flags) +{ + char *buf; + errcode_t retval; + struct ext2_inode inode; + struct mkjournal_struct es; + + if ((retval = ext2fs_create_journal_superblock(fs, size, flags, &buf))) + return retval; + + if ((retval = ext2fs_read_bitmaps(fs))) + return retval; + + if ((retval = ext2fs_read_inode(fs, journal_ino, &inode))) + return retval; + + if (inode.i_blocks > 0) + return EEXIST; + + es.num_blocks = size; + es.newblocks = 0; + es.buf = buf; + es.err = 0; + + retval = ext2fs_block_iterate2(fs, journal_ino, BLOCK_FLAG_APPEND, + 0, mkjournal_proc, &es); + if (es.err) { + retval = es.err; + goto errout; + } + + if ((retval = ext2fs_read_inode(fs, journal_ino, &inode))) + goto errout; + + inode.i_size += fs->blocksize * size; + inode.i_blocks += (fs->blocksize / 512) * es.newblocks; + inode.i_mtime = inode.i_ctime = time(0); + inode.i_links_count = 1; + inode.i_mode = LINUX_S_IFREG | 0600; + + if ((retval = ext2fs_write_inode(fs, journal_ino, &inode))) + goto errout; + retval = 0; + + memcpy(fs->super->s_jnl_blocks, inode.i_block, EXT2_N_BLOCKS*4); + fs->super->s_jnl_blocks[16] = inode.i_size; + fs->super->s_jnl_backup_type = EXT3_JNL_BACKUP_BLOCKS; + ext2fs_mark_super_dirty(fs); + +errout: + ext2fs_free_mem(&buf); + return retval; +} + +/* + * This function adds a journal device to a filesystem + */ +errcode_t ext2fs_add_journal_device(ext2_filsys fs, ext2_filsys journal_dev) +{ + struct stat st; + errcode_t retval; + char buf[1024]; + journal_superblock_t *jsb; + int start; + __u32 i, nr_users; + + /* Make sure the device exists and is a block device */ + if (stat(journal_dev->device_name, &st) < 0) + return errno; + + if (!S_ISBLK(st.st_mode)) + return EXT2_ET_JOURNAL_NOT_BLOCK; /* Must be a block device */ + + /* Get the journal superblock */ + start = 1; + if (journal_dev->blocksize == 1024) + start++; + if ((retval = io_channel_read_blk(journal_dev->io, start, -1024, buf))) + return retval; + + jsb = (journal_superblock_t *) buf; + if ((jsb->s_header.h_magic != (unsigned) ntohl(JFS_MAGIC_NUMBER)) || + (jsb->s_header.h_blocktype != (unsigned) ntohl(JFS_SUPERBLOCK_V2))) + return EXT2_ET_NO_JOURNAL_SB; + + if (ntohl(jsb->s_blocksize) != (unsigned long) fs->blocksize) + return EXT2_ET_UNEXPECTED_BLOCK_SIZE; + + /* Check and see if this filesystem has already been added */ + nr_users = ntohl(jsb->s_nr_users); + for (i=0; i < nr_users; i++) { + if (memcmp(fs->super->s_uuid, + &jsb->s_users[i*16], 16) == 0) + break; + } + if (i >= nr_users) { + memcpy(&jsb->s_users[nr_users*16], + fs->super->s_uuid, 16); + jsb->s_nr_users = htonl(nr_users+1); + } + + /* Writeback the journal superblock */ + if ((retval = io_channel_write_blk(journal_dev->io, start, -1024, buf))) + return retval; + + fs->super->s_journal_inum = 0; + fs->super->s_journal_dev = st.st_rdev; + memcpy(fs->super->s_journal_uuid, jsb->s_uuid, + sizeof(fs->super->s_journal_uuid)); + fs->super->s_feature_compat |= EXT3_FEATURE_COMPAT_HAS_JOURNAL; + ext2fs_mark_super_dirty(fs); + return 0; +} + +/* + * This function adds a journal inode to a filesystem, using either + * POSIX routines if the filesystem is mounted, or using direct I/O + * functions if it is not. + */ +errcode_t ext2fs_add_journal_inode(ext2_filsys fs, blk_t size, int flags) +{ + errcode_t retval; + ext2_ino_t journal_ino; + struct stat st; + char jfile[1024]; + int fd, mount_flags, f; + + retval = ext2fs_check_mount_point(fs->device_name, &mount_flags, + jfile, sizeof(jfile)-10); + if (retval) + return retval; + + if (mount_flags & EXT2_MF_MOUNTED) { + strcat(jfile, "/.journal"); + + /* + * If .../.journal already exists, make sure any + * immutable or append-only flags are cleared. + */ +#if defined(HAVE_CHFLAGS) && defined(UF_NODUMP) + (void) chflags (jfile, 0); +#else +#if HAVE_EXT2_IOCTLS + fd = open(jfile, O_RDONLY); + if (fd >= 0) { + f = 0; + ioctl(fd, EXT2_IOC_SETFLAGS, &f); + close(fd); + } +#endif +#endif + + /* Create the journal file */ + if ((fd = open(jfile, O_CREAT|O_WRONLY, 0600)) < 0) + return errno; + + if ((retval = write_journal_file(fs, jfile, size, flags))) + goto errout; + + /* Get inode number of the journal file */ + if (fstat(fd, &st) < 0) + goto errout; + +#if defined(HAVE_CHFLAGS) && defined(UF_NODUMP) + retval = fchflags (fd, UF_NODUMP|UF_IMMUTABLE); +#else +#if HAVE_EXT2_IOCTLS + f = EXT2_NODUMP_FL | EXT2_IMMUTABLE_FL; + retval = ioctl(fd, EXT2_IOC_SETFLAGS, &f); +#endif +#endif + if (retval) + goto errout; + + close(fd); + journal_ino = st.st_ino; + } else { + journal_ino = EXT2_JOURNAL_INO; + if ((retval = write_journal_inode(fs, journal_ino, + size, flags))) + return retval; + } + + fs->super->s_journal_inum = journal_ino; + fs->super->s_journal_dev = 0; + memset(fs->super->s_journal_uuid, 0, + sizeof(fs->super->s_journal_uuid)); + fs->super->s_feature_compat |= EXT3_FEATURE_COMPAT_HAS_JOURNAL; + + ext2fs_mark_super_dirty(fs); + return 0; +errout: + close(fd); + return retval; +} + +#ifdef DEBUG +main(int argc, char **argv) +{ + errcode_t retval; + char *device_name; + ext2_filsys fs; + + if (argc < 2) { + fprintf(stderr, "Usage: %s filesystem\n", argv[0]); + exit(1); + } + device_name = argv[1]; + + retval = ext2fs_open (device_name, EXT2_FLAG_RW, 0, 0, + unix_io_manager, &fs); + if (retval) { + com_err(argv[0], retval, "while opening %s", device_name); + exit(1); + } + + retval = ext2fs_add_journal_inode(fs, 1024); + if (retval) { + com_err(argv[0], retval, "while adding journal to %s", + device_name); + exit(1); + } + retval = ext2fs_flush(fs); + if (retval) { + printf("Warning, had trouble writing out superblocks.\n"); + } + ext2fs_close(fs); + exit(0); + +} +#endif diff --git a/e2fsprogs/ext2fs/namei.c b/e2fsprogs/ext2fs/namei.c new file mode 100644 index 000000000..988967009 --- /dev/null +++ b/e2fsprogs/ext2fs/namei.c @@ -0,0 +1,205 @@ +/* vi: set sw=4 ts=4: */ +/* + * namei.c --- ext2fs directory lookup operations + * + * Copyright (C) 1993, 1994, 1994, 1995 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif + +/* #define NAMEI_DEBUG */ + +#include "ext2_fs.h" +#include "ext2fs.h" + +static errcode_t open_namei(ext2_filsys fs, ext2_ino_t root, ext2_ino_t base, + const char *pathname, size_t pathlen, int follow, + int link_count, char *buf, ext2_ino_t *res_inode); + +static errcode_t follow_link(ext2_filsys fs, ext2_ino_t root, ext2_ino_t dir, + ext2_ino_t inode, int link_count, + char *buf, ext2_ino_t *res_inode) +{ + char *pathname; + char *buffer = 0; + errcode_t retval; + struct ext2_inode ei; + +#ifdef NAMEI_DEBUG + printf("follow_link: root=%lu, dir=%lu, inode=%lu, lc=%d\n", + root, dir, inode, link_count); + +#endif + retval = ext2fs_read_inode (fs, inode, &ei); + if (retval) return retval; + if (!LINUX_S_ISLNK (ei.i_mode)) { + *res_inode = inode; + return 0; + } + if (link_count++ > 5) { + return EXT2_ET_SYMLINK_LOOP; + } + if (ext2fs_inode_data_blocks(fs,&ei)) { + retval = ext2fs_get_mem(fs->blocksize, &buffer); + if (retval) + return retval; + retval = io_channel_read_blk(fs->io, ei.i_block[0], 1, buffer); + if (retval) { + ext2fs_free_mem(&buffer); + return retval; + } + pathname = buffer; + } else + pathname = (char *)&(ei.i_block[0]); + retval = open_namei(fs, root, dir, pathname, ei.i_size, 1, + link_count, buf, res_inode); + ext2fs_free_mem(&buffer); + return retval; +} + +/* + * This routine interprets a pathname in the context of the current + * directory and the root directory, and returns the inode of the + * containing directory, and a pointer to the filename of the file + * (pointing into the pathname) and the length of the filename. + */ +static errcode_t dir_namei(ext2_filsys fs, ext2_ino_t root, ext2_ino_t dir, + const char *pathname, int pathlen, + int link_count, char *buf, + const char **name, int *namelen, + ext2_ino_t *res_inode) +{ + char c; + const char *thisname; + int len; + ext2_ino_t inode; + errcode_t retval; + + if ((c = *pathname) == '/') { + dir = root; + pathname++; + pathlen--; + } + while (1) { + thisname = pathname; + for (len=0; --pathlen >= 0;len++) { + c = *(pathname++); + if (c == '/') + break; + } + if (pathlen < 0) + break; + retval = ext2fs_lookup (fs, dir, thisname, len, buf, &inode); + if (retval) return retval; + retval = follow_link (fs, root, dir, inode, + link_count, buf, &dir); + if (retval) return retval; + } + *name = thisname; + *namelen = len; + *res_inode = dir; + return 0; +} + +static errcode_t open_namei(ext2_filsys fs, ext2_ino_t root, ext2_ino_t base, + const char *pathname, size_t pathlen, int follow, + int link_count, char *buf, ext2_ino_t *res_inode) +{ + const char *basename; + int namelen; + ext2_ino_t dir, inode; + errcode_t retval; + +#ifdef NAMEI_DEBUG + printf("open_namei: root=%lu, dir=%lu, path=%*s, lc=%d\n", + root, base, pathlen, pathname, link_count); +#endif + retval = dir_namei(fs, root, base, pathname, pathlen, + link_count, buf, &basename, &namelen, &dir); + if (retval) return retval; + if (!namelen) { /* special case: '/usr/' etc */ + *res_inode=dir; + return 0; + } + retval = ext2fs_lookup (fs, dir, basename, namelen, buf, &inode); + if (retval) + return retval; + if (follow) { + retval = follow_link(fs, root, dir, inode, link_count, + buf, &inode); + if (retval) + return retval; + } +#ifdef NAMEI_DEBUG + printf("open_namei: (link_count=%d) returns %lu\n", + link_count, inode); +#endif + *res_inode = inode; + return 0; +} + +errcode_t ext2fs_namei(ext2_filsys fs, ext2_ino_t root, ext2_ino_t cwd, + const char *name, ext2_ino_t *inode) +{ + char *buf; + errcode_t retval; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + retval = ext2fs_get_mem(fs->blocksize, &buf); + if (retval) + return retval; + + retval = open_namei(fs, root, cwd, name, strlen(name), 0, 0, + buf, inode); + + ext2fs_free_mem(&buf); + return retval; +} + +errcode_t ext2fs_namei_follow(ext2_filsys fs, ext2_ino_t root, ext2_ino_t cwd, + const char *name, ext2_ino_t *inode) +{ + char *buf; + errcode_t retval; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + retval = ext2fs_get_mem(fs->blocksize, &buf); + if (retval) + return retval; + + retval = open_namei(fs, root, cwd, name, strlen(name), 1, 0, + buf, inode); + + ext2fs_free_mem(&buf); + return retval; +} + +errcode_t ext2fs_follow_link(ext2_filsys fs, ext2_ino_t root, ext2_ino_t cwd, + ext2_ino_t inode, ext2_ino_t *res_inode) +{ + char *buf; + errcode_t retval; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + retval = ext2fs_get_mem(fs->blocksize, &buf); + if (retval) + return retval; + + retval = follow_link(fs, root, cwd, inode, 0, buf, res_inode); + + ext2fs_free_mem(&buf); + return retval; +} + diff --git a/e2fsprogs/ext2fs/newdir.c b/e2fsprogs/ext2fs/newdir.c new file mode 100644 index 000000000..9470e7f56 --- /dev/null +++ b/e2fsprogs/ext2fs/newdir.c @@ -0,0 +1,73 @@ +/* vi: set sw=4 ts=4: */ +/* + * newdir.c --- create a new directory block + * + * Copyright (C) 1994, 1995 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +#ifndef EXT2_FT_DIR +#define EXT2_FT_DIR 2 +#endif + +/* + * Create new directory block + */ +errcode_t ext2fs_new_dir_block(ext2_filsys fs, ext2_ino_t dir_ino, + ext2_ino_t parent_ino, char **block) +{ + struct ext2_dir_entry *dir = NULL; + errcode_t retval; + char *buf; + int rec_len; + int filetype = 0; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + retval = ext2fs_get_mem(fs->blocksize, &buf); + if (retval) + return retval; + memset(buf, 0, fs->blocksize); + dir = (struct ext2_dir_entry *) buf; + dir->rec_len = fs->blocksize; + + if (dir_ino) { + if (fs->super->s_feature_incompat & + EXT2_FEATURE_INCOMPAT_FILETYPE) + filetype = EXT2_FT_DIR << 8; + /* + * Set up entry for '.' + */ + dir->inode = dir_ino; + dir->name_len = 1 | filetype; + dir->name[0] = '.'; + rec_len = dir->rec_len - EXT2_DIR_REC_LEN(1); + dir->rec_len = EXT2_DIR_REC_LEN(1); + + /* + * Set up entry for '..' + */ + dir = (struct ext2_dir_entry *) (buf + dir->rec_len); + dir->rec_len = rec_len; + dir->inode = parent_ino; + dir->name_len = 2 | filetype; + dir->name[0] = '.'; + dir->name[1] = '.'; + + } + *block = buf; + return 0; +} diff --git a/e2fsprogs/ext2fs/openfs.c b/e2fsprogs/ext2fs/openfs.c new file mode 100644 index 000000000..716be060f --- /dev/null +++ b/e2fsprogs/ext2fs/openfs.c @@ -0,0 +1,330 @@ +/* vi: set sw=4 ts=4: */ +/* + * openfs.c --- open an ext2 filesystem + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" + + +#include "ext2fs.h" +#include "e2image.h" + +blk_t ext2fs_descriptor_block_loc(ext2_filsys fs, blk_t group_block, dgrp_t i) +{ + int bg; + int has_super = 0; + int ret_blk; + + if (!(fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG) || + (i < fs->super->s_first_meta_bg)) + return (group_block + i + 1); + + bg = (fs->blocksize / sizeof (struct ext2_group_desc)) * i; + if (ext2fs_bg_has_super(fs, bg)) + has_super = 1; + ret_blk = (fs->super->s_first_data_block + has_super + + (bg * fs->super->s_blocks_per_group)); + /* + * If group_block is not the normal value, we're trying to use + * the backup group descriptors and superblock --- so use the + * alternate location of the second block group in the + * metablock group. Ideally we should be testing each bg + * descriptor block individually for correctness, but we don't + * have the infrastructure in place to do that. + */ + if (group_block != fs->super->s_first_data_block && + ((ret_blk + fs->super->s_blocks_per_group) < + fs->super->s_blocks_count)) + ret_blk += fs->super->s_blocks_per_group; + return ret_blk; +} + +errcode_t ext2fs_open(const char *name, int flags, int superblock, + unsigned int block_size, io_manager manager, + ext2_filsys *ret_fs) +{ + return ext2fs_open2(name, 0, flags, superblock, block_size, + manager, ret_fs); +} + +/* + * Note: if superblock is non-zero, block-size must also be non-zero. + * Superblock and block_size can be zero to use the default size. + * + * Valid flags for ext2fs_open() + * + * EXT2_FLAG_RW - Open the filesystem for read/write. + * EXT2_FLAG_FORCE - Open the filesystem even if some of the + * features aren't supported. + * EXT2_FLAG_JOURNAL_DEV_OK - Open an ext3 journal device + */ +errcode_t ext2fs_open2(const char *name, const char *io_options, + int flags, int superblock, + unsigned int block_size, io_manager manager, + ext2_filsys *ret_fs) +{ + ext2_filsys fs; + errcode_t retval; + unsigned long i; + int groups_per_block, blocks_per_group; + blk_t group_block, blk; + char *dest, *cp; +#if BB_BIG_ENDIAN + int j; + struct ext2_group_desc *gdp; +#endif + + EXT2_CHECK_MAGIC(manager, EXT2_ET_MAGIC_IO_MANAGER); + + retval = ext2fs_get_mem(sizeof(struct struct_ext2_filsys), &fs); + if (retval) + return retval; + + memset(fs, 0, sizeof(struct struct_ext2_filsys)); + fs->magic = EXT2_ET_MAGIC_EXT2FS_FILSYS; + fs->flags = flags; + fs->umask = 022; + retval = ext2fs_get_mem(strlen(name)+1, &fs->device_name); + if (retval) + goto cleanup; + strcpy(fs->device_name, name); + cp = strchr(fs->device_name, '?'); + if (!io_options && cp) { + *cp++ = 0; + io_options = cp; + } + + retval = manager->open(fs->device_name, + (flags & EXT2_FLAG_RW) ? IO_FLAG_RW : 0, + &fs->io); + if (retval) + goto cleanup; + if (io_options && + (retval = io_channel_set_options(fs->io, io_options))) + goto cleanup; + fs->image_io = fs->io; + fs->io->app_data = fs; + retval = ext2fs_get_mem(SUPERBLOCK_SIZE, &fs->super); + if (retval) + goto cleanup; + if (flags & EXT2_FLAG_IMAGE_FILE) { + retval = ext2fs_get_mem(sizeof(struct ext2_image_hdr), + &fs->image_header); + if (retval) + goto cleanup; + retval = io_channel_read_blk(fs->io, 0, + -(int)sizeof(struct ext2_image_hdr), + fs->image_header); + if (retval) + goto cleanup; + if (fs->image_header->magic_number != EXT2_ET_MAGIC_E2IMAGE) + return EXT2_ET_MAGIC_E2IMAGE; + superblock = 1; + block_size = fs->image_header->fs_blocksize; + } + + /* + * If the user specifies a specific block # for the + * superblock, then he/she must also specify the block size! + * Otherwise, read the master superblock located at offset + * SUPERBLOCK_OFFSET from the start of the partition. + * + * Note: we only save a backup copy of the superblock if we + * are reading the superblock from the primary superblock location. + */ + if (superblock) { + if (!block_size) { + retval = EXT2_ET_INVALID_ARGUMENT; + goto cleanup; + } + io_channel_set_blksize(fs->io, block_size); + group_block = superblock; + fs->orig_super = 0; + } else { + io_channel_set_blksize(fs->io, SUPERBLOCK_OFFSET); + superblock = 1; + group_block = 0; + retval = ext2fs_get_mem(SUPERBLOCK_SIZE, &fs->orig_super); + if (retval) + goto cleanup; + } + retval = io_channel_read_blk(fs->io, superblock, -SUPERBLOCK_SIZE, + fs->super); + if (retval) + goto cleanup; + if (fs->orig_super) + memcpy(fs->orig_super, fs->super, SUPERBLOCK_SIZE); + +#if BB_BIG_ENDIAN + if ((fs->super->s_magic == ext2fs_swab16(EXT2_SUPER_MAGIC)) || + (fs->flags & EXT2_FLAG_SWAP_BYTES)) { + fs->flags |= EXT2_FLAG_SWAP_BYTES; + + ext2fs_swap_super(fs->super); + } +#endif + + if (fs->super->s_magic != EXT2_SUPER_MAGIC) { + retval = EXT2_ET_BAD_MAGIC; + goto cleanup; + } + if (fs->super->s_rev_level > EXT2_LIB_CURRENT_REV) { + retval = EXT2_ET_REV_TOO_HIGH; + goto cleanup; + } + + /* + * Check for feature set incompatibility + */ + if (!(flags & EXT2_FLAG_FORCE)) { + if (fs->super->s_feature_incompat & + ~EXT2_LIB_FEATURE_INCOMPAT_SUPP) { + retval = EXT2_ET_UNSUPP_FEATURE; + goto cleanup; + } + if ((flags & EXT2_FLAG_RW) && + (fs->super->s_feature_ro_compat & + ~EXT2_LIB_FEATURE_RO_COMPAT_SUPP)) { + retval = EXT2_ET_RO_UNSUPP_FEATURE; + goto cleanup; + } + if (!(flags & EXT2_FLAG_JOURNAL_DEV_OK) && + (fs->super->s_feature_incompat & + EXT3_FEATURE_INCOMPAT_JOURNAL_DEV)) { + retval = EXT2_ET_UNSUPP_FEATURE; + goto cleanup; + } + } + + fs->blocksize = EXT2_BLOCK_SIZE(fs->super); + if (fs->blocksize == 0) { + retval = EXT2_ET_CORRUPT_SUPERBLOCK; + goto cleanup; + } + fs->fragsize = EXT2_FRAG_SIZE(fs->super); + fs->inode_blocks_per_group = ((fs->super->s_inodes_per_group * + EXT2_INODE_SIZE(fs->super) + + EXT2_BLOCK_SIZE(fs->super) - 1) / + EXT2_BLOCK_SIZE(fs->super)); + if (block_size) { + if (block_size != fs->blocksize) { + retval = EXT2_ET_UNEXPECTED_BLOCK_SIZE; + goto cleanup; + } + } + /* + * Set the blocksize to the filesystem's blocksize. + */ + io_channel_set_blksize(fs->io, fs->blocksize); + + /* + * If this is an external journal device, don't try to read + * the group descriptors, because they're not there. + */ + if (fs->super->s_feature_incompat & + EXT3_FEATURE_INCOMPAT_JOURNAL_DEV) { + fs->group_desc_count = 0; + *ret_fs = fs; + return 0; + } + + /* + * Read group descriptors + */ + blocks_per_group = EXT2_BLOCKS_PER_GROUP(fs->super); + if (blocks_per_group == 0 || + blocks_per_group > EXT2_MAX_BLOCKS_PER_GROUP(fs->super) || + fs->inode_blocks_per_group > EXT2_MAX_INODES_PER_GROUP(fs->super)) { + retval = EXT2_ET_CORRUPT_SUPERBLOCK; + goto cleanup; + } + fs->group_desc_count = (fs->super->s_blocks_count - + fs->super->s_first_data_block + + blocks_per_group - 1) / blocks_per_group; + fs->desc_blocks = (fs->group_desc_count + + EXT2_DESC_PER_BLOCK(fs->super) - 1) + / EXT2_DESC_PER_BLOCK(fs->super); + retval = ext2fs_get_mem(fs->desc_blocks * fs->blocksize, + &fs->group_desc); + if (retval) + goto cleanup; + if (!group_block) + group_block = fs->super->s_first_data_block; + dest = (char *) fs->group_desc; + groups_per_block = fs->blocksize / sizeof(struct ext2_group_desc); + for (i=0 ; i < fs->desc_blocks; i++) { + blk = ext2fs_descriptor_block_loc(fs, group_block, i); + retval = io_channel_read_blk(fs->io, blk, 1, dest); + if (retval) + goto cleanup; +#if BB_BIG_ENDIAN + if (fs->flags & EXT2_FLAG_SWAP_BYTES) { + gdp = (struct ext2_group_desc *) dest; + for (j=0; j < groups_per_block; j++) + ext2fs_swap_group_desc(gdp++); + } +#endif + dest += fs->blocksize; + } + + *ret_fs = fs; + return 0; +cleanup: + ext2fs_free(fs); + return retval; +} + +/* + * Set/get the filesystem data I/O channel. + * + * These functions are only valid if EXT2_FLAG_IMAGE_FILE is true. + */ +errcode_t ext2fs_get_data_io(ext2_filsys fs, io_channel *old_io) +{ + if ((fs->flags & EXT2_FLAG_IMAGE_FILE) == 0) + return EXT2_ET_NOT_IMAGE_FILE; + if (old_io) { + *old_io = (fs->image_io == fs->io) ? 0 : fs->io; + } + return 0; +} + +errcode_t ext2fs_set_data_io(ext2_filsys fs, io_channel new_io) +{ + if ((fs->flags & EXT2_FLAG_IMAGE_FILE) == 0) + return EXT2_ET_NOT_IMAGE_FILE; + fs->io = new_io ? new_io : fs->image_io; + return 0; +} + +errcode_t ext2fs_rewrite_to_io(ext2_filsys fs, io_channel new_io) +{ + if ((fs->flags & EXT2_FLAG_IMAGE_FILE) == 0) + return EXT2_ET_NOT_IMAGE_FILE; + fs->io = fs->image_io = new_io; + fs->flags |= EXT2_FLAG_DIRTY | EXT2_FLAG_RW | + EXT2_FLAG_BB_DIRTY | EXT2_FLAG_IB_DIRTY; + fs->flags &= ~EXT2_FLAG_IMAGE_FILE; + return 0; +} diff --git a/e2fsprogs/ext2fs/read_bb.c b/e2fsprogs/ext2fs/read_bb.c new file mode 100644 index 000000000..4766157c2 --- /dev/null +++ b/e2fsprogs/ext2fs/read_bb.c @@ -0,0 +1,98 @@ +/* vi: set sw=4 ts=4: */ +/* + * read_bb --- read the bad blocks inode + * + * Copyright (C) 1994 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +struct read_bb_record { + ext2_badblocks_list bb_list; + errcode_t err; +}; + +/* + * Helper function for ext2fs_read_bb_inode() + */ +#ifdef __TURBOC__ +# pragma argsused +#endif +static int mark_bad_block(ext2_filsys fs, blk_t *block_nr, + e2_blkcnt_t blockcnt EXT2FS_ATTR((unused)), + blk_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct read_bb_record *rb = (struct read_bb_record *) priv_data; + + if (blockcnt < 0) + return 0; + + if ((*block_nr < fs->super->s_first_data_block) || + (*block_nr >= fs->super->s_blocks_count)) + return 0; /* Ignore illegal blocks */ + + rb->err = ext2fs_badblocks_list_add(rb->bb_list, *block_nr); + if (rb->err) + return BLOCK_ABORT; + return 0; +} + +/* + * Reads the current bad blocks from the bad blocks inode. + */ +errcode_t ext2fs_read_bb_inode(ext2_filsys fs, ext2_badblocks_list *bb_list) +{ + errcode_t retval; + struct read_bb_record rb; + struct ext2_inode inode; + blk_t numblocks; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (!*bb_list) { + retval = ext2fs_read_inode(fs, EXT2_BAD_INO, &inode); + if (retval) + return retval; + if (inode.i_blocks < 500) + numblocks = (inode.i_blocks / + (fs->blocksize / 512)) + 20; + else + numblocks = 500; + retval = ext2fs_badblocks_list_create(bb_list, numblocks); + if (retval) + return retval; + } + + rb.bb_list = *bb_list; + rb.err = 0; + retval = ext2fs_block_iterate2(fs, EXT2_BAD_INO, 0, 0, + mark_bad_block, &rb); + if (retval) + return retval; + + return rb.err; +} + + diff --git a/e2fsprogs/ext2fs/read_bb_file.c b/e2fsprogs/ext2fs/read_bb_file.c new file mode 100644 index 000000000..831adcc3a --- /dev/null +++ b/e2fsprogs/ext2fs/read_bb_file.c @@ -0,0 +1,98 @@ +/* vi: set sw=4 ts=4: */ +/* + * read_bb_file.c --- read a list of bad blocks from a FILE * + * + * Copyright (C) 1994, 1995, 2000 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * Reads a list of bad blocks from a FILE * + */ +errcode_t ext2fs_read_bb_FILE2(ext2_filsys fs, FILE *f, + ext2_badblocks_list *bb_list, + void *priv_data, + void (*invalid)(ext2_filsys fs, + blk_t blk, + char *badstr, + void *priv_data)) +{ + errcode_t retval; + blk_t blockno; + int count; + char buf[128]; + + if (fs) + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (!*bb_list) { + retval = ext2fs_badblocks_list_create(bb_list, 10); + if (retval) + return retval; + } + + while (!feof (f)) { + if (fgets(buf, sizeof(buf), f) == NULL) + break; + count = sscanf(buf, "%u", &blockno); + if (count <= 0) + continue; + if (fs && + ((blockno < fs->super->s_first_data_block) || + (blockno >= fs->super->s_blocks_count))) { + if (invalid) + (invalid)(fs, blockno, buf, priv_data); + continue; + } + retval = ext2fs_badblocks_list_add(*bb_list, blockno); + if (retval) + return retval; + } + return 0; +} + +static void call_compat_invalid(ext2_filsys fs, blk_t blk, + char *badstr EXT2FS_ATTR((unused)), + void *priv_data) +{ + void (*invalid)(ext2_filsys, blk_t); + + invalid = (void (*)(ext2_filsys, blk_t)) priv_data; + if (invalid) + invalid(fs, blk); +} + + +/* + * Reads a list of bad blocks from a FILE * + */ +errcode_t ext2fs_read_bb_FILE(ext2_filsys fs, FILE *f, + ext2_badblocks_list *bb_list, + void (*invalid)(ext2_filsys fs, blk_t blk)) +{ + return ext2fs_read_bb_FILE2(fs, f, bb_list, (void *) invalid, + call_compat_invalid); +} + + diff --git a/e2fsprogs/ext2fs/res_gdt.c b/e2fsprogs/ext2fs/res_gdt.c new file mode 100644 index 000000000..f7ee8b1fb --- /dev/null +++ b/e2fsprogs/ext2fs/res_gdt.c @@ -0,0 +1,221 @@ +/* vi: set sw=4 ts=4: */ +/* + * res_gdt.c --- reserve blocks for growing the group descriptor table + * during online resizing. + * + * Copyright (C) 2002 Andreas Dilger + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#include +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * Iterate through the groups which hold BACKUP superblock/GDT copies in an + * ext3 filesystem. The counters should be initialized to 1, 5, and 7 before + * calling this for the first time. In a sparse filesystem it will be the + * sequence of powers of 3, 5, and 7: 1, 3, 5, 7, 9, 25, 27, 49, 81, ... + * For a non-sparse filesystem it will be every group: 1, 2, 3, 4, ... + */ +static unsigned int list_backups(ext2_filsys fs, unsigned int *three, + unsigned int *five, unsigned int *seven) +{ + unsigned int *min = three; + int mult = 3; + unsigned int ret; + + if (!(fs->super->s_feature_ro_compat & + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER)) { + ret = *min; + *min += 1; + return ret; + } + + if (*five < *min) { + min = five; + mult = 5; + } + if (*seven < *min) { + min = seven; + mult = 7; + } + + ret = *min; + *min *= mult; + + return ret; +} + +/* + * This code assumes that the reserved blocks have already been marked in-use + * during ext2fs_initialize(), so that they are not allocated for other + * uses before we can add them to the resize inode (which has to come + * after the creation of the inode table). + */ +errcode_t ext2fs_create_resize_inode(ext2_filsys fs) +{ + errcode_t retval, retval2; + struct ext2_super_block *sb; + struct ext2_inode inode; + __u32 *dindir_buf, *gdt_buf; + int rsv_add; + unsigned long long apb, inode_size; + blk_t dindir_blk, rsv_off, gdt_off, gdt_blk; + int dindir_dirty = 0, inode_dirty = 0; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + sb = fs->super; + + retval = ext2fs_get_mem(2 * fs->blocksize, (void *)&dindir_buf); + if (retval) + goto out_free; + gdt_buf = (__u32 *)((char *)dindir_buf + fs->blocksize); + + retval = ext2fs_read_inode(fs, EXT2_RESIZE_INO, &inode); + if (retval) + goto out_free; + + /* Maximum possible file size (we donly use the dindirect blocks) */ + apb = EXT2_ADDR_PER_BLOCK(sb); + rsv_add = fs->blocksize / 512; + if ((dindir_blk = inode.i_block[EXT2_DIND_BLOCK])) { +#ifdef RES_GDT_DEBUG + printf("reading GDT dindir %u\n", dindir_blk); +#endif + retval = ext2fs_read_ind_block(fs, dindir_blk, dindir_buf); + if (retval) + goto out_inode; + } else { + blk_t goal = 3 + sb->s_reserved_gdt_blocks + + fs->desc_blocks + fs->inode_blocks_per_group; + + retval = ext2fs_alloc_block(fs, goal, 0, &dindir_blk); + if (retval) + goto out_free; + inode.i_mode = LINUX_S_IFREG | 0600; + inode.i_links_count = 1; + inode.i_block[EXT2_DIND_BLOCK] = dindir_blk; + inode.i_blocks = rsv_add; + memset(dindir_buf, 0, fs->blocksize); +#ifdef RES_GDT_DEBUG + printf("allocated GDT dindir %u\n", dindir_blk); +#endif + dindir_dirty = inode_dirty = 1; + inode_size = apb*apb + apb + EXT2_NDIR_BLOCKS; + inode_size *= fs->blocksize; + inode.i_size = inode_size & 0xFFFFFFFF; + inode.i_size_high = (inode_size >> 32) & 0xFFFFFFFF; + if(inode.i_size_high) { + sb->s_feature_ro_compat |= + EXT2_FEATURE_RO_COMPAT_LARGE_FILE; + } + inode.i_ctime = time(0); + } + + for (rsv_off = 0, gdt_off = fs->desc_blocks, + gdt_blk = sb->s_first_data_block + 1 + fs->desc_blocks; + rsv_off < sb->s_reserved_gdt_blocks; + rsv_off++, gdt_off++, gdt_blk++) { + unsigned int three = 1, five = 5, seven = 7; + unsigned int grp, last = 0; + int gdt_dirty = 0; + + gdt_off %= apb; + if (!dindir_buf[gdt_off]) { + /* FIXME XXX XXX + blk_t new_blk; + + retval = ext2fs_new_block(fs, gdt_blk, 0, &new_blk); + if (retval) + goto out_free; + if (new_blk != gdt_blk) { + // XXX free block + retval = -1; // XXX + } + */ + gdt_dirty = dindir_dirty = inode_dirty = 1; + memset(gdt_buf, 0, fs->blocksize); + dindir_buf[gdt_off] = gdt_blk; + inode.i_blocks += rsv_add; +#ifdef RES_GDT_DEBUG + printf("added primary GDT block %u at %u[%u]\n", + gdt_blk, dindir_blk, gdt_off); +#endif + } else if (dindir_buf[gdt_off] == gdt_blk) { +#ifdef RES_GDT_DEBUG + printf("reading primary GDT block %u\n", gdt_blk); +#endif + retval = ext2fs_read_ind_block(fs, gdt_blk, gdt_buf); + if (retval) + goto out_dindir; + } else { +#ifdef RES_GDT_DEBUG + printf("bad primary GDT %u != %u at %u[%u]\n", + dindir_buf[gdt_off], gdt_blk,dindir_blk,gdt_off); +#endif + retval = EXT2_ET_RESIZE_INODE_CORRUPT; + goto out_dindir; + } + + while ((grp = list_backups(fs, &three, &five, &seven)) < + fs->group_desc_count) { + blk_t expect = gdt_blk + grp * sb->s_blocks_per_group; + + if (!gdt_buf[last]) { +#ifdef RES_GDT_DEBUG + printf("added backup GDT %u grp %u@%u[%u]\n", + expect, grp, gdt_blk, last); +#endif + gdt_buf[last] = expect; + inode.i_blocks += rsv_add; + gdt_dirty = inode_dirty = 1; + } else if (gdt_buf[last] != expect) { +#ifdef RES_GDT_DEBUG + printf("bad backup GDT %u != %u at %u[%u]\n", + gdt_buf[last], expect, gdt_blk, last); +#endif + retval = EXT2_ET_RESIZE_INODE_CORRUPT; + goto out_dindir; + } + last++; + } + if (gdt_dirty) { +#ifdef RES_GDT_DEBUG + printf("writing primary GDT block %u\n", gdt_blk); +#endif + retval = ext2fs_write_ind_block(fs, gdt_blk, gdt_buf); + if (retval) + goto out_dindir; + } + } + +out_dindir: + if (dindir_dirty) { + retval2 = ext2fs_write_ind_block(fs, dindir_blk, dindir_buf); + if (!retval) + retval = retval2; + } +out_inode: +#ifdef RES_GDT_DEBUG + printf("inode.i_blocks = %u, i_size = %u\n", inode.i_blocks, + inode.i_size); +#endif + if (inode_dirty) { + inode.i_atime = inode.i_mtime = time(0); + retval2 = ext2fs_write_inode(fs, EXT2_RESIZE_INO, &inode); + if (!retval) + retval = retval2; + } +out_free: + ext2fs_free_mem((void *)&dindir_buf); + return retval; +} + diff --git a/e2fsprogs/ext2fs/rs_bitmap.c b/e2fsprogs/ext2fs/rs_bitmap.c new file mode 100644 index 000000000..e932b3c9d --- /dev/null +++ b/e2fsprogs/ext2fs/rs_bitmap.c @@ -0,0 +1,107 @@ +/* vi: set sw=4 ts=4: */ +/* + * rs_bitmap.c --- routine for changing the size of a bitmap + * + * Copyright (C) 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +errcode_t ext2fs_resize_generic_bitmap(__u32 new_end, __u32 new_real_end, + ext2fs_generic_bitmap bmap) +{ + errcode_t retval; + size_t size, new_size; + __u32 bitno; + + if (!bmap) + return EXT2_ET_INVALID_ARGUMENT; + + EXT2_CHECK_MAGIC(bmap, EXT2_ET_MAGIC_GENERIC_BITMAP); + + /* + * If we're expanding the bitmap, make sure all of the new + * parts of the bitmap are zero. + */ + if (new_end > bmap->end) { + bitno = bmap->real_end; + if (bitno > new_end) + bitno = new_end; + for (; bitno > bmap->end; bitno--) + ext2fs_clear_bit(bitno - bmap->start, bmap->bitmap); + } + if (new_real_end == bmap->real_end) { + bmap->end = new_end; + return 0; + } + + size = ((bmap->real_end - bmap->start) / 8) + 1; + new_size = ((new_real_end - bmap->start) / 8) + 1; + + if (size != new_size) { + retval = ext2fs_resize_mem(size, new_size, &bmap->bitmap); + if (retval) + return retval; + } + if (new_size > size) + memset(bmap->bitmap + size, 0, new_size - size); + + bmap->end = new_end; + bmap->real_end = new_real_end; + return 0; +} + +errcode_t ext2fs_resize_inode_bitmap(__u32 new_end, __u32 new_real_end, + ext2fs_inode_bitmap bmap) +{ + errcode_t retval; + + if (!bmap) + return EXT2_ET_INVALID_ARGUMENT; + + EXT2_CHECK_MAGIC(bmap, EXT2_ET_MAGIC_INODE_BITMAP); + + bmap->magic = EXT2_ET_MAGIC_GENERIC_BITMAP; + retval = ext2fs_resize_generic_bitmap(new_end, new_real_end, + bmap); + bmap->magic = EXT2_ET_MAGIC_INODE_BITMAP; + return retval; +} + +errcode_t ext2fs_resize_block_bitmap(__u32 new_end, __u32 new_real_end, + ext2fs_block_bitmap bmap) +{ + errcode_t retval; + + if (!bmap) + return EXT2_ET_INVALID_ARGUMENT; + + EXT2_CHECK_MAGIC(bmap, EXT2_ET_MAGIC_BLOCK_BITMAP); + + bmap->magic = EXT2_ET_MAGIC_GENERIC_BITMAP; + retval = ext2fs_resize_generic_bitmap(new_end, new_real_end, + bmap); + bmap->magic = EXT2_ET_MAGIC_BLOCK_BITMAP; + return retval; +} + diff --git a/e2fsprogs/ext2fs/rw_bitmaps.c b/e2fsprogs/ext2fs/rw_bitmaps.c new file mode 100644 index 000000000..a5782db95 --- /dev/null +++ b/e2fsprogs/ext2fs/rw_bitmaps.c @@ -0,0 +1,296 @@ +/* vi: set sw=4 ts=4: */ +/* + * rw_bitmaps.c --- routines to read and write the inode and block bitmaps. + * + * Copyright (C) 1993, 1994, 1994, 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#ifdef HAVE_SYS_STAT_H +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" +#include "e2image.h" + +#if defined(__powerpc__) && BB_BIG_ENDIAN +/* + * On the PowerPC, the big-endian variant of the ext2 filesystem + * has its bitmaps stored as 32-bit words with bit 0 as the LSB + * of each word. Thus a bitmap with only bit 0 set would be, as + * a string of bytes, 00 00 00 01 00 ... + * To cope with this, we byte-reverse each word of a bitmap if + * we have a big-endian filesystem, that is, if we are *not* + * byte-swapping other word-sized numbers. + */ +#define EXT2_BIG_ENDIAN_BITMAPS +#endif + +#ifdef EXT2_BIG_ENDIAN_BITMAPS +static void ext2fs_swap_bitmap(ext2_filsys fs, char *bitmap, int nbytes) +{ + __u32 *p = (__u32 *) bitmap; + int n; + + for (n = nbytes / sizeof(__u32); n > 0; --n, ++p) + *p = ext2fs_swab32(*p); +} +#endif + +errcode_t ext2fs_write_inode_bitmap(ext2_filsys fs) +{ + dgrp_t i; + size_t nbytes; + errcode_t retval; + char * inode_bitmap = fs->inode_map->bitmap; + char * bitmap_block = NULL; + blk_t blk; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (!(fs->flags & EXT2_FLAG_RW)) + return EXT2_ET_RO_FILSYS; + if (!inode_bitmap) + return 0; + nbytes = (size_t) ((EXT2_INODES_PER_GROUP(fs->super)+7) / 8); + + retval = ext2fs_get_mem(fs->blocksize, &bitmap_block); + if (retval) + return retval; + memset(bitmap_block, 0xff, fs->blocksize); + for (i = 0; i < fs->group_desc_count; i++) { + memcpy(bitmap_block, inode_bitmap, nbytes); + blk = fs->group_desc[i].bg_inode_bitmap; + if (blk) { +#ifdef EXT2_BIG_ENDIAN_BITMAPS + if (!((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_WRITE))) + ext2fs_swap_bitmap(fs, bitmap_block, nbytes); +#endif + retval = io_channel_write_blk(fs->io, blk, 1, + bitmap_block); + if (retval) + return EXT2_ET_INODE_BITMAP_WRITE; + } + inode_bitmap += nbytes; + } + fs->flags &= ~EXT2_FLAG_IB_DIRTY; + ext2fs_free_mem(&bitmap_block); + return 0; +} + +errcode_t ext2fs_write_block_bitmap (ext2_filsys fs) +{ + dgrp_t i; + unsigned int j; + int nbytes; + unsigned int nbits; + errcode_t retval; + char * block_bitmap = fs->block_map->bitmap; + char * bitmap_block = NULL; + blk_t blk; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (!(fs->flags & EXT2_FLAG_RW)) + return EXT2_ET_RO_FILSYS; + if (!block_bitmap) + return 0; + nbytes = EXT2_BLOCKS_PER_GROUP(fs->super) / 8; + retval = ext2fs_get_mem(fs->blocksize, &bitmap_block); + if (retval) + return retval; + memset(bitmap_block, 0xff, fs->blocksize); + for (i = 0; i < fs->group_desc_count; i++) { + memcpy(bitmap_block, block_bitmap, nbytes); + if (i == fs->group_desc_count - 1) { + /* Force bitmap padding for the last group */ + nbits = ((fs->super->s_blocks_count + - fs->super->s_first_data_block) + % EXT2_BLOCKS_PER_GROUP(fs->super)); + if (nbits) + for (j = nbits; j < fs->blocksize * 8; j++) + ext2fs_set_bit(j, bitmap_block); + } + blk = fs->group_desc[i].bg_block_bitmap; + if (blk) { +#ifdef EXT2_BIG_ENDIAN_BITMAPS + if (!((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_WRITE))) + ext2fs_swap_bitmap(fs, bitmap_block, nbytes); +#endif + retval = io_channel_write_blk(fs->io, blk, 1, + bitmap_block); + if (retval) + return EXT2_ET_BLOCK_BITMAP_WRITE; + } + block_bitmap += nbytes; + } + fs->flags &= ~EXT2_FLAG_BB_DIRTY; + ext2fs_free_mem(&bitmap_block); + return 0; +} + +static errcode_t read_bitmaps(ext2_filsys fs, int do_inode, int do_block) +{ + dgrp_t i; + char *block_bitmap = 0, *inode_bitmap = 0; + char *buf; + errcode_t retval; + int block_nbytes = (int) EXT2_BLOCKS_PER_GROUP(fs->super) / 8; + int inode_nbytes = (int) EXT2_INODES_PER_GROUP(fs->super) / 8; + blk_t blk; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + fs->write_bitmaps = ext2fs_write_bitmaps; + + retval = ext2fs_get_mem(strlen(fs->device_name) + 80, &buf); + if (retval) + return retval; + if (do_block) { + ext2fs_free_block_bitmap(fs->block_map); + sprintf(buf, "block bitmap for %s", fs->device_name); + retval = ext2fs_allocate_block_bitmap(fs, buf, &fs->block_map); + if (retval) + goto cleanup; + block_bitmap = fs->block_map->bitmap; + } + if (do_inode) { + ext2fs_free_inode_bitmap(fs->inode_map); + sprintf(buf, "inode bitmap for %s", fs->device_name); + retval = ext2fs_allocate_inode_bitmap(fs, buf, &fs->inode_map); + if (retval) + goto cleanup; + inode_bitmap = fs->inode_map->bitmap; + } + ext2fs_free_mem(&buf); + + if (fs->flags & EXT2_FLAG_IMAGE_FILE) { + if (inode_bitmap) { + blk = (fs->image_header->offset_inodemap / + fs->blocksize); + retval = io_channel_read_blk(fs->image_io, blk, + -(inode_nbytes * fs->group_desc_count), + inode_bitmap); + if (retval) + goto cleanup; + } + if (block_bitmap) { + blk = (fs->image_header->offset_blockmap / + fs->blocksize); + retval = io_channel_read_blk(fs->image_io, blk, + -(block_nbytes * fs->group_desc_count), + block_bitmap); + if (retval) + goto cleanup; + } + return 0; + } + + for (i = 0; i < fs->group_desc_count; i++) { + if (block_bitmap) { + blk = fs->group_desc[i].bg_block_bitmap; + if (blk) { + retval = io_channel_read_blk(fs->io, blk, + -block_nbytes, block_bitmap); + if (retval) { + retval = EXT2_ET_BLOCK_BITMAP_READ; + goto cleanup; + } +#ifdef EXT2_BIG_ENDIAN_BITMAPS + if (!((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_READ))) + ext2fs_swap_bitmap(fs, block_bitmap, block_nbytes); +#endif + } else + memset(block_bitmap, 0, block_nbytes); + block_bitmap += block_nbytes; + } + if (inode_bitmap) { + blk = fs->group_desc[i].bg_inode_bitmap; + if (blk) { + retval = io_channel_read_blk(fs->io, blk, + -inode_nbytes, inode_bitmap); + if (retval) { + retval = EXT2_ET_INODE_BITMAP_READ; + goto cleanup; + } +#ifdef EXT2_BIG_ENDIAN_BITMAPS + if (!((fs->flags & EXT2_FLAG_SWAP_BYTES) || + (fs->flags & EXT2_FLAG_SWAP_BYTES_READ))) + ext2fs_swap_bitmap(fs, inode_bitmap, inode_nbytes); +#endif + } else + memset(inode_bitmap, 0, inode_nbytes); + inode_bitmap += inode_nbytes; + } + } + return 0; + +cleanup: + if (do_block) { + ext2fs_free_mem(&fs->block_map); + } + if (do_inode) { + ext2fs_free_mem(&fs->inode_map); + } + ext2fs_free_mem(&buf); + return retval; +} + +errcode_t ext2fs_read_inode_bitmap (ext2_filsys fs) +{ + return read_bitmaps(fs, 1, 0); +} + +errcode_t ext2fs_read_block_bitmap(ext2_filsys fs) +{ + return read_bitmaps(fs, 0, 1); +} + +errcode_t ext2fs_read_bitmaps(ext2_filsys fs) +{ + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (fs->inode_map && fs->block_map) + return 0; + + return read_bitmaps(fs, !fs->inode_map, !fs->block_map); +} + +errcode_t ext2fs_write_bitmaps(ext2_filsys fs) +{ + errcode_t retval; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (fs->block_map && ext2fs_test_bb_dirty(fs)) { + retval = ext2fs_write_block_bitmap(fs); + if (retval) + return retval; + } + if (fs->inode_map && ext2fs_test_ib_dirty(fs)) { + retval = ext2fs_write_inode_bitmap(fs); + if (retval) + return retval; + } + return 0; +} + diff --git a/e2fsprogs/ext2fs/sparse.c b/e2fsprogs/ext2fs/sparse.c new file mode 100644 index 000000000..b3d3071e9 --- /dev/null +++ b/e2fsprogs/ext2fs/sparse.c @@ -0,0 +1,79 @@ +/* vi: set sw=4 ts=4: */ +/* + * sparse.c --- find the groups in an ext2 filesystem with metadata backups + * + * Copyright (C) 1993, 1994, 1995, 1996 Theodore Ts'o. + * Copyright (C) 2002 Andreas Dilger. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include + +#include "ext2_fs.h" +#include "ext2fsP.h" + +static int test_root(int a, int b) +{ + if (a == 0) + return 1; + while (1) { + if (a == 1) + return 1; + if (a % b) + return 0; + a = a / b; + } +} + +int ext2fs_bg_has_super(ext2_filsys fs, int group_block) +{ + if (!(fs->super->s_feature_ro_compat & + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER)) + return 1; + + if (test_root(group_block, 3) || (test_root(group_block, 5)) || + test_root(group_block, 7)) + return 1; + + return 0; +} + +/* + * Iterate through the groups which hold BACKUP superblock/GDT copies in an + * ext3 filesystem. The counters should be initialized to 1, 5, and 7 before + * calling this for the first time. In a sparse filesystem it will be the + * sequence of powers of 3, 5, and 7: 1, 3, 5, 7, 9, 25, 27, 49, 81, ... + * For a non-sparse filesystem it will be every group: 1, 2, 3, 4, ... + */ +unsigned int ext2fs_list_backups(ext2_filsys fs, unsigned int *three, + unsigned int *five, unsigned int *seven) +{ + unsigned int *min = three; + int mult = 3; + unsigned int ret; + + if (!(fs->super->s_feature_ro_compat & + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER)) { + ret = *min; + *min += 1; + return ret; + } + + if (*five < *min) { + min = five; + mult = 5; + } + if (*seven < *min) { + min = seven; + mult = 7; + } + + ret = *min; + *min *= mult; + + return ret; +} diff --git a/e2fsprogs/ext2fs/swapfs.c b/e2fsprogs/ext2fs/swapfs.c new file mode 100644 index 000000000..2fca3cfbb --- /dev/null +++ b/e2fsprogs/ext2fs/swapfs.c @@ -0,0 +1,236 @@ +/* vi: set sw=4 ts=4: */ +/* + * swapfs.c --- swap ext2 filesystem data structures + * + * Copyright (C) 1995, 1996, 2002 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#include +#include + +#include "ext2_fs.h" +#include "ext2fs.h" +#include "ext2_ext_attr.h" + +#if BB_BIG_ENDIAN +void ext2fs_swap_super(struct ext2_super_block * sb) +{ + int i; + sb->s_inodes_count = ext2fs_swab32(sb->s_inodes_count); + sb->s_blocks_count = ext2fs_swab32(sb->s_blocks_count); + sb->s_r_blocks_count = ext2fs_swab32(sb->s_r_blocks_count); + sb->s_free_blocks_count = ext2fs_swab32(sb->s_free_blocks_count); + sb->s_free_inodes_count = ext2fs_swab32(sb->s_free_inodes_count); + sb->s_first_data_block = ext2fs_swab32(sb->s_first_data_block); + sb->s_log_block_size = ext2fs_swab32(sb->s_log_block_size); + sb->s_log_frag_size = ext2fs_swab32(sb->s_log_frag_size); + sb->s_blocks_per_group = ext2fs_swab32(sb->s_blocks_per_group); + sb->s_frags_per_group = ext2fs_swab32(sb->s_frags_per_group); + sb->s_inodes_per_group = ext2fs_swab32(sb->s_inodes_per_group); + sb->s_mtime = ext2fs_swab32(sb->s_mtime); + sb->s_wtime = ext2fs_swab32(sb->s_wtime); + sb->s_mnt_count = ext2fs_swab16(sb->s_mnt_count); + sb->s_max_mnt_count = ext2fs_swab16(sb->s_max_mnt_count); + sb->s_magic = ext2fs_swab16(sb->s_magic); + sb->s_state = ext2fs_swab16(sb->s_state); + sb->s_errors = ext2fs_swab16(sb->s_errors); + sb->s_minor_rev_level = ext2fs_swab16(sb->s_minor_rev_level); + sb->s_lastcheck = ext2fs_swab32(sb->s_lastcheck); + sb->s_checkinterval = ext2fs_swab32(sb->s_checkinterval); + sb->s_creator_os = ext2fs_swab32(sb->s_creator_os); + sb->s_rev_level = ext2fs_swab32(sb->s_rev_level); + sb->s_def_resuid = ext2fs_swab16(sb->s_def_resuid); + sb->s_def_resgid = ext2fs_swab16(sb->s_def_resgid); + sb->s_first_ino = ext2fs_swab32(sb->s_first_ino); + sb->s_inode_size = ext2fs_swab16(sb->s_inode_size); + sb->s_block_group_nr = ext2fs_swab16(sb->s_block_group_nr); + sb->s_feature_compat = ext2fs_swab32(sb->s_feature_compat); + sb->s_feature_incompat = ext2fs_swab32(sb->s_feature_incompat); + sb->s_feature_ro_compat = ext2fs_swab32(sb->s_feature_ro_compat); + sb->s_algorithm_usage_bitmap = ext2fs_swab32(sb->s_algorithm_usage_bitmap); + sb->s_reserved_gdt_blocks = ext2fs_swab16(sb->s_reserved_gdt_blocks); + sb->s_journal_inum = ext2fs_swab32(sb->s_journal_inum); + sb->s_journal_dev = ext2fs_swab32(sb->s_journal_dev); + sb->s_last_orphan = ext2fs_swab32(sb->s_last_orphan); + sb->s_default_mount_opts = ext2fs_swab32(sb->s_default_mount_opts); + sb->s_first_meta_bg = ext2fs_swab32(sb->s_first_meta_bg); + sb->s_mkfs_time = ext2fs_swab32(sb->s_mkfs_time); + for (i=0; i < 4; i++) + sb->s_hash_seed[i] = ext2fs_swab32(sb->s_hash_seed[i]); + for (i=0; i < 17; i++) + sb->s_jnl_blocks[i] = ext2fs_swab32(sb->s_jnl_blocks[i]); + +} + +void ext2fs_swap_group_desc(struct ext2_group_desc *gdp) +{ + gdp->bg_block_bitmap = ext2fs_swab32(gdp->bg_block_bitmap); + gdp->bg_inode_bitmap = ext2fs_swab32(gdp->bg_inode_bitmap); + gdp->bg_inode_table = ext2fs_swab32(gdp->bg_inode_table); + gdp->bg_free_blocks_count = ext2fs_swab16(gdp->bg_free_blocks_count); + gdp->bg_free_inodes_count = ext2fs_swab16(gdp->bg_free_inodes_count); + gdp->bg_used_dirs_count = ext2fs_swab16(gdp->bg_used_dirs_count); +} + +void ext2fs_swap_ext_attr(char *to, char *from, int bufsize, int has_header) +{ + struct ext2_ext_attr_header *from_header = + (struct ext2_ext_attr_header *)from; + struct ext2_ext_attr_header *to_header = + (struct ext2_ext_attr_header *)to; + struct ext2_ext_attr_entry *from_entry, *to_entry; + char *from_end = (char *)from_header + bufsize; + int n; + + if (to_header != from_header) + memcpy(to_header, from_header, bufsize); + + from_entry = (struct ext2_ext_attr_entry *)from_header; + to_entry = (struct ext2_ext_attr_entry *)to_header; + + if (has_header) { + to_header->h_magic = ext2fs_swab32(from_header->h_magic); + to_header->h_blocks = ext2fs_swab32(from_header->h_blocks); + to_header->h_refcount = ext2fs_swab32(from_header->h_refcount); + for (n=0; n<4; n++) + to_header->h_reserved[n] = + ext2fs_swab32(from_header->h_reserved[n]); + from_entry = (struct ext2_ext_attr_entry *)(from_header+1); + to_entry = (struct ext2_ext_attr_entry *)(to_header+1); + } + + while ((char *)from_entry < from_end && *(__u32 *)from_entry) { + to_entry->e_value_offs = + ext2fs_swab16(from_entry->e_value_offs); + to_entry->e_value_block = + ext2fs_swab32(from_entry->e_value_block); + to_entry->e_value_size = + ext2fs_swab32(from_entry->e_value_size); + from_entry = EXT2_EXT_ATTR_NEXT(from_entry); + to_entry = EXT2_EXT_ATTR_NEXT(to_entry); + } +} + +void ext2fs_swap_inode_full(ext2_filsys fs, struct ext2_inode_large *t, + struct ext2_inode_large *f, int hostorder, + int bufsize) +{ + unsigned i; + int islnk = 0; + __u32 *eaf, *eat; + + if (hostorder && LINUX_S_ISLNK(f->i_mode)) + islnk = 1; + t->i_mode = ext2fs_swab16(f->i_mode); + if (!hostorder && LINUX_S_ISLNK(t->i_mode)) + islnk = 1; + t->i_uid = ext2fs_swab16(f->i_uid); + t->i_size = ext2fs_swab32(f->i_size); + t->i_atime = ext2fs_swab32(f->i_atime); + t->i_ctime = ext2fs_swab32(f->i_ctime); + t->i_mtime = ext2fs_swab32(f->i_mtime); + t->i_dtime = ext2fs_swab32(f->i_dtime); + t->i_gid = ext2fs_swab16(f->i_gid); + t->i_links_count = ext2fs_swab16(f->i_links_count); + t->i_blocks = ext2fs_swab32(f->i_blocks); + t->i_flags = ext2fs_swab32(f->i_flags); + t->i_file_acl = ext2fs_swab32(f->i_file_acl); + t->i_dir_acl = ext2fs_swab32(f->i_dir_acl); + if (!islnk || ext2fs_inode_data_blocks(fs, (struct ext2_inode *)t)) { + for (i = 0; i < EXT2_N_BLOCKS; i++) + t->i_block[i] = ext2fs_swab32(f->i_block[i]); + } else if (t != f) { + for (i = 0; i < EXT2_N_BLOCKS; i++) + t->i_block[i] = f->i_block[i]; + } + t->i_generation = ext2fs_swab32(f->i_generation); + t->i_faddr = ext2fs_swab32(f->i_faddr); + + switch (fs->super->s_creator_os) { + case EXT2_OS_LINUX: + t->osd1.linux1.l_i_reserved1 = + ext2fs_swab32(f->osd1.linux1.l_i_reserved1); + t->osd2.linux2.l_i_frag = f->osd2.linux2.l_i_frag; + t->osd2.linux2.l_i_fsize = f->osd2.linux2.l_i_fsize; + t->osd2.linux2.i_pad1 = ext2fs_swab16(f->osd2.linux2.i_pad1); + t->osd2.linux2.l_i_uid_high = + ext2fs_swab16 (f->osd2.linux2.l_i_uid_high); + t->osd2.linux2.l_i_gid_high = + ext2fs_swab16 (f->osd2.linux2.l_i_gid_high); + t->osd2.linux2.l_i_reserved2 = + ext2fs_swab32(f->osd2.linux2.l_i_reserved2); + break; + case EXT2_OS_HURD: + t->osd1.hurd1.h_i_translator = + ext2fs_swab32 (f->osd1.hurd1.h_i_translator); + t->osd2.hurd2.h_i_frag = f->osd2.hurd2.h_i_frag; + t->osd2.hurd2.h_i_fsize = f->osd2.hurd2.h_i_fsize; + t->osd2.hurd2.h_i_mode_high = + ext2fs_swab16 (f->osd2.hurd2.h_i_mode_high); + t->osd2.hurd2.h_i_uid_high = + ext2fs_swab16 (f->osd2.hurd2.h_i_uid_high); + t->osd2.hurd2.h_i_gid_high = + ext2fs_swab16 (f->osd2.hurd2.h_i_gid_high); + t->osd2.hurd2.h_i_author = + ext2fs_swab32 (f->osd2.hurd2.h_i_author); + break; + case EXT2_OS_MASIX: + t->osd1.masix1.m_i_reserved1 = + ext2fs_swab32(f->osd1.masix1.m_i_reserved1); + t->osd2.masix2.m_i_frag = f->osd2.masix2.m_i_frag; + t->osd2.masix2.m_i_fsize = f->osd2.masix2.m_i_fsize; + t->osd2.masix2.m_pad1 = ext2fs_swab16(f->osd2.masix2.m_pad1); + t->osd2.masix2.m_i_reserved2[0] = + ext2fs_swab32(f->osd2.masix2.m_i_reserved2[0]); + t->osd2.masix2.m_i_reserved2[1] = + ext2fs_swab32(f->osd2.masix2.m_i_reserved2[1]); + break; + } + + if (bufsize < (int) (sizeof(struct ext2_inode) + sizeof(__u16))) + return; /* no i_extra_isize field */ + + t->i_extra_isize = ext2fs_swab16(f->i_extra_isize); + if (t->i_extra_isize > EXT2_INODE_SIZE(fs->super) - + sizeof(struct ext2_inode)) { + /* this is error case: i_extra_size is too large */ + return; + } + + i = sizeof(struct ext2_inode) + t->i_extra_isize + sizeof(__u32); + if (bufsize < (int) i) + return; /* no space for EA magic */ + + eaf = (__u32 *) (((char *) f) + sizeof(struct ext2_inode) + + f->i_extra_isize); + + if (ext2fs_swab32(*eaf) != EXT2_EXT_ATTR_MAGIC) + return; /* it seems no magic here */ + + eat = (__u32 *) (((char *) t) + sizeof(struct ext2_inode) + + f->i_extra_isize); + *eat = ext2fs_swab32(*eaf); + + /* convert EA(s) */ + ext2fs_swap_ext_attr((char *) (eat + 1), (char *) (eaf + 1), + bufsize - sizeof(struct ext2_inode) - + t->i_extra_isize - sizeof(__u32), 0); + +} + +void ext2fs_swap_inode(ext2_filsys fs, struct ext2_inode *t, + struct ext2_inode *f, int hostorder) +{ + ext2fs_swap_inode_full(fs, (struct ext2_inode_large *) t, + (struct ext2_inode_large *) f, hostorder, + sizeof(struct ext2_inode)); +} + +#endif diff --git a/e2fsprogs/ext2fs/test_io.c b/e2fsprogs/ext2fs/test_io.c new file mode 100644 index 000000000..bd74225c0 --- /dev/null +++ b/e2fsprogs/ext2fs/test_io.c @@ -0,0 +1,380 @@ +/* vi: set sw=4 ts=4: */ +/* + * test_io.c --- This is the Test I/O interface. + * + * Copyright (C) 1996 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * For checking structure magic numbers... + */ + +#define EXT2_CHECK_MAGIC(struct, code) \ + if ((struct)->magic != (code)) return (code) + +struct test_private_data { + int magic; + io_channel real; + int flags; + FILE *outfile; + unsigned long block; + int read_abort_count, write_abort_count; + void (*read_blk)(unsigned long block, int count, errcode_t err); + void (*write_blk)(unsigned long block, int count, errcode_t err); + void (*set_blksize)(int blksize, errcode_t err); + void (*write_byte)(unsigned long block, int count, errcode_t err); +}; + +static errcode_t test_open(const char *name, int flags, io_channel *channel); +static errcode_t test_close(io_channel channel); +static errcode_t test_set_blksize(io_channel channel, int blksize); +static errcode_t test_read_blk(io_channel channel, unsigned long block, + int count, void *data); +static errcode_t test_write_blk(io_channel channel, unsigned long block, + int count, const void *data); +static errcode_t test_flush(io_channel channel); +static errcode_t test_write_byte(io_channel channel, unsigned long offset, + int count, const void *buf); +static errcode_t test_set_option(io_channel channel, const char *option, + const char *arg); + +static struct struct_io_manager struct_test_manager = { + EXT2_ET_MAGIC_IO_MANAGER, + "Test I/O Manager", + test_open, + test_close, + test_set_blksize, + test_read_blk, + test_write_blk, + test_flush, + test_write_byte, + test_set_option +}; + +io_manager test_io_manager = &struct_test_manager; + +/* + * These global variable can be set by the test program as + * necessary *before* calling test_open + */ +io_manager test_io_backing_manager = 0; +void (*test_io_cb_read_blk) + (unsigned long block, int count, errcode_t err) = 0; +void (*test_io_cb_write_blk) + (unsigned long block, int count, errcode_t err) = 0; +void (*test_io_cb_set_blksize) + (int blksize, errcode_t err) = 0; +void (*test_io_cb_write_byte) + (unsigned long block, int count, errcode_t err) = 0; + +/* + * Test flags + */ +#define TEST_FLAG_READ 0x01 +#define TEST_FLAG_WRITE 0x02 +#define TEST_FLAG_SET_BLKSIZE 0x04 +#define TEST_FLAG_FLUSH 0x08 +#define TEST_FLAG_DUMP 0x10 +#define TEST_FLAG_SET_OPTION 0x20 + +static void test_dump_block(io_channel channel, + struct test_private_data *data, + unsigned long block, const void *buf) +{ + const unsigned char *cp; + FILE *f = data->outfile; + int i; + unsigned long cksum = 0; + + for (i=0, cp = buf; i < channel->block_size; i++, cp++) { + cksum += *cp; + } + fprintf(f, "Contents of block %lu, checksum %08lu:\n", block, cksum); + for (i=0, cp = buf; i < channel->block_size; i++, cp++) { + if ((i % 16) == 0) + fprintf(f, "%04x: ", i); + fprintf(f, "%02x%c", *cp, ((i % 16) == 15) ? '\n' : ' '); + } +} + +static void test_abort(io_channel channel, unsigned long block) +{ + struct test_private_data *data; + FILE *f; + + data = (struct test_private_data *) channel->private_data; + f = data->outfile; + test_flush(channel); + + fprintf(f, "Aborting due to I/O to block %lu\n", block); + fflush(f); + abort(); +} + +static errcode_t test_open(const char *name, int flags, io_channel *channel) +{ + io_channel io = NULL; + struct test_private_data *data = NULL; + errcode_t retval; + char *value; + + if (name == 0) + return EXT2_ET_BAD_DEVICE_NAME; + retval = ext2fs_get_mem(sizeof(struct struct_io_channel), &io); + if (retval) + return retval; + memset(io, 0, sizeof(struct struct_io_channel)); + io->magic = EXT2_ET_MAGIC_IO_CHANNEL; + retval = ext2fs_get_mem(sizeof(struct test_private_data), &data); + if (retval) { + retval = EXT2_ET_NO_MEMORY; + goto cleanup; + } + io->manager = test_io_manager; + retval = ext2fs_get_mem(strlen(name)+1, &io->name); + if (retval) + goto cleanup; + + strcpy(io->name, name); + io->private_data = data; + io->block_size = 1024; + io->read_error = 0; + io->write_error = 0; + io->refcount = 1; + + memset(data, 0, sizeof(struct test_private_data)); + data->magic = EXT2_ET_MAGIC_TEST_IO_CHANNEL; + if (test_io_backing_manager) { + retval = test_io_backing_manager->open(name, flags, + &data->real); + if (retval) + goto cleanup; + } else + data->real = 0; + data->read_blk = test_io_cb_read_blk; + data->write_blk = test_io_cb_write_blk; + data->set_blksize = test_io_cb_set_blksize; + data->write_byte = test_io_cb_write_byte; + + data->outfile = NULL; + if ((value = getenv("TEST_IO_LOGFILE")) != NULL) + data->outfile = fopen(value, "w"); + if (!data->outfile) + data->outfile = stderr; + + data->flags = 0; + if ((value = getenv("TEST_IO_FLAGS")) != NULL) + data->flags = strtoul(value, NULL, 0); + + data->block = 0; + if ((value = getenv("TEST_IO_BLOCK")) != NULL) + data->block = strtoul(value, NULL, 0); + + data->read_abort_count = 0; + if ((value = getenv("TEST_IO_READ_ABORT")) != NULL) + data->read_abort_count = strtoul(value, NULL, 0); + + data->write_abort_count = 0; + if ((value = getenv("TEST_IO_WRITE_ABORT")) != NULL) + data->write_abort_count = strtoul(value, NULL, 0); + + *channel = io; + return 0; + +cleanup: + ext2fs_free_mem(&io); + ext2fs_free_mem(&data); + return retval; +} + +static errcode_t test_close(io_channel channel) +{ + struct test_private_data *data; + errcode_t retval = 0; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct test_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_TEST_IO_CHANNEL); + + if (--channel->refcount > 0) + return 0; + + if (data->real) + retval = io_channel_close(data->real); + + if (data->outfile && data->outfile != stderr) + fclose(data->outfile); + + ext2fs_free_mem(&channel->private_data); + ext2fs_free_mem(&channel->name); + ext2fs_free_mem(&channel); + return retval; +} + +static errcode_t test_set_blksize(io_channel channel, int blksize) +{ + struct test_private_data *data; + errcode_t retval = 0; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct test_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_TEST_IO_CHANNEL); + + if (data->real) + retval = io_channel_set_blksize(data->real, blksize); + if (data->set_blksize) + data->set_blksize(blksize, retval); + if (data->flags & TEST_FLAG_SET_BLKSIZE) + fprintf(data->outfile, + "Test_io: set_blksize(%d) returned %s\n", + blksize, retval ? error_message(retval) : "OK"); + channel->block_size = blksize; + return retval; +} + + +static errcode_t test_read_blk(io_channel channel, unsigned long block, + int count, void *buf) +{ + struct test_private_data *data; + errcode_t retval = 0; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct test_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_TEST_IO_CHANNEL); + + if (data->real) + retval = io_channel_read_blk(data->real, block, count, buf); + if (data->read_blk) + data->read_blk(block, count, retval); + if (data->flags & TEST_FLAG_READ) + fprintf(data->outfile, + "Test_io: read_blk(%lu, %d) returned %s\n", + block, count, retval ? error_message(retval) : "OK"); + if (data->block && data->block == block) { + if (data->flags & TEST_FLAG_DUMP) + test_dump_block(channel, data, block, buf); + if (--data->read_abort_count == 0) + test_abort(channel, block); + } + return retval; +} + +static errcode_t test_write_blk(io_channel channel, unsigned long block, + int count, const void *buf) +{ + struct test_private_data *data; + errcode_t retval = 0; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct test_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_TEST_IO_CHANNEL); + + if (data->real) + retval = io_channel_write_blk(data->real, block, count, buf); + if (data->write_blk) + data->write_blk(block, count, retval); + if (data->flags & TEST_FLAG_WRITE) + fprintf(data->outfile, + "Test_io: write_blk(%lu, %d) returned %s\n", + block, count, retval ? error_message(retval) : "OK"); + if (data->block && data->block == block) { + if (data->flags & TEST_FLAG_DUMP) + test_dump_block(channel, data, block, buf); + if (--data->write_abort_count == 0) + test_abort(channel, block); + } + return retval; +} + +static errcode_t test_write_byte(io_channel channel, unsigned long offset, + int count, const void *buf) +{ + struct test_private_data *data; + errcode_t retval = 0; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct test_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_TEST_IO_CHANNEL); + + if (data->real && data->real->manager->write_byte) + retval = io_channel_write_byte(data->real, offset, count, buf); + if (data->write_byte) + data->write_byte(offset, count, retval); + if (data->flags & TEST_FLAG_WRITE) + fprintf(data->outfile, + "Test_io: write_byte(%lu, %d) returned %s\n", + offset, count, retval ? error_message(retval) : "OK"); + return retval; +} + +/* + * Flush data buffers to disk. + */ +static errcode_t test_flush(io_channel channel) +{ + struct test_private_data *data; + errcode_t retval = 0; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct test_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_TEST_IO_CHANNEL); + + if (data->real) + retval = io_channel_flush(data->real); + + if (data->flags & TEST_FLAG_FLUSH) + fprintf(data->outfile, "Test_io: flush() returned %s\n", + retval ? error_message(retval) : "OK"); + + return retval; +} + +static errcode_t test_set_option(io_channel channel, const char *option, + const char *arg) +{ + struct test_private_data *data; + errcode_t retval = 0; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct test_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_TEST_IO_CHANNEL); + + + if (data->flags & TEST_FLAG_SET_OPTION) + fprintf(data->outfile, "Test_io: set_option(%s, %s) ", + option, arg); + if (data->real && data->real->manager->set_option) { + retval = (data->real->manager->set_option)(data->real, + option, arg); + if (data->flags & TEST_FLAG_SET_OPTION) + fprintf(data->outfile, "returned %s\n", + retval ? error_message(retval) : "OK"); + } else { + if (data->flags & TEST_FLAG_SET_OPTION) + fprintf(data->outfile, "not implemented\n"); + } + return retval; +} diff --git a/e2fsprogs/ext2fs/unix_io.c b/e2fsprogs/ext2fs/unix_io.c new file mode 100644 index 000000000..474f07340 --- /dev/null +++ b/e2fsprogs/ext2fs/unix_io.c @@ -0,0 +1,703 @@ +/* vi: set sw=4 ts=4: */ +/* + * unix_io.c --- This is the Unix (well, really POSIX) implementation + * of the I/O manager. + * + * Implements a one-block write-through cache. + * + * Includes support for Windows NT support under Cygwin. + * + * Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, + * 2002 by Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif +#if HAVE_ERRNO_H +#include +#endif +#include +#include +#ifdef __linux__ +#include +#endif +#if HAVE_SYS_STAT_H +#include +#endif +#if HAVE_SYS_TYPES_H +#include +#endif +#include + +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * For checking structure magic numbers... + */ + +#define EXT2_CHECK_MAGIC(struct, code) \ + if ((struct)->magic != (code)) return (code) + +struct unix_cache { + char *buf; + unsigned long block; + int access_time; + unsigned dirty:1; + unsigned in_use:1; +}; + +#define CACHE_SIZE 8 +#define WRITE_DIRECT_SIZE 4 /* Must be smaller than CACHE_SIZE */ +#define READ_DIRECT_SIZE 4 /* Should be smaller than CACHE_SIZE */ + +struct unix_private_data { + int magic; + int dev; + int flags; + int access_time; + ext2_loff_t offset; + struct unix_cache cache[CACHE_SIZE]; +}; + +static errcode_t unix_open(const char *name, int flags, io_channel *channel); +static errcode_t unix_close(io_channel channel); +static errcode_t unix_set_blksize(io_channel channel, int blksize); +static errcode_t unix_read_blk(io_channel channel, unsigned long block, + int count, void *data); +static errcode_t unix_write_blk(io_channel channel, unsigned long block, + int count, const void *data); +static errcode_t unix_flush(io_channel channel); +static errcode_t unix_write_byte(io_channel channel, unsigned long offset, + int size, const void *data); +static errcode_t unix_set_option(io_channel channel, const char *option, + const char *arg); + +static void reuse_cache(io_channel channel, struct unix_private_data *data, + struct unix_cache *cache, unsigned long block); + +/* __FreeBSD_kernel__ is defined by GNU/kFreeBSD - the FreeBSD kernel + * does not know buffered block devices - everything is raw. */ +#if defined(__CYGWIN__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +#define NEED_BOUNCE_BUFFER +#else +#undef NEED_BOUNCE_BUFFER +#endif + +static struct struct_io_manager struct_unix_manager = { + EXT2_ET_MAGIC_IO_MANAGER, + "Unix I/O Manager", + unix_open, + unix_close, + unix_set_blksize, + unix_read_blk, + unix_write_blk, + unix_flush, +#ifdef NEED_BOUNCE_BUFFER + 0, +#else + unix_write_byte, +#endif + unix_set_option +}; + +io_manager unix_io_manager = &struct_unix_manager; + +/* + * Here are the raw I/O functions + */ +#ifndef NEED_BOUNCE_BUFFER +static errcode_t raw_read_blk(io_channel channel, + struct unix_private_data *data, + unsigned long block, + int count, void *buf) +{ + errcode_t retval; + ssize_t size; + ext2_loff_t location; + int actual = 0; + + size = (count < 0) ? -count : count * channel->block_size; + location = ((ext2_loff_t) block * channel->block_size) + data->offset; + if (ext2fs_llseek(data->dev, location, SEEK_SET) != location) { + retval = errno ? errno : EXT2_ET_LLSEEK_FAILED; + goto error_out; + } + actual = read(data->dev, buf, size); + if (actual != size) { + if (actual < 0) + actual = 0; + retval = EXT2_ET_SHORT_READ; + goto error_out; + } + return 0; + +error_out: + memset((char *) buf+actual, 0, size-actual); + if (channel->read_error) + retval = (channel->read_error)(channel, block, count, buf, + size, actual, retval); + return retval; +} +#else /* NEED_BOUNCE_BUFFER */ +/* + * Windows and FreeBSD block devices only allow sector alignment IO in offset and size + */ +static errcode_t raw_read_blk(io_channel channel, + struct unix_private_data *data, + unsigned long block, + int count, void *buf) +{ + errcode_t retval; + size_t size, alignsize, fragment; + ext2_loff_t location; + int total = 0, actual; +#define BLOCKALIGN 512 + char sector[BLOCKALIGN]; + + size = (count < 0) ? -count : count * channel->block_size; + location = ((ext2_loff_t) block * channel->block_size) + data->offset; +#ifdef DEBUG + printf("count=%d, size=%d, block=%d, blk_size=%d, location=%lx\n", + count, size, block, channel->block_size, location); +#endif + if (ext2fs_llseek(data->dev, location, SEEK_SET) != location) { + retval = errno ? errno : EXT2_ET_LLSEEK_FAILED; + goto error_out; + } + fragment = size % BLOCKALIGN; + alignsize = size - fragment; + if (alignsize) { + actual = read(data->dev, buf, alignsize); + if (actual != alignsize) + goto short_read; + } + if (fragment) { + actual = read(data->dev, sector, BLOCKALIGN); + if (actual != BLOCKALIGN) + goto short_read; + memcpy(buf+alignsize, sector, fragment); + } + return 0; + +short_read: + if (actual>0) + total += actual; + retval = EXT2_ET_SHORT_READ; + +error_out: + memset((char *) buf+total, 0, size-actual); + if (channel->read_error) + retval = (channel->read_error)(channel, block, count, buf, + size, actual, retval); + return retval; +} +#endif + +static errcode_t raw_write_blk(io_channel channel, + struct unix_private_data *data, + unsigned long block, + int count, const void *buf) +{ + ssize_t size; + ext2_loff_t location; + int actual = 0; + errcode_t retval; + + if (count == 1) + size = channel->block_size; + else { + if (count < 0) + size = -count; + else + size = count * channel->block_size; + } + + location = ((ext2_loff_t) block * channel->block_size) + data->offset; + if (ext2fs_llseek(data->dev, location, SEEK_SET) != location) { + retval = errno ? errno : EXT2_ET_LLSEEK_FAILED; + goto error_out; + } + + actual = write(data->dev, buf, size); + if (actual != size) { + retval = EXT2_ET_SHORT_WRITE; + goto error_out; + } + return 0; + +error_out: + if (channel->write_error) + retval = (channel->write_error)(channel, block, count, buf, + size, actual, retval); + return retval; +} + + +/* + * Here we implement the cache functions + */ + +/* Allocate the cache buffers */ +static errcode_t alloc_cache(io_channel channel, + struct unix_private_data *data) +{ + errcode_t retval; + struct unix_cache *cache; + int i; + + data->access_time = 0; + for (i=0, cache = data->cache; i < CACHE_SIZE; i++, cache++) { + cache->block = 0; + cache->access_time = 0; + cache->dirty = 0; + cache->in_use = 0; + if ((retval = ext2fs_get_mem(channel->block_size, + &cache->buf))) + return retval; + } + return 0; +} + +/* Free the cache buffers */ +static void free_cache(struct unix_private_data *data) +{ + struct unix_cache *cache; + int i; + + data->access_time = 0; + for (i=0, cache = data->cache; i < CACHE_SIZE; i++, cache++) { + cache->block = 0; + cache->access_time = 0; + cache->dirty = 0; + cache->in_use = 0; + ext2fs_free_mem(&cache->buf); + cache->buf = 0; + } +} + +#ifndef NO_IO_CACHE +/* + * Try to find a block in the cache. If the block is not found, and + * eldest is a non-zero pointer, then fill in eldest with the cache + * entry to that should be reused. + */ +static struct unix_cache *find_cached_block(struct unix_private_data *data, + unsigned long block, + struct unix_cache **eldest) +{ + struct unix_cache *cache, *unused_cache, *oldest_cache; + int i; + + unused_cache = oldest_cache = 0; + for (i=0, cache = data->cache; i < CACHE_SIZE; i++, cache++) { + if (!cache->in_use) { + if (!unused_cache) + unused_cache = cache; + continue; + } + if (cache->block == block) { + cache->access_time = ++data->access_time; + return cache; + } + if (!oldest_cache || + (cache->access_time < oldest_cache->access_time)) + oldest_cache = cache; + } + if (eldest) + *eldest = (unused_cache) ? unused_cache : oldest_cache; + return 0; +} + +/* + * Reuse a particular cache entry for another block. + */ +static void reuse_cache(io_channel channel, struct unix_private_data *data, + struct unix_cache *cache, unsigned long block) +{ + if (cache->dirty && cache->in_use) + raw_write_blk(channel, data, cache->block, 1, cache->buf); + + cache->in_use = 1; + cache->dirty = 0; + cache->block = block; + cache->access_time = ++data->access_time; +} + +/* + * Flush all of the blocks in the cache + */ +static errcode_t flush_cached_blocks(io_channel channel, + struct unix_private_data *data, + int invalidate) + +{ + struct unix_cache *cache; + errcode_t retval, retval2; + int i; + + retval2 = 0; + for (i=0, cache = data->cache; i < CACHE_SIZE; i++, cache++) { + if (!cache->in_use) + continue; + + if (invalidate) + cache->in_use = 0; + + if (!cache->dirty) + continue; + + retval = raw_write_blk(channel, data, + cache->block, 1, cache->buf); + if (retval) + retval2 = retval; + else + cache->dirty = 0; + } + return retval2; +} +#endif /* NO_IO_CACHE */ + +static errcode_t unix_open(const char *name, int flags, io_channel *channel) +{ + io_channel io = NULL; + struct unix_private_data *data = NULL; + errcode_t retval; + int open_flags; + struct stat st; +#ifdef __linux__ + struct utsname ut; +#endif + + if (name == 0) + return EXT2_ET_BAD_DEVICE_NAME; + retval = ext2fs_get_mem(sizeof(struct struct_io_channel), &io); + if (retval) + return retval; + memset(io, 0, sizeof(struct struct_io_channel)); + io->magic = EXT2_ET_MAGIC_IO_CHANNEL; + retval = ext2fs_get_mem(sizeof(struct unix_private_data), &data); + if (retval) + goto cleanup; + + io->manager = unix_io_manager; + retval = ext2fs_get_mem(strlen(name)+1, &io->name); + if (retval) + goto cleanup; + + strcpy(io->name, name); + io->private_data = data; + io->block_size = 1024; + io->read_error = 0; + io->write_error = 0; + io->refcount = 1; + + memset(data, 0, sizeof(struct unix_private_data)); + data->magic = EXT2_ET_MAGIC_UNIX_IO_CHANNEL; + + if ((retval = alloc_cache(io, data))) + goto cleanup; + + open_flags = (flags & IO_FLAG_RW) ? O_RDWR : O_RDONLY; +#ifdef CONFIG_LFS + data->dev = open64(io->name, open_flags); +#else + data->dev = open(io->name, open_flags); +#endif + if (data->dev < 0) { + retval = errno; + goto cleanup; + } + +#ifdef __linux__ +#undef RLIM_INFINITY +#if (defined(__alpha__) || ((defined(__sparc__) || defined(__mips__)) && (SIZEOF_LONG == 4))) +#define RLIM_INFINITY ((unsigned long)(~0UL>>1)) +#else +#define RLIM_INFINITY (~0UL) +#endif + /* + * Work around a bug in 2.4.10-2.4.18 kernels where writes to + * block devices are wrongly getting hit by the filesize + * limit. This workaround isn't perfect, since it won't work + * if glibc wasn't built against 2.2 header files. (Sigh.) + * + */ + if ((flags & IO_FLAG_RW) && + (uname(&ut) == 0) && + ((ut.release[0] == '2') && (ut.release[1] == '.') && + (ut.release[2] == '4') && (ut.release[3] == '.') && + (ut.release[4] == '1') && (ut.release[5] >= '0') && + (ut.release[5] < '8')) && + (fstat(data->dev, &st) == 0) && + (S_ISBLK(st.st_mode))) { + struct rlimit rlim; + + rlim.rlim_cur = rlim.rlim_max = (unsigned long) RLIM_INFINITY; + setrlimit(RLIMIT_FSIZE, &rlim); + getrlimit(RLIMIT_FSIZE, &rlim); + if (((unsigned long) rlim.rlim_cur) < + ((unsigned long) rlim.rlim_max)) { + rlim.rlim_cur = rlim.rlim_max; + setrlimit(RLIMIT_FSIZE, &rlim); + } + } +#endif + *channel = io; + return 0; + +cleanup: + if (data) { + free_cache(data); + ext2fs_free_mem(&data); + } + ext2fs_free_mem(&io); + return retval; +} + +static errcode_t unix_close(io_channel channel) +{ + struct unix_private_data *data; + errcode_t retval = 0; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct unix_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL); + + if (--channel->refcount > 0) + return 0; + +#ifndef NO_IO_CACHE + retval = flush_cached_blocks(channel, data, 0); +#endif + + if (close(data->dev) < 0) + retval = errno; + free_cache(data); + + ext2fs_free_mem(&channel->private_data); + ext2fs_free_mem(&channel->name); + ext2fs_free_mem(&channel); + return retval; +} + +static errcode_t unix_set_blksize(io_channel channel, int blksize) +{ + struct unix_private_data *data; + errcode_t retval; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct unix_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL); + + if (channel->block_size != blksize) { +#ifndef NO_IO_CACHE + if ((retval = flush_cached_blocks(channel, data, 0))) + return retval; +#endif + + channel->block_size = blksize; + free_cache(data); + if ((retval = alloc_cache(channel, data))) + return retval; + } + return 0; +} + + +static errcode_t unix_read_blk(io_channel channel, unsigned long block, + int count, void *buf) +{ + struct unix_private_data *data; + struct unix_cache *cache, *reuse[READ_DIRECT_SIZE]; + errcode_t retval; + char *cp; + int i, j; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct unix_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL); + +#ifdef NO_IO_CACHE + return raw_read_blk(channel, data, block, count, buf); +#else + /* + * If we're doing an odd-sized read or a very large read, + * flush out the cache and then do a direct read. + */ + if (count < 0 || count > WRITE_DIRECT_SIZE) { + if ((retval = flush_cached_blocks(channel, data, 0))) + return retval; + return raw_read_blk(channel, data, block, count, buf); + } + + cp = buf; + while (count > 0) { + /* If it's in the cache, use it! */ + if ((cache = find_cached_block(data, block, &reuse[0]))) { +#ifdef DEBUG + printf("Using cached block %d\n", block); +#endif + memcpy(cp, cache->buf, channel->block_size); + count--; + block++; + cp += channel->block_size; + continue; + } + /* + * Find the number of uncached blocks so we can do a + * single read request + */ + for (i=1; i < count; i++) + if (find_cached_block(data, block+i, &reuse[i])) + break; +#ifdef DEBUG + printf("Reading %d blocks starting at %d\n", i, block); +#endif + if ((retval = raw_read_blk(channel, data, block, i, cp))) + return retval; + + /* Save the results in the cache */ + for (j=0; j < i; j++) { + count--; + cache = reuse[j]; + reuse_cache(channel, data, cache, block++); + memcpy(cache->buf, cp, channel->block_size); + cp += channel->block_size; + } + } + return 0; +#endif /* NO_IO_CACHE */ +} + +static errcode_t unix_write_blk(io_channel channel, unsigned long block, + int count, const void *buf) +{ + struct unix_private_data *data; + struct unix_cache *cache, *reuse; + errcode_t retval = 0; + const char *cp; + int writethrough; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct unix_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL); + +#ifdef NO_IO_CACHE + return raw_write_blk(channel, data, block, count, buf); +#else + /* + * If we're doing an odd-sized write or a very large write, + * flush out the cache completely and then do a direct write. + */ + if (count < 0 || count > WRITE_DIRECT_SIZE) { + if ((retval = flush_cached_blocks(channel, data, 1))) + return retval; + return raw_write_blk(channel, data, block, count, buf); + } + + /* + * For a moderate-sized multi-block write, first force a write + * if we're in write-through cache mode, and then fill the + * cache with the blocks. + */ + writethrough = channel->flags & CHANNEL_FLAGS_WRITETHROUGH; + if (writethrough) + retval = raw_write_blk(channel, data, block, count, buf); + + cp = buf; + while (count > 0) { + cache = find_cached_block(data, block, &reuse); + if (!cache) { + cache = reuse; + reuse_cache(channel, data, cache, block); + } + memcpy(cache->buf, cp, channel->block_size); + cache->dirty = !writethrough; + count--; + block++; + cp += channel->block_size; + } + return retval; +#endif /* NO_IO_CACHE */ +} + +static errcode_t unix_write_byte(io_channel channel, unsigned long offset, + int size, const void *buf) +{ + struct unix_private_data *data; + errcode_t retval = 0; + ssize_t actual; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct unix_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL); + +#ifndef NO_IO_CACHE + /* + * Flush out the cache completely + */ + if ((retval = flush_cached_blocks(channel, data, 1))) + return retval; +#endif + + if (lseek(data->dev, offset + data->offset, SEEK_SET) < 0) + return errno; + + actual = write(data->dev, buf, size); + if (actual != size) + return EXT2_ET_SHORT_WRITE; + + return 0; +} + +/* + * Flush data buffers to disk. + */ +static errcode_t unix_flush(io_channel channel) +{ + struct unix_private_data *data; + errcode_t retval = 0; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct unix_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL); + +#ifndef NO_IO_CACHE + retval = flush_cached_blocks(channel, data, 0); +#endif + fsync(data->dev); + return retval; +} + +static errcode_t unix_set_option(io_channel channel, const char *option, + const char *arg) +{ + struct unix_private_data *data; + unsigned long tmp; + char *end; + + EXT2_CHECK_MAGIC(channel, EXT2_ET_MAGIC_IO_CHANNEL); + data = (struct unix_private_data *) channel->private_data; + EXT2_CHECK_MAGIC(data, EXT2_ET_MAGIC_UNIX_IO_CHANNEL); + + if (!strcmp(option, "offset")) { + if (!arg) + return EXT2_ET_INVALID_ARGUMENT; + + tmp = strtoul(arg, &end, 0); + if (*end) + return EXT2_ET_INVALID_ARGUMENT; + data->offset = tmp; + return 0; + } + return EXT2_ET_INVALID_ARGUMENT; +} diff --git a/e2fsprogs/ext2fs/unlink.c b/e2fsprogs/ext2fs/unlink.c new file mode 100644 index 000000000..83ac2713e --- /dev/null +++ b/e2fsprogs/ext2fs/unlink.c @@ -0,0 +1,100 @@ +/* vi: set sw=4 ts=4: */ +/* + * unlink.c --- delete links in a ext2fs directory + * + * Copyright (C) 1993, 1994, 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#if HAVE_UNISTD_H +#include +#endif + +#include "ext2_fs.h" +#include "ext2fs.h" + +struct link_struct { + const char *name; + int namelen; + ext2_ino_t inode; + int flags; + struct ext2_dir_entry *prev; + int done; +}; + +#ifdef __TURBOC__ +# pragma argsused +#endif +static int unlink_proc(struct ext2_dir_entry *dirent, + int offset EXT2FS_ATTR((unused)), + int blocksize EXT2FS_ATTR((unused)), + char *buf EXT2FS_ATTR((unused)), + void *priv_data) +{ + struct link_struct *ls = (struct link_struct *) priv_data; + struct ext2_dir_entry *prev; + + prev = ls->prev; + ls->prev = dirent; + + if (ls->name) { + if ((dirent->name_len & 0xFF) != ls->namelen) + return 0; + if (strncmp(ls->name, dirent->name, dirent->name_len & 0xFF)) + return 0; + } + if (ls->inode) { + if (dirent->inode != ls->inode) + return 0; + } else { + if (!dirent->inode) + return 0; + } + + if (prev) + prev->rec_len += dirent->rec_len; + else + dirent->inode = 0; + ls->done++; + return DIRENT_ABORT|DIRENT_CHANGED; +} + +#ifdef __TURBOC__ + #pragma argsused +#endif +errcode_t ext2fs_unlink(ext2_filsys fs, ext2_ino_t dir, + const char *name, ext2_ino_t ino, + int flags EXT2FS_ATTR((unused))) +{ + errcode_t retval; + struct link_struct ls; + + EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); + + if (!name && !ino) + return EXT2_ET_INVALID_ARGUMENT; + + if (!(fs->flags & EXT2_FLAG_RW)) + return EXT2_ET_RO_FILSYS; + + ls.name = name; + ls.namelen = name ? strlen(name) : 0; + ls.inode = ino; + ls.flags = 0; + ls.done = 0; + ls.prev = 0; + + retval = ext2fs_dir_iterate(fs, dir, DIRENT_FLAG_INCLUDE_EMPTY, + 0, unlink_proc, &ls); + if (retval) + return retval; + + return (ls.done) ? 0 : EXT2_ET_DIR_NO_SPACE; +} + diff --git a/e2fsprogs/ext2fs/valid_blk.c b/e2fsprogs/ext2fs/valid_blk.c new file mode 100644 index 000000000..8ed77ae2a --- /dev/null +++ b/e2fsprogs/ext2fs/valid_blk.c @@ -0,0 +1,57 @@ +/* vi: set sw=4 ts=4: */ +/* + * valid_blk.c --- does the inode have valid blocks? + * + * Copyright 1997 by Theodore Ts'o + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + * + */ + +#include +#if HAVE_UNISTD_H +#include +#endif +#include +#include + +#include "ext2_fs.h" +#include "ext2fs.h" + +/* + * This function returns 1 if the inode's block entries actually + * contain block entries. + */ +int ext2fs_inode_has_valid_blocks(struct ext2_inode *inode) +{ + /* + * Only directories, regular files, and some symbolic links + * have valid block entries. + */ + if (!LINUX_S_ISDIR(inode->i_mode) && !LINUX_S_ISREG(inode->i_mode) && + !LINUX_S_ISLNK(inode->i_mode)) + return 0; + + /* + * If the symbolic link is a "fast symlink", then the symlink + * target is stored in the block entries. + */ + if (LINUX_S_ISLNK (inode->i_mode)) { + if (inode->i_file_acl == 0) { + /* With no EA block, we can rely on i_blocks */ + if (inode->i_blocks == 0) + return 0; + } else { + /* With an EA block, life gets more tricky */ + if (inode->i_size >= EXT2_N_BLOCKS*4) + return 1; /* definitely using i_block[] */ + if (inode->i_size > 4 && inode->i_block[1] == 0) + return 1; /* definitely using i_block[] */ + return 0; /* Probably a fast symlink */ + } + } + return 1; +} diff --git a/e2fsprogs/ext2fs/version.c b/e2fsprogs/ext2fs/version.c new file mode 100644 index 000000000..d2981e867 --- /dev/null +++ b/e2fsprogs/ext2fs/version.c @@ -0,0 +1,51 @@ +/* vi: set sw=4 ts=4: */ +/* + * version.c --- Return the version of the ext2 library + * + * Copyright (C) 1997 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#if HAVE_UNISTD_H +#include +#endif +#include +#include +#include + +#include "ext2_fs.h" +#include "ext2fs.h" + +static const char *lib_version = E2FSPROGS_VERSION; +static const char *lib_date = E2FSPROGS_DATE; + +int ext2fs_parse_version_string(const char *ver_string) +{ + const char *cp; + int version = 0; + + for (cp = ver_string; *cp; cp++) { + if (*cp == '.') + continue; + if (!isdigit(*cp)) + break; + version = (version * 10) + (*cp - '0'); + } + return version; +} + + +int ext2fs_get_library_version(const char **ver_string, + const char **date_string) +{ + if (ver_string) + *ver_string = lib_version; + if (date_string) + *date_string = lib_date; + + return ext2fs_parse_version_string(lib_version); +} diff --git a/e2fsprogs/ext2fs/write_bb_file.c b/e2fsprogs/ext2fs/write_bb_file.c new file mode 100644 index 000000000..5b19eefa0 --- /dev/null +++ b/e2fsprogs/ext2fs/write_bb_file.c @@ -0,0 +1,35 @@ +/* vi: set sw=4 ts=4: */ +/* + * write_bb_file.c --- write a list of bad blocks to a FILE * + * + * Copyright (C) 1994, 1995 Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include + +#include "ext2_fs.h" +#include "ext2fs.h" + +errcode_t ext2fs_write_bb_FILE(ext2_badblocks_list bb_list, + unsigned int flags EXT2FS_ATTR((unused)), + FILE *f) +{ + badblocks_iterate bb_iter; + blk_t blk; + errcode_t retval; + + retval = ext2fs_badblocks_list_iterate_begin(bb_list, &bb_iter); + if (retval) + return retval; + + while (ext2fs_badblocks_list_iterate(bb_iter, &blk)) { + fprintf(f, "%d\n", blk); + } + ext2fs_badblocks_list_iterate_end(bb_iter); + return 0; +} diff --git a/e2fsprogs/fsck.c b/e2fsprogs/fsck.c new file mode 100644 index 000000000..f0c1316a9 --- /dev/null +++ b/e2fsprogs/fsck.c @@ -0,0 +1,1392 @@ +/* vi: set sw=4 ts=4: */ +/* + * pfsck --- A generic, parallelizing front-end for the fsck program. + * It will automatically try to run fsck programs in parallel if the + * devices are on separate spindles. It is based on the same ideas as + * the generic front end for fsck by David Engel and Fred van Kempen, + * but it has been completely rewritten from scratch to support + * parallel execution. + * + * Written by Theodore Ts'o, + * + * Miquel van Smoorenburg (miquels@drinkel.ow.org) 20-Oct-1994: + * o Changed -t fstype to behave like with mount when -A (all file + * systems) or -M (like mount) is specified. + * o fsck looks if it can find the fsck.type program to decide + * if it should ignore the fs type. This way more fsck programs + * can be added without changing this front-end. + * o -R flag skip root file system. + * + * Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, + * 2001, 2002, 2003, 2004, 2005 by Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fsck.h" +#include "blkid/blkid.h" + +#include "e2fsbb.h" + +#include "busybox.h" + +#ifndef _PATH_MNTTAB +#define _PATH_MNTTAB "/etc/fstab" +#endif + +/* + * fsck.h + */ + +#ifndef DEFAULT_FSTYPE +#define DEFAULT_FSTYPE "ext2" +#endif + +#define MAX_DEVICES 32 +#define MAX_ARGS 32 + +/* + * Internal structure for mount tabel entries. + */ + +struct fs_info { + char *device; + char *mountpt; + char *type; + char *opts; + int freq; + int passno; + int flags; + struct fs_info *next; +}; + +#define FLAG_DONE 1 +#define FLAG_PROGRESS 2 + +/* + * Structure to allow exit codes to be stored + */ +struct fsck_instance { + int pid; + int flags; + int exit_status; + time_t start_time; + char * prog; + char * type; + char * device; + char * base_device; + struct fsck_instance *next; +}; + +/* + * base_device.c + * + * Return the "base device" given a particular device; this is used to + * assure that we only fsck one partition on a particular drive at any + * one time. Otherwise, the disk heads will be seeking all over the + * place. If the base device cannot be determined, return NULL. + * + * The base_device() function returns an allocated string which must + * be freed. + * + */ + + +#ifdef CONFIG_FEATURE_DEVFS +/* + * Required for the uber-silly devfs /dev/ide/host1/bus2/target3/lun3 + * pathames. + */ +static const char * const devfs_hier[] = { + "host", "bus", "target", "lun", 0 +}; +#endif + +static char *base_device(const char *device) +{ + char *str, *cp; +#ifdef CONFIG_FEATURE_DEVFS + const char * const *hier; + const char *disk; + int len; +#endif + + cp = str = xstrdup(device); + + /* Skip over /dev/; if it's not present, give up. */ + if (strncmp(cp, "/dev/", 5) != 0) + goto errout; + cp += 5; + + /* + * For md devices, we treat them all as if they were all + * on one disk, since we don't know how to parallelize them. + */ + if (cp[0] == 'm' && cp[1] == 'd') { + *(cp+2) = 0; + return str; + } + + /* Handle DAC 960 devices */ + if (strncmp(cp, "rd/", 3) == 0) { + cp += 3; + if (cp[0] != 'c' || cp[2] != 'd' || + !isdigit(cp[1]) || !isdigit(cp[3])) + goto errout; + *(cp+4) = 0; + return str; + } + + /* Now let's handle /dev/hd* and /dev/sd* devices.... */ + if ((cp[0] == 'h' || cp[0] == 's') && (cp[1] == 'd')) { + cp += 2; + /* If there's a single number after /dev/hd, skip it */ + if (isdigit(*cp)) + cp++; + /* What follows must be an alpha char, or give up */ + if (!isalpha(*cp)) + goto errout; + *(cp + 1) = 0; + return str; + } + +#ifdef CONFIG_FEATURE_DEVFS + /* Now let's handle devfs (ugh) names */ + len = 0; + if (strncmp(cp, "ide/", 4) == 0) + len = 4; + if (strncmp(cp, "scsi/", 5) == 0) + len = 5; + if (len) { + cp += len; + /* + * Now we proceed down the expected devfs hierarchy. + * i.e., .../host1/bus2/target3/lun4/... + * If we don't find the expected token, followed by + * some number of digits at each level, abort. + */ + for (hier = devfs_hier; *hier; hier++) { + len = strlen(*hier); + if (strncmp(cp, *hier, len) != 0) + goto errout; + cp += len; + while (*cp != '/' && *cp != 0) { + if (!isdigit(*cp)) + goto errout; + cp++; + } + cp++; + } + *(cp - 1) = 0; + return str; + } + + /* Now handle devfs /dev/disc or /dev/disk names */ + disk = 0; + if (strncmp(cp, "discs/", 6) == 0) + disk = "disc"; + else if (strncmp(cp, "disks/", 6) == 0) + disk = "disk"; + if (disk) { + cp += 6; + if (strncmp(cp, disk, 4) != 0) + goto errout; + cp += 4; + while (*cp != '/' && *cp != 0) { + if (!isdigit(*cp)) + goto errout; + cp++; + } + *cp = 0; + return str; + } +#endif + +errout: + free(str); + return NULL; +} + + +static const char * const ignored_types[] = { + "ignore", + "iso9660", + "nfs", + "proc", + "sw", + "swap", + "tmpfs", + "devpts", + NULL +}; + +static const char * const really_wanted[] = { + "minix", + "ext2", + "ext3", + "jfs", + "reiserfs", + "xiafs", + "xfs", + NULL +}; + +#define BASE_MD "/dev/md" + +/* + * Global variables for options + */ +static char *devices[MAX_DEVICES]; +static char *args[MAX_ARGS]; +static int num_devices, num_args; + +static int verbose; +static int doall; +static int noexecute; +static int serialize; +static int skip_root; +static int like_mount; +static int notitle; +static int parallel_root; +static int progress; +static int progress_fd; +static int force_all_parallel; +static int num_running; +static int max_running; +static volatile int cancel_requested; +static int kill_sent; +static char *fstype; +static struct fs_info *filesys_info, *filesys_last; +static struct fsck_instance *instance_list; +static char *fsck_path; +static blkid_cache cache; + +static char *string_copy(const char *s) +{ + char *ret; + + if (!s) + return 0; + ret = strdup(s); + return ret; +} + +static int string_to_int(const char *s) +{ + long l; + char *p; + + l = strtol(s, &p, 0); + if (*p || l == LONG_MIN || l == LONG_MAX || l < 0 || l > INT_MAX) + return -1; + else + return (int) l; +} + +static char *skip_over_blank(char *cp) +{ + while (*cp && isspace(*cp)) + cp++; + return cp; +} + +static char *skip_over_word(char *cp) +{ + while (*cp && !isspace(*cp)) + cp++; + return cp; +} + +static void strip_line(char *line) +{ + char *p; + + while (*line) { + p = line + strlen(line) - 1; + if ((*p == '\n') || (*p == '\r')) + *p = 0; + else + break; + } +} + +static char *parse_word(char **buf) +{ + char *word, *next; + + word = *buf; + if (*word == 0) + return 0; + + word = skip_over_blank(word); + next = skip_over_word(word); + if (*next) + *next++ = 0; + *buf = next; + return word; +} + +static void parse_escape(char *word) +{ + char *q, c; + const char *p; + + if (!word) + return; + + for (p = q = word; *p; q++) { + c = *p++; + if (c != '\\') { + *q = c; + } else { + *q = bb_process_escape_sequence(&p); + } + } + *q = 0; +} + +static void free_instance(struct fsck_instance *i) +{ + if (i->prog) + free(i->prog); + if (i->device) + free(i->device); + if (i->base_device) + free(i->base_device); + free(i); + return; +} + +static struct fs_info *create_fs_device(const char *device, const char *mntpnt, + const char *type, const char *opts, + int freq, int passno) +{ + struct fs_info *fs; + + if (!(fs = malloc(sizeof(struct fs_info)))) + return NULL; + + fs->device = string_copy(device); + fs->mountpt = string_copy(mntpnt); + fs->type = string_copy(type); + fs->opts = string_copy(opts ? opts : ""); + fs->freq = freq; + fs->passno = passno; + fs->flags = 0; + fs->next = NULL; + + if (!filesys_info) + filesys_info = fs; + else + filesys_last->next = fs; + filesys_last = fs; + + return fs; +} + + + +static int parse_fstab_line(char *line, struct fs_info **ret_fs) +{ + char *dev, *device, *mntpnt, *type, *opts, *freq, *passno, *cp; + struct fs_info *fs; + + *ret_fs = 0; + strip_line(line); + if ((cp = strchr(line, '#'))) + *cp = 0; /* Ignore everything after the comment char */ + cp = line; + + device = parse_word(&cp); + mntpnt = parse_word(&cp); + type = parse_word(&cp); + opts = parse_word(&cp); + freq = parse_word(&cp); + passno = parse_word(&cp); + + if (!device) + return 0; /* Allow blank lines */ + + if (!mntpnt || !type) + return -1; + + parse_escape(device); + parse_escape(mntpnt); + parse_escape(type); + parse_escape(opts); + parse_escape(freq); + parse_escape(passno); + + dev = blkid_get_devname(cache, device, NULL); + if (dev) + device = dev; + + if (strchr(type, ',')) + type = 0; + + fs = create_fs_device(device, mntpnt, type ? type : "auto", opts, + freq ? atoi(freq) : -1, + passno ? atoi(passno) : -1); + if (dev) + free(dev); + + if (!fs) + return -1; + *ret_fs = fs; + return 0; +} + +static void interpret_type(struct fs_info *fs) +{ + char *t; + + if (strcmp(fs->type, "auto") != 0) + return; + t = blkid_get_tag_value(cache, "TYPE", fs->device); + if (t) { + free(fs->type); + fs->type = t; + } +} + +/* + * Load the filesystem database from /etc/fstab + */ +static void load_fs_info(const char *filename) +{ + FILE *f; + char buf[1024]; + int lineno = 0; + int old_fstab = 1; + struct fs_info *fs; + + if ((f = fopen(filename, "r")) == NULL) { + bb_perror_msg("WARNING: cannot open %s", filename); + return; + } + while (!feof(f)) { + lineno++; + if (!fgets(buf, sizeof(buf), f)) + break; + buf[sizeof(buf)-1] = 0; + if (parse_fstab_line(buf, &fs) < 0) { + bb_error_msg("WARNING: bad format " + "on line %d of %s\n", lineno, filename); + continue; + } + if (!fs) + continue; + if (fs->passno < 0) + fs->passno = 0; + else + old_fstab = 0; + } + + fclose(f); + + if (old_fstab) { + fputs("\007\007\007" + "WARNING: Your /etc/fstab does not contain the fsck passno\n" + " field. I will kludge around things for you, but you\n" + " should fix your /etc/fstab file as soon as you can.\n\n", stderr); + + for (fs = filesys_info; fs; fs = fs->next) { + fs->passno = 1; + } + } +} + +/* Lookup filesys in /etc/fstab and return the corresponding entry. */ +static struct fs_info *lookup(char *filesys) +{ + struct fs_info *fs; + + /* No filesys name given. */ + if (filesys == NULL) + return NULL; + + for (fs = filesys_info; fs; fs = fs->next) { + if (!strcmp(filesys, fs->device) || + (fs->mountpt && !strcmp(filesys, fs->mountpt))) + break; + } + + return fs; +} + +/* Find fsck program for a given fs type. */ +static char *find_fsck(char *type) +{ + char *s; + const char *tpl; + char *p = string_copy(fsck_path); + struct stat st; + + /* Are we looking for a program or just a type? */ + tpl = (strncmp(type, "fsck.", 5) ? "%s/fsck.%s" : "%s/%s"); + + for(s = strtok(p, ":"); s; s = strtok(NULL, ":")) { + s = xasprintf(tpl, s, type); + if (stat(s, &st) == 0) break; + free(s); + } + free(p); + return s; +} + +static int progress_active(void) +{ + struct fsck_instance *inst; + + for (inst = instance_list; inst; inst = inst->next) { + if (inst->flags & FLAG_DONE) + continue; + if (inst->flags & FLAG_PROGRESS) + return 1; + } + return 0; +} + +/* + * Execute a particular fsck program, and link it into the list of + * child processes we are waiting for. + */ +static int execute(const char *type, const char *device, const char *mntpt, + int interactive) +{ + char *s, *argv[80]; + char *prog; + int argc, i; + struct fsck_instance *inst, *p; + pid_t pid; + + inst = malloc(sizeof(struct fsck_instance)); + if (!inst) + return ENOMEM; + memset(inst, 0, sizeof(struct fsck_instance)); + + prog = xasprintf("fsck.%s", type); + argv[0] = prog; + argc = 1; + + for (i=0; i flags |= FLAG_PROGRESS; + } + } + + argv[argc++] = string_copy(device); + argv[argc] = 0; + + s = find_fsck(prog); + if (s == NULL) { + bb_error_msg("%s: not found", prog); + return ENOENT; + } + + if (verbose || noexecute) { + printf("[%s (%d) -- %s] ", s, num_running, + mntpt ? mntpt : device); + for (i=0; i < argc; i++) + printf("%s ", argv[i]); + puts(""); + } + + /* Fork and execute the correct program. */ + if (noexecute) + pid = -1; + else if ((pid = fork()) < 0) { + perror("fork"); + return errno; + } else if (pid == 0) { + if (!interactive) + close(0); + (void) execv(s, argv); + bb_perror_msg_and_die("%s", argv[0]); + } + + for (i = 1; i < argc; i++) + free(argv[i]); + + free(s); + inst->pid = pid; + inst->prog = prog; + inst->type = string_copy(type); + inst->device = string_copy(device); + inst->base_device = base_device(device); + inst->start_time = time(0); + inst->next = NULL; + + /* + * Find the end of the list, so we add the instance on at the end. + */ + for (p = instance_list; p && p->next; p = p->next); + + if (p) + p->next = inst; + else + instance_list = inst; + + return 0; +} + +/* + * Send a signal to all outstanding fsck child processes + */ +static int kill_all(int signum) +{ + struct fsck_instance *inst; + int n = 0; + + for (inst = instance_list; inst; inst = inst->next) { + if (inst->flags & FLAG_DONE) + continue; + kill(inst->pid, signum); + n++; + } + return n; +} + +/* + * Wait for one child process to exit; when it does, unlink it from + * the list of executing child processes, and return it. + */ +static struct fsck_instance *wait_one(int flags) +{ + int status; + int sig; + struct fsck_instance *inst, *inst2, *prev; + pid_t pid; + + if (!instance_list) + return NULL; + + if (noexecute) { + inst = instance_list; + prev = 0; +#ifdef RANDOM_DEBUG + while (inst->next && (random() & 1)) { + prev = inst; + inst = inst->next; + } +#endif + inst->exit_status = 0; + goto ret_inst; + } + + /* + * gcc -Wall fails saving throw against stupidity + * (inst and prev are thought to be uninitialized variables) + */ + inst = prev = NULL; + + do { + pid = waitpid(-1, &status, flags); + if (cancel_requested && !kill_sent) { + kill_all(SIGTERM); + kill_sent++; + } + if ((pid == 0) && (flags & WNOHANG)) + return NULL; + if (pid < 0) { + if ((errno == EINTR) || (errno == EAGAIN)) + continue; + if (errno == ECHILD) { + bb_error_msg("wait: no more child process?!?"); + return NULL; + } + perror("wait"); + continue; + } + for (prev = 0, inst = instance_list; + inst; + prev = inst, inst = inst->next) { + if (inst->pid == pid) + break; + } + } while (!inst); + + if (WIFEXITED(status)) + status = WEXITSTATUS(status); + else if (WIFSIGNALED(status)) { + sig = WTERMSIG(status); + if (sig == SIGINT) { + status = EXIT_UNCORRECTED; + } else { + printf("Warning... %s for device %s exited " + "with signal %d.\n", + inst->prog, inst->device, sig); + status = EXIT_ERROR; + } + } else { + printf("%s %s: status is %x, should never happen.\n", + inst->prog, inst->device, status); + status = EXIT_ERROR; + } + inst->exit_status = status; + if (progress && (inst->flags & FLAG_PROGRESS) && + !progress_active()) { + for (inst2 = instance_list; inst2; inst2 = inst2->next) { + if (inst2->flags & FLAG_DONE) + continue; + if (strcmp(inst2->type, "ext2") && + strcmp(inst2->type, "ext3")) + continue; + /* + * If we've just started the fsck, wait a tiny + * bit before sending the kill, to give it + * time to set up the signal handler + */ + if (inst2->start_time < time(0)+2) { + if (fork() == 0) { + sleep(1); + kill(inst2->pid, SIGUSR1); + exit(0); + } + } else + kill(inst2->pid, SIGUSR1); + inst2->flags |= FLAG_PROGRESS; + break; + } + } +ret_inst: + if (prev) + prev->next = inst->next; + else + instance_list = inst->next; + if (verbose > 1) + printf("Finished with %s (exit status %d)\n", + inst->device, inst->exit_status); + num_running--; + return inst; +} + +#define FLAG_WAIT_ALL 0 +#define FLAG_WAIT_ATLEAST_ONE 1 +/* + * Wait until all executing child processes have exited; return the + * logical OR of all of their exit code values. + */ +static int wait_many(int flags) +{ + struct fsck_instance *inst; + int global_status = 0; + int wait_flags = 0; + + while ((inst = wait_one(wait_flags))) { + global_status |= inst->exit_status; + free_instance(inst); +#ifdef RANDOM_DEBUG + if (noexecute && (flags & WNOHANG) && !(random() % 3)) + break; +#endif + if (flags & FLAG_WAIT_ATLEAST_ONE) + wait_flags = WNOHANG; + } + return global_status; +} + +/* + * Run the fsck program on a particular device + * + * If the type is specified using -t, and it isn't prefixed with "no" + * (as in "noext2") and only one filesystem type is specified, then + * use that type regardless of what is specified in /etc/fstab. + * + * If the type isn't specified by the user, then use either the type + * specified in /etc/fstab, or DEFAULT_FSTYPE. + */ +static void fsck_device(struct fs_info *fs, int interactive) +{ + const char *type; + int retval; + + interpret_type(fs); + + if (strcmp(fs->type, "auto") != 0) + type = fs->type; + else if (fstype && strncmp(fstype, "no", 2) && + strncmp(fstype, "opts=", 5) && strncmp(fstype, "loop", 4) && + !strchr(fstype, ',')) + type = fstype; + else + type = DEFAULT_FSTYPE; + + num_running++; + retval = execute(type, fs->device, fs->mountpt, interactive); + if (retval) { + bb_error_msg("error %d while executing fsck.%s for %s", + retval, type, fs->device); + num_running--; + } +} + + +/* + * Deal with the fsck -t argument. + */ +struct fs_type_compile { + char **list; + int *type; + int negate; +} fs_type_compiled; + +#define FS_TYPE_NORMAL 0 +#define FS_TYPE_OPT 1 +#define FS_TYPE_NEGOPT 2 + +static const char fs_type_syntax_error[] = +"Either all or none of the filesystem types passed to -t must be prefixed\n" + "with 'no' or '!'."; + +static void compile_fs_type(char *fs_type, struct fs_type_compile *cmp) +{ + char *cp, *list, *s; + int num = 2; + int negate, first_negate = 1; + + if (fs_type) { + for (cp=fs_type; *cp; cp++) { + if (*cp == ',') + num++; + } + } + + cmp->list = xzalloc(num * sizeof(char *)); + cmp->type = xzalloc(num * sizeof(int)); + cmp->negate = 0; + + if (!fs_type) + return; + + list = string_copy(fs_type); + num = 0; + s = strtok(list, ","); + while(s) { + negate = 0; + if (strncmp(s, "no", 2) == 0) { + s += 2; + negate = 1; + } else if (*s == '!') { + s++; + negate = 1; + } + if (strcmp(s, "loop") == 0) + /* loop is really short-hand for opts=loop */ + goto loop_special_case; + else if (strncmp(s, "opts=", 5) == 0) { + s += 5; + loop_special_case: + cmp->type[num] = negate ? FS_TYPE_NEGOPT : FS_TYPE_OPT; + } else { + if (first_negate) { + cmp->negate = negate; + first_negate = 0; + } + if ((negate && !cmp->negate) || + (!negate && cmp->negate)) { + bb_error_msg_and_die("%s", fs_type_syntax_error); + } + } + cmp->list[num++] = string_copy(s); + s = strtok(NULL, ","); + } + free(list); +} + +/* + * This function returns true if a particular option appears in a + * comma-delimited options list + */ +static int opt_in_list(char *opt, char *optlist) +{ + char *list, *s; + + if (!optlist) + return 0; + list = string_copy(optlist); + + s = strtok(list, ","); + while(s) { + if (strcmp(s, opt) == 0) { + free(list); + return 1; + } + s = strtok(NULL, ","); + } + free(list); + return 0; +} + +/* See if the filesystem matches the criteria given by the -t option */ +static int fs_match(struct fs_info *fs, struct fs_type_compile *cmp) +{ + int n, ret = 0, checked_type = 0; + char *cp; + + if (cmp->list == 0 || cmp->list[0] == 0) + return 1; + + for (n=0; (cp = cmp->list[n]); n++) { + switch (cmp->type[n]) { + case FS_TYPE_NORMAL: + checked_type++; + if (strcmp(cp, fs->type) == 0) { + ret = 1; + } + break; + case FS_TYPE_NEGOPT: + if (opt_in_list(cp, fs->opts)) + return 0; + break; + case FS_TYPE_OPT: + if (!opt_in_list(cp, fs->opts)) + return 0; + break; + } + } + if (checked_type == 0) + return 1; + return (cmp->negate ? !ret : ret); +} + +/* Check if we should ignore this filesystem. */ +static int ignore(struct fs_info *fs) +{ + int wanted; + char *s; + + /* + * If the pass number is 0, ignore it. + */ + if (fs->passno == 0) + return 1; + + interpret_type(fs); + + /* + * If a specific fstype is specified, and it doesn't match, + * ignore it. + */ + if (!fs_match(fs, &fs_type_compiled)) return 1; + + /* Are we ignoring this type? */ + if (index_in_str_array(ignored_types, fs->type) >= 0) + return 1; + + /* Do we really really want to check this fs? */ + wanted = index_in_str_array(really_wanted, fs->type) >= 0; + + /* See if the program is available. */ + s = find_fsck(fs->type); + if (s == NULL) { + if (wanted) + bb_error_msg("cannot check %s: fsck.%s not found", + fs->device, fs->type); + return 1; + } + free(s); + + /* We can and want to check this file system type. */ + return 0; +} + +/* + * Returns TRUE if a partition on the same disk is already being + * checked. + */ +static int device_already_active(char *device) +{ + struct fsck_instance *inst; + char *base; + + if (force_all_parallel) + return 0; + +#ifdef BASE_MD + /* Don't check a soft raid disk with any other disk */ + if (instance_list && + (!strncmp(instance_list->device, BASE_MD, sizeof(BASE_MD)-1) || + !strncmp(device, BASE_MD, sizeof(BASE_MD)-1))) + return 1; +#endif + + base = base_device(device); + /* + * If we don't know the base device, assume that the device is + * already active if there are any fsck instances running. + */ + if (!base) + return (instance_list != 0); + for (inst = instance_list; inst; inst = inst->next) { + if (!inst->base_device || !strcmp(base, inst->base_device)) { + free(base); + return 1; + } + } + free(base); + return 0; +} + +/* Check all file systems, using the /etc/fstab table. */ +static int check_all(void) +{ + struct fs_info *fs = NULL; + int status = EXIT_OK; + int not_done_yet = 1; + int passno = 1; + int pass_done; + + if (verbose) + fputs("Checking all file systems.\n", stdout); + + /* + * Do an initial scan over the filesystem; mark filesystems + * which should be ignored as done, and resolve any "auto" + * filesystem types (done as a side-effect of calling ignore()). + */ + for (fs = filesys_info; fs; fs = fs->next) { + if (ignore(fs)) + fs->flags |= FLAG_DONE; + } + + /* + * Find and check the root filesystem. + */ + if (!parallel_root) { + for (fs = filesys_info; fs; fs = fs->next) { + if (!strcmp(fs->mountpt, "/")) + break; + } + if (fs) { + if (!skip_root && !ignore(fs)) { + fsck_device(fs, 1); + status |= wait_many(FLAG_WAIT_ALL); + if (status > EXIT_NONDESTRUCT) + return status; + } + fs->flags |= FLAG_DONE; + } + } + /* + * This is for the bone-headed user who enters the root + * filesystem twice. Skip root will skep all root entries. + */ + if (skip_root) + for (fs = filesys_info; fs; fs = fs->next) + if (!strcmp(fs->mountpt, "/")) + fs->flags |= FLAG_DONE; + + while (not_done_yet) { + not_done_yet = 0; + pass_done = 1; + + for (fs = filesys_info; fs; fs = fs->next) { + if (cancel_requested) + break; + if (fs->flags & FLAG_DONE) + continue; + /* + * If the filesystem's pass number is higher + * than the current pass number, then we don't + * do it yet. + */ + if (fs->passno > passno) { + not_done_yet++; + continue; + } + /* + * If a filesystem on a particular device has + * already been spawned, then we need to defer + * this to another pass. + */ + if (device_already_active(fs->device)) { + pass_done = 0; + continue; + } + /* + * Spawn off the fsck process + */ + fsck_device(fs, serialize); + fs->flags |= FLAG_DONE; + + /* + * Only do one filesystem at a time, or if we + * have a limit on the number of fsck's extant + * at one time, apply that limit. + */ + if (serialize || + (max_running && (num_running >= max_running))) { + pass_done = 0; + break; + } + } + if (cancel_requested) + break; + if (verbose > 1) + printf("--waiting-- (pass %d)\n", passno); + status |= wait_many(pass_done ? FLAG_WAIT_ALL : + FLAG_WAIT_ATLEAST_ONE); + if (pass_done) { + if (verbose > 1) + printf("----------------------------------\n"); + passno++; + } else + not_done_yet++; + } + if (cancel_requested && !kill_sent) { + kill_all(SIGTERM); + kill_sent++; + } + status |= wait_many(FLAG_WAIT_ATLEAST_ONE); + return status; +} + +static void signal_cancel(int sig FSCK_ATTR((unused))) +{ + cancel_requested++; +} + +static void PRS(int argc, char *argv[]) +{ + int i, j; + char *arg, *dev, *tmp = 0; + char options[128]; + int opt = 0; + int opts_for_fsck = 0; + struct sigaction sa; + + /* + * Set up signal action + */ + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = signal_cancel; + sigaction(SIGINT, &sa, 0); + sigaction(SIGTERM, &sa, 0); + + num_devices = 0; + num_args = 0; + instance_list = 0; + + for (i=1; i < argc; i++) { + arg = argv[i]; + if (!arg) + continue; + if ((arg[0] == '/' && !opts_for_fsck) || strchr(arg, '=')) { + if (num_devices >= MAX_DEVICES) { + bb_error_msg_and_die("too many devices"); + } + dev = blkid_get_devname(cache, arg, NULL); + if (!dev && strchr(arg, '=')) { + /* + * Check to see if we failed because + * /proc/partitions isn't found. + */ + if (access("/proc/partitions", R_OK) < 0) { + bb_perror_msg_and_die("cannot open /proc/partitions " + "(is /proc mounted?)"); + } + /* + * Check to see if this is because + * we're not running as root + */ + if (geteuid()) + bb_error_msg_and_die( + "must be root to scan for matching filesystems: %s\n", arg); + else + bb_error_msg_and_die( + "cannot find matching filesystem: %s", arg); + } + devices[num_devices++] = dev ? dev : string_copy(arg); + continue; + } + if (arg[0] != '-' || opts_for_fsck) { + if (num_args >= MAX_ARGS) { + bb_error_msg_and_die("too many arguments"); + } + args[num_args++] = string_copy(arg); + continue; + } + for (j=1; arg[j]; j++) { + if (opts_for_fsck) { + options[++opt] = arg[j]; + continue; + } + switch (arg[j]) { + case 'A': + doall++; + break; + case 'C': + progress++; + if (arg[j+1]) { + progress_fd = string_to_int(arg+j+1); + if (progress_fd < 0) + progress_fd = 0; + else + goto next_arg; + } else if ((i+1) < argc && + !strncmp(argv[i+1], "-", 1) == 0) { + progress_fd = string_to_int(argv[i]); + if (progress_fd < 0) + progress_fd = 0; + else { + goto next_arg; + i++; + } + } + break; + case 'V': + verbose++; + break; + case 'N': + noexecute++; + break; + case 'R': + skip_root++; + break; + case 'T': + notitle++; + break; + case 'M': + like_mount++; + break; + case 'P': + parallel_root++; + break; + case 's': + serialize++; + break; + case 't': + tmp = 0; + if (fstype) + bb_show_usage(); + if (arg[j+1]) + tmp = arg+j+1; + else if ((i+1) < argc) + tmp = argv[++i]; + else + bb_show_usage(); + fstype = string_copy(tmp); + compile_fs_type(fstype, &fs_type_compiled); + goto next_arg; + case '-': + opts_for_fsck++; + break; + case '?': + bb_show_usage(); + break; + default: + options[++opt] = arg[j]; + break; + } + } + next_arg: + if (opt) { + options[0] = '-'; + options[++opt] = '\0'; + if (num_args >= MAX_ARGS) { + bb_error_msg("too many arguments"); + } + args[num_args++] = string_copy(options); + opt = 0; + } + } + if (getenv("FSCK_FORCE_ALL_PARALLEL")) + force_all_parallel++; + if ((tmp = getenv("FSCK_MAX_INST"))) + max_running = atoi(tmp); +} + +int fsck_main(int argc, char *argv[]) +{ + int i, status = 0; + int interactive = 0; + const char *fstab; + struct fs_info *fs; + + setvbuf(stdout, NULL, _IONBF, BUFSIZ); + setvbuf(stderr, NULL, _IONBF, BUFSIZ); + + blkid_get_cache(&cache, NULL); + PRS(argc, argv); + + if (!notitle) + printf("fsck %s (%s)\n", E2FSPROGS_VERSION, E2FSPROGS_DATE); + + fstab = getenv("FSTAB_FILE"); + if (!fstab) + fstab = _PATH_MNTTAB; + load_fs_info(fstab); + + fsck_path = e2fs_set_sbin_path(); + + if ((num_devices == 1) || (serialize)) + interactive = 1; + + /* If -A was specified ("check all"), do that! */ + if (doall) + return check_all(); + + if (num_devices == 0) { + serialize++; + interactive++; + return check_all(); + } + for (i = 0 ; i < num_devices; i++) { + if (cancel_requested) { + if (!kill_sent) { + kill_all(SIGTERM); + kill_sent++; + } + break; + } + fs = lookup(devices[i]); + if (!fs) { + fs = create_fs_device(devices[i], 0, "auto", + 0, -1, -1); + if (!fs) + continue; + } + fsck_device(fs, interactive); + if (serialize || + (max_running && (num_running >= max_running))) { + struct fsck_instance *inst; + + inst = wait_one(0); + if (inst) { + status |= inst->exit_status; + free_instance(inst); + } + if (verbose > 1) + printf("----------------------------------\n"); + } + } + status |= wait_many(FLAG_WAIT_ALL); + blkid_put_cache(cache); + return status; +} diff --git a/e2fsprogs/fsck.h b/e2fsprogs/fsck.h new file mode 100644 index 000000000..2ca2af7da --- /dev/null +++ b/e2fsprogs/fsck.h @@ -0,0 +1,16 @@ +/* vi: set sw=4 ts=4: */ +/* + * fsck.h + */ + +#define FSCK_ATTR(x) __attribute__(x) + +#define EXIT_OK 0 +#define EXIT_NONDESTRUCT 1 +#define EXIT_DESTRUCT 2 +#define EXIT_UNCORRECTED 4 +#define EXIT_ERROR 8 +#define EXIT_USAGE 16 +#define FSCK_CANCELED 32 /* Aborted with a signal or ^C */ + +extern char *e2fs_set_sbin_path(void); diff --git a/e2fsprogs/lsattr.c b/e2fsprogs/lsattr.c new file mode 100644 index 000000000..eb28fcbab --- /dev/null +++ b/e2fsprogs/lsattr.c @@ -0,0 +1,128 @@ +/* vi: set sw=4 ts=4: */ +/* + * lsattr.c - List file attributes on an ext2 file system + * + * Copyright (C) 1993, 1994 Remy Card + * Laboratoire MASI, Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * This file can be redistributed under the terms of the GNU General + * Public License + */ + +/* + * History: + * 93/10/30 - Creation + * 93/11/13 - Replace stat() calls by lstat() to avoid loops + * 94/02/27 - Integrated in Ted's distribution + * 98/12/29 - Display version info only when -V specified (G M Sipe) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ext2fs/ext2_fs.h" +#include "e2fsbb.h" +#include "e2p/e2p.h" + +#define OPT_RECUR 1 +#define OPT_ALL 2 +#define OPT_DIRS_OPT 4 +#define OPT_PF_LONG 8 +#define OPT_GENERATION 16 +static int flags; + +static void list_attributes(const char *name) +{ + unsigned long fsflags; + unsigned long generation; + + if (fgetflags(name, &fsflags) == -1) + goto read_err; + if (flags & OPT_GENERATION) { + if (fgetversion(name, &generation) == -1) + goto read_err; + printf("%5lu ", generation); + } + + if (flags & OPT_PF_LONG) { + printf("%-28s ", name); + print_flags(stdout, fsflags, PFOPT_LONG); + puts(""); + } else { + print_flags(stdout, fsflags, 0); + printf(" %s\n", name); + } + + return; +read_err: + bb_perror_msg("reading %s", name); +} + +static int lsattr_dir_proc(const char *, struct dirent *, void *); + +static void lsattr_args(const char *name) +{ + struct stat st; + + if (lstat(name, &st) == -1) { + bb_perror_msg("stating %s", name); + } else { + if (S_ISDIR(st.st_mode) && !(flags & OPT_DIRS_OPT)) + iterate_on_dir(name, lsattr_dir_proc, NULL); + else + list_attributes(name); + } +} + +static int lsattr_dir_proc(const char *dir_name, struct dirent *de, + void *private) +{ + struct stat st; + char *path; + + path = concat_path_file(dir_name, de->d_name); + + if (lstat(path, &st) == -1) + bb_perror_msg(path); + else { + if (de->d_name[0] != '.' || (flags & OPT_ALL)) { + list_attributes(path); + if (S_ISDIR(st.st_mode) && (flags & OPT_RECUR) && + (de->d_name[0] != '.' && (de->d_name[1] != '\0' || + (de->d_name[1] != '.' && de->d_name[2] != '\0')))) { + printf("\n%s:\n", path); + iterate_on_dir(path, lsattr_dir_proc, NULL); + puts(""); + } + } + } + + free(path); + + return 0; +} + +int lsattr_main(int argc, char **argv) +{ + int i; + + flags = getopt32(argc, argv, "Radlv"); + + if (optind > argc - 1) + lsattr_args("."); + else + for (i = optind; i < argc; i++) + lsattr_args(argv[i]); + + return EXIT_SUCCESS; +} diff --git a/e2fsprogs/mke2fs.c b/e2fsprogs/mke2fs.c new file mode 100644 index 000000000..f25ecfb6c --- /dev/null +++ b/e2fsprogs/mke2fs.c @@ -0,0 +1,1336 @@ +/* vi: set sw=4 ts=4: */ +/* + * mke2fs.c - Make a ext2fs filesystem. + * + * Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, + * 2003, 2004, 2005 by Theodore Ts'o. + * + * This file may be redistributed under the terms of the GNU Public + * License. + */ + +/* Usage: mke2fs [options] device + * + * The device may be a block device or a image of one, but this isn't + * enforced (but it's not much fun on a character device :-). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "e2fsbb.h" +#include "ext2fs/ext2_fs.h" +#include "uuid/uuid.h" +#include "e2p/e2p.h" +#include "ext2fs/ext2fs.h" +#include "util.h" + +#define STRIDE_LENGTH 8 + +#ifndef __sparc__ +#define ZAP_BOOTBLOCK +#endif + +static const char * device_name; + +/* Command line options */ +static int cflag; +static int quiet; +static int super_only; +static int force; +static int noaction; +static int journal_size; +static int journal_flags; +static const char *bad_blocks_filename; +static __u32 fs_stride; + +static struct ext2_super_block param; +static char *creator_os; +static char *volume_label; +static char *mount_dir; +static char *journal_device = NULL; +static int sync_kludge; /* Set using the MKE2FS_SYNC env. option */ + +static int sys_page_size = 4096; +static int linux_version_code = 0; + +static int int_log2(int arg) +{ + int l = 0; + + arg >>= 1; + while (arg) { + l++; + arg >>= 1; + } + return l; +} + +static int int_log10(unsigned int arg) +{ + int l; + + for (l=0; arg ; l++) + arg = arg / 10; + return l; +} + +/* + * This function sets the default parameters for a filesystem + * + * The type is specified by the user. The size is the maximum size + * (in megabytes) for which a set of parameters applies, with a size + * of zero meaning that it is the default parameter for the type. + * Note that order is important in the table below. + */ +#define DEF_MAX_BLOCKSIZE -1 +static const char default_str[] = "default"; +struct mke2fs_defaults { + const char *type; + int size; + int blocksize; + int inode_ratio; +}; + +static const struct mke2fs_defaults settings[] = { + { default_str, 0, 4096, 8192 }, + { default_str, 512, 1024, 4096 }, + { default_str, 3, 1024, 8192 }, + { "journal", 0, 4096, 8192 }, + { "news", 0, 4096, 4096 }, + { "largefile", 0, 4096, 1024 * 1024 }, + { "largefile4", 0, 4096, 4096 * 1024 }, + { 0, 0, 0, 0}, +}; + +static void set_fs_defaults(const char *fs_type, + struct ext2_super_block *super, + int blocksize, int sector_size, + int *inode_ratio) +{ + int megs; + int ratio = 0; + const struct mke2fs_defaults *p; + int use_bsize = 1024; + + megs = super->s_blocks_count * (EXT2_BLOCK_SIZE(super) / 1024) / 1024; + if (inode_ratio) + ratio = *inode_ratio; + if (!fs_type) + fs_type = default_str; + for (p = settings; p->type; p++) { + if ((strcmp(p->type, fs_type) != 0) && + (strcmp(p->type, default_str) != 0)) + continue; + if ((p->size != 0) && (megs > p->size)) + continue; + if (ratio == 0) + *inode_ratio = p->inode_ratio < blocksize ? + blocksize : p->inode_ratio; + use_bsize = p->blocksize; + } + if (blocksize <= 0) { + if (use_bsize == DEF_MAX_BLOCKSIZE) { + use_bsize = sys_page_size; + if ((linux_version_code < (2*65536 + 6*256)) && + (use_bsize > 4096)) + use_bsize = 4096; + } + if (sector_size && use_bsize < sector_size) + use_bsize = sector_size; + if ((blocksize < 0) && (use_bsize < (-blocksize))) + use_bsize = -blocksize; + blocksize = use_bsize; + super->s_blocks_count /= blocksize / 1024; + } + super->s_log_frag_size = super->s_log_block_size = + int_log2(blocksize >> EXT2_MIN_BLOCK_LOG_SIZE); +} + + +/* + * Helper function for read_bb_file and test_disk + */ +static void invalid_block(ext2_filsys fs EXT2FS_ATTR((unused)), blk_t blk) +{ + bb_error_msg("Bad block %u out of range; ignored", blk); + return; +} + +/* + * Busybox stuff + */ +static void mke2fs_error_msg_and_die(int retval, const char *fmt, ...)__attribute__ ((format (printf, 2, 3))); +static void mke2fs_error_msg_and_die(int retval, const char *fmt, ...) +{ + va_list ap; + + if (retval) { + va_start(ap, fmt); + fprintf(stderr,"\nCould not "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + exit(EXIT_FAILURE); + } +} + +static void mke2fs_verbose(const char *fmt, ...)__attribute__ ((format (printf, 1, 2))); +static void mke2fs_verbose(const char *fmt, ...) +{ + va_list ap; + + if (!quiet) { + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + fflush(stdout); + va_end(ap); + } +} + +static void mke2fs_verbose_done(void) +{ + mke2fs_verbose("done\n"); +} + +static void mke2fs_warning_msg(int retval, char *fmt, ... )__attribute__ ((format (printf, 2, 3))); +static void mke2fs_warning_msg(int retval, char *fmt, ... ) +{ + va_list ap; + + if (retval) { + va_start(ap, fmt); + fprintf(stderr,"\nWarning: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + } +} + +/* + * Reads the bad blocks list from a file + */ +static void read_bb_file(ext2_filsys fs, badblocks_list *bb_list, + const char *bad_blocks_file) +{ + FILE *f; + errcode_t retval; + + f = xfopen(bad_blocks_file, "r"); + retval = ext2fs_read_bb_FILE(fs, f, bb_list, invalid_block); + fclose (f); + mke2fs_error_msg_and_die(retval, "read bad blocks from list"); +} + +/* + * Runs the badblocks program to test the disk + */ +static void test_disk(ext2_filsys fs, badblocks_list *bb_list) +{ + FILE *f; + errcode_t retval; + char buf[1024]; + + sprintf(buf, "badblocks -b %d %s%s%s %d", fs->blocksize, + quiet ? "" : "-s ", (cflag > 1) ? "-w " : "", + fs->device_name, fs->super->s_blocks_count); + mke2fs_verbose("Running command: %s\n", buf); + f = popen(buf, "r"); + if (!f) { + bb_perror_msg_and_die("cannot run '%s'", buf); + } + retval = ext2fs_read_bb_FILE(fs, f, bb_list, invalid_block); + pclose(f); + mke2fs_error_msg_and_die(retval, "read bad blocks from program"); +} + +static void handle_bad_blocks(ext2_filsys fs, badblocks_list bb_list) +{ + dgrp_t i; + blk_t j; + unsigned must_be_good; + blk_t blk; + badblocks_iterate bb_iter; + errcode_t retval; + blk_t group_block; + int group; + int group_bad; + + if (!bb_list) + return; + + /* + * The primary superblock and group descriptors *must* be + * good; if not, abort. + */ + must_be_good = fs->super->s_first_data_block + 1 + fs->desc_blocks; + for (i = fs->super->s_first_data_block; i <= must_be_good; i++) { + if (ext2fs_badblocks_list_test(bb_list, i)) { + bb_error_msg_and_die( + "Block %d in primary superblock/group descriptor area bad\n" + "Blocks %d through %d must be good in order to build a filesystem\n" + "Aborting ...", i, fs->super->s_first_data_block, must_be_good); + } + } + + /* + * See if any of the bad blocks are showing up in the backup + * superblocks and/or group descriptors. If so, issue a + * warning and adjust the block counts appropriately. + */ + group_block = fs->super->s_first_data_block + + fs->super->s_blocks_per_group; + + for (i = 1; i < fs->group_desc_count; i++) { + group_bad = 0; + for (j=0; j < fs->desc_blocks+1; j++) { + if (ext2fs_badblocks_list_test(bb_list, + group_block + j)) { + mke2fs_warning_msg(!group_bad, + "the backup superblock/group descriptors at block %d contain\n" + "bad blocks\n", group_block); + group_bad++; + group = ext2fs_group_of_blk(fs, group_block+j); + fs->group_desc[group].bg_free_blocks_count++; + fs->super->s_free_blocks_count++; + } + } + group_block += fs->super->s_blocks_per_group; + } + + /* + * Mark all the bad blocks as used... + */ + retval = ext2fs_badblocks_list_iterate_begin(bb_list, &bb_iter); + mke2fs_error_msg_and_die(retval, "mark bad blocks as used"); + + while (ext2fs_badblocks_list_iterate(bb_iter, &blk)) + ext2fs_mark_block_bitmap(fs->block_map, blk); + ext2fs_badblocks_list_iterate_end(bb_iter); +} + +/* + * These functions implement a generalized progress meter. + */ +struct progress_struct { + char format[20]; + char backup[80]; + __u32 max; + int skip_progress; +}; + +static void progress_init(struct progress_struct *progress, + const char *label,__u32 max) +{ + int i; + + memset(progress, 0, sizeof(struct progress_struct)); + if (quiet) + return; + + /* + * Figure out how many digits we need + */ + i = int_log10(max); + sprintf(progress->format, "%%%dd/%%%dld", i, i); + memset(progress->backup, '\b', sizeof(progress->backup)-1); + progress->backup[sizeof(progress->backup)-1] = 0; + if ((2*i)+1 < (int) sizeof(progress->backup)) + progress->backup[(2*i)+1] = 0; + progress->max = max; + + progress->skip_progress = 0; + if (getenv("MKE2FS_SKIP_PROGRESS")) + progress->skip_progress++; + + fputs(label, stdout); + fflush(stdout); +} + +static void progress_update(struct progress_struct *progress, __u32 val) +{ + if ((progress->format[0] == 0) || progress->skip_progress) + return; + printf(progress->format, val, progress->max); + fputs(progress->backup, stdout); +} + +static void progress_close(struct progress_struct *progress) +{ + if (progress->format[0] == 0) + return; + printf("%-28s\n", "done"); +} + + +/* + * Helper function which zeros out _num_ blocks starting at _blk_. In + * case of an error, the details of the error is returned via _ret_blk_ + * and _ret_count_ if they are non-NULL pointers. Returns 0 on + * success, and an error code on an error. + * + * As a special case, if the first argument is NULL, then it will + * attempt to free the static zeroizing buffer. (This is to keep + * programs that check for memory leaks happy.) + */ +static errcode_t zero_blocks(ext2_filsys fs, blk_t blk, int num, + struct progress_struct *progress, + blk_t *ret_blk, int *ret_count) +{ + int j, count, next_update, next_update_incr; + static char *buf; + errcode_t retval; + + /* If fs is null, clean up the static buffer and return */ + if (!fs) { + if (buf) { + free(buf); + buf = 0; + } + return 0; + } + /* Allocate the zeroizing buffer if necessary */ + if (!buf) { + buf = xzalloc(fs->blocksize * STRIDE_LENGTH); + } + /* OK, do the write loop */ + next_update = 0; + next_update_incr = num / 100; + if (next_update_incr < 1) + next_update_incr = 1; + for (j=0; j < num; j += STRIDE_LENGTH, blk += STRIDE_LENGTH) { + count = num - j; + if (count > STRIDE_LENGTH) + count = STRIDE_LENGTH; + retval = io_channel_write_blk(fs->io, blk, count, buf); + if (retval) { + if (ret_count) + *ret_count = count; + if (ret_blk) + *ret_blk = blk; + return retval; + } + if (progress && j > next_update) { + next_update += num / 100; + progress_update(progress, blk); + } + } + return 0; +} + +static void write_inode_tables(ext2_filsys fs) +{ + errcode_t retval; + blk_t blk; + dgrp_t i; + int num; + struct progress_struct progress; + + if (quiet) + memset(&progress, 0, sizeof(progress)); + else + progress_init(&progress, "Writing inode tables: ", + fs->group_desc_count); + + for (i = 0; i < fs->group_desc_count; i++) { + progress_update(&progress, i); + + blk = fs->group_desc[i].bg_inode_table; + num = fs->inode_blocks_per_group; + + retval = zero_blocks(fs, blk, num, 0, &blk, &num); + mke2fs_error_msg_and_die(retval, + "write %d blocks in inode table starting at %d.", + num, blk); + if (sync_kludge) { + if (sync_kludge == 1) + sync(); + else if ((i % sync_kludge) == 0) + sync(); + } + } + zero_blocks(0, 0, 0, 0, 0, 0); + progress_close(&progress); +} + +static void create_root_dir(ext2_filsys fs) +{ + errcode_t retval; + struct ext2_inode inode; + + retval = ext2fs_mkdir(fs, EXT2_ROOT_INO, EXT2_ROOT_INO, 0); + mke2fs_error_msg_and_die(retval, "create root dir"); + if (geteuid()) { + retval = ext2fs_read_inode(fs, EXT2_ROOT_INO, &inode); + mke2fs_error_msg_and_die(retval, "read root inode"); + inode.i_uid = getuid(); + if (inode.i_uid) + inode.i_gid = getgid(); + retval = ext2fs_write_new_inode(fs, EXT2_ROOT_INO, &inode); + mke2fs_error_msg_and_die(retval, "set root inode ownership"); + } +} + +static void create_lost_and_found(ext2_filsys fs) +{ + errcode_t retval; + ext2_ino_t ino; + const char *name = "lost+found"; + int i = 1; + char *msg = "create"; + int lpf_size = 0; + + fs->umask = 077; + retval = ext2fs_mkdir(fs, EXT2_ROOT_INO, 0, name); + if (retval) { + goto CREATE_LOST_AND_FOUND_ERROR; + } + + retval = ext2fs_lookup(fs, EXT2_ROOT_INO, name, strlen(name), 0, &ino); + if (retval) { + msg = "lookup"; + goto CREATE_LOST_AND_FOUND_ERROR; + } + + for (; i < EXT2_NDIR_BLOCKS; i++) { + if ((lpf_size += fs->blocksize) >= 16*1024) + break; + retval = ext2fs_expand_dir(fs, ino); + msg = "expand"; +CREATE_LOST_AND_FOUND_ERROR: + mke2fs_error_msg_and_die(retval, "%s %s", msg, name); + } +} + +static void create_bad_block_inode(ext2_filsys fs, badblocks_list bb_list) +{ + errcode_t retval; + + ext2fs_mark_inode_bitmap(fs->inode_map, EXT2_BAD_INO); + fs->group_desc[0].bg_free_inodes_count--; + fs->super->s_free_inodes_count--; + retval = ext2fs_update_bb_inode(fs, bb_list); + mke2fs_error_msg_and_die(retval, "set bad block inode"); +} + +static void reserve_inodes(ext2_filsys fs) +{ + ext2_ino_t i; + int group; + + for (i = EXT2_ROOT_INO + 1; i < EXT2_FIRST_INODE(fs->super); i++) { + ext2fs_mark_inode_bitmap(fs->inode_map, i); + group = ext2fs_group_of_ino(fs, i); + fs->group_desc[group].bg_free_inodes_count--; + fs->super->s_free_inodes_count--; + } + ext2fs_mark_ib_dirty(fs); +} + +#define BSD_DISKMAGIC (0x82564557UL) /* The disk magic number */ +#define BSD_MAGICDISK (0x57455682UL) /* The disk magic number reversed */ +#define BSD_LABEL_OFFSET 64 + +static void zap_sector(ext2_filsys fs, int sect, int nsect) +{ + char *buf; + char *fmt = "could not %s %d"; + int retval; + unsigned int *magic; + + buf = xmalloc(512*nsect); + + if (sect == 0) { + /* Check for a BSD disklabel, and don't erase it if so */ + retval = io_channel_read_blk(fs->io, 0, -512, buf); + if (retval) + mke2fs_warning_msg(retval, fmt, "read block", 0); + else { + magic = (unsigned int *) (buf + BSD_LABEL_OFFSET); + if ((*magic == BSD_DISKMAGIC) || + (*magic == BSD_MAGICDISK)) + return; + } + } + + memset(buf, 0, 512*nsect); + io_channel_set_blksize(fs->io, 512); + retval = io_channel_write_blk(fs->io, sect, -512*nsect, buf); + io_channel_set_blksize(fs->io, fs->blocksize); + free(buf); + mke2fs_warning_msg(retval, fmt, "erase sector", sect); +} + +static void create_journal_dev(ext2_filsys fs) +{ + struct progress_struct progress; + errcode_t retval; + char *buf; + char *fmt = "%s journal superblock"; + blk_t blk; + int count; + + retval = ext2fs_create_journal_superblock(fs, + fs->super->s_blocks_count, 0, &buf); + mke2fs_error_msg_and_die(retval, fmt, "init"); + if (quiet) + memset(&progress, 0, sizeof(progress)); + else + progress_init(&progress, "Zeroing journal device: ", + fs->super->s_blocks_count); + + retval = zero_blocks(fs, 0, fs->super->s_blocks_count, + &progress, &blk, &count); + mke2fs_error_msg_and_die(retval, "zero journal device (block %u, count %d)", + blk, count); + zero_blocks(0, 0, 0, 0, 0, 0); + + retval = io_channel_write_blk(fs->io, + fs->super->s_first_data_block+1, + 1, buf); + mke2fs_error_msg_and_die(retval, fmt, "write"); + progress_close(&progress); +} + +static void show_stats(ext2_filsys fs) +{ + struct ext2_super_block *s = fs->super; + char *os; + blk_t group_block; + dgrp_t i; + int need, col_left; + + mke2fs_warning_msg((param.s_blocks_count != s->s_blocks_count), + "%d blocks unused\n", param.s_blocks_count - s->s_blocks_count); + os = e2p_os2string(fs->super->s_creator_os); + printf( "Filesystem label=%.*s\n" + "OS type: %s\n" + "Block size=%u (log=%u)\n" + "Fragment size=%u (log=%u)\n" + "%u inodes, %u blocks\n" + "%u blocks (%2.2f%%) reserved for the super user\n" + "First data block=%u\n", + (int) sizeof(s->s_volume_name), + s->s_volume_name, + os, + fs->blocksize, s->s_log_block_size, + fs->fragsize, s->s_log_frag_size, + s->s_inodes_count, s->s_blocks_count, + s->s_r_blocks_count, 100.0 * s->s_r_blocks_count / s->s_blocks_count, + s->s_first_data_block); + free(os); + if (s->s_reserved_gdt_blocks) { + printf("Maximum filesystem blocks=%lu\n", + (s->s_reserved_gdt_blocks + fs->desc_blocks) * + (fs->blocksize / sizeof(struct ext2_group_desc)) * + s->s_blocks_per_group); + } + printf( "%u block group%s\n" + "%u blocks per group, %u fragments per group\n" + "%u inodes per group\n", + fs->group_desc_count, (fs->group_desc_count > 1) ? "s" : "", + s->s_blocks_per_group, s->s_frags_per_group, + s->s_inodes_per_group); + if (fs->group_desc_count == 1) { + puts(""); + return; + } + + printf("Superblock backups stored on blocks: "); + group_block = s->s_first_data_block; + col_left = 0; + for (i = 1; i < fs->group_desc_count; i++) { + group_block += s->s_blocks_per_group; + if (!ext2fs_bg_has_super(fs, i)) + continue; + if (i != 1) + printf(", "); + need = int_log10(group_block) + 2; + if (need > col_left) { + printf("\n\t"); + col_left = 72; + } + col_left -= need; + printf("%u", group_block); + } + puts("\n"); +} + +/* + * Set the S_CREATOR_OS field. Return true if OS is known, + * otherwise, 0. + */ +static int set_os(struct ext2_super_block *sb, char *os) +{ + if (isdigit (*os)) { + sb->s_creator_os = atoi(os); + return 1; + } + + if((sb->s_creator_os = e2p_string2os(os)) >= 0) { + return 1; + } else if (!strcasecmp("GNU", os)) { + sb->s_creator_os = EXT2_OS_HURD; + return 1; + } + return 0; +} + +static void parse_extended_opts(struct ext2_super_block *sb_param, + const char *opts) +{ + char *buf, *token, *next, *p, *arg; + int r_usage = 0; + + buf = xstrdup(opts); + for (token = buf; token && *token; token = next) { + p = strchr(token, ','); + next = 0; + if (p) { + *p = 0; + next = p+1; + } + arg = strchr(token, '='); + if (arg) { + *arg = 0; + arg++; + } + if (strcmp(token, "stride") == 0) { + if (!arg) { + r_usage++; + continue; + } + fs_stride = strtoul(arg, &p, 0); + if (*p || (fs_stride == 0)) { + bb_error_msg("Invalid stride parameter: %s", arg); + r_usage++; + continue; + } + } else if (!strcmp(token, "resize")) { + unsigned long resize, bpg, rsv_groups; + unsigned long group_desc_count, desc_blocks; + unsigned int gdpb, blocksize; + int rsv_gdb; + + if (!arg) { + r_usage++; + continue; + } + + resize = parse_num_blocks(arg, + sb_param->s_log_block_size); + + if (resize == 0) { + bb_error_msg("Invalid resize parameter: %s", arg); + r_usage++; + continue; + } + if (resize <= sb_param->s_blocks_count) { + bb_error_msg("The resize maximum must be greater " + "than the filesystem size"); + r_usage++; + continue; + } + + blocksize = EXT2_BLOCK_SIZE(sb_param); + bpg = sb_param->s_blocks_per_group; + if (!bpg) + bpg = blocksize * 8; + gdpb = blocksize / sizeof(struct ext2_group_desc); + group_desc_count = (sb_param->s_blocks_count + + bpg - 1) / bpg; + desc_blocks = (group_desc_count + + gdpb - 1) / gdpb; + rsv_groups = (resize + bpg - 1) / bpg; + rsv_gdb = (rsv_groups + gdpb - 1) / gdpb - + desc_blocks; + if (rsv_gdb > EXT2_ADDR_PER_BLOCK(sb_param)) + rsv_gdb = EXT2_ADDR_PER_BLOCK(sb_param); + + if (rsv_gdb > 0) { + sb_param->s_feature_compat |= + EXT2_FEATURE_COMPAT_RESIZE_INODE; + + sb_param->s_reserved_gdt_blocks = rsv_gdb; + } + } else + r_usage++; + } + if (r_usage) { + bb_error_msg_and_die( + "\nBad options specified.\n\n" + "Extended options are separated by commas, " + "and may take an argument which\n" + "\tis set off by an equals ('=') sign.\n\n" + "Valid extended options are:\n" + "\tstride=\n" + "\tresize=\n"); + } +} + +static __u32 ok_features[3] = { + EXT3_FEATURE_COMPAT_HAS_JOURNAL | + EXT2_FEATURE_COMPAT_RESIZE_INODE | + EXT2_FEATURE_COMPAT_DIR_INDEX, /* Compat */ + EXT2_FEATURE_INCOMPAT_FILETYPE| /* Incompat */ + EXT3_FEATURE_INCOMPAT_JOURNAL_DEV| + EXT2_FEATURE_INCOMPAT_META_BG, + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER /* R/O compat */ +}; + +static int PRS(int argc, char *argv[]) +{ + int c; + int size; + char * tmp; + int blocksize = 0; + int inode_ratio = 0; + int inode_size = 0; + int reserved_ratio = 5; + int sector_size = 0; + int show_version_only = 0; + ext2_ino_t num_inodes = 0; + errcode_t retval; + char * extended_opts = 0; + const char * fs_type = 0; + blk_t dev_size; + long sysval; + + /* Update our PATH to include /sbin */ + e2fs_set_sbin_path(); + + tmp = getenv("MKE2FS_SYNC"); + if (tmp) + sync_kludge = atoi(tmp); + + /* Determine the system page size if possible */ +#if (!defined(_SC_PAGESIZE) && defined(_SC_PAGE_SIZE)) +#define _SC_PAGESIZE _SC_PAGE_SIZE +#endif +#ifdef _SC_PAGESIZE + sysval = sysconf(_SC_PAGESIZE); + if (sysval > 0) + sys_page_size = sysval; +#endif /* _SC_PAGESIZE */ + + setbuf(stdout, NULL); + setbuf(stderr, NULL); + memset(¶m, 0, sizeof(struct ext2_super_block)); + param.s_rev_level = 1; /* Create revision 1 filesystems now */ + param.s_feature_incompat |= EXT2_FEATURE_INCOMPAT_FILETYPE; + param.s_feature_ro_compat |= EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER; + +#ifdef __linux__ + linux_version_code = get_linux_version_code(); + if (linux_version_code && linux_version_code < KERNEL_VERSION(2,2,0)) { + param.s_rev_level = 0; + param.s_feature_incompat = 0; + param.s_feature_compat = 0; + param.s_feature_ro_compat = 0; + } +#endif + + /* If called as mkfs.ext3, create a journal inode */ + if (last_char_is(applet_name, '3')) + journal_size = -1; + + while ((c = getopt (argc, argv, + "b:cE:f:g:i:jl:m:no:qr:R:s:tvI:J:ST:FL:M:N:O:V")) != EOF) { + switch (c) { + case 'b': + blocksize = xatou_range(optarg, EXT2_MIN_BLOCK_SIZE, EXT2_MAX_BLOCK_SIZE); + mke2fs_warning_msg((blocksize > 4096), + "blocksize %d not usable on most systems", + blocksize); + param.s_log_block_size = + int_log2(blocksize >> EXT2_MIN_BLOCK_LOG_SIZE); + break; + case 'c': /* Check for bad blocks */ + case 't': /* deprecated */ + cflag++; + break; + case 'f': + size = xatou_range(optarg, EXT2_MIN_BLOCK_SIZE, EXT2_MAX_BLOCK_SIZE); + param.s_log_frag_size = + int_log2(size >> EXT2_MIN_BLOCK_LOG_SIZE); + mke2fs_warning_msg(1, "fragments not supported. Ignoring -f option"); + break; + case 'g': + param.s_blocks_per_group = xatou32(optarg); + if ((param.s_blocks_per_group % 8) != 0) { + bb_error_msg_and_die("blocks per group must be multiple of 8"); + } + break; + case 'i': + /* Huh? is "* 1024" correct? */ + inode_ratio = xatou_range(optarg, EXT2_MIN_BLOCK_SIZE, EXT2_MAX_BLOCK_SIZE * 1024); + break; + case 'J': + parse_journal_opts(&journal_device, &journal_flags, &journal_size, optarg); + break; + case 'j': + param.s_feature_compat |= + EXT3_FEATURE_COMPAT_HAS_JOURNAL; + if (!journal_size) + journal_size = -1; + break; + case 'l': + bad_blocks_filename = optarg; + break; + case 'm': + reserved_ratio = xatou_range(optarg, 0, 50); + break; + case 'n': + noaction++; + break; + case 'o': + creator_os = optarg; + break; + case 'r': + param.s_rev_level = xatoi_u(optarg); + if (param.s_rev_level == EXT2_GOOD_OLD_REV) { + param.s_feature_incompat = 0; + param.s_feature_compat = 0; + param.s_feature_ro_compat = 0; + } + break; + case 's': /* deprecated */ + if (xatou(optarg)) + param.s_feature_ro_compat |= + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER; + else + param.s_feature_ro_compat &= + ~EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER; + break; +#ifdef EXT2_DYNAMIC_REV + case 'I': + inode_size = xatoi_u(optarg); + break; +#endif + case 'N': + num_inodes = xatoi_u(optarg); + break; + case 'v': + quiet = 0; + break; + case 'q': + quiet = 1; + break; + case 'F': + force = 1; + break; + case 'L': + volume_label = optarg; + break; + case 'M': + mount_dir = optarg; + break; + case 'O': + if (!strcmp(optarg, "none")) { + param.s_feature_compat = 0; + param.s_feature_incompat = 0; + param.s_feature_ro_compat = 0; + break; + } + if (e2p_edit_feature(optarg, + ¶m.s_feature_compat, + ok_features)) { + bb_error_msg_and_die("Invalid filesystem option set: %s", optarg); + } + break; + case 'E': + case 'R': + extended_opts = optarg; + break; + case 'S': + super_only = 1; + break; + case 'T': + fs_type = optarg; + break; + case 'V': + /* Print version number and exit */ + show_version_only = 1; + quiet = 0; + break; + default: + bb_show_usage(); + } + } + if ((optind == argc) /*&& !show_version_only*/) + bb_show_usage(); + device_name = argv[optind++]; + + mke2fs_verbose("mke2fs %s (%s)\n", E2FSPROGS_VERSION, E2FSPROGS_DATE); + + if (show_version_only) { + return 0; + } + + /* + * If there's no blocksize specified and there is a journal + * device, use it to figure out the blocksize + */ + if (blocksize <= 0 && journal_device) { + ext2_filsys jfs; + io_manager io_ptr; + +#ifdef CONFIG_TESTIO_DEBUG + io_ptr = test_io_manager; + test_io_backing_manager = unix_io_manager; +#else + io_ptr = unix_io_manager; +#endif + retval = ext2fs_open(journal_device, + EXT2_FLAG_JOURNAL_DEV_OK, 0, + 0, io_ptr, &jfs); + mke2fs_error_msg_and_die(retval, "open journal device %s", journal_device); + if ((blocksize < 0) && (jfs->blocksize < (unsigned) (-blocksize))) { + bb_error_msg_and_die( + "Journal dev blocksize (%d) smaller than " + "minimum blocksize %d\n", jfs->blocksize, + -blocksize); + } + blocksize = jfs->blocksize; + param.s_log_block_size = + int_log2(blocksize >> EXT2_MIN_BLOCK_LOG_SIZE); + ext2fs_close(jfs); + } + + if (blocksize > sys_page_size) { + mke2fs_warning_msg(1, "%d-byte blocks too big for system (max %d)", + blocksize, sys_page_size); + if (!force) { + proceed_question(); + } + bb_error_msg("Forced to continue"); + } + mke2fs_warning_msg(((blocksize > 4096) && + (param.s_feature_compat & EXT3_FEATURE_COMPAT_HAS_JOURNAL)), + "some 2.4 kernels do not support " + "blocksizes greater than 4096 using ext3.\n" + "Use -b 4096 if this is an issue for you\n"); + + if (optind < argc) { + param.s_blocks_count = parse_num_blocks(argv[optind++], + param.s_log_block_size); + mke2fs_error_msg_and_die(!param.s_blocks_count, "invalid blocks count - %s", argv[optind - 1]); + } + if (optind < argc) + bb_show_usage(); + + if (param.s_feature_incompat & EXT3_FEATURE_INCOMPAT_JOURNAL_DEV) { + if (!fs_type) + fs_type = "journal"; + reserved_ratio = 0; + param.s_feature_incompat = EXT3_FEATURE_INCOMPAT_JOURNAL_DEV; + param.s_feature_compat = 0; + param.s_feature_ro_compat = 0; + } + if (param.s_rev_level == EXT2_GOOD_OLD_REV && + (param.s_feature_compat || param.s_feature_ro_compat || + param.s_feature_incompat)) + param.s_rev_level = 1; /* Create a revision 1 filesystem */ + + check_plausibility(device_name , force); + check_mount(device_name, force, "filesystem"); + + param.s_log_frag_size = param.s_log_block_size; + + if (noaction && param.s_blocks_count) { + dev_size = param.s_blocks_count; + retval = 0; + } else { + retry: + retval = ext2fs_get_device_size(device_name, + EXT2_BLOCK_SIZE(¶m), + &dev_size); + if ((retval == EFBIG) && + (blocksize == 0) && + (param.s_log_block_size == 0)) { + param.s_log_block_size = 2; + blocksize = 4096; + goto retry; + } + } + + mke2fs_error_msg_and_die((retval && (retval != EXT2_ET_UNIMPLEMENTED)),"determine filesystem size"); + + if (!param.s_blocks_count) { + if (retval == EXT2_ET_UNIMPLEMENTED) { + mke2fs_error_msg_and_die(1, + "determine device size; you " + "must specify\nthe size of the " + "filesystem"); + } else { + if (dev_size == 0) { + bb_error_msg_and_die( + "Device size reported to be zero. " + "Invalid partition specified, or\n\t" + "partition table wasn't reread " + "after running fdisk, due to\n\t" + "a modified partition being busy " + "and in use. You may need to reboot\n\t" + "to re-read your partition table.\n" + ); + } + param.s_blocks_count = dev_size; + if (sys_page_size > EXT2_BLOCK_SIZE(¶m)) + param.s_blocks_count &= ~((sys_page_size / + EXT2_BLOCK_SIZE(¶m))-1); + } + + } else if (!force && (param.s_blocks_count > dev_size)) { + bb_error_msg("Filesystem larger than apparent device size"); + proceed_question(); + } + + /* + * If the user asked for HAS_JOURNAL, then make sure a journal + * gets created. + */ + if ((param.s_feature_compat & EXT3_FEATURE_COMPAT_HAS_JOURNAL) && + !journal_size) + journal_size = -1; + + /* Set first meta blockgroup via an environment variable */ + /* (this is mostly for debugging purposes) */ + if ((param.s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG) && + ((tmp = getenv("MKE2FS_FIRST_META_BG")))) + param.s_first_meta_bg = atoi(tmp); + + /* Get the hardware sector size, if available */ + retval = ext2fs_get_device_sectsize(device_name, §or_size); + mke2fs_error_msg_and_die(retval, "determine hardware sector size"); + + if ((tmp = getenv("MKE2FS_DEVICE_SECTSIZE")) != NULL) + sector_size = atoi(tmp); + + set_fs_defaults(fs_type, ¶m, blocksize, sector_size, &inode_ratio); + blocksize = EXT2_BLOCK_SIZE(¶m); + + if (extended_opts) + parse_extended_opts(¶m, extended_opts); + + /* Since sparse_super is the default, we would only have a problem + * here if it was explicitly disabled. + */ + if ((param.s_feature_compat & EXT2_FEATURE_COMPAT_RESIZE_INODE) && + !(param.s_feature_ro_compat&EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER)) { + bb_error_msg_and_die("reserved online resize blocks not supported " + "on non-sparse filesystem"); + } + + if (param.s_blocks_per_group) { + if (param.s_blocks_per_group < 256 || + param.s_blocks_per_group > 8 * (unsigned) blocksize) { + bb_error_msg_and_die("blocks per group count out of range"); + } + } + + if (!force && param.s_blocks_count >= (1 << 31)) { + bb_error_msg_and_die("Filesystem too large. No more than 2**31-1 blocks\n" + "\t (8TB using a blocksize of 4k) are currently supported."); + } + + if (inode_size) { + if (inode_size < EXT2_GOOD_OLD_INODE_SIZE || + inode_size > EXT2_BLOCK_SIZE(¶m) || + inode_size & (inode_size - 1)) { + bb_error_msg_and_die("invalid inode size %d (min %d/max %d)", + inode_size, EXT2_GOOD_OLD_INODE_SIZE, + blocksize); + } + mke2fs_warning_msg((inode_size != EXT2_GOOD_OLD_INODE_SIZE), + "%d-byte inodes not usable on most systems", + inode_size); + param.s_inode_size = inode_size; + } + + /* + * Calculate number of inodes based on the inode ratio + */ + param.s_inodes_count = num_inodes ? num_inodes : + ((__u64) param.s_blocks_count * blocksize) + / inode_ratio; + + /* + * Calculate number of blocks to reserve + */ + param.s_r_blocks_count = (param.s_blocks_count * reserved_ratio) / 100; + return 1; +} + +static void mke2fs_clean_up(void) +{ + if (ENABLE_FEATURE_CLEAN_UP && journal_device) free(journal_device); +} + +int mke2fs_main (int argc, char *argv[]) +{ + errcode_t retval; + ext2_filsys fs; + badblocks_list bb_list = 0; + unsigned int i; + int val; + io_manager io_ptr; + + if (ENABLE_FEATURE_CLEAN_UP) + atexit(mke2fs_clean_up); + if(!PRS(argc, argv)) + return 0; + +#ifdef CONFIG_TESTIO_DEBUG + io_ptr = test_io_manager; + test_io_backing_manager = unix_io_manager; +#else + io_ptr = unix_io_manager; +#endif + + /* + * Initialize the superblock.... + */ + retval = ext2fs_initialize(device_name, 0, ¶m, + io_ptr, &fs); + mke2fs_error_msg_and_die(retval, "set up superblock"); + + /* + * Wipe out the old on-disk superblock + */ + if (!noaction) + zap_sector(fs, 2, 6); + + /* + * Generate a UUID for it... + */ + uuid_generate(fs->super->s_uuid); + + /* + * Initialize the directory index variables + */ + fs->super->s_def_hash_version = EXT2_HASH_TEA; + uuid_generate((unsigned char *) fs->super->s_hash_seed); + + /* + * Add "jitter" to the superblock's check interval so that we + * don't check all the filesystems at the same time. We use a + * kludgy hack of using the UUID to derive a random jitter value. + */ + for (i = 0, val = 0 ; i < sizeof(fs->super->s_uuid); i++) + val += fs->super->s_uuid[i]; + fs->super->s_max_mnt_count += val % EXT2_DFL_MAX_MNT_COUNT; + + /* + * Override the creator OS, if applicable + */ + if (creator_os && !set_os(fs->super, creator_os)) { + bb_error_msg_and_die("unknown os - %s", creator_os); + } + + /* + * For the Hurd, we will turn off filetype since it doesn't + * support it. + */ + if (fs->super->s_creator_os == EXT2_OS_HURD) + fs->super->s_feature_incompat &= + ~EXT2_FEATURE_INCOMPAT_FILETYPE; + + /* + * Set the volume label... + */ + if (volume_label) { + snprintf(fs->super->s_volume_name, sizeof(fs->super->s_volume_name), "%s", volume_label); + } + + /* + * Set the last mount directory + */ + if (mount_dir) { + snprintf(fs->super->s_last_mounted, sizeof(fs->super->s_last_mounted), "%s", mount_dir); + } + + if (!quiet || noaction) + show_stats(fs); + + if (noaction) + return 0; + + if (fs->super->s_feature_incompat & + EXT3_FEATURE_INCOMPAT_JOURNAL_DEV) { + create_journal_dev(fs); + return (ext2fs_close(fs) ? 1 : 0); + } + + if (bad_blocks_filename) + read_bb_file(fs, &bb_list, bad_blocks_filename); + if (cflag) + test_disk(fs, &bb_list); + + handle_bad_blocks(fs, bb_list); + fs->stride = fs_stride; + retval = ext2fs_allocate_tables(fs); + mke2fs_error_msg_and_die(retval, "allocate filesystem tables"); + if (super_only) { + fs->super->s_state |= EXT2_ERROR_FS; + fs->flags &= ~(EXT2_FLAG_IB_DIRTY|EXT2_FLAG_BB_DIRTY); + } else { + /* rsv must be a power of two (64kB is MD RAID sb alignment) */ + unsigned int rsv = 65536 / fs->blocksize; + unsigned long blocks = fs->super->s_blocks_count; + unsigned long start; + blk_t ret_blk; + +#ifdef ZAP_BOOTBLOCK + zap_sector(fs, 0, 2); +#endif + + /* + * Wipe out any old MD RAID (or other) metadata at the end + * of the device. This will also verify that the device is + * as large as we think. Be careful with very small devices. + */ + start = (blocks & ~(rsv - 1)); + if (start > rsv) + start -= rsv; + if (start > 0) + retval = zero_blocks(fs, start, blocks - start, + NULL, &ret_blk, NULL); + + mke2fs_warning_msg(retval, "cannot zero block %u at end of filesystem", ret_blk); + write_inode_tables(fs); + create_root_dir(fs); + create_lost_and_found(fs); + reserve_inodes(fs); + create_bad_block_inode(fs, bb_list); + if (fs->super->s_feature_compat & + EXT2_FEATURE_COMPAT_RESIZE_INODE) { + retval = ext2fs_create_resize_inode(fs); + mke2fs_error_msg_and_die(retval, "reserve blocks for online resize"); + } + } + + if (journal_device) { + make_journal_device(journal_device, fs, quiet, force); + } else if (journal_size) { + make_journal_blocks(fs, journal_size, journal_flags, quiet); + } + + mke2fs_verbose("Writing superblocks and filesystem accounting information: "); + retval = ext2fs_flush(fs); + mke2fs_warning_msg(retval, "had trouble writing out superblocks"); + mke2fs_verbose_done(); + if (!quiet && !getenv("MKE2FS_SKIP_CHECK_MSG")) + print_check_message(fs); + val = ext2fs_close(fs); + return (retval || val) ? 1 : 0; +} diff --git a/e2fsprogs/tune2fs.c b/e2fsprogs/tune2fs.c new file mode 100644 index 000000000..a2ca1ba09 --- /dev/null +++ b/e2fsprogs/tune2fs.c @@ -0,0 +1,729 @@ +/* vi: set sw=4 ts=4: */ +/* + * tune2fs.c - Change the file system parameters on an ext2 file system + * + * Copyright (C) 1992, 1993, 1994 Remy Card + * Laboratoire MASI, Institut Blaise Pascal + * Universite Pierre et Marie Curie (Paris VI) + * + * Copyright 1995, 1996, 1997, 1998, 1999, 2000 by Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +/* + * History: + * 93/06/01 - Creation + * 93/10/31 - Added the -c option to change the maximal mount counts + * 93/12/14 - Added -l flag to list contents of superblock + * M.J.E. Mol (marcel@duteca.et.tudelft.nl) + * F.W. ten Wolde (franky@duteca.et.tudelft.nl) + * 93/12/29 - Added the -e option to change errors behavior + * 94/02/27 - Ported to use the ext2fs library + * 94/03/06 - Added the checks interval from Uwe Ohse (uwe@tirka.gun.de) + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "e2fsbb.h" +#include "ext2fs/ext2_fs.h" +#include "ext2fs/ext2fs.h" +#include "uuid/uuid.h" +#include "e2p/e2p.h" +#include "ext2fs/kernel-jbd.h" +#include "util.h" +#include "blkid/blkid.h" + +#include "busybox.h" + +static char * device_name = NULL; +static char * new_label, *new_last_mounted, *new_UUID; +static char * io_options; +static int c_flag, C_flag, e_flag, f_flag, g_flag, i_flag, l_flag, L_flag; +static int m_flag, M_flag, r_flag, s_flag = -1, u_flag, U_flag, T_flag; +static time_t last_check_time; +static int print_label; +static int max_mount_count, mount_count, mount_flags; +static unsigned long interval, reserved_blocks; +static unsigned reserved_ratio; +static unsigned long resgid, resuid; +static unsigned short errors; +static int open_flag; +static char *features_cmd; +static char *mntopts_cmd; + +static int journal_size, journal_flags; +static char *journal_device = NULL; + +static const char *please_fsck = "Please run e2fsck on the filesystem\n"; + +static __u32 ok_features[3] = { + EXT3_FEATURE_COMPAT_HAS_JOURNAL | EXT2_FEATURE_COMPAT_DIR_INDEX, + EXT2_FEATURE_INCOMPAT_FILETYPE, + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER +}; + +/* + * Remove an external journal from the filesystem + */ +static void remove_journal_device(ext2_filsys fs) +{ + char *journal_path; + ext2_filsys jfs; + char buf[1024]; + journal_superblock_t *jsb; + int i, nr_users; + errcode_t retval; + int commit_remove_journal = 0; + io_manager io_ptr; + + if (f_flag) + commit_remove_journal = 1; /* force removal even if error */ + + uuid_unparse(fs->super->s_journal_uuid, buf); + journal_path = blkid_get_devname(NULL, "UUID", buf); + + if (!journal_path) { + journal_path = + ext2fs_find_block_device(fs->super->s_journal_dev); + if (!journal_path) + return; + } + + io_ptr = unix_io_manager; + retval = ext2fs_open(journal_path, EXT2_FLAG_RW| + EXT2_FLAG_JOURNAL_DEV_OK, 0, + fs->blocksize, io_ptr, &jfs); + if (retval) { + bb_error_msg("Failed to open external journal"); + goto no_valid_journal; + } + if (!(jfs->super->s_feature_incompat & EXT3_FEATURE_INCOMPAT_JOURNAL_DEV)) { + bb_error_msg("%s is not a journal device", journal_path); + goto no_valid_journal; + } + + /* Get the journal superblock */ + if ((retval = io_channel_read_blk(jfs->io, 1, -1024, buf))) { + bb_error_msg("Failed to read journal superblock"); + goto no_valid_journal; + } + + jsb = (journal_superblock_t *) buf; + if ((jsb->s_header.h_magic != (unsigned) ntohl(JFS_MAGIC_NUMBER)) || + (jsb->s_header.h_blocktype != (unsigned) ntohl(JFS_SUPERBLOCK_V2))) { + bb_error_msg("Journal superblock not found!"); + goto no_valid_journal; + } + + /* Find the filesystem UUID */ + nr_users = ntohl(jsb->s_nr_users); + for (i=0; i < nr_users; i++) { + if (memcmp(fs->super->s_uuid, + &jsb->s_users[i*16], 16) == 0) + break; + } + if (i >= nr_users) { + bb_error_msg("Filesystem's UUID not found on journal device"); + commit_remove_journal = 1; + goto no_valid_journal; + } + nr_users--; + for (i=0; i < nr_users; i++) + memcpy(&jsb->s_users[i*16], &jsb->s_users[(i+1)*16], 16); + jsb->s_nr_users = htonl(nr_users); + + /* Write back the journal superblock */ + if ((retval = io_channel_write_blk(jfs->io, 1, -1024, buf))) { + bb_error_msg("Failed to write journal superblock"); + goto no_valid_journal; + } + + commit_remove_journal = 1; + +no_valid_journal: + if (commit_remove_journal == 0) + bb_error_msg_and_die("Journal NOT removed"); + fs->super->s_journal_dev = 0; + uuid_clear(fs->super->s_journal_uuid); + ext2fs_mark_super_dirty(fs); + puts("Journal removed"); + free(journal_path); +} + +/* Helper function for remove_journal_inode */ +static int release_blocks_proc(ext2_filsys fs, blk_t *blocknr, + int blockcnt EXT2FS_ATTR((unused)), + void *private EXT2FS_ATTR((unused))) +{ + blk_t block; + int group; + + block = *blocknr; + ext2fs_unmark_block_bitmap(fs->block_map,block); + group = ext2fs_group_of_blk(fs, block); + fs->group_desc[group].bg_free_blocks_count++; + fs->super->s_free_blocks_count++; + return 0; +} + +/* + * Remove the journal inode from the filesystem + */ +static void remove_journal_inode(ext2_filsys fs) +{ + struct ext2_inode inode; + errcode_t retval; + ino_t ino = fs->super->s_journal_inum; + char *msg = "to read"; + char *s = "journal inode"; + + retval = ext2fs_read_inode(fs, ino, &inode); + if (retval) + goto REMOVE_JOURNAL_INODE_ERROR; + if (ino == EXT2_JOURNAL_INO) { + retval = ext2fs_read_bitmaps(fs); + if (retval) { + msg = "to read bitmaps"; + s = ""; + goto REMOVE_JOURNAL_INODE_ERROR; + } + retval = ext2fs_block_iterate(fs, ino, 0, NULL, + release_blocks_proc, NULL); + if (retval) { + msg = "clearing"; + goto REMOVE_JOURNAL_INODE_ERROR; + } + memset(&inode, 0, sizeof(inode)); + ext2fs_mark_bb_dirty(fs); + fs->flags &= ~EXT2_FLAG_SUPER_ONLY; + } else + inode.i_flags &= ~EXT2_IMMUTABLE_FL; + retval = ext2fs_write_inode(fs, ino, &inode); + if (retval) { + msg = "writing"; +REMOVE_JOURNAL_INODE_ERROR: + bb_error_msg_and_die("Failed %s %s", msg, s); + } + fs->super->s_journal_inum = 0; + ext2fs_mark_super_dirty(fs); +} + +/* + * Update the default mount options + */ +static void update_mntopts(ext2_filsys fs, char *mntopts) +{ + struct ext2_super_block *sb= fs->super; + + if (e2p_edit_mntopts(mntopts, &sb->s_default_mount_opts, ~0)) + bb_error_msg_and_die("Invalid mount option set: %s", mntopts); + ext2fs_mark_super_dirty(fs); +} + +/* + * Update the feature set as provided by the user. + */ +static void update_feature_set(ext2_filsys fs, char *features) +{ + int sparse, old_sparse, filetype, old_filetype; + int journal, old_journal, dxdir, old_dxdir; + struct ext2_super_block *sb= fs->super; + __u32 old_compat, old_incompat, old_ro_compat; + + old_compat = sb->s_feature_compat; + old_ro_compat = sb->s_feature_ro_compat; + old_incompat = sb->s_feature_incompat; + + old_sparse = sb->s_feature_ro_compat & + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER; + old_filetype = sb->s_feature_incompat & + EXT2_FEATURE_INCOMPAT_FILETYPE; + old_journal = sb->s_feature_compat & + EXT3_FEATURE_COMPAT_HAS_JOURNAL; + old_dxdir = sb->s_feature_compat & + EXT2_FEATURE_COMPAT_DIR_INDEX; + if (e2p_edit_feature(features, &sb->s_feature_compat, ok_features)) + bb_error_msg_and_die("Invalid filesystem option set: %s", features); + sparse = sb->s_feature_ro_compat & + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER; + filetype = sb->s_feature_incompat & + EXT2_FEATURE_INCOMPAT_FILETYPE; + journal = sb->s_feature_compat & + EXT3_FEATURE_COMPAT_HAS_JOURNAL; + dxdir = sb->s_feature_compat & + EXT2_FEATURE_COMPAT_DIR_INDEX; + if (old_journal && !journal) { + if ((mount_flags & EXT2_MF_MOUNTED) && + !(mount_flags & EXT2_MF_READONLY)) { + bb_error_msg_and_die( + "The has_journal flag may only be " + "cleared when the filesystem is\n" + "unmounted or mounted " + "read-only"); + } + if (sb->s_feature_incompat & + EXT3_FEATURE_INCOMPAT_RECOVER) { + bb_error_msg_and_die( + "The needs_recovery flag is set. " + "%s before clearing the has_journal flag.", + please_fsck); + } + if (sb->s_journal_inum) { + remove_journal_inode(fs); + } + if (sb->s_journal_dev) { + remove_journal_device(fs); + } + } + if (journal && !old_journal) { + /* + * If adding a journal flag, let the create journal + * code below handle creating setting the flag and + * creating the journal. We supply a default size if + * necessary. + */ + if (!journal_size) + journal_size = -1; + sb->s_feature_compat &= ~EXT3_FEATURE_COMPAT_HAS_JOURNAL; + } + if (dxdir && !old_dxdir) { + if (!sb->s_def_hash_version) + sb->s_def_hash_version = EXT2_HASH_TEA; + if (uuid_is_null((unsigned char *) sb->s_hash_seed)) + uuid_generate((unsigned char *) sb->s_hash_seed); + } + + if (sb->s_rev_level == EXT2_GOOD_OLD_REV && + (sb->s_feature_compat || sb->s_feature_ro_compat || + sb->s_feature_incompat)) + ext2fs_update_dynamic_rev(fs); + if ((sparse != old_sparse) || + (filetype != old_filetype)) { + sb->s_state &= ~EXT2_VALID_FS; + printf("\n%s\n", please_fsck); + } + if ((old_compat != sb->s_feature_compat) || + (old_ro_compat != sb->s_feature_ro_compat) || + (old_incompat != sb->s_feature_incompat)) + ext2fs_mark_super_dirty(fs); +} + +/* + * Add a journal to the filesystem. + */ +static void add_journal(ext2_filsys fs) +{ + if (fs->super->s_feature_compat & + EXT3_FEATURE_COMPAT_HAS_JOURNAL) { + bb_error_msg_and_die("The filesystem already has a journal"); + } + if (journal_device) { + make_journal_device(journal_device, fs, 0, 0); + } else if (journal_size) { + make_journal_blocks(fs, journal_size, journal_flags, 0); + /* + * If the filesystem wasn't mounted, we need to force + * the block group descriptors out. + */ + if ((mount_flags & EXT2_MF_MOUNTED) == 0) + fs->flags &= ~EXT2_FLAG_SUPER_ONLY; + } + print_check_message(fs); + return; +} + +/* + * Busybox stuff + */ +static char * x_blkid_get_devname(const char *token) +{ + char * dev_name; + + if (!(dev_name = blkid_get_devname(NULL, token, NULL))) + bb_error_msg_and_die("Unable to resolve '%s'", token); + return dev_name; +} + +#ifdef CONFIG_E2LABEL +static void parse_e2label_options(int argc, char ** argv) +{ + if ((argc < 2) || (argc > 3)) + bb_show_usage(); + io_options = strchr(argv[1], '?'); + if (io_options) + *io_options++ = 0; + device_name = x_blkid_get_devname(argv[1]); + if (argc == 3) { + open_flag = EXT2_FLAG_RW | EXT2_FLAG_JOURNAL_DEV_OK; + L_flag = 1; + new_label = argv[2]; + } else + print_label++; +} +#else +#define parse_e2label_options(x,y) +#endif + +static time_t parse_time(char *str) +{ + struct tm ts; + + if (strcmp(str, "now") == 0) { + return time(0); + } + memset(&ts, 0, sizeof(ts)); +#ifdef HAVE_STRPTIME + strptime(str, "%Y%m%d%H%M%S", &ts); +#else + sscanf(str, "%4d%2d%2d%2d%2d%2d", &ts.tm_year, &ts.tm_mon, + &ts.tm_mday, &ts.tm_hour, &ts.tm_min, &ts.tm_sec); + ts.tm_year -= 1900; + ts.tm_mon -= 1; + if (ts.tm_year < 0 || ts.tm_mon < 0 || ts.tm_mon > 11 || + ts.tm_mday < 0 || ts.tm_mday > 31 || ts.tm_hour > 23 || + ts.tm_min > 59 || ts.tm_sec > 61) + ts.tm_mday = 0; +#endif + if (ts.tm_mday == 0) { + bb_error_msg_and_die("Cannot parse date/time specifier: %s", str); + } + return mktime(&ts); +} + +static void parse_tune2fs_options(int argc, char **argv) +{ + int c; + char * tmp; + + printf("tune2fs %s (%s)\n", E2FSPROGS_VERSION, E2FSPROGS_DATE); + while ((c = getopt(argc, argv, "c:e:fg:i:jlm:o:r:s:u:C:J:L:M:O:T:U:")) != EOF) + switch (c) + { + case 'c': + max_mount_count = xatou_range(optarg, 0, 16000); + if (max_mount_count == 0) + max_mount_count = -1; + c_flag = 1; + open_flag = EXT2_FLAG_RW; + break; + case 'C': + mount_count = xatou_range(optarg, 0, 16000); + C_flag = 1; + open_flag = EXT2_FLAG_RW; + break; + case 'e': + if (strcmp (optarg, "continue") == 0) + errors = EXT2_ERRORS_CONTINUE; + else if (strcmp (optarg, "remount-ro") == 0) + errors = EXT2_ERRORS_RO; + else if (strcmp (optarg, "panic") == 0) + errors = EXT2_ERRORS_PANIC; + else { + bb_error_msg_and_die("bad error behavior - %s", optarg); + } + e_flag = 1; + open_flag = EXT2_FLAG_RW; + break; + case 'f': /* Force */ + f_flag = 1; + break; + case 'g': + resgid = bb_strtoul(optarg, NULL, 10); + if (errno) + resgid = bb_xgetgrnam(optarg); + g_flag = 1; + open_flag = EXT2_FLAG_RW; + break; + case 'i': + interval = strtoul(optarg, &tmp, 0); + switch (*tmp) { + case 's': + tmp++; + break; + case '\0': + case 'd': + case 'D': /* days */ + interval *= 86400; + if (*tmp != '\0') + tmp++; + break; + case 'm': + case 'M': /* months! */ + interval *= 86400 * 30; + tmp++; + break; + case 'w': + case 'W': /* weeks */ + interval *= 86400 * 7; + tmp++; + break; + } + if (*tmp || interval > (365 * 86400)) { + bb_error_msg_and_die("bad interval - %s", optarg); + } + i_flag = 1; + open_flag = EXT2_FLAG_RW; + break; + case 'j': + if (!journal_size) + journal_size = -1; + open_flag = EXT2_FLAG_RW; + break; + case 'J': + parse_journal_opts(&journal_device, &journal_flags, &journal_size, optarg); + open_flag = EXT2_FLAG_RW; + break; + case 'l': + l_flag = 1; + break; + case 'L': + new_label = optarg; + L_flag = 1; + open_flag = EXT2_FLAG_RW | + EXT2_FLAG_JOURNAL_DEV_OK; + break; + case 'm': + reserved_ratio = xatou_range(optarg, 0, 50); + m_flag = 1; + open_flag = EXT2_FLAG_RW; + break; + case 'M': + new_last_mounted = optarg; + M_flag = 1; + open_flag = EXT2_FLAG_RW; + break; + case 'o': + if (mntopts_cmd) { + bb_error_msg_and_die("-o may only be specified once"); + } + mntopts_cmd = optarg; + open_flag = EXT2_FLAG_RW; + break; + + case 'O': + if (features_cmd) { + bb_error_msg_and_die("-O may only be specified once"); + } + features_cmd = optarg; + open_flag = EXT2_FLAG_RW; + break; + case 'r': + reserved_blocks = xatoul(optarg); + r_flag = 1; + open_flag = EXT2_FLAG_RW; + break; + case 's': + s_flag = atoi(optarg); + open_flag = EXT2_FLAG_RW; + break; + case 'T': + T_flag = 1; + last_check_time = parse_time(optarg); + open_flag = EXT2_FLAG_RW; + break; + case 'u': + resuid = bb_strtoul(optarg, NULL, 10); + if (errno) + resuid = bb_xgetpwnam(optarg); + u_flag = 1; + open_flag = EXT2_FLAG_RW; + break; + case 'U': + new_UUID = optarg; + U_flag = 1; + open_flag = EXT2_FLAG_RW | + EXT2_FLAG_JOURNAL_DEV_OK; + break; + default: + bb_show_usage(); + } + if (optind < argc - 1 || optind == argc) + bb_show_usage(); + if (!open_flag && !l_flag) + bb_show_usage(); + io_options = strchr(argv[optind], '?'); + if (io_options) + *io_options++ = 0; + device_name = x_blkid_get_devname(argv[optind]); +} + +#ifdef CONFIG_FINDFS +static ATTRIBUTE_NORETURN void do_findfs(int argc, char **argv) +{ + if ((argc != 2) || + (strncmp(argv[1], "LABEL=", 6) && strncmp(argv[1], "UUID=", 5))) + bb_show_usage(); + device_name = x_blkid_get_devname(argv[1]); + puts(device_name); + exit(0); +} +#else +#define do_findfs(x, y) +#endif + +static void tune2fs_clean_up(void) +{ + if (ENABLE_FEATURE_CLEAN_UP && device_name) free(device_name); + if (ENABLE_FEATURE_CLEAN_UP && journal_device) free(journal_device); +} + +int tune2fs_main(int argc, char **argv) +{ + errcode_t retval; + ext2_filsys fs; + struct ext2_super_block *sb; + io_manager io_ptr; + + if (ENABLE_FEATURE_CLEAN_UP) + atexit(tune2fs_clean_up); + + if (ENABLE_FINDFS && (applet_name[0] == 'f')) /* findfs */ + do_findfs(argc, argv); /* no return */ + else if (ENABLE_E2LABEL && (applet_name[0] == 'e')) /* e2label */ + parse_e2label_options(argc, argv); + else + parse_tune2fs_options(argc, argv); /* tune2fs */ + + io_ptr = unix_io_manager; + retval = ext2fs_open2(device_name, io_options, open_flag, + 0, 0, io_ptr, &fs); + if (retval) + bb_error_msg_and_die("No valid superblock on %s", device_name); + sb = fs->super; + if (print_label) { + /* For e2label emulation */ + printf("%.*s\n", (int) sizeof(sb->s_volume_name), + sb->s_volume_name); + return 0; + } + retval = ext2fs_check_if_mounted(device_name, &mount_flags); + if (retval) + bb_error_msg_and_die("cannot determine if %s is mounted", device_name); + /* Normally we only need to write out the superblock */ + fs->flags |= EXT2_FLAG_SUPER_ONLY; + + if (c_flag) { + sb->s_max_mnt_count = max_mount_count; + ext2fs_mark_super_dirty(fs); + printf("Setting maximal mount count to %d\n", max_mount_count); + } + if (C_flag) { + sb->s_mnt_count = mount_count; + ext2fs_mark_super_dirty(fs); + printf("Setting current mount count to %d\n", mount_count); + } + if (e_flag) { + sb->s_errors = errors; + ext2fs_mark_super_dirty(fs); + printf("Setting error behavior to %d\n", errors); + } + if (g_flag) { + sb->s_def_resgid = resgid; + ext2fs_mark_super_dirty(fs); + printf("Setting reserved blocks gid to %lu\n", resgid); + } + if (i_flag) { + sb->s_checkinterval = interval; + ext2fs_mark_super_dirty(fs); + printf("Setting interval between check %lu seconds\n", interval); + } + if (m_flag) { + sb->s_r_blocks_count = (sb->s_blocks_count / 100) + * reserved_ratio; + ext2fs_mark_super_dirty(fs); + printf("Setting reserved blocks percentage to %u (%u blocks)\n", + reserved_ratio, sb->s_r_blocks_count); + } + if (r_flag) { + if (reserved_blocks >= sb->s_blocks_count/2) + bb_error_msg_and_die("reserved blocks count is too big (%lu)", reserved_blocks); + sb->s_r_blocks_count = reserved_blocks; + ext2fs_mark_super_dirty(fs); + printf("Setting reserved blocks count to %lu\n", reserved_blocks); + } + if (s_flag == 1) { + if (sb->s_feature_ro_compat & + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER) + bb_error_msg("\nThe filesystem already has sparse superblocks"); + else { + sb->s_feature_ro_compat |= + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER; + sb->s_state &= ~EXT2_VALID_FS; + ext2fs_mark_super_dirty(fs); + printf("\nSparse superblock flag set. %s", please_fsck); + } + } + if (s_flag == 0) { + if (!(sb->s_feature_ro_compat & + EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER)) + bb_error_msg("\nThe filesystem already has sparse superblocks disabled"); + else { + sb->s_feature_ro_compat &= + ~EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER; + sb->s_state &= ~EXT2_VALID_FS; + fs->flags |= EXT2_FLAG_MASTER_SB_ONLY; + ext2fs_mark_super_dirty(fs); + printf("\nSparse superblock flag cleared. %s", please_fsck); + } + } + if (T_flag) { + sb->s_lastcheck = last_check_time; + ext2fs_mark_super_dirty(fs); + printf("Setting time filesystem last checked to %s\n", + ctime(&last_check_time)); + } + if (u_flag) { + sb->s_def_resuid = resuid; + ext2fs_mark_super_dirty(fs); + printf("Setting reserved blocks uid to %lu\n", resuid); + } + if (L_flag) { + if (strlen(new_label) > sizeof(sb->s_volume_name)) + bb_error_msg("Warning: label too long, truncating"); + memset(sb->s_volume_name, 0, sizeof(sb->s_volume_name)); + safe_strncpy(sb->s_volume_name, new_label, + sizeof(sb->s_volume_name)); + ext2fs_mark_super_dirty(fs); + } + if (M_flag) { + memset(sb->s_last_mounted, 0, sizeof(sb->s_last_mounted)); + safe_strncpy(sb->s_last_mounted, new_last_mounted, + sizeof(sb->s_last_mounted)); + ext2fs_mark_super_dirty(fs); + } + if (mntopts_cmd) + update_mntopts(fs, mntopts_cmd); + if (features_cmd) + update_feature_set(fs, features_cmd); + if (journal_size || journal_device) + add_journal(fs); + + if (U_flag) { + if ((strcasecmp(new_UUID, "null") == 0) || + (strcasecmp(new_UUID, "clear") == 0)) { + uuid_clear(sb->s_uuid); + } else if (strcasecmp(new_UUID, "time") == 0) { + uuid_generate_time(sb->s_uuid); + } else if (strcasecmp(new_UUID, "random") == 0) { + uuid_generate(sb->s_uuid); + } else if (uuid_parse(new_UUID, sb->s_uuid)) { + bb_error_msg_and_die("Invalid UUID format"); + } + ext2fs_mark_super_dirty(fs); + } + + if (l_flag) + list_super (sb); + return (ext2fs_close (fs) ? 1 : 0); +} diff --git a/e2fsprogs/util.c b/e2fsprogs/util.c new file mode 100644 index 000000000..b30c294b8 --- /dev/null +++ b/e2fsprogs/util.c @@ -0,0 +1,267 @@ +/* vi: set sw=4 ts=4: */ +/* + * util.c --- helper functions used by tune2fs and mke2fs + * + * Copyright 1995, 1996, 1997, 1998, 1999, 2000 by Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#include +#include +#include +#include +#include + +#include "e2fsbb.h" +#include "e2p/e2p.h" +#include "ext2fs/ext2_fs.h" +#include "ext2fs/ext2fs.h" +#include "blkid/blkid.h" +#include "util.h" + +void proceed_question(void) +{ + fputs("Proceed anyway? (y,n) ", stdout); + if (bb_ask_confirmation() == 0) + exit(1); +} + +void check_plausibility(const char *device, int force) +{ + int val; + struct stat s; + val = stat(device, &s); + if (force) + return; + if(val == -1) + bb_perror_msg_and_die("cannot stat %s", device); + if (!S_ISBLK(s.st_mode)) { + printf("%s is not a block special device.\n", device); + proceed_question(); + return; + } + +#ifdef HAVE_LINUX_MAJOR_H +#ifndef MAJOR +#define MAJOR(dev) ((dev)>>8) +#define MINOR(dev) ((dev) & 0xff) +#endif +#ifndef SCSI_BLK_MAJOR +#ifdef SCSI_DISK0_MAJOR +#ifdef SCSI_DISK8_MAJOR +#define SCSI_DISK_MAJOR(M) ((M) == SCSI_DISK0_MAJOR || \ + ((M) >= SCSI_DISK1_MAJOR && (M) <= SCSI_DISK7_MAJOR) || \ + ((M) >= SCSI_DISK8_MAJOR && (M) <= SCSI_DISK15_MAJOR)) +#else +#define SCSI_DISK_MAJOR(M) ((M) == SCSI_DISK0_MAJOR || \ + ((M) >= SCSI_DISK1_MAJOR && (M) <= SCSI_DISK7_MAJOR)) +#endif /* defined(SCSI_DISK8_MAJOR) */ +#define SCSI_BLK_MAJOR(M) (SCSI_DISK_MAJOR((M)) || (M) == SCSI_CDROM_MAJOR) +#else +#define SCSI_BLK_MAJOR(M) ((M) == SCSI_DISK_MAJOR || (M) == SCSI_CDROM_MAJOR) +#endif /* defined(SCSI_DISK0_MAJOR) */ +#endif /* defined(SCSI_BLK_MAJOR) */ + if (((MAJOR(s.st_rdev) == HD_MAJOR && + MINOR(s.st_rdev)%64 == 0) || + (SCSI_BLK_MAJOR(MAJOR(s.st_rdev)) && + MINOR(s.st_rdev)%16 == 0))) { + printf("%s is entire device, not just one partition!\n", device); + proceed_question(); + } +#endif +} + +void check_mount(const char *device, int force, const char *type) +{ + errcode_t retval; + int mount_flags; + + retval = ext2fs_check_if_mounted(device, &mount_flags); + if (retval) { + bb_error_msg("cannot determine if %s is mounted", device); + return; + } + if (mount_flags & EXT2_MF_MOUNTED) { + bb_error_msg("%s is mounted !", device); +force_check: + if (force) + bb_error_msg("badblocks forced anyways"); + else + bb_error_msg_and_die("it's not safe to run badblocks!"); + } + + if (mount_flags & EXT2_MF_BUSY) { + bb_error_msg("%s is apparently in use by the system", device); + goto force_check; + } + +} + +void parse_journal_opts(char **journal_device, int *journal_flags, + int *journal_size, const char *opts) +{ + char *buf, *token, *next, *p, *arg; + int journal_usage = 0; + buf = xstrdup(opts); + for (token = buf; token && *token; token = next) { + p = strchr(token, ','); + next = 0; + if (p) { + *p = 0; + next = p+1; + } + arg = strchr(token, '='); + if (arg) { + *arg = 0; + arg++; + } + if (strcmp(token, "device") == 0) { + *journal_device = blkid_get_devname(NULL, arg, NULL); + if (!journal_device) { + journal_usage++; + continue; + } + } else if (strcmp(token, "size") == 0) { + if (!arg) { + journal_usage++; + continue; + } + (*journal_size) = strtoul(arg, &p, 0); + if (*p) + journal_usage++; + } else if (strcmp(token, "v1_superblock") == 0) { + (*journal_flags) |= EXT2_MKJOURNAL_V1_SUPER; + continue; + } else + journal_usage++; + } + if (journal_usage) + bb_error_msg_and_die( + "\nBad journal options specified.\n\n" + "Journal options are separated by commas, " + "and may take an argument which\n" + "\tis set off by an equals ('=') sign.\n\n" + "Valid journal options are:\n" + "\tsize=\n" + "\tdevice=\n\n" + "The journal size must be between " + "1024 and 102400 filesystem blocks.\n\n"); +} + +/* + * Determine the number of journal blocks to use, either via + * user-specified # of megabytes, or via some intelligently selected + * defaults. + * + * Find a reasonable journal file size (in blocks) given the number of blocks + * in the filesystem. For very small filesystems, it is not reasonable to + * have a journal that fills more than half of the filesystem. + */ +int figure_journal_size(int size, ext2_filsys fs) +{ + blk_t j_blocks; + + if (fs->super->s_blocks_count < 2048) { + bb_error_msg("Filesystem too small for a journal"); + return 0; + } + + if (size >= 0) { + j_blocks = size * 1024 / (fs->blocksize / 1024); + if (j_blocks < 1024 || j_blocks > 102400) + bb_error_msg_and_die("\nThe requested journal " + "size is %d blocks;\n it must be " + "between 1024 and 102400 blocks; Aborting", + j_blocks); + if (j_blocks > fs->super->s_free_blocks_count) + bb_error_msg_and_die("Journal size too big for filesystem"); + return j_blocks; + } + + if (fs->super->s_blocks_count < 32768) + j_blocks = 1024; + else if (fs->super->s_blocks_count < 256*1024) + j_blocks = 4096; + else if (fs->super->s_blocks_count < 512*1024) + j_blocks = 8192; + else if (fs->super->s_blocks_count < 1024*1024) + j_blocks = 16384; + else + j_blocks = 32768; + + return j_blocks; +} + +void print_check_message(ext2_filsys fs) +{ + printf("This filesystem will be automatically " + "checked every %d mounts or\n" + "%g days, whichever comes first. " + "Use tune2fs -c or -i to override.\n", + fs->super->s_max_mnt_count, + (double)fs->super->s_checkinterval / (3600 * 24)); +} + +void make_journal_device(char *journal_device, ext2_filsys fs, int quiet, int force) +{ + errcode_t retval; + ext2_filsys jfs; + io_manager io_ptr; + + check_plausibility(journal_device, force); + check_mount(journal_device, force, "journal"); + io_ptr = unix_io_manager; + retval = ext2fs_open(journal_device, EXT2_FLAG_RW| + EXT2_FLAG_JOURNAL_DEV_OK, 0, + fs->blocksize, io_ptr, &jfs); + if (retval) + bb_error_msg_and_die("cannot journal device %s", journal_device); + if(!quiet) + printf("Adding journal to device %s: ", journal_device); + fflush(stdout); + retval = ext2fs_add_journal_device(fs, jfs); + if(retval) + bb_error_msg_and_die("\nFailed to add journal to device %s", journal_device); + if(!quiet) + puts("done"); + ext2fs_close(jfs); +} + +void make_journal_blocks(ext2_filsys fs, int journal_size, int journal_flags, int quiet) +{ + unsigned long journal_blocks; + errcode_t retval; + + journal_blocks = figure_journal_size(journal_size, fs); + if (!journal_blocks) { + fs->super->s_feature_compat &= + ~EXT3_FEATURE_COMPAT_HAS_JOURNAL; + return; + } + if(!quiet) + printf("Creating journal (%ld blocks): ", journal_blocks); + fflush(stdout); + retval = ext2fs_add_journal_inode(fs, journal_blocks, + journal_flags); + if(retval) + bb_error_msg_and_die("cannot create journal"); + if(!quiet) + puts("done"); +} + +char *e2fs_set_sbin_path(void) +{ + char *oldpath = getenv("PATH"); + /* Update our PATH to include /sbin */ +#define PATH_SET "/sbin" + if (oldpath) + oldpath = xasprintf("%s:%s", PATH_SET, oldpath); + else + oldpath = PATH_SET; + putenv (oldpath); + return oldpath; +} diff --git a/e2fsprogs/util.h b/e2fsprogs/util.h new file mode 100644 index 000000000..80d241718 --- /dev/null +++ b/e2fsprogs/util.h @@ -0,0 +1,22 @@ +/* vi: set sw=4 ts=4: */ +/* + * util.h --- header file defining prototypes for helper functions + * used by tune2fs and mke2fs + * + * Copyright 2000 by Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +extern void proceed_question(void); +extern void check_plausibility(const char *device, int force); +extern void parse_journal_opts(char **, int *, int *, const char *opts); +extern void check_mount(const char *device, int force, const char *type); +extern int figure_journal_size(int size, ext2_filsys fs); +extern void print_check_message(ext2_filsys fs); +extern void make_journal_device(char *journal_device, ext2_filsys fs, int quiet, int force); +extern void make_journal_blocks(ext2_filsys fs, int journal_size, int journal_flags, int quiet); +extern char *e2fs_set_sbin_path(void); diff --git a/e2fsprogs/uuid/Kbuild b/e2fsprogs/uuid/Kbuild new file mode 100644 index 000000000..dde981840 --- /dev/null +++ b/e2fsprogs/uuid/Kbuild @@ -0,0 +1,14 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +NEEDED-$(CONFIG_E2FSCK) = y +NEEDED-$(CONFIG_FSCK) = y +NEEDED-$(CONFIG_MKE2FS) = y +NEEDED-$(CONFIG_TUNE2FS) = y + +lib-y:= +lib-$(NEEDED-y) += compare.o gen_uuid.o pack.o parse.o unpack.o unparse.o \ + uuid_time.o diff --git a/e2fsprogs/uuid/compare.c b/e2fsprogs/uuid/compare.c new file mode 100644 index 000000000..348ea7c1f --- /dev/null +++ b/e2fsprogs/uuid/compare.c @@ -0,0 +1,55 @@ +/* vi: set sw=4 ts=4: */ +/* + * compare.c --- compare whether or not two UUID's are the same + * + * Returns 0 if the two UUID's are different, and 1 if they are the same. + * + * Copyright (C) 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF + * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * %End-Header% + */ + +#include "uuidP.h" +#include + +#define UUCMP(u1,u2) if (u1 != u2) return (u1 < u2) ? -1 : 1; + +int uuid_compare(const uuid_t uu1, const uuid_t uu2) +{ + struct uuid uuid1, uuid2; + + uuid_unpack(uu1, &uuid1); + uuid_unpack(uu2, &uuid2); + + UUCMP(uuid1.time_low, uuid2.time_low); + UUCMP(uuid1.time_mid, uuid2.time_mid); + UUCMP(uuid1.time_hi_and_version, uuid2.time_hi_and_version); + UUCMP(uuid1.clock_seq, uuid2.clock_seq); + return memcmp(uuid1.node, uuid2.node, 6); +} diff --git a/e2fsprogs/uuid/gen_uuid.c b/e2fsprogs/uuid/gen_uuid.c new file mode 100644 index 000000000..9d700a015 --- /dev/null +++ b/e2fsprogs/uuid/gen_uuid.c @@ -0,0 +1,305 @@ +/* vi: set sw=4 ts=4: */ +/* + * gen_uuid.c --- generate a DCE-compatible uuid + * + * Copyright (C) 1996, 1997, 1998, 1999 Theodore Ts'o. + * + * %Begin-Header% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF + * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * %End-Header% + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_IOCTL_H +#include +#endif +#include +#ifdef HAVE_SYS_SOCKIO_H +#include +#endif +#ifdef HAVE_NET_IF_H +#include +#endif +#ifdef HAVE_NETINET_IN_H +#include +#endif +#ifdef HAVE_NET_IF_DL_H +#include +#endif + +#include "uuidP.h" + +#ifdef HAVE_SRANDOM +#define srand(x) srandom(x) +#define rand() random() +#endif + +static int get_random_fd(void) +{ + struct timeval tv; + static int fd = -2; + int i; + + if (fd == -2) { + gettimeofday(&tv, 0); + fd = open("/dev/urandom", O_RDONLY); + if (fd == -1) + fd = open("/dev/random", O_RDONLY | O_NONBLOCK); + srand((getpid() << 16) ^ getuid() ^ tv.tv_sec ^ tv.tv_usec); + } + /* Crank the random number generator a few times */ + gettimeofday(&tv, 0); + for (i = (tv.tv_sec ^ tv.tv_usec) & 0x1F; i > 0; i--) + rand(); + return fd; +} + + +/* + * Generate a series of random bytes. Use /dev/urandom if possible, + * and if not, use srandom/random. + */ +static void get_random_bytes(void *buf, int nbytes) +{ + int i, n = nbytes, fd = get_random_fd(); + int lose_counter = 0; + unsigned char *cp = (unsigned char *) buf; + + if (fd >= 0) { + while (n > 0) { + i = read(fd, cp, n); + if (i <= 0) { + if (lose_counter++ > 16) + break; + continue; + } + n -= i; + cp += i; + lose_counter = 0; + } + } + + /* + * We do this all the time, but this is the only source of + * randomness if /dev/random/urandom is out to lunch. + */ + for (cp = buf, i = 0; i < nbytes; i++) + *cp++ ^= (rand() >> 7) & 0xFF; + return; +} + +/* + * Get the ethernet hardware address, if we can find it... + */ +static int get_node_id(unsigned char *node_id) +{ +#ifdef HAVE_NET_IF_H + int sd; + struct ifreq ifr, *ifrp; + struct ifconf ifc; + char buf[1024]; + int n, i; + unsigned char *a; +#ifdef HAVE_NET_IF_DL_H + struct sockaddr_dl *sdlp; +#endif + +/* + * BSD 4.4 defines the size of an ifreq to be + * max(sizeof(ifreq), sizeof(ifreq.ifr_name)+ifreq.ifr_addr.sa_len + * However, under earlier systems, sa_len isn't present, so the size is + * just sizeof(struct ifreq) + */ +#ifdef HAVE_SA_LEN +#ifndef max +#define max(a,b) ((a) > (b) ? (a) : (b)) +#endif +#define ifreq_size(i) max(sizeof(struct ifreq),\ + sizeof((i).ifr_name)+(i).ifr_addr.sa_len) +#else +#define ifreq_size(i) sizeof(struct ifreq) +#endif /* HAVE_SA_LEN*/ + + sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (sd < 0) { + return -1; + } + memset(buf, 0, sizeof(buf)); + ifc.ifc_len = sizeof(buf); + ifc.ifc_buf = buf; + if (ioctl (sd, SIOCGIFCONF, (char *)&ifc) < 0) { + close(sd); + return -1; + } + n = ifc.ifc_len; + for (i = 0; i < n; i+= ifreq_size(*ifrp) ) { + ifrp = (struct ifreq *)((char *) ifc.ifc_buf+i); + strncpy(ifr.ifr_name, ifrp->ifr_name, IFNAMSIZ); +#ifdef SIOCGIFHWADDR + if (ioctl(sd, SIOCGIFHWADDR, &ifr) < 0) + continue; + a = (unsigned char *) &ifr.ifr_hwaddr.sa_data; +#else +#ifdef SIOCGENADDR + if (ioctl(sd, SIOCGENADDR, &ifr) < 0) + continue; + a = (unsigned char *) ifr.ifr_enaddr; +#else +#ifdef HAVE_NET_IF_DL_H + sdlp = (struct sockaddr_dl *) &ifrp->ifr_addr; + if ((sdlp->sdl_family != AF_LINK) || (sdlp->sdl_alen != 6)) + continue; + a = (unsigned char *) &sdlp->sdl_data[sdlp->sdl_nlen]; +#else + /* + * XXX we don't have a way of getting the hardware + * address + */ + close(sd); + return 0; +#endif /* HAVE_NET_IF_DL_H */ +#endif /* SIOCGENADDR */ +#endif /* SIOCGIFHWADDR */ + if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5]) + continue; + if (node_id) { + memcpy(node_id, a, 6); + close(sd); + return 1; + } + } + close(sd); +#endif + return 0; +} + +/* Assume that the gettimeofday() has microsecond granularity */ +#define MAX_ADJUSTMENT 10 + +static int get_clock(uint32_t *clock_high, uint32_t *clock_low, uint16_t *ret_clock_seq) +{ + static int adjustment = 0; + static struct timeval last = {0, 0}; + static uint16_t clock_seq; + struct timeval tv; + unsigned long long clock_reg; + +try_again: + gettimeofday(&tv, 0); + if ((last.tv_sec == 0) && (last.tv_usec == 0)) { + get_random_bytes(&clock_seq, sizeof(clock_seq)); + clock_seq &= 0x3FFF; + last = tv; + last.tv_sec--; + } + if ((tv.tv_sec < last.tv_sec) || + ((tv.tv_sec == last.tv_sec) && + (tv.tv_usec < last.tv_usec))) { + clock_seq = (clock_seq+1) & 0x3FFF; + adjustment = 0; + last = tv; + } else if ((tv.tv_sec == last.tv_sec) && + (tv.tv_usec == last.tv_usec)) { + if (adjustment >= MAX_ADJUSTMENT) + goto try_again; + adjustment++; + } else { + adjustment = 0; + last = tv; + } + + clock_reg = tv.tv_usec*10 + adjustment; + clock_reg += ((unsigned long long) tv.tv_sec)*10000000; + clock_reg += (((unsigned long long) 0x01B21DD2) << 32) + 0x13814000; + + *clock_high = clock_reg >> 32; + *clock_low = clock_reg; + *ret_clock_seq = clock_seq; + return 0; +} + +void uuid_generate_time(uuid_t out) +{ + static unsigned char node_id[6]; + static int has_init = 0; + struct uuid uu; + uint32_t clock_mid; + + if (!has_init) { + if (get_node_id(node_id) <= 0) { + get_random_bytes(node_id, 6); + /* + * Set multicast bit, to prevent conflicts + * with IEEE 802 addresses obtained from + * network cards + */ + node_id[0] |= 0x01; + } + has_init = 1; + } + get_clock(&clock_mid, &uu.time_low, &uu.clock_seq); + uu.clock_seq |= 0x8000; + uu.time_mid = (uint16_t) clock_mid; + uu.time_hi_and_version = ((clock_mid >> 16) & 0x0FFF) | 0x1000; + memcpy(uu.node, node_id, 6); + uuid_pack(&uu, out); +} + +void uuid_generate_random(uuid_t out) +{ + uuid_t buf; + struct uuid uu; + + get_random_bytes(buf, sizeof(buf)); + uuid_unpack(buf, &uu); + + uu.clock_seq = (uu.clock_seq & 0x3FFF) | 0x8000; + uu.time_hi_and_version = (uu.time_hi_and_version & 0x0FFF) | 0x4000; + uuid_pack(&uu, out); +} + +/* + * This is the generic front-end to uuid_generate_random and + * uuid_generate_time. It uses uuid_generate_random only if + * /dev/urandom is available, since otherwise we won't have + * high-quality randomness. + */ +void uuid_generate(uuid_t out) +{ + if (get_random_fd() >= 0) + uuid_generate_random(out); + else + uuid_generate_time(out); +} diff --git a/e2fsprogs/uuid/pack.c b/e2fsprogs/uuid/pack.c new file mode 100644 index 000000000..217cfce5d --- /dev/null +++ b/e2fsprogs/uuid/pack.c @@ -0,0 +1,69 @@ +/* vi: set sw=4 ts=4: */ +/* + * Internal routine for packing UUID's + * + * Copyright (C) 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF + * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * %End-Header% + */ + +#include +#include "uuidP.h" + +void uuid_pack(const struct uuid *uu, uuid_t ptr) +{ + uint32_t tmp; + unsigned char *out = ptr; + + tmp = uu->time_low; + out[3] = (unsigned char) tmp; + tmp >>= 8; + out[2] = (unsigned char) tmp; + tmp >>= 8; + out[1] = (unsigned char) tmp; + tmp >>= 8; + out[0] = (unsigned char) tmp; + + tmp = uu->time_mid; + out[5] = (unsigned char) tmp; + tmp >>= 8; + out[4] = (unsigned char) tmp; + + tmp = uu->time_hi_and_version; + out[7] = (unsigned char) tmp; + tmp >>= 8; + out[6] = (unsigned char) tmp; + + tmp = uu->clock_seq; + out[9] = (unsigned char) tmp; + tmp >>= 8; + out[8] = (unsigned char) tmp; + + memcpy(out+10, uu->node, 6); +} diff --git a/e2fsprogs/uuid/parse.c b/e2fsprogs/uuid/parse.c new file mode 100644 index 000000000..9a3f9cb92 --- /dev/null +++ b/e2fsprogs/uuid/parse.c @@ -0,0 +1,80 @@ +/* vi: set sw=4 ts=4: */ +/* + * parse.c --- UUID parsing + * + * Copyright (C) 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF + * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * %End-Header% + */ + +#include +#include +#include +#include + +#include "uuidP.h" + +int uuid_parse(const char *in, uuid_t uu) +{ + struct uuid uuid; + int i; + const char *cp; + char buf[3]; + + if (strlen(in) != 36) + return -1; + for (i=0, cp = in; i <= 36; i++,cp++) { + if ((i == 8) || (i == 13) || (i == 18) || + (i == 23)) { + if (*cp == '-') + continue; + else + return -1; + } + if (i== 36) + if (*cp == 0) + continue; + if (!isxdigit(*cp)) + return -1; + } + uuid.time_low = strtoul(in, NULL, 16); + uuid.time_mid = strtoul(in+9, NULL, 16); + uuid.time_hi_and_version = strtoul(in+14, NULL, 16); + uuid.clock_seq = strtoul(in+19, NULL, 16); + cp = in+24; + buf[2] = 0; + for (i=0; i < 6; i++) { + buf[0] = *cp++; + buf[1] = *cp++; + uuid.node[i] = strtoul(buf, NULL, 16); + } + + uuid_pack(&uuid, uu); + return 0; +} diff --git a/e2fsprogs/uuid/unpack.c b/e2fsprogs/uuid/unpack.c new file mode 100644 index 000000000..95d3aab4a --- /dev/null +++ b/e2fsprogs/uuid/unpack.c @@ -0,0 +1,63 @@ +/* vi: set sw=4 ts=4: */ +/* + * Internal routine for unpacking UUID + * + * Copyright (C) 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF + * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * %End-Header% + */ + +#include +#include "uuidP.h" + +void uuid_unpack(const uuid_t in, struct uuid *uu) +{ + const uint8_t *ptr = in; + uint32_t tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + tmp = (tmp << 8) | *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->time_low = tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->time_mid = tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->time_hi_and_version = tmp; + + tmp = *ptr++; + tmp = (tmp << 8) | *ptr++; + uu->clock_seq = tmp; + + memcpy(uu->node, ptr, 6); +} diff --git a/e2fsprogs/uuid/unparse.c b/e2fsprogs/uuid/unparse.c new file mode 100644 index 000000000..d2948fe6d --- /dev/null +++ b/e2fsprogs/uuid/unparse.c @@ -0,0 +1,77 @@ +/* vi: set sw=4 ts=4: */ +/* + * unparse.c -- convert a UUID to string + * + * Copyright (C) 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF + * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * %End-Header% + */ + +#include + +#include "uuidP.h" + +static const char *fmt_lower = + "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x"; + +static const char *fmt_upper = + "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X"; + +#ifdef UUID_UNPARSE_DEFAULT_UPPER +#define FMT_DEFAULT fmt_upper +#else +#define FMT_DEFAULT fmt_lower +#endif + +static void uuid_unparse_x(const uuid_t uu, char *out, const char *fmt) +{ + struct uuid uuid; + + uuid_unpack(uu, &uuid); + sprintf(out, fmt, + uuid.time_low, uuid.time_mid, uuid.time_hi_and_version, + uuid.clock_seq >> 8, uuid.clock_seq & 0xFF, + uuid.node[0], uuid.node[1], uuid.node[2], + uuid.node[3], uuid.node[4], uuid.node[5]); +} + +void uuid_unparse_lower(const uuid_t uu, char *out) +{ + uuid_unparse_x(uu, out, fmt_lower); +} + +void uuid_unparse_upper(const uuid_t uu, char *out) +{ + uuid_unparse_x(uu, out, fmt_upper); +} + +void uuid_unparse(const uuid_t uu, char *out) +{ + uuid_unparse_x(uu, out, FMT_DEFAULT); +} diff --git a/e2fsprogs/uuid/uuid.h b/e2fsprogs/uuid/uuid.h new file mode 100644 index 000000000..bd53b15d0 --- /dev/null +++ b/e2fsprogs/uuid/uuid.h @@ -0,0 +1,104 @@ +/* vi: set sw=4 ts=4: */ +/* + * Public include file for the UUID library + * + * Copyright (C) 1996, 1997, 1998 Theodore Ts'o. + * + * %Begin-Header% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF + * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * %End-Header% + */ + +#ifndef _UUID_UUID_H +#define _UUID_UUID_H + +#include +#include + +typedef unsigned char uuid_t[16]; + +/* UUID Variant definitions */ +#define UUID_VARIANT_NCS 0 +#define UUID_VARIANT_DCE 1 +#define UUID_VARIANT_MICROSOFT 2 +#define UUID_VARIANT_OTHER 3 + +/* UUID Type definitions */ +#define UUID_TYPE_DCE_TIME 1 +#define UUID_TYPE_DCE_RANDOM 4 + +/* Allow UUID constants to be defined */ +#ifdef __GNUC__ +#define UUID_DEFINE(name,u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10,u11,u12,u13,u14,u15) \ + static const uuid_t name ATTRIBUTE_UNUSED = {u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10,u11,u12,u13,u14,u15} +#else +#define UUID_DEFINE(name,u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10,u11,u12,u13,u14,u15) \ + static const uuid_t name = {u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,u10,u11,u12,u13,u14,u15} +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* clear.c */ +/*void uuid_clear(uuid_t uu);*/ +#define uuid_clear(uu) memset(uu, 0, sizeof(uu)) + +/* compare.c */ +int uuid_compare(const uuid_t uu1, const uuid_t uu2); + +/* copy.c */ +/*void uuid_copy(uuid_t dst, const uuid_t src);*/ +#define uuid_copy(dst,src) memcpy(dst, src, sizeof(dst)) + +/* gen_uuid.c */ +void uuid_generate(uuid_t out); +void uuid_generate_random(uuid_t out); +void uuid_generate_time(uuid_t out); + +/* isnull.c */ +/*int uuid_is_null(const uuid_t uu);*/ +#define uuid_is_null(uu) (!memcmp(uu, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", sizeof(uu))) + +/* parse.c */ +int uuid_parse(const char *in, uuid_t uu); + +/* unparse.c */ +void uuid_unparse(const uuid_t uu, char *out); +void uuid_unparse_lower(const uuid_t uu, char *out); +void uuid_unparse_upper(const uuid_t uu, char *out); + +/* uuid_time.c */ +time_t uuid_time(const uuid_t uu, struct timeval *ret_tv); +int uuid_type(const uuid_t uu); +int uuid_variant(const uuid_t uu); + +#ifdef __cplusplus +} +#endif + +#endif /* _UUID_UUID_H */ diff --git a/e2fsprogs/uuid/uuidP.h b/e2fsprogs/uuid/uuidP.h new file mode 100644 index 000000000..87041ef0a --- /dev/null +++ b/e2fsprogs/uuid/uuidP.h @@ -0,0 +1,60 @@ +/* vi: set sw=4 ts=4: */ +/* + * uuid.h -- private header file for uuids + * + * Copyright (C) 1996, 1997 Theodore Ts'o. + * + * %Begin-Header% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF + * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * %End-Header% + */ + +#include +#include + +#include "uuid.h" + +/* + * Offset between 15-Oct-1582 and 1-Jan-70 + */ +#define TIME_OFFSET_HIGH 0x01B21DD2 +#define TIME_OFFSET_LOW 0x13814000 + +struct uuid { + uint32_t time_low; + uint16_t time_mid; + uint16_t time_hi_and_version; + uint16_t clock_seq; + uint8_t node[6]; +}; + + +/* + * prototypes + */ +void uuid_pack(const struct uuid *uu, uuid_t ptr); +void uuid_unpack(const uuid_t in, struct uuid *uu); diff --git a/e2fsprogs/uuid/uuid_time.c b/e2fsprogs/uuid/uuid_time.c new file mode 100644 index 000000000..b54d67322 --- /dev/null +++ b/e2fsprogs/uuid/uuid_time.c @@ -0,0 +1,161 @@ +/* vi: set sw=4 ts=4: */ +/* + * uuid_time.c --- Interpret the time field from a uuid. This program + * violates the UUID abstraction barrier by reaching into the guts + * of a UUID and interpreting it. + * + * Copyright (C) 1998, 1999 Theodore Ts'o. + * + * %Begin-Header% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ALL OF + * WHICH ARE HEREBY DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF NOT ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * %End-Header% + */ + +#include +#include +#include +#include +#include + +#include "uuidP.h" + +time_t uuid_time(const uuid_t uu, struct timeval *ret_tv) +{ + struct uuid uuid; + uint32_t high; + struct timeval tv; + unsigned long long clock_reg; + + uuid_unpack(uu, &uuid); + + high = uuid.time_mid | ((uuid.time_hi_and_version & 0xFFF) << 16); + clock_reg = uuid.time_low | ((unsigned long long) high << 32); + + clock_reg -= (((unsigned long long) 0x01B21DD2) << 32) + 0x13814000; + tv.tv_sec = clock_reg / 10000000; + tv.tv_usec = (clock_reg % 10000000) / 10; + + if (ret_tv) + *ret_tv = tv; + + return tv.tv_sec; +} + +int uuid_type(const uuid_t uu) +{ + struct uuid uuid; + + uuid_unpack(uu, &uuid); + return ((uuid.time_hi_and_version >> 12) & 0xF); +} + +int uuid_variant(const uuid_t uu) +{ + struct uuid uuid; + int var; + + uuid_unpack(uu, &uuid); + var = uuid.clock_seq; + + if ((var & 0x8000) == 0) + return UUID_VARIANT_NCS; + if ((var & 0x4000) == 0) + return UUID_VARIANT_DCE; + if ((var & 0x2000) == 0) + return UUID_VARIANT_MICROSOFT; + return UUID_VARIANT_OTHER; +} + +#ifdef DEBUG +static const char *variant_string(int variant) +{ + switch (variant) { + case UUID_VARIANT_NCS: + return "NCS"; + case UUID_VARIANT_DCE: + return "DCE"; + case UUID_VARIANT_MICROSOFT: + return "Microsoft"; + default: + return "Other"; + } +} + + +int +main(int argc, char **argv) +{ + uuid_t buf; + time_t time_reg; + struct timeval tv; + int type, variant; + + if (argc != 2) { + fprintf(stderr, "Usage: %s uuid\n", argv[0]); + exit(1); + } + if (uuid_parse(argv[1], buf)) { + fprintf(stderr, "Invalid UUID: %s\n", argv[1]); + exit(1); + } + variant = uuid_variant(buf); + type = uuid_type(buf); + time_reg = uuid_time(buf, &tv); + + printf("UUID variant is %d (%s)\n", variant, variant_string(variant)); + if (variant != UUID_VARIANT_DCE) { + printf("Warning: This program only knows how to interpret " + "DCE UUIDs.\n\tThe rest of the output is likely " + "to be incorrect!!\n"); + } + printf("UUID type is %d", type); + switch (type) { + case 1: + printf(" (time based)\n"); + break; + case 2: + printf(" (DCE)\n"); + break; + case 3: + printf(" (name-based)\n"); + break; + case 4: + printf(" (random)\n"); + break; + default: + puts(""); + } + if (type != 1) { + printf("Warning: not a time-based UUID, so UUID time " + "decoding will likely not work!\n"); + } + printf("UUID time is: (%ld, %ld): %s\n", tv.tv_sec, tv.tv_usec, + ctime(&time_reg)); + + return 0; +} +#endif diff --git a/editors/Config.in b/editors/Config.in new file mode 100644 index 000000000..4ba009019 --- /dev/null +++ b/editors/Config.in @@ -0,0 +1,131 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Editors" + +config AWK + bool "awk" + default n + help + Awk is used as a pattern scanning and processing language. This is + the BusyBox implementation of that programming language. + +config FEATURE_AWK_MATH + bool "Enable math functions (requires libm)" + default y + depends on AWK + help + Enable math functions of the Awk programming language. + NOTE: This will require libm to be present for linking. + +config ED + bool "ed" + default n + help + The original 1970's Unix text editor, from the days of teletypes. + Small, simple, evil. Part of SUSv3. If you're not already using + this, you don't need it. + +config PATCH + bool "patch" + default n + help + Apply a unified diff formatted patch. + +config SED + bool "sed" + default n + help + sed is used to perform text transformations on a file + or input from a pipeline. + +config VI + bool "vi" + default n + help + 'vi' is a text editor. More specifically, it is the One True + text editor . It does, however, have a rather steep + learning curve. If you are not already comfortable with 'vi' + you may wish to use something else. + +config FEATURE_VI_COLON + bool "Enable \":\" colon commands (no \"ex\" mode)" + default y + depends on VI + help + Enable a limited set of colon commands for vi. This does not + provide an "ex" mode. + +config FEATURE_VI_YANKMARK + bool "Enable yank/put commands and mark cmds" + default y + depends on VI + help + This will enable you to use yank and put, as well as mark in + busybox vi. + +config FEATURE_VI_SEARCH + bool "Enable search and replace cmds" + default y + depends on VI + help + Select this if you wish to be able to do search and replace in + busybox vi. + +config FEATURE_VI_USE_SIGNALS + bool "Catch signals" + default y + depends on VI + help + Selecting this option will make busybox vi signal aware. This will + make busybox vi support SIGWINCH to deal with Window Changes, catch + Ctrl-Z and Ctrl-C and alarms. + +config FEATURE_VI_DOT_CMD + bool "Remember previous cmd and \".\" cmd" + default y + depends on VI + help + Make busybox vi remember the last command and be able to repeat it. + +config FEATURE_VI_READONLY + bool "Enable -R option and \"view\" mode" + default y + depends on VI + help + Enable the read-only command line option, which allows the user to + open a file in read-only mode. + +config FEATURE_VI_SETOPTS + bool "Enable set-able options, ai ic showmatch" + default y + depends on VI + help + Enable the editor to set some (ai, ic, showmatch) options. + +config FEATURE_VI_SET + bool "Support for :set" + default y + depends on VI + help + Support for ":set". + +config FEATURE_VI_WIN_RESIZE + bool "Handle window resize" + default y + depends on VI + help + Make busybox vi behave nicely with terminals that get resized. + +config FEATURE_VI_OPTIMIZE_CURSOR + bool "Optimize cursor movement" + default y + depends on VI + help + This will make the cursor movement faster, but requires more memory + and it makes the applet a tiny bit larger. + +endmenu + diff --git a/editors/Kbuild b/editors/Kbuild new file mode 100644 index 000000000..d991e1faf --- /dev/null +++ b/editors/Kbuild @@ -0,0 +1,12 @@ +# 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_AWK) += awk.o +lib-$(CONFIG_ED) += ed.o +lib-$(CONFIG_PATCH) += patch.o +lib-$(CONFIG_SED) += sed.o +lib-$(CONFIG_VI) += vi.o diff --git a/editors/awk.c b/editors/awk.c new file mode 100644 index 000000000..9386f4ec0 --- /dev/null +++ b/editors/awk.c @@ -0,0 +1,2769 @@ +/* vi: set sw=4 ts=4: */ +/* + * awk implementation for busybox + * + * Copyright (C) 2002 by Dmitry Zakharov + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include "xregex.h" +#include + + +#define MAXVARFMT 240 +#define MINNVBLOCK 64 + +/* variable flags */ +#define VF_NUMBER 0x0001 /* 1 = primary type is number */ +#define VF_ARRAY 0x0002 /* 1 = it's an array */ + +#define VF_CACHED 0x0100 /* 1 = num/str value has cached str/num eq */ +#define VF_USER 0x0200 /* 1 = user input (may be numeric string) */ +#define VF_SPECIAL 0x0400 /* 1 = requires extra handling when changed */ +#define VF_WALK 0x0800 /* 1 = variable has alloc'd x.walker list */ +#define VF_FSTR 0x1000 /* 1 = string points to fstring buffer */ +#define VF_CHILD 0x2000 /* 1 = function arg; x.parent points to source */ +#define VF_DIRTY 0x4000 /* 1 = variable was set explicitly */ + +/* these flags are static, don't change them when value is changed */ +#define VF_DONTTOUCH (VF_ARRAY | VF_SPECIAL | VF_WALK | VF_CHILD | VF_DIRTY) + +/* Variable */ +typedef struct var_s { + unsigned short type; /* flags */ + double number; + char *string; + union { + int aidx; /* func arg idx (for compilation stage) */ + struct xhash_s *array; /* array ptr */ + struct var_s *parent; /* for func args, ptr to actual parameter */ + char **walker; /* list of array elements (for..in) */ + } x; +} var; + +/* Node chain (pattern-action chain, BEGIN, END, function bodies) */ +typedef struct chain_s { + struct node_s *first; + struct node_s *last; + char *programname; +} chain; + +/* Function */ +typedef struct func_s { + unsigned short nargs; + struct chain_s body; +} func; + +/* I/O stream */ +typedef struct rstream_s { + FILE *F; + char *buffer; + int adv; + int size; + int pos; + unsigned short is_pipe; +} rstream; + +typedef struct hash_item_s { + union { + struct var_s v; /* variable/array hash */ + struct rstream_s rs; /* redirect streams hash */ + struct func_s f; /* functions hash */ + } data; + struct hash_item_s *next; /* next in chain */ + char name[1]; /* really it's longer */ +} hash_item; + +typedef struct xhash_s { + unsigned int nel; /* num of elements */ + unsigned int csize; /* current hash size */ + unsigned int nprime; /* next hash size in PRIMES[] */ + unsigned int glen; /* summary length of item names */ + struct hash_item_s **items; +} xhash; + +/* Tree node */ +typedef struct node_s { + uint32_t info; + unsigned short lineno; + union { + struct node_s *n; + var *v; + int i; + char *s; + regex_t *re; + } l; + union { + struct node_s *n; + regex_t *ire; + func *f; + int argno; + } r; + union { + struct node_s *n; + } a; +} node; + +/* Block of temporary variables */ +typedef struct nvblock_s { + int size; + var *pos; + struct nvblock_s *prev; + struct nvblock_s *next; + var nv[0]; +} nvblock; + +typedef struct tsplitter_s { + node n; + regex_t re[2]; +} tsplitter; + +/* simple token classes */ +/* Order and hex values are very important!!! See next_token() */ +#define TC_SEQSTART 1 /* ( */ +#define TC_SEQTERM (1 << 1) /* ) */ +#define TC_REGEXP (1 << 2) /* /.../ */ +#define TC_OUTRDR (1 << 3) /* | > >> */ +#define TC_UOPPOST (1 << 4) /* unary postfix operator */ +#define TC_UOPPRE1 (1 << 5) /* unary prefix operator */ +#define TC_BINOPX (1 << 6) /* two-opnd operator */ +#define TC_IN (1 << 7) +#define TC_COMMA (1 << 8) +#define TC_PIPE (1 << 9) /* input redirection pipe */ +#define TC_UOPPRE2 (1 << 10) /* unary prefix operator */ +#define TC_ARRTERM (1 << 11) /* ] */ +#define TC_GRPSTART (1 << 12) /* { */ +#define TC_GRPTERM (1 << 13) /* } */ +#define TC_SEMICOL (1 << 14) +#define TC_NEWLINE (1 << 15) +#define TC_STATX (1 << 16) /* ctl statement (for, next...) */ +#define TC_WHILE (1 << 17) +#define TC_ELSE (1 << 18) +#define TC_BUILTIN (1 << 19) +#define TC_GETLINE (1 << 20) +#define TC_FUNCDECL (1 << 21) /* `function' `func' */ +#define TC_BEGIN (1 << 22) +#define TC_END (1 << 23) +#define TC_EOF (1 << 24) +#define TC_VARIABLE (1 << 25) +#define TC_ARRAY (1 << 26) +#define TC_FUNCTION (1 << 27) +#define TC_STRING (1 << 28) +#define TC_NUMBER (1 << 29) + +#define TC_UOPPRE (TC_UOPPRE1 | TC_UOPPRE2) + +/* combined token classes */ +#define TC_BINOP (TC_BINOPX | TC_COMMA | TC_PIPE | TC_IN) +#define TC_UNARYOP (TC_UOPPRE | TC_UOPPOST) +#define TC_OPERAND (TC_VARIABLE | TC_ARRAY | TC_FUNCTION | \ + TC_BUILTIN | TC_GETLINE | TC_SEQSTART | TC_STRING | TC_NUMBER) + +#define TC_STATEMNT (TC_STATX | TC_WHILE) +#define TC_OPTERM (TC_SEMICOL | TC_NEWLINE) + +/* word tokens, cannot mean something else if not expected */ +#define TC_WORD (TC_IN | TC_STATEMNT | TC_ELSE | TC_BUILTIN | \ + TC_GETLINE | TC_FUNCDECL | TC_BEGIN | TC_END) + +/* discard newlines after these */ +#define TC_NOTERM (TC_COMMA | TC_GRPSTART | TC_GRPTERM | \ + TC_BINOP | TC_OPTERM) + +/* what can expression begin with */ +#define TC_OPSEQ (TC_OPERAND | TC_UOPPRE | TC_REGEXP) +/* what can group begin with */ +#define TC_GRPSEQ (TC_OPSEQ | TC_OPTERM | TC_STATEMNT | TC_GRPSTART) + +/* if previous token class is CONCAT1 and next is CONCAT2, concatenation */ +/* operator is inserted between them */ +#define TC_CONCAT1 (TC_VARIABLE | TC_ARRTERM | TC_SEQTERM | \ + TC_STRING | TC_NUMBER | TC_UOPPOST) +#define TC_CONCAT2 (TC_OPERAND | TC_UOPPRE) + +#define OF_RES1 0x010000 +#define OF_RES2 0x020000 +#define OF_STR1 0x040000 +#define OF_STR2 0x080000 +#define OF_NUM1 0x100000 +#define OF_CHECKED 0x200000 + +/* combined operator flags */ +#define xx 0 +#define xV OF_RES2 +#define xS (OF_RES2 | OF_STR2) +#define Vx OF_RES1 +#define VV (OF_RES1 | OF_RES2) +#define Nx (OF_RES1 | OF_NUM1) +#define NV (OF_RES1 | OF_NUM1 | OF_RES2) +#define Sx (OF_RES1 | OF_STR1) +#define SV (OF_RES1 | OF_STR1 | OF_RES2) +#define SS (OF_RES1 | OF_STR1 | OF_RES2 | OF_STR2) + +#define OPCLSMASK 0xFF00 +#define OPNMASK 0x007F + +/* operator priority is a highest byte (even: r->l, odd: l->r grouping) + * For builtins it has different meaning: n n s3 s2 s1 v3 v2 v1, + * n - min. number of args, vN - resolve Nth arg to var, sN - resolve to string + */ +#define P(x) (x << 24) +#define PRIMASK 0x7F000000 +#define PRIMASK2 0x7E000000 + +/* Operation classes */ + +#define SHIFT_TIL_THIS 0x0600 +#define RECUR_FROM_THIS 0x1000 + +enum { + OC_DELETE=0x0100, OC_EXEC=0x0200, OC_NEWSOURCE=0x0300, + OC_PRINT=0x0400, OC_PRINTF=0x0500, OC_WALKINIT=0x0600, + + OC_BR=0x0700, OC_BREAK=0x0800, OC_CONTINUE=0x0900, + OC_EXIT=0x0a00, OC_NEXT=0x0b00, OC_NEXTFILE=0x0c00, + OC_TEST=0x0d00, OC_WALKNEXT=0x0e00, + + OC_BINARY=0x1000, OC_BUILTIN=0x1100, OC_COLON=0x1200, + OC_COMMA=0x1300, OC_COMPARE=0x1400, OC_CONCAT=0x1500, + OC_FBLTIN=0x1600, OC_FIELD=0x1700, OC_FNARG=0x1800, + OC_FUNC=0x1900, OC_GETLINE=0x1a00, OC_IN=0x1b00, + OC_LAND=0x1c00, OC_LOR=0x1d00, OC_MATCH=0x1e00, + OC_MOVE=0x1f00, OC_PGETLINE=0x2000, OC_REGEXP=0x2100, + OC_REPLACE=0x2200, OC_RETURN=0x2300, OC_SPRINTF=0x2400, + OC_TERNARY=0x2500, OC_UNARY=0x2600, OC_VAR=0x2700, + OC_DONE=0x2800, + + ST_IF=0x3000, ST_DO=0x3100, ST_FOR=0x3200, + ST_WHILE=0x3300 +}; + +/* simple builtins */ +enum { + F_in=0, F_rn, F_co, F_ex, F_lg, F_si, F_sq, F_sr, + F_ti, F_le, F_sy, F_ff, F_cl +}; + +/* builtins */ +enum { + B_a2=0, B_ix, B_ma, B_sp, B_ss, B_ti, B_lo, B_up, + B_ge, B_gs, B_su, + B_an, B_co, B_ls, B_or, B_rs, B_xo, +}; + +/* tokens and their corresponding info values */ + +#define NTC "\377" /* switch to next token class (tc<<1) */ +#define NTCC '\377' + +#define OC_B OC_BUILTIN + +static char * const tokenlist = + "\1(" NTC + "\1)" NTC + "\1/" NTC /* REGEXP */ + "\2>>" "\1>" "\1|" NTC /* OUTRDR */ + "\2++" "\2--" NTC /* UOPPOST */ + "\2++" "\2--" "\1$" NTC /* UOPPRE1 */ + "\2==" "\1=" "\2+=" "\2-=" /* BINOPX */ + "\2*=" "\2/=" "\2%=" "\2^=" + "\1+" "\1-" "\3**=" "\2**" + "\1/" "\1%" "\1^" "\1*" + "\2!=" "\2>=" "\2<=" "\1>" + "\1<" "\2!~" "\1~" "\2&&" + "\2||" "\1?" "\1:" NTC + "\2in" NTC + "\1," NTC + "\1|" NTC + "\1+" "\1-" "\1!" NTC /* UOPPRE2 */ + "\1]" NTC + "\1{" NTC + "\1}" NTC + "\1;" NTC + "\1\n" NTC + "\2if" "\2do" "\3for" "\5break" /* STATX */ + "\10continue" "\6delete" "\5print" + "\6printf" "\4next" "\10nextfile" + "\6return" "\4exit" NTC + "\5while" NTC + "\4else" NTC + + "\3and" "\5compl" "\6lshift" "\2or" + "\6rshift" "\3xor" + "\5close" "\6system" "\6fflush" "\5atan2" /* BUILTIN */ + "\3cos" "\3exp" "\3int" "\3log" + "\4rand" "\3sin" "\4sqrt" "\5srand" + "\6gensub" "\4gsub" "\5index" "\6length" + "\5match" "\5split" "\7sprintf" "\3sub" + "\6substr" "\7systime" "\10strftime" + "\7tolower" "\7toupper" NTC + "\7getline" NTC + "\4func" "\10function" NTC + "\5BEGIN" NTC + "\3END" "\0" + ; + +static const uint32_t tokeninfo[] = { + + 0, + 0, + OC_REGEXP, + xS|'a', xS|'w', xS|'|', + OC_UNARY|xV|P(9)|'p', OC_UNARY|xV|P(9)|'m', + OC_UNARY|xV|P(9)|'P', OC_UNARY|xV|P(9)|'M', + OC_FIELD|xV|P(5), + OC_COMPARE|VV|P(39)|5, OC_MOVE|VV|P(74), + OC_REPLACE|NV|P(74)|'+', OC_REPLACE|NV|P(74)|'-', + OC_REPLACE|NV|P(74)|'*', OC_REPLACE|NV|P(74)|'/', + OC_REPLACE|NV|P(74)|'%', OC_REPLACE|NV|P(74)|'&', + OC_BINARY|NV|P(29)|'+', OC_BINARY|NV|P(29)|'-', + OC_REPLACE|NV|P(74)|'&', OC_BINARY|NV|P(15)|'&', + OC_BINARY|NV|P(25)|'/', OC_BINARY|NV|P(25)|'%', + OC_BINARY|NV|P(15)|'&', OC_BINARY|NV|P(25)|'*', + OC_COMPARE|VV|P(39)|4, OC_COMPARE|VV|P(39)|3, + OC_COMPARE|VV|P(39)|0, OC_COMPARE|VV|P(39)|1, + OC_COMPARE|VV|P(39)|2, OC_MATCH|Sx|P(45)|'!', + OC_MATCH|Sx|P(45)|'~', OC_LAND|Vx|P(55), + OC_LOR|Vx|P(59), OC_TERNARY|Vx|P(64)|'?', + OC_COLON|xx|P(67)|':', + OC_IN|SV|P(49), + OC_COMMA|SS|P(80), + OC_PGETLINE|SV|P(37), + OC_UNARY|xV|P(19)|'+', OC_UNARY|xV|P(19)|'-', + OC_UNARY|xV|P(19)|'!', + 0, + 0, + 0, + 0, + 0, + ST_IF, ST_DO, ST_FOR, OC_BREAK, + OC_CONTINUE, OC_DELETE|Vx, OC_PRINT, + OC_PRINTF, OC_NEXT, OC_NEXTFILE, + OC_RETURN|Vx, OC_EXIT|Nx, + ST_WHILE, + 0, + + OC_B|B_an|P(0x83), OC_B|B_co|P(0x41), OC_B|B_ls|P(0x83), OC_B|B_or|P(0x83), + OC_B|B_rs|P(0x83), OC_B|B_xo|P(0x83), + OC_FBLTIN|Sx|F_cl, OC_FBLTIN|Sx|F_sy, OC_FBLTIN|Sx|F_ff, OC_B|B_a2|P(0x83), + OC_FBLTIN|Nx|F_co, OC_FBLTIN|Nx|F_ex, OC_FBLTIN|Nx|F_in, OC_FBLTIN|Nx|F_lg, + OC_FBLTIN|F_rn, OC_FBLTIN|Nx|F_si, OC_FBLTIN|Nx|F_sq, OC_FBLTIN|Nx|F_sr, + OC_B|B_ge|P(0xd6), OC_B|B_gs|P(0xb6), OC_B|B_ix|P(0x9b), OC_FBLTIN|Sx|F_le, + OC_B|B_ma|P(0x89), OC_B|B_sp|P(0x8b), OC_SPRINTF, OC_B|B_su|P(0xb6), + OC_B|B_ss|P(0x8f), OC_FBLTIN|F_ti, OC_B|B_ti|P(0x0b), + OC_B|B_lo|P(0x49), OC_B|B_up|P(0x49), + OC_GETLINE|SV|P(0), + 0, 0, + 0, + 0 +}; + +/* internal variable names and their initial values */ +/* asterisk marks SPECIAL vars; $ is just no-named Field0 */ +enum { + CONVFMT=0, OFMT, FS, OFS, + ORS, RS, RT, FILENAME, + SUBSEP, ARGIND, ARGC, ARGV, + ERRNO, FNR, + NR, NF, IGNORECASE, + ENVIRON, F0, _intvarcount_ +}; + +static char * vNames = + "CONVFMT\0" "OFMT\0" "FS\0*" "OFS\0" + "ORS\0" "RS\0*" "RT\0" "FILENAME\0" + "SUBSEP\0" "ARGIND\0" "ARGC\0" "ARGV\0" + "ERRNO\0" "FNR\0" + "NR\0" "NF\0*" "IGNORECASE\0*" + "ENVIRON\0" "$\0*" "\0"; + +static char * vValues = + "%.6g\0" "%.6g\0" " \0" " \0" + "\n\0" "\n\0" "\0" "\0" + "\034\0" + "\377"; + +/* hash size may grow to these values */ +#define FIRST_PRIME 61; +static const unsigned int PRIMES[] = { 251, 1021, 4093, 16381, 65521 }; +enum { NPRIMES = sizeof(PRIMES) / sizeof(unsigned int) }; + +/* globals */ + +extern char **environ; + +static var * V[_intvarcount_]; +static chain beginseq, mainseq, endseq, *seq; +static int nextrec, nextfile; +static node *break_ptr, *continue_ptr; +static rstream *iF; +static xhash *vhash, *ahash, *fdhash, *fnhash; +static char *programname; +static short lineno; +static int is_f0_split; +static int nfields; +static var *Fields; +static tsplitter fsplitter, rsplitter; +static nvblock *cb; +static char *pos; +static char *buf; +static int icase; +static int exiting; + +static struct { + uint32_t tclass; + uint32_t info; + char *string; + double number; + short lineno; + int rollback; +} t; + +/* function prototypes */ +static void handle_special(var *); +static node *parse_expr(uint32_t); +static void chain_group(void); +static var *evaluate(node *, var *); +static rstream *next_input_file(void); +static int fmt_num(char *, int, const char *, double, int); +static int awk_exit(int) ATTRIBUTE_NORETURN; + +/* ---- error handling ---- */ + +static const char EMSG_INTERNAL_ERROR[] = "Internal error"; +static const char EMSG_UNEXP_EOS[] = "Unexpected end of string"; +static const char EMSG_UNEXP_TOKEN[] = "Unexpected token"; +static const char EMSG_DIV_BY_ZERO[] = "Division by zero"; +static const char EMSG_INV_FMT[] = "Invalid format specifier"; +static const char EMSG_TOO_FEW_ARGS[] = "Too few arguments for builtin"; +static const char EMSG_NOT_ARRAY[] = "Not an array"; +static const char EMSG_POSSIBLE_ERROR[] = "Possible syntax error"; +static const char EMSG_UNDEF_FUNC[] = "Call to undefined function"; +#ifndef CONFIG_FEATURE_AWK_MATH +static const char EMSG_NO_MATH[] = "Math support is not compiled in"; +#endif + +static void syntax_error(const char * const message) ATTRIBUTE_NORETURN; +static void syntax_error(const char * const message) +{ + bb_error_msg_and_die("%s:%i: %s", programname, lineno, message); +} + +#define runtime_error(x) syntax_error(x) + + +/* ---- hash stuff ---- */ + +static unsigned int hashidx(const char *name) +{ + unsigned int idx=0; + + while (*name) idx = *name++ + (idx << 6) - idx; + return idx; +} + +/* create new hash */ +static xhash *hash_init(void) +{ + xhash *newhash; + + newhash = (xhash *)xzalloc(sizeof(xhash)); + newhash->csize = FIRST_PRIME; + newhash->items = (hash_item **)xzalloc(newhash->csize * sizeof(hash_item *)); + + return newhash; +} + +/* find item in hash, return ptr to data, NULL if not found */ +static void *hash_search(xhash *hash, const char *name) +{ + hash_item *hi; + + hi = hash->items [ hashidx(name) % hash->csize ]; + while (hi) { + if (strcmp(hi->name, name) == 0) + return &(hi->data); + hi = hi->next; + } + return NULL; +} + +/* grow hash if it becomes too big */ +static void hash_rebuild(xhash *hash) +{ + unsigned int newsize, i, idx; + hash_item **newitems, *hi, *thi; + + if (hash->nprime == NPRIMES) + return; + + newsize = PRIMES[hash->nprime++]; + newitems = (hash_item **)xzalloc(newsize * sizeof(hash_item *)); + + for (i=0; icsize; i++) { + hi = hash->items[i]; + while (hi) { + thi = hi; + hi = thi->next; + idx = hashidx(thi->name) % newsize; + thi->next = newitems[idx]; + newitems[idx] = thi; + } + } + + free(hash->items); + hash->csize = newsize; + hash->items = newitems; +} + +/* find item in hash, add it if necessary. Return ptr to data */ +static void *hash_find(xhash *hash, const char *name) +{ + hash_item *hi; + unsigned int idx; + int l; + + hi = hash_search(hash, name); + if (! hi) { + if (++hash->nel / hash->csize > 10) + hash_rebuild(hash); + + l = strlen(name) + 1; + hi = xzalloc(sizeof(hash_item) + l); + memcpy(hi->name, name, l); + + idx = hashidx(name) % hash->csize; + hi->next = hash->items[idx]; + hash->items[idx] = hi; + hash->glen += l; + } + return &(hi->data); +} + +#define findvar(hash, name) (var *) hash_find ( (hash) , (name) ) +#define newvar(name) (var *) hash_find ( vhash , (name) ) +#define newfile(name) (rstream *) hash_find ( fdhash , (name) ) +#define newfunc(name) (func *) hash_find ( fnhash , (name) ) + +static void hash_remove(xhash *hash, const char *name) +{ + hash_item *hi, **phi; + + phi = &(hash->items[ hashidx(name) % hash->csize ]); + while (*phi) { + hi = *phi; + if (strcmp(hi->name, name) == 0) { + hash->glen -= (strlen(name) + 1); + hash->nel--; + *phi = hi->next; + free(hi); + break; + } + phi = &(hi->next); + } +} + +/* ------ some useful functions ------ */ + +static void skip_spaces(char **s) +{ + char *p = *s; + + while(*p == ' ' || *p == '\t' || + (*p == '\\' && *(p+1) == '\n' && (++p, ++t.lineno))) { + p++; + } + *s = p; +} + +static char *nextword(char **s) +{ + char *p = *s; + + while (*(*s)++) ; + + return p; +} + +static char nextchar(char **s) +{ + char c, *pps; + + c = *((*s)++); + pps = *s; + if (c == '\\') c = bb_process_escape_sequence((const char**)s); + if (c == '\\' && *s == pps) c = *((*s)++); + return c; +} + +static int ATTRIBUTE_ALWAYS_INLINE isalnum_(int c) +{ + return (isalnum(c) || c == '_'); +} + +static FILE *afopen(const char *path, const char *mode) +{ + return (*path == '-' && *(path+1) == '\0') ? stdin : xfopen(path, mode); +} + +/* -------- working with variables (set/get/copy/etc) -------- */ + +static xhash *iamarray(var *v) +{ + var *a = v; + + while (a->type & VF_CHILD) + a = a->x.parent; + + if (! (a->type & VF_ARRAY)) { + a->type |= VF_ARRAY; + a->x.array = hash_init(); + } + return a->x.array; +} + +static void clear_array(xhash *array) +{ + unsigned int i; + hash_item *hi, *thi; + + for (i=0; icsize; i++) { + hi = array->items[i]; + while (hi) { + thi = hi; + hi = hi->next; + free(thi->data.v.string); + free(thi); + } + array->items[i] = NULL; + } + array->glen = array->nel = 0; +} + +/* clear a variable */ +static var *clrvar(var *v) +{ + if (!(v->type & VF_FSTR)) + free(v->string); + + v->type &= VF_DONTTOUCH; + v->type |= VF_DIRTY; + v->string = NULL; + return v; +} + +/* assign string value to variable */ +static var *setvar_p(var *v, char *value) +{ + clrvar(v); + v->string = value; + handle_special(v); + + return v; +} + +/* same as setvar_p but make a copy of string */ +static var *setvar_s(var *v, const char *value) +{ + return setvar_p(v, (value && *value) ? xstrdup(value) : NULL); +} + +/* same as setvar_s but set USER flag */ +static var *setvar_u(var *v, const char *value) +{ + setvar_s(v, value); + v->type |= VF_USER; + return v; +} + +/* set array element to user string */ +static void setari_u(var *a, int idx, const char *s) +{ + var *v; + static char sidx[12]; + + sprintf(sidx, "%d", idx); + v = findvar(iamarray(a), sidx); + setvar_u(v, s); +} + +/* assign numeric value to variable */ +static var *setvar_i(var *v, double value) +{ + clrvar(v); + v->type |= VF_NUMBER; + v->number = value; + handle_special(v); + return v; +} + +static char *getvar_s(var *v) +{ + /* if v is numeric and has no cached string, convert it to string */ + if ((v->type & (VF_NUMBER | VF_CACHED)) == VF_NUMBER) { + fmt_num(buf, MAXVARFMT, getvar_s(V[CONVFMT]), v->number, TRUE); + v->string = xstrdup(buf); + v->type |= VF_CACHED; + } + return (v->string == NULL) ? "" : v->string; +} + +static double getvar_i(var *v) +{ + char *s; + + if ((v->type & (VF_NUMBER | VF_CACHED)) == 0) { + v->number = 0; + s = v->string; + if (s && *s) { + v->number = strtod(s, &s); + if (v->type & VF_USER) { + skip_spaces(&s); + if (*s != '\0') + v->type &= ~VF_USER; + } + } else { + v->type &= ~VF_USER; + } + v->type |= VF_CACHED; + } + return v->number; +} + +static var *copyvar(var *dest, const var *src) +{ + if (dest != src) { + clrvar(dest); + dest->type |= (src->type & ~VF_DONTTOUCH); + dest->number = src->number; + if (src->string) + dest->string = xstrdup(src->string); + } + handle_special(dest); + return dest; +} + +static var *incvar(var *v) +{ + return setvar_i(v, getvar_i(v)+1.); +} + +/* return true if v is number or numeric string */ +static int is_numeric(var *v) +{ + getvar_i(v); + return ((v->type ^ VF_DIRTY) & (VF_NUMBER | VF_USER | VF_DIRTY)); +} + +/* return 1 when value of v corresponds to true, 0 otherwise */ +static int istrue(var *v) +{ + if (is_numeric(v)) + return (v->number == 0) ? 0 : 1; + else + return (v->string && *(v->string)) ? 1 : 0; +} + +/* temporary variables allocator. Last allocated should be first freed */ +static var *nvalloc(int n) +{ + nvblock *pb = NULL; + var *v, *r; + int size; + + while (cb) { + pb = cb; + if ((cb->pos - cb->nv) + n <= cb->size) break; + cb = cb->next; + } + + if (! cb) { + size = (n <= MINNVBLOCK) ? MINNVBLOCK : n; + cb = (nvblock *)xmalloc(sizeof(nvblock) + size * sizeof(var)); + cb->size = size; + cb->pos = cb->nv; + cb->prev = pb; + cb->next = NULL; + if (pb) pb->next = cb; + } + + v = r = cb->pos; + cb->pos += n; + + while (v < cb->pos) { + v->type = 0; + v->string = NULL; + v++; + } + + return r; +} + +static void nvfree(var *v) +{ + var *p; + + if (v < cb->nv || v >= cb->pos) + runtime_error(EMSG_INTERNAL_ERROR); + + for (p=v; ppos; p++) { + if ((p->type & (VF_ARRAY|VF_CHILD)) == VF_ARRAY) { + clear_array(iamarray(p)); + free(p->x.array->items); + free(p->x.array); + } + if (p->type & VF_WALK) + free(p->x.walker); + + clrvar(p); + } + + cb->pos = v; + while (cb->prev && cb->pos == cb->nv) { + cb = cb->prev; + } +} + +/* ------- awk program text parsing ------- */ + +/* Parse next token pointed by global pos, place results into global t. + * If token isn't expected, give away. Return token class + */ +static uint32_t next_token(uint32_t expected) +{ + char *p, *pp, *s; + char *tl; + uint32_t tc; + const uint32_t *ti; + int l; + static int concat_inserted; + static uint32_t save_tclass, save_info; + static uint32_t ltclass = TC_OPTERM; + + if (t.rollback) { + + t.rollback = FALSE; + + } else if (concat_inserted) { + + concat_inserted = FALSE; + t.tclass = save_tclass; + t.info = save_info; + + } else { + + p = pos; + + readnext: + skip_spaces(&p); + lineno = t.lineno; + if (*p == '#') + while (*p != '\n' && *p != '\0') p++; + + if (*p == '\n') + t.lineno++; + + if (*p == '\0') { + tc = TC_EOF; + + } else if (*p == '\"') { + /* it's a string */ + t.string = s = ++p; + while (*p != '\"') { + if (*p == '\0' || *p == '\n') + syntax_error(EMSG_UNEXP_EOS); + *(s++) = nextchar(&p); + } + p++; + *s = '\0'; + tc = TC_STRING; + + } else if ((expected & TC_REGEXP) && *p == '/') { + /* it's regexp */ + t.string = s = ++p; + while (*p != '/') { + if (*p == '\0' || *p == '\n') + syntax_error(EMSG_UNEXP_EOS); + if ((*s++ = *p++) == '\\') { + pp = p; + *(s-1) = bb_process_escape_sequence((const char **)&p); + if (*pp == '\\') *s++ = '\\'; + if (p == pp) *s++ = *p++; + } + } + p++; + *s = '\0'; + tc = TC_REGEXP; + + } else if (*p == '.' || isdigit(*p)) { + /* it's a number */ + t.number = strtod(p, &p); + if (*p == '.') + syntax_error(EMSG_UNEXP_TOKEN); + tc = TC_NUMBER; + + } else { + /* search for something known */ + tl = tokenlist; + tc = 0x00000001; + ti = tokeninfo; + while (*tl) { + l = *(tl++); + if (l == NTCC) { + tc <<= 1; + continue; + } + /* if token class is expected, token + * matches and it's not a longer word, + * then this is what we are looking for + */ + if ((tc & (expected | TC_WORD | TC_NEWLINE)) && + *tl == *p && strncmp(p, tl, l) == 0 && + !((tc & TC_WORD) && isalnum_(*(p + l)))) { + t.info = *ti; + p += l; + break; + } + ti++; + tl += l; + } + + if (! *tl) { + /* it's a name (var/array/function), + * otherwise it's something wrong + */ + if (! isalnum_(*p)) + syntax_error(EMSG_UNEXP_TOKEN); + + t.string = --p; + while(isalnum_(*(++p))) { + *(p-1) = *p; + } + *(p-1) = '\0'; + tc = TC_VARIABLE; + /* also consume whitespace between functionname and bracket */ + if (! (expected & TC_VARIABLE)) skip_spaces(&p); + if (*p == '(') { + tc = TC_FUNCTION; + } else { + if (*p == '[') { + p++; + tc = TC_ARRAY; + } + } + } + } + pos = p; + + /* skipping newlines in some cases */ + if ((ltclass & TC_NOTERM) && (tc & TC_NEWLINE)) + goto readnext; + + /* insert concatenation operator when needed */ + if ((ltclass&TC_CONCAT1) && (tc&TC_CONCAT2) && (expected&TC_BINOP)) { + concat_inserted = TRUE; + save_tclass = tc; + save_info = t.info; + tc = TC_BINOP; + t.info = OC_CONCAT | SS | P(35); + } + + t.tclass = tc; + } + ltclass = t.tclass; + + /* Are we ready for this? */ + if (! (ltclass & expected)) + syntax_error((ltclass & (TC_NEWLINE | TC_EOF)) ? + EMSG_UNEXP_EOS : EMSG_UNEXP_TOKEN); + + return ltclass; +} + +static void rollback_token(void) { t.rollback = TRUE; } + +static node *new_node(uint32_t info) +{ + node *n; + + n = (node *)xzalloc(sizeof(node)); + n->info = info; + n->lineno = lineno; + return n; +} + +static node *mk_re_node(char *s, node *n, regex_t *re) +{ + n->info = OC_REGEXP; + n->l.re = re; + n->r.ire = re + 1; + xregcomp(re, s, REG_EXTENDED); + xregcomp(re+1, s, REG_EXTENDED | REG_ICASE); + + return n; +} + +static node *condition(void) +{ + next_token(TC_SEQSTART); + return parse_expr(TC_SEQTERM); +} + +/* parse expression terminated by given argument, return ptr + * to built subtree. Terminator is eaten by parse_expr */ +static node *parse_expr(uint32_t iexp) +{ + node sn; + node *cn = &sn; + node *vn, *glptr; + uint32_t tc, xtc; + var *v; + + sn.info = PRIMASK; + sn.r.n = glptr = NULL; + xtc = TC_OPERAND | TC_UOPPRE | TC_REGEXP | iexp; + + while (! ((tc = next_token(xtc)) & iexp)) { + if (glptr && (t.info == (OC_COMPARE|VV|P(39)|2))) { + /* input redirection (<) attached to glptr node */ + cn = glptr->l.n = new_node(OC_CONCAT|SS|P(37)); + cn->a.n = glptr; + xtc = TC_OPERAND | TC_UOPPRE; + glptr = NULL; + + } else if (tc & (TC_BINOP | TC_UOPPOST)) { + /* for binary and postfix-unary operators, jump back over + * previous operators with higher priority */ + vn = cn; + while ( ((t.info & PRIMASK) > (vn->a.n->info & PRIMASK2)) || + ((t.info == vn->info) && ((t.info & OPCLSMASK) == OC_COLON)) ) + vn = vn->a.n; + if ((t.info & OPCLSMASK) == OC_TERNARY) + t.info += P(6); + cn = vn->a.n->r.n = new_node(t.info); + cn->a.n = vn->a.n; + if (tc & TC_BINOP) { + cn->l.n = vn; + xtc = TC_OPERAND | TC_UOPPRE | TC_REGEXP; + if ((t.info & OPCLSMASK) == OC_PGETLINE) { + /* it's a pipe */ + next_token(TC_GETLINE); + /* give maximum priority to this pipe */ + cn->info &= ~PRIMASK; + xtc = TC_OPERAND | TC_UOPPRE | TC_BINOP | iexp; + } + } else { + cn->r.n = vn; + xtc = TC_OPERAND | TC_UOPPRE | TC_BINOP | iexp; + } + vn->a.n = cn; + + } else { + /* for operands and prefix-unary operators, attach them + * to last node */ + vn = cn; + cn = vn->r.n = new_node(t.info); + cn->a.n = vn; + xtc = TC_OPERAND | TC_UOPPRE | TC_REGEXP; + if (tc & (TC_OPERAND | TC_REGEXP)) { + xtc = TC_UOPPRE | TC_UOPPOST | TC_BINOP | TC_OPERAND | iexp; + /* one should be very careful with switch on tclass - + * only simple tclasses should be used! */ + switch (tc) { + case TC_VARIABLE: + case TC_ARRAY: + cn->info = OC_VAR; + if ((v = hash_search(ahash, t.string)) != NULL) { + cn->info = OC_FNARG; + cn->l.i = v->x.aidx; + } else { + cn->l.v = newvar(t.string); + } + if (tc & TC_ARRAY) { + cn->info |= xS; + cn->r.n = parse_expr(TC_ARRTERM); + } + break; + + case TC_NUMBER: + case TC_STRING: + cn->info = OC_VAR; + v = cn->l.v = xzalloc(sizeof(var)); + if (tc & TC_NUMBER) + setvar_i(v, t.number); + else + setvar_s(v, t.string); + break; + + case TC_REGEXP: + mk_re_node(t.string, cn, + (regex_t *)xzalloc(sizeof(regex_t)*2)); + break; + + case TC_FUNCTION: + cn->info = OC_FUNC; + cn->r.f = newfunc(t.string); + cn->l.n = condition(); + break; + + case TC_SEQSTART: + cn = vn->r.n = parse_expr(TC_SEQTERM); + cn->a.n = vn; + break; + + case TC_GETLINE: + glptr = cn; + xtc = TC_OPERAND | TC_UOPPRE | TC_BINOP | iexp; + break; + + case TC_BUILTIN: + cn->l.n = condition(); + break; + } + } + } + } + return sn.r.n; +} + +/* add node to chain. Return ptr to alloc'd node */ +static node *chain_node(uint32_t info) +{ + node *n; + + if (! seq->first) + seq->first = seq->last = new_node(0); + + if (seq->programname != programname) { + seq->programname = programname; + n = chain_node(OC_NEWSOURCE); + n->l.s = xstrdup(programname); + } + + n = seq->last; + n->info = info; + seq->last = n->a.n = new_node(OC_DONE); + + return n; +} + +static void chain_expr(uint32_t info) +{ + node *n; + + n = chain_node(info); + n->l.n = parse_expr(TC_OPTERM | TC_GRPTERM); + if (t.tclass & TC_GRPTERM) + rollback_token(); +} + +static node *chain_loop(node *nn) +{ + node *n, *n2, *save_brk, *save_cont; + + save_brk = break_ptr; + save_cont = continue_ptr; + + n = chain_node(OC_BR | Vx); + continue_ptr = new_node(OC_EXEC); + break_ptr = new_node(OC_EXEC); + chain_group(); + n2 = chain_node(OC_EXEC | Vx); + n2->l.n = nn; + n2->a.n = n; + continue_ptr->a.n = n2; + break_ptr->a.n = n->r.n = seq->last; + + continue_ptr = save_cont; + break_ptr = save_brk; + + return n; +} + +/* parse group and attach it to chain */ +static void chain_group(void) +{ + uint32_t c; + node *n, *n2, *n3; + + do { + c = next_token(TC_GRPSEQ); + } while (c & TC_NEWLINE); + + if (c & TC_GRPSTART) { + while(next_token(TC_GRPSEQ | TC_GRPTERM) != TC_GRPTERM) { + if (t.tclass & TC_NEWLINE) continue; + rollback_token(); + chain_group(); + } + } else if (c & (TC_OPSEQ | TC_OPTERM)) { + rollback_token(); + chain_expr(OC_EXEC | Vx); + } else { /* TC_STATEMNT */ + switch (t.info & OPCLSMASK) { + case ST_IF: + n = chain_node(OC_BR | Vx); + n->l.n = condition(); + chain_group(); + n2 = chain_node(OC_EXEC); + n->r.n = seq->last; + if (next_token(TC_GRPSEQ | TC_GRPTERM | TC_ELSE)==TC_ELSE) { + chain_group(); + n2->a.n = seq->last; + } else { + rollback_token(); + } + break; + + case ST_WHILE: + n2 = condition(); + n = chain_loop(NULL); + n->l.n = n2; + break; + + case ST_DO: + n2 = chain_node(OC_EXEC); + n = chain_loop(NULL); + n2->a.n = n->a.n; + next_token(TC_WHILE); + n->l.n = condition(); + break; + + case ST_FOR: + next_token(TC_SEQSTART); + n2 = parse_expr(TC_SEMICOL | TC_SEQTERM); + if (t.tclass & TC_SEQTERM) { /* for-in */ + if ((n2->info & OPCLSMASK) != OC_IN) + syntax_error(EMSG_UNEXP_TOKEN); + n = chain_node(OC_WALKINIT | VV); + n->l.n = n2->l.n; + n->r.n = n2->r.n; + n = chain_loop(NULL); + n->info = OC_WALKNEXT | Vx; + n->l.n = n2->l.n; + } else { /* for(;;) */ + n = chain_node(OC_EXEC | Vx); + n->l.n = n2; + n2 = parse_expr(TC_SEMICOL); + n3 = parse_expr(TC_SEQTERM); + n = chain_loop(n3); + n->l.n = n2; + if (! n2) + n->info = OC_EXEC; + } + break; + + case OC_PRINT: + case OC_PRINTF: + n = chain_node(t.info); + n->l.n = parse_expr(TC_OPTERM | TC_OUTRDR | TC_GRPTERM); + if (t.tclass & TC_OUTRDR) { + n->info |= t.info; + n->r.n = parse_expr(TC_OPTERM | TC_GRPTERM); + } + if (t.tclass & TC_GRPTERM) + rollback_token(); + break; + + case OC_BREAK: + n = chain_node(OC_EXEC); + n->a.n = break_ptr; + break; + + case OC_CONTINUE: + n = chain_node(OC_EXEC); + n->a.n = continue_ptr; + break; + + /* delete, next, nextfile, return, exit */ + default: + chain_expr(t.info); + + } + } +} + +static void parse_program(char *p) +{ + uint32_t tclass; + node *cn; + func *f; + var *v; + + pos = p; + t.lineno = 1; + while((tclass = next_token(TC_EOF | TC_OPSEQ | TC_GRPSTART | + TC_OPTERM | TC_BEGIN | TC_END | TC_FUNCDECL)) != TC_EOF) { + + if (tclass & TC_OPTERM) + continue; + + seq = &mainseq; + if (tclass & TC_BEGIN) { + seq = &beginseq; + chain_group(); + + } else if (tclass & TC_END) { + seq = &endseq; + chain_group(); + + } else if (tclass & TC_FUNCDECL) { + next_token(TC_FUNCTION); + pos++; + f = newfunc(t.string); + f->body.first = NULL; + f->nargs = 0; + while(next_token(TC_VARIABLE | TC_SEQTERM) & TC_VARIABLE) { + v = findvar(ahash, t.string); + v->x.aidx = (f->nargs)++; + + if (next_token(TC_COMMA | TC_SEQTERM) & TC_SEQTERM) + break; + } + seq = &(f->body); + chain_group(); + clear_array(ahash); + + } else if (tclass & TC_OPSEQ) { + rollback_token(); + cn = chain_node(OC_TEST); + cn->l.n = parse_expr(TC_OPTERM | TC_EOF | TC_GRPSTART); + if (t.tclass & TC_GRPSTART) { + rollback_token(); + chain_group(); + } else { + chain_node(OC_PRINT); + } + cn->r.n = mainseq.last; + + } else /* if (tclass & TC_GRPSTART) */ { + rollback_token(); + chain_group(); + } + } +} + + +/* -------- program execution part -------- */ + +static node *mk_splitter(char *s, tsplitter *spl) +{ + regex_t *re, *ire; + node *n; + + re = &spl->re[0]; + ire = &spl->re[1]; + n = &spl->n; + if ((n->info & OPCLSMASK) == OC_REGEXP) { + regfree(re); + regfree(ire); + } + if (strlen(s) > 1) { + mk_re_node(s, n, re); + } else { + n->info = (uint32_t) *s; + } + + return n; +} + +/* use node as a regular expression. Supplied with node ptr and regex_t + * storage space. Return ptr to regex (if result points to preg, it should + * be later regfree'd manually + */ +static regex_t *as_regex(node *op, regex_t *preg) +{ + var *v; + char *s; + + if ((op->info & OPCLSMASK) == OC_REGEXP) { + return icase ? op->r.ire : op->l.re; + } else { + v = nvalloc(1); + s = getvar_s(evaluate(op, v)); + xregcomp(preg, s, icase ? REG_EXTENDED | REG_ICASE : REG_EXTENDED); + nvfree(v); + return preg; + } +} + +/* gradually increasing buffer */ +static void qrealloc(char **b, int n, int *size) +{ + if (! *b || n >= *size) + *b = xrealloc(*b, *size = n + (n>>1) + 80); +} + +/* resize field storage space */ +static void fsrealloc(int size) +{ + static int maxfields = 0; + int i; + + if (size >= maxfields) { + i = maxfields; + maxfields = size + 16; + Fields = (var *)xrealloc(Fields, maxfields * sizeof(var)); + for (; iinfo; + c[2] = c[3] = '\0'; + if (*getvar_s(V[RS]) == '\0') c[2] = '\n'; + + if ((spl->info & OPCLSMASK) == OC_REGEXP) { /* regex split */ + while (*s) { + l = strcspn(s, c+2); + if (regexec(icase ? spl->r.ire : spl->l.re, s, 1, pmatch, 0) == 0 && + pmatch[0].rm_so <= l) { + l = pmatch[0].rm_so; + if (pmatch[0].rm_eo == 0) { l++; pmatch[0].rm_eo++; } + } else { + pmatch[0].rm_eo = l; + if (*(s+l)) pmatch[0].rm_eo++; + } + + memcpy(s1, s, l); + *(s1+l) = '\0'; + nextword(&s1); + s += pmatch[0].rm_eo; + n++; + } + } else if (c[0] == '\0') { /* null split */ + while(*s) { + *(s1++) = *(s++); + *(s1++) = '\0'; + n++; + } + } else if (c[0] != ' ') { /* single-character split */ + if (icase) { + c[0] = toupper(c[0]); + c[1] = tolower(c[1]); + } + if (*s1) n++; + while ((s1 = strpbrk(s1, c))) { + *(s1++) = '\0'; + n++; + } + } else { /* space split */ + while (*s) { + s = skip_whitespace(s); + if (! *s) break; + n++; + while (*s && !isspace(*s)) + *(s1++) = *(s++); + *(s1++) = '\0'; + } + } + return n; +} + +static void split_f0(void) +{ + static char *fstrings = NULL; + int i, n; + char *s; + + if (is_f0_split) + return; + + is_f0_split = TRUE; + free(fstrings); + fsrealloc(0); + n = awk_split(getvar_s(V[F0]), &fsplitter.n, &fstrings); + fsrealloc(n); + s = fstrings; + for (i=0; itype = VF_NUMBER | VF_SPECIAL; + V[NF]->number = nfields; +} + +/* perform additional actions when some internal variables changed */ +static void handle_special(var *v) +{ + int n; + char *b, *sep, *s; + int sl, l, len, i, bsize; + + if (! (v->type & VF_SPECIAL)) + return; + + if (v == V[NF]) { + n = (int)getvar_i(v); + fsrealloc(n); + + /* recalculate $0 */ + sep = getvar_s(V[OFS]); + sl = strlen(sep); + b = NULL; + len = 0; + for (i=0; i v-Fields ? n : v-Fields+1); + /* right here v is invalid. Just to note... */ + } +} + +/* step through func/builtin/etc arguments */ +static node *nextarg(node **pn) +{ + node *n; + + n = *pn; + if (n && (n->info & OPCLSMASK) == OC_COMMA) { + *pn = n->r.n; + n = n->l.n; + } else { + *pn = NULL; + } + return n; +} + +static void hashwalk_init(var *v, xhash *array) +{ + char **w; + hash_item *hi; + int i; + + if (v->type & VF_WALK) + free(v->x.walker); + + v->type |= VF_WALK; + w = v->x.walker = (char **)xzalloc(2 + 2*sizeof(char *) + array->glen); + *w = *(w+1) = (char *)(w + 2); + for (i=0; icsize; i++) { + hi = array->items[i]; + while(hi) { + strcpy(*w, hi->name); + nextword(w); + hi = hi->next; + } + } +} + +static int hashwalk_next(var *v) +{ + char **w; + + w = v->x.walker; + if (*(w+1) == *w) + return FALSE; + + setvar_s(v, nextword(w+1)); + return TRUE; +} + +/* evaluate node, return 1 when result is true, 0 otherwise */ +static int ptest(node *pattern) +{ + static var v; + return istrue(evaluate(pattern, &v)); +} + +/* read next record from stream rsm into a variable v */ +static int awk_getline(rstream *rsm, var *v) +{ + char *b; + regmatch_t pmatch[2]; + int a, p, pp=0, size; + int fd, so, eo, r, rp; + char c, *m, *s; + + /* we're using our own buffer since we need access to accumulating + * characters + */ + fd = fileno(rsm->F); + m = rsm->buffer; + a = rsm->adv; + p = rsm->pos; + size = rsm->size; + c = (char) rsplitter.n.info; + rp = 0; + + if (! m) qrealloc(&m, 256, &size); + do { + b = m + a; + so = eo = p; + r = 1; + if (p > 0) { + if ((rsplitter.n.info & OPCLSMASK) == OC_REGEXP) { + if (regexec(icase ? rsplitter.n.r.ire : rsplitter.n.l.re, + b, 1, pmatch, 0) == 0) { + so = pmatch[0].rm_so; + eo = pmatch[0].rm_eo; + if (b[eo] != '\0') + break; + } + } else if (c != '\0') { + s = strchr(b+pp, c); + if (! s) s = memchr(b+pp, '\0', p - pp); + if (s) { + so = eo = s-b; + eo++; + break; + } + } else { + while (b[rp] == '\n') + rp++; + s = strstr(b+rp, "\n\n"); + if (s) { + so = eo = s-b; + while (b[eo] == '\n') eo++; + if (b[eo] != '\0') + break; + } + } + } + + if (a > 0) { + memmove(m, (const void *)(m+a), p+1); + b = m; + a = 0; + } + + qrealloc(&m, a+p+128, &size); + b = m + a; + pp = p; + p += safe_read(fd, b+p, size-p-1); + if (p < pp) { + p = 0; + r = 0; + setvar_i(V[ERRNO], errno); + } + b[p] = '\0'; + + } while (p > pp); + + if (p == 0) { + r--; + } else { + c = b[so]; b[so] = '\0'; + setvar_s(v, b+rp); + v->type |= VF_USER; + b[so] = c; + c = b[eo]; b[eo] = '\0'; + setvar_s(V[RT], b+so); + b[eo] = c; + } + + rsm->buffer = m; + rsm->adv = a + eo; + rsm->pos = p - eo; + rsm->size = size; + + return r; +} + +static int fmt_num(char *b, int size, const char *format, double n, int int_as_int) +{ + int r=0; + char c; + const char *s=format; + + if (int_as_int && n == (int)n) { + r = snprintf(b, size, "%d", (int)n); + } else { + do { c = *s; } while (*s && *++s); + if (strchr("diouxX", c)) { + r = snprintf(b, size, format, (int)n); + } else if (strchr("eEfgG", c)) { + r = snprintf(b, size, format, n); + } else { + runtime_error(EMSG_INV_FMT); + } + } + return r; +} + + +/* formatted output into an allocated buffer, return ptr to buffer */ +static char *awk_printf(node *n) +{ + char *b = NULL; + char *fmt, *s, *s1, *f; + int i, j, incr, bsize; + char c, c1; + var *v, *arg; + + v = nvalloc(1); + fmt = f = xstrdup(getvar_s(evaluate(nextarg(&n), v))); + + i = 0; + while (*f) { + s = f; + while (*f && (*f != '%' || *(++f) == '%')) + f++; + while (*f && !isalpha(*f)) + f++; + + incr = (f - s) + MAXVARFMT; + qrealloc(&b, incr+i, &bsize); + c = *f; if (c != '\0') f++; + c1 = *f ; *f = '\0'; + arg = evaluate(nextarg(&n), v); + + j = i; + if (c == 'c' || !c) { + i += sprintf(b+i, s, + is_numeric(arg) ? (char)getvar_i(arg) : *getvar_s(arg)); + + } else if (c == 's') { + s1 = getvar_s(arg); + qrealloc(&b, incr+i+strlen(s1), &bsize); + i += sprintf(b+i, s, s1); + + } else { + i += fmt_num(b+i, incr, s, getvar_i(arg), FALSE); + } + *f = c1; + + /* if there was an error while sprintf, return value is negative */ + if (i < j) i = j; + + } + + b = xrealloc(b, i+1); + free(fmt); + nvfree(v); + b[i] = '\0'; + return b; +} + +/* common substitution routine + * replace (nm) substring of (src) that match (n) with (repl), store + * result into (dest), return number of substitutions. If nm=0, replace + * all matches. If src or dst is NULL, use $0. If ex=TRUE, enable + * subexpression matching (\1-\9) + */ +static int awk_sub(node *rn, char *repl, int nm, var *src, var *dest, int ex) +{ + char *ds = NULL; + char *sp, *s; + int c, i, j, di, rl, so, eo, nbs, n, dssize; + regmatch_t pmatch[10]; + regex_t sreg, *re; + + re = as_regex(rn, &sreg); + if (! src) src = V[F0]; + if (! dest) dest = V[F0]; + + i = di = 0; + sp = getvar_s(src); + rl = strlen(repl); + while (regexec(re, sp, 10, pmatch, sp==getvar_s(src) ? 0:REG_NOTBOL) == 0) { + so = pmatch[0].rm_so; + eo = pmatch[0].rm_eo; + + qrealloc(&ds, di + eo + rl, &dssize); + memcpy(ds + di, sp, eo); + di += eo; + if (++i >= nm) { + /* replace */ + di -= (eo - so); + nbs = 0; + for (s = repl; *s; s++) { + ds[di++] = c = *s; + if (c == '\\') { + nbs++; + continue; + } + if (c == '&' || (ex && c >= '0' && c <= '9')) { + di -= ((nbs + 3) >> 1); + j = 0; + if (c != '&') { + j = c - '0'; + nbs++; + } + if (nbs % 2) { + ds[di++] = c; + } else { + n = pmatch[j].rm_eo - pmatch[j].rm_so; + qrealloc(&ds, di + rl + n, &dssize); + memcpy(ds + di, sp + pmatch[j].rm_so, n); + di += n; + } + } + nbs = 0; + } + } + + sp += eo; + if (i == nm) break; + if (eo == so) { + if (! (ds[di++] = *sp++)) break; + } + } + + qrealloc(&ds, di + strlen(sp), &dssize); + strcpy(ds + di, sp); + setvar_p(dest, ds); + if (re == &sreg) regfree(re); + return i; +} + +static var *exec_builtin(node *op, var *res) +{ + int (*to_xxx)(int); + var *tv; + node *an[4]; + var *av[4]; + char *as[4]; + regmatch_t pmatch[2]; + regex_t sreg, *re; + static tsplitter tspl; + node *spl; + uint32_t isr, info; + int nargs; + time_t tt; + char *s, *s1; + int i, l, ll, n; + + tv = nvalloc(4); + isr = info = op->info; + op = op->l.n; + + av[2] = av[3] = NULL; + for (i=0 ; i<4 && op ; i++) { + an[i] = nextarg(&op); + if (isr & 0x09000000) av[i] = evaluate(an[i], &tv[i]); + if (isr & 0x08000000) as[i] = getvar_s(av[i]); + isr >>= 1; + } + + nargs = i; + if (nargs < (info >> 30)) + runtime_error(EMSG_TOO_FEW_ARGS); + + switch (info & OPNMASK) { + + case B_a2: +#ifdef CONFIG_FEATURE_AWK_MATH + setvar_i(res, atan2(getvar_i(av[i]), getvar_i(av[1]))); +#else + runtime_error(EMSG_NO_MATH); +#endif + break; + + case B_sp: + if (nargs > 2) { + spl = (an[2]->info & OPCLSMASK) == OC_REGEXP ? + an[2] : mk_splitter(getvar_s(evaluate(an[2], &tv[2])), &tspl); + } else { + spl = &fsplitter.n; + } + + n = awk_split(as[0], spl, &s); + s1 = s; + clear_array(iamarray(av[1])); + for (i=1; i<=n; i++) + setari_u(av[1], i, nextword(&s1)); + free(s); + setvar_i(res, n); + break; + + case B_ss: + l = strlen(as[0]); + i = getvar_i(av[1]) - 1; + if (i>l) i=l; if (i<0) i=0; + n = (nargs > 2) ? getvar_i(av[2]) : l-i; + if (n<0) n=0; + s = xmalloc(n+1); + strncpy(s, as[0]+i, n); + s[n] = '\0'; + setvar_p(res, s); + break; + + case B_an: + setvar_i(res, (long)getvar_i(av[0]) & (long)getvar_i(av[1])); + break; + + case B_co: + setvar_i(res, ~(long)getvar_i(av[0])); + break; + + case B_ls: + setvar_i(res, (long)getvar_i(av[0]) << (long)getvar_i(av[1])); + break; + + case B_or: + setvar_i(res, (long)getvar_i(av[0]) | (long)getvar_i(av[1])); + break; + + case B_rs: + setvar_i(res, (long)((unsigned long)getvar_i(av[0]) >> (unsigned long)getvar_i(av[1]))); + break; + + case B_xo: + setvar_i(res, (long)getvar_i(av[0]) ^ (long)getvar_i(av[1])); + break; + + case B_lo: + to_xxx = tolower; + goto lo_cont; + + case B_up: + to_xxx = toupper; +lo_cont: + s1 = s = xstrdup(as[0]); + while (*s1) { + *s1 = (*to_xxx)(*s1); + s1++; + } + setvar_p(res, s); + break; + + case B_ix: + n = 0; + ll = strlen(as[1]); + l = strlen(as[0]) - ll; + if (ll > 0 && l >= 0) { + if (! icase) { + s = strstr(as[0], as[1]); + if (s) n = (s - as[0]) + 1; + } else { + /* this piece of code is terribly slow and + * really should be rewritten + */ + for (i=0; i<=l; i++) { + if (strncasecmp(as[0]+i, as[1], ll) == 0) { + n = i+1; + break; + } + } + } + } + setvar_i(res, n); + break; + + case B_ti: + if (nargs > 1) + tt = getvar_i(av[1]); + else + time(&tt); + s = (nargs > 0) ? as[0] : "%a %b %d %H:%M:%S %Z %Y"; + i = strftime(buf, MAXVARFMT, s, localtime(&tt)); + buf[i] = '\0'; + setvar_s(res, buf); + break; + + case B_ma: + re = as_regex(an[1], &sreg); + n = regexec(re, as[0], 1, pmatch, 0); + if (n == 0) { + pmatch[0].rm_so++; + pmatch[0].rm_eo++; + } else { + pmatch[0].rm_so = 0; + pmatch[0].rm_eo = -1; + } + setvar_i(newvar("RSTART"), pmatch[0].rm_so); + setvar_i(newvar("RLENGTH"), pmatch[0].rm_eo - pmatch[0].rm_so); + setvar_i(res, pmatch[0].rm_so); + if (re == &sreg) regfree(re); + break; + + case B_ge: + awk_sub(an[0], as[1], getvar_i(av[2]), av[3], res, TRUE); + break; + + case B_gs: + setvar_i(res, awk_sub(an[0], as[1], 0, av[2], av[2], FALSE)); + break; + + case B_su: + setvar_i(res, awk_sub(an[0], as[1], 1, av[2], av[2], FALSE)); + break; + } + + nvfree(tv); + return res; +} + +/* + * Evaluate node - the heart of the program. Supplied with subtree + * and place where to store result. returns ptr to result. + */ +#define XC(n) ((n) >> 8) + +static var *evaluate(node *op, var *res) +{ + /* This procedure is recursive so we should count every byte */ + static var *fnargs = NULL; + static unsigned int seed = 1; + static regex_t sreg; + node *op1; + var *v1; + union { + var *v; + char *s; + double d; + int i; + } L, R; + uint32_t opinfo; + short opn; + union { + char *s; + rstream *rsm; + FILE *F; + var *v; + regex_t *re; + uint32_t info; + } X; + + if (! op) + return setvar_s(res, NULL); + + v1 = nvalloc(2); + + while (op) { + + opinfo = op->info; + opn = (short)(opinfo & OPNMASK); + lineno = op->lineno; + + /* execute inevitable things */ + op1 = op->l.n; + if (opinfo & OF_RES1) X.v = L.v = evaluate(op1, v1); + if (opinfo & OF_RES2) R.v = evaluate(op->r.n, v1+1); + if (opinfo & OF_STR1) L.s = getvar_s(L.v); + if (opinfo & OF_STR2) R.s = getvar_s(R.v); + if (opinfo & OF_NUM1) L.d = getvar_i(L.v); + + switch (XC(opinfo & OPCLSMASK)) { + + /* -- iterative node type -- */ + + /* test pattern */ + case XC( OC_TEST ): + if ((op1->info & OPCLSMASK) == OC_COMMA) { + /* it's range pattern */ + if ((opinfo & OF_CHECKED) || ptest(op1->l.n)) { + op->info |= OF_CHECKED; + if (ptest(op1->r.n)) + op->info &= ~OF_CHECKED; + + op = op->a.n; + } else { + op = op->r.n; + } + } else { + op = (ptest(op1)) ? op->a.n : op->r.n; + } + break; + + /* just evaluate an expression, also used as unconditional jump */ + case XC( OC_EXEC ): + break; + + /* branch, used in if-else and various loops */ + case XC( OC_BR ): + op = istrue(L.v) ? op->a.n : op->r.n; + break; + + /* initialize for-in loop */ + case XC( OC_WALKINIT ): + hashwalk_init(L.v, iamarray(R.v)); + break; + + /* get next array item */ + case XC( OC_WALKNEXT ): + op = hashwalk_next(L.v) ? op->a.n : op->r.n; + break; + + case XC( OC_PRINT ): + case XC( OC_PRINTF ): + X.F = stdout; + if (op->r.n) { + X.rsm = newfile(R.s); + if (! X.rsm->F) { + if (opn == '|') { + if((X.rsm->F = popen(R.s, "w")) == NULL) + bb_perror_msg_and_die("popen"); + X.rsm->is_pipe = 1; + } else { + X.rsm->F = xfopen(R.s, opn=='w' ? "w" : "a"); + } + } + X.F = X.rsm->F; + } + + if ((opinfo & OPCLSMASK) == OC_PRINT) { + if (! op1) { + fputs(getvar_s(V[F0]), X.F); + } else { + while (op1) { + L.v = evaluate(nextarg(&op1), v1); + if (L.v->type & VF_NUMBER) { + fmt_num(buf, MAXVARFMT, getvar_s(V[OFMT]), + getvar_i(L.v), TRUE); + fputs(buf, X.F); + } else { + fputs(getvar_s(L.v), X.F); + } + + if (op1) fputs(getvar_s(V[OFS]), X.F); + } + } + fputs(getvar_s(V[ORS]), X.F); + + } else { /* OC_PRINTF */ + L.s = awk_printf(op1); + fputs(L.s, X.F); + free(L.s); + } + fflush(X.F); + break; + + case XC( OC_DELETE ): + X.info = op1->info & OPCLSMASK; + if (X.info == OC_VAR) { + R.v = op1->l.v; + } else if (X.info == OC_FNARG) { + R.v = &fnargs[op1->l.i]; + } else { + runtime_error(EMSG_NOT_ARRAY); + } + + if (op1->r.n) { + clrvar(L.v); + L.s = getvar_s(evaluate(op1->r.n, v1)); + hash_remove(iamarray(R.v), L.s); + } else { + clear_array(iamarray(R.v)); + } + break; + + case XC( OC_NEWSOURCE ): + programname = op->l.s; + break; + + case XC( OC_RETURN ): + copyvar(res, L.v); + break; + + case XC( OC_NEXTFILE ): + nextfile = TRUE; + case XC( OC_NEXT ): + nextrec = TRUE; + case XC( OC_DONE ): + clrvar(res); + break; + + case XC( OC_EXIT ): + awk_exit(L.d); + + /* -- recursive node type -- */ + + case XC( OC_VAR ): + L.v = op->l.v; + if (L.v == V[NF]) + split_f0(); + goto v_cont; + + case XC( OC_FNARG ): + L.v = &fnargs[op->l.i]; + +v_cont: + res = (op->r.n) ? findvar(iamarray(L.v), R.s) : L.v; + break; + + case XC( OC_IN ): + setvar_i(res, hash_search(iamarray(R.v), L.s) ? 1 : 0); + break; + + case XC( OC_REGEXP ): + op1 = op; + L.s = getvar_s(V[F0]); + goto re_cont; + + case XC( OC_MATCH ): + op1 = op->r.n; +re_cont: + X.re = as_regex(op1, &sreg); + R.i = regexec(X.re, L.s, 0, NULL, 0); + if (X.re == &sreg) regfree(X.re); + setvar_i(res, (R.i == 0 ? 1 : 0) ^ (opn == '!' ? 1 : 0)); + break; + + case XC( OC_MOVE ): + /* if source is a temporary string, jusk relink it to dest */ + if (R.v == v1+1 && R.v->string) { + res = setvar_p(L.v, R.v->string); + R.v->string = NULL; + } else { + res = copyvar(L.v, R.v); + } + break; + + case XC( OC_TERNARY ): + if ((op->r.n->info & OPCLSMASK) != OC_COLON) + runtime_error(EMSG_POSSIBLE_ERROR); + res = evaluate(istrue(L.v) ? op->r.n->l.n : op->r.n->r.n, res); + break; + + case XC( OC_FUNC ): + if (! op->r.f->body.first) + runtime_error(EMSG_UNDEF_FUNC); + + X.v = R.v = nvalloc(op->r.f->nargs+1); + while (op1) { + L.v = evaluate(nextarg(&op1), v1); + copyvar(R.v, L.v); + R.v->type |= VF_CHILD; + R.v->x.parent = L.v; + if (++R.v - X.v >= op->r.f->nargs) + break; + } + + R.v = fnargs; + fnargs = X.v; + + L.s = programname; + res = evaluate(op->r.f->body.first, res); + programname = L.s; + + nvfree(fnargs); + fnargs = R.v; + break; + + case XC( OC_GETLINE ): + case XC( OC_PGETLINE ): + if (op1) { + X.rsm = newfile(L.s); + if (! X.rsm->F) { + if ((opinfo & OPCLSMASK) == OC_PGETLINE) { + X.rsm->F = popen(L.s, "r"); + X.rsm->is_pipe = TRUE; + } else { + X.rsm->F = fopen(L.s, "r"); /* not xfopen! */ + } + } + } else { + if (! iF) iF = next_input_file(); + X.rsm = iF; + } + + if (! X.rsm->F) { + setvar_i(V[ERRNO], errno); + setvar_i(res, -1); + break; + } + + if (! op->r.n) + R.v = V[F0]; + + L.i = awk_getline(X.rsm, R.v); + if (L.i > 0) { + if (! op1) { + incvar(V[FNR]); + incvar(V[NR]); + } + } + setvar_i(res, L.i); + break; + + /* simple builtins */ + case XC( OC_FBLTIN ): + switch (opn) { + + case F_in: + R.d = (int)L.d; + break; + + case F_rn: + R.d = (double)rand() / (double)RAND_MAX; + break; + +#ifdef CONFIG_FEATURE_AWK_MATH + case F_co: + R.d = cos(L.d); + break; + + case F_ex: + R.d = exp(L.d); + break; + + case F_lg: + R.d = log(L.d); + break; + + case F_si: + R.d = sin(L.d); + break; + + case F_sq: + R.d = sqrt(L.d); + break; +#else + case F_co: + case F_ex: + case F_lg: + case F_si: + case F_sq: + runtime_error(EMSG_NO_MATH); + break; +#endif + + case F_sr: + R.d = (double)seed; + seed = op1 ? (unsigned int)L.d : (unsigned int)time(NULL); + srand(seed); + break; + + case F_ti: + R.d = time(NULL); + break; + + case F_le: + if (! op1) + L.s = getvar_s(V[F0]); + R.d = strlen(L.s); + break; + + case F_sy: + fflush(NULL); + R.d = (L.s && *L.s) ? (system(L.s) >> 8) : 0; + break; + + case F_ff: + if (! op1) + fflush(stdout); + else { + if (L.s && *L.s) { + X.rsm = newfile(L.s); + fflush(X.rsm->F); + } else { + fflush(NULL); + } + } + break; + + case F_cl: + X.rsm = (rstream *)hash_search(fdhash, L.s); + if (X.rsm) { + R.i = X.rsm->is_pipe ? pclose(X.rsm->F) : fclose(X.rsm->F); + free(X.rsm->buffer); + hash_remove(fdhash, L.s); + } + if (R.i != 0) + setvar_i(V[ERRNO], errno); + R.d = (double)R.i; + break; + } + setvar_i(res, R.d); + break; + + case XC( OC_BUILTIN ): + res = exec_builtin(op, res); + break; + + case XC( OC_SPRINTF ): + setvar_p(res, awk_printf(op1)); + break; + + case XC( OC_UNARY ): + X.v = R.v; + L.d = R.d = getvar_i(R.v); + switch (opn) { + case 'P': + L.d = ++R.d; + goto r_op_change; + case 'p': + R.d++; + goto r_op_change; + case 'M': + L.d = --R.d; + goto r_op_change; + case 'm': + R.d--; + goto r_op_change; + case '!': + L.d = istrue(X.v) ? 0 : 1; + break; + case '-': + L.d = -R.d; + break; + r_op_change: + setvar_i(X.v, R.d); + } + setvar_i(res, L.d); + break; + + case XC( OC_FIELD ): + R.i = (int)getvar_i(R.v); + if (R.i == 0) { + res = V[F0]; + } else { + split_f0(); + if (R.i > nfields) + fsrealloc(R.i); + + res = &Fields[R.i-1]; + } + break; + + /* concatenation (" ") and index joining (",") */ + case XC( OC_CONCAT ): + case XC( OC_COMMA ): + opn = strlen(L.s) + strlen(R.s) + 2; + X.s = (char *)xmalloc(opn); + strcpy(X.s, L.s); + if ((opinfo & OPCLSMASK) == OC_COMMA) { + L.s = getvar_s(V[SUBSEP]); + X.s = (char *)xrealloc(X.s, opn + strlen(L.s)); + strcat(X.s, L.s); + } + strcat(X.s, R.s); + setvar_p(res, X.s); + break; + + case XC( OC_LAND ): + setvar_i(res, istrue(L.v) ? ptest(op->r.n) : 0); + break; + + case XC( OC_LOR ): + setvar_i(res, istrue(L.v) ? 1 : ptest(op->r.n)); + break; + + case XC( OC_BINARY ): + case XC( OC_REPLACE ): + R.d = getvar_i(R.v); + switch (opn) { + case '+': + L.d += R.d; + break; + case '-': + L.d -= R.d; + break; + case '*': + L.d *= R.d; + break; + case '/': + if (R.d == 0) runtime_error(EMSG_DIV_BY_ZERO); + L.d /= R.d; + break; + case '&': +#ifdef CONFIG_FEATURE_AWK_MATH + L.d = pow(L.d, R.d); +#else + runtime_error(EMSG_NO_MATH); +#endif + break; + case '%': + if (R.d == 0) runtime_error(EMSG_DIV_BY_ZERO); + L.d -= (int)(L.d / R.d) * R.d; + break; + } + res = setvar_i(((opinfo&OPCLSMASK) == OC_BINARY) ? res : X.v, L.d); + break; + + case XC( OC_COMPARE ): + if (is_numeric(L.v) && is_numeric(R.v)) { + L.d = getvar_i(L.v) - getvar_i(R.v); + } else { + L.s = getvar_s(L.v); + R.s = getvar_s(R.v); + L.d = icase ? strcasecmp(L.s, R.s) : strcmp(L.s, R.s); + } + switch (opn & 0xfe) { + case 0: + R.i = (L.d > 0); + break; + case 2: + R.i = (L.d >= 0); + break; + case 4: + R.i = (L.d == 0); + break; + } + setvar_i(res, (opn & 0x1 ? R.i : !R.i) ? 1 : 0); + break; + + default: + runtime_error(EMSG_POSSIBLE_ERROR); + } + if ((opinfo & OPCLSMASK) <= SHIFT_TIL_THIS) + op = op->a.n; + if ((opinfo & OPCLSMASK) >= RECUR_FROM_THIS) + break; + if (nextrec) + break; + } + nvfree(v1); + return res; +} + + +/* -------- main & co. -------- */ + +static int awk_exit(int r) +{ + unsigned int i; + hash_item *hi; + static var tv; + + if (! exiting) { + exiting = TRUE; + nextrec = FALSE; + evaluate(endseq.first, &tv); + } + + /* waiting for children */ + for (i=0; icsize; i++) { + hi = fdhash->items[i]; + while(hi) { + if (hi->data.rs.F && hi->data.rs.is_pipe) + pclose(hi->data.rs.F); + hi = hi->next; + } + } + + exit(r); +} + +/* if expr looks like "var=value", perform assignment and return 1, + * otherwise return 0 */ +static int is_assignment(const char *expr) +{ + char *exprc, *s, *s0, *s1; + + exprc = xstrdup(expr); + if (!isalnum_(*exprc) || (s = strchr(exprc, '=')) == NULL) { + free(exprc); + return FALSE; + } + + *(s++) = '\0'; + s0 = s1 = s; + while (*s) + *(s1++) = nextchar(&s); + + *s1 = '\0'; + setvar_u(newvar(exprc), s0); + free(exprc); + return TRUE; +} + +/* switch to next input file */ +static rstream *next_input_file(void) +{ + static rstream rsm; + FILE *F = NULL; + char *fname, *ind; + static int files_happen = FALSE; + + if (rsm.F) fclose(rsm.F); + rsm.F = NULL; + rsm.pos = rsm.adv = 0; + + do { + if (getvar_i(V[ARGIND])+1 >= getvar_i(V[ARGC])) { + if (files_happen) + return NULL; + fname = "-"; + F = stdin; + } else { + ind = getvar_s(incvar(V[ARGIND])); + fname = getvar_s(findvar(iamarray(V[ARGV]), ind)); + if (fname && *fname && !is_assignment(fname)) + F = afopen(fname, "r"); + } + } while (!F); + + files_happen = TRUE; + setvar_s(V[FILENAME], fname); + rsm.F = F; + return &rsm; +} + +int awk_main(int argc, char **argv) +{ + unsigned opt; + char *opt_F, *opt_v, *opt_W; + char *s, *s1; + int i, j, c, flen; + var *v; + static var tv; + char **envp; + static int from_file = FALSE; + rstream *rsm; + FILE *F, *stdfiles[3]; + static char * stdnames = "/dev/stdin\0/dev/stdout\0/dev/stderr"; + + /* allocate global buffer */ + buf = xmalloc(MAXVARFMT+1); + + vhash = hash_init(); + ahash = hash_init(); + fdhash = hash_init(); + fnhash = hash_init(); + + /* initialize variables */ + for (i=0; *vNames; i++) { + V[i] = v = newvar(nextword(&vNames)); + if (*vValues != '\377') + setvar_s(v, nextword(&vValues)); + else + setvar_i(v, 0); + + if (*vNames == '*') { + v->type |= VF_SPECIAL; + vNames++; + } + } + + handle_special(V[FS]); + handle_special(V[RS]); + + stdfiles[0] = stdin; + stdfiles[1] = stdout; + stdfiles[2] = stderr; + for (i=0; i<3; i++) { + rsm = newfile(nextword(&stdnames)); + rsm->F = stdfiles[i]; + } + + for (envp=environ; *envp; envp++) { + s = xstrdup(*envp); + s1 = strchr(s, '='); + if (!s1) { + goto keep_going; + } + *(s1++) = '\0'; + setvar_u(findvar(iamarray(V[ENVIRON]), s), s1); +keep_going: + free(s); + } + + opt = getopt32(argc, argv, "F:v:f:W:", &opt_F, &opt_v, &programname, &opt_W); + if (opt & 0x1) setvar_s(V[FS], opt_F); // -F + if (opt & 0x2) if (!is_assignment(opt_v)) bb_show_usage(); // -v + if (opt & 0x4) { // -f + from_file = TRUE; + F = afopen(programname, "r"); + s = NULL; + /* one byte is reserved for some trick in next_token */ + if (fseek(F, 0, SEEK_END) == 0) { + flen = ftell(F); + s = (char *)xmalloc(flen+4); + fseek(F, 0, SEEK_SET); + i = 1 + fread(s+1, 1, flen, F); + } else { + for (i=j=1; j>0; i+=j) { + s = (char *)xrealloc(s, i+4096); + j = fread(s+i, 1, 4094, F); + } + } + s[i] = '\0'; + fclose(F); + parse_program(s+1); + free(s); + } + if (opt & 0x8) // -W + bb_error_msg("warning: unrecognized option '-W %s' ignored", opt_W); + + if (!from_file) { + if (argc == optind) + bb_show_usage(); + programname = "cmd. line"; + parse_program(argv[optind++]); + + } + + /* fill in ARGV array */ + setvar_i(V[ARGC], argc - optind + 1); + setari_u(V[ARGV], 0, "awk"); + for(i=optind; i < argc; i++) + setari_u(V[ARGV], i+1-optind, argv[i]); + + evaluate(beginseq.first, &tv); + if (! mainseq.first && ! endseq.first) + awk_exit(EXIT_SUCCESS); + + /* input file could already be opened in BEGIN block */ + if (! iF) iF = next_input_file(); + + /* passing through input files */ + while (iF) { + + nextfile = FALSE; + setvar_i(V[FNR], 0); + + while ((c = awk_getline(iF, V[F0])) > 0) { + + nextrec = FALSE; + incvar(V[NR]); + incvar(V[FNR]); + evaluate(mainseq.first, &tv); + + if (nextfile) + break; + } + + if (c < 0) + runtime_error(strerror(errno)); + + iF = next_input_file(); + + } + + awk_exit(EXIT_SUCCESS); + + return 0; +} diff --git a/editors/ed.c b/editors/ed.c new file mode 100644 index 000000000..3aca75912 --- /dev/null +++ b/editors/ed.c @@ -0,0 +1,1231 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (c) 2002 by David I. Bell + * Permission is granted to use, distribute, or modify this source, + * provided that this copyright notice remains intact. + * + * The "ed" built-in command (much simplified) + */ + +#include "busybox.h" + +#define USERSIZE 1024 /* max line length typed in by user */ +#define INITBUF_SIZE 1024 /* initial buffer size */ +typedef struct LINE { + struct LINE *next; + struct LINE *prev; + int len; + char data[1]; +} LINE; + +static LINE lines, *curLine; +static int curNum, lastNum, marks[26], dirty; +static char *bufBase, *bufPtr, *fileName, searchString[USERSIZE]; +static int bufUsed, bufSize; + +static void doCommands(void); +static void subCommand(const char *cmd, int num1, int num2); +static int getNum(const char **retcp, int *retHaveNum, int *retNum); +static int setCurNum(int num); +static int initEdit(void); +static void termEdit(void); +static void addLines(int num); +static int insertLine(int num, const char *data, int len); +static int deleteLines(int num1, int num2); +static int printLines(int num1, int num2, int expandFlag); +static int writeLines(const char *file, int num1, int num2); +static int readLines(const char *file, int num); +static int searchLines(const char *str, int num1, int num2); +static LINE *findLine(int num); + +static int findString(const LINE *lp, const char * str, int len, int offset); + +int ed_main(int argc, char **argv) +{ + if (!initEdit()) + return EXIT_FAILURE; + + if (argc > 1) { + fileName = strdup(argv[1]); + + if (fileName == NULL) { + bb_error_msg("no memory"); + termEdit(); + return EXIT_SUCCESS; + } + + if (!readLines(fileName, 1)) { + termEdit(); + return EXIT_SUCCESS; + } + + if (lastNum) + setCurNum(1); + + dirty = FALSE; + } + + doCommands(); + + termEdit(); + return EXIT_SUCCESS; +} + +/* + * Read commands until we are told to stop. + */ +static void doCommands(void) +{ + const char *cp; + char *endbuf, *newname, buf[USERSIZE]; + int len, num1, num2, have1, have2; + + while (TRUE) { + printf(": "); + fflush(stdout); + + if (fgets(buf, sizeof(buf), stdin) == NULL) + return; + + len = strlen(buf); + + if (len == 0) + return; + + endbuf = &buf[len - 1]; + + if (*endbuf != '\n') { + bb_error_msg("command line too long"); + + do { + len = fgetc(stdin); + } while ((len != EOF) && (len != '\n')); + + continue; + } + + while ((endbuf > buf) && isblank(endbuf[-1])) + endbuf--; + + *endbuf = '\0'; + + cp = buf; + + while (isblank(*cp)) + cp++; + + have1 = FALSE; + have2 = FALSE; + + if ((curNum == 0) && (lastNum > 0)) { + curNum = 1; + curLine = lines.next; + } + + if (!getNum(&cp, &have1, &num1)) + continue; + + while (isblank(*cp)) + cp++; + + if (*cp == ',') { + cp++; + + if (!getNum(&cp, &have2, &num2)) + continue; + + if (!have1) + num1 = 1; + + if (!have2) + num2 = lastNum; + + have1 = TRUE; + have2 = TRUE; + } + + if (!have1) + num1 = curNum; + + if (!have2) + num2 = num1; + + switch (*cp++) { + case 'a': + addLines(num1 + 1); + break; + + case 'c': + deleteLines(num1, num2); + addLines(num1); + break; + + case 'd': + deleteLines(num1, num2); + break; + + case 'f': + if (*cp && !isblank(*cp)) { + bb_error_msg("bad file command"); + break; + } + + while (isblank(*cp)) + cp++; + + if (*cp == '\0') { + if (fileName) + printf("\"%s\"\n", fileName); + else + printf("No file name\n"); + break; + } + + newname = strdup(cp); + + if (newname == NULL) { + bb_error_msg("no memory for file name"); + break; + } + + if (fileName) + free(fileName); + + fileName = newname; + break; + + case 'i': + addLines(num1); + break; + + case 'k': + while (isblank(*cp)) + cp++; + + if ((*cp < 'a') || (*cp > 'a') || cp[1]) { + bb_error_msg("bad mark name"); + break; + } + + marks[*cp - 'a'] = num2; + break; + + case 'l': + printLines(num1, num2, TRUE); + break; + + case 'p': + printLines(num1, num2, FALSE); + break; + + case 'q': + while (isblank(*cp)) + cp++; + + if (have1 || *cp) { + bb_error_msg("bad quit command"); + break; + } + + if (!dirty) + return; + + printf("Really quit? "); + fflush(stdout); + + buf[0] = '\0'; + fgets(buf, sizeof(buf), stdin); + cp = buf; + + while (isblank(*cp)) + cp++; + + if ((*cp == 'y') || (*cp == 'Y')) + return; + + break; + + case 'r': + if (*cp && !isblank(*cp)) { + bb_error_msg("bad read command"); + break; + } + + while (isblank(*cp)) + cp++; + + if (*cp == '\0') { + bb_error_msg("no file name"); + break; + } + + if (!have1) + num1 = lastNum; + + if (readLines(cp, num1 + 1)) + break; + + if (fileName == NULL) + fileName = strdup(cp); + + break; + + case 's': + subCommand(cp, num1, num2); + break; + + case 'w': + if (*cp && !isblank(*cp)) { + bb_error_msg("bad write command"); + break; + } + + while (isblank(*cp)) + cp++; + + if (!have1) { + num1 = 1; + num2 = lastNum; + } + + if (*cp == '\0') + cp = fileName; + + if (cp == NULL) { + bb_error_msg("no file name specified"); + break; + } + + writeLines(cp, num1, num2); + break; + + case 'z': + switch (*cp) { + case '-': + printLines(curNum-21, curNum, FALSE); + break; + case '.': + printLines(curNum-11, curNum+10, FALSE); + break; + default: + printLines(curNum, curNum+21, FALSE); + break; + } + break; + + case '.': + if (have1) { + bb_error_msg("no arguments allowed"); + break; + } + + printLines(curNum, curNum, FALSE); + break; + + case '-': + if (setCurNum(curNum - 1)) + printLines(curNum, curNum, FALSE); + + break; + + case '=': + printf("%d\n", num1); + break; + + case '\0': + if (have1) { + printLines(num2, num2, FALSE); + break; + } + + if (setCurNum(curNum + 1)) + printLines(curNum, curNum, FALSE); + + break; + + default: + bb_error_msg("unimplemented command"); + break; + } + } +} + + +/* + * Do the substitute command. + * The current line is set to the last substitution done. + */ +static void subCommand(const char * cmd, int num1, int num2) +{ + char *cp, *oldStr, *newStr, buf[USERSIZE]; + int delim, oldLen, newLen, deltaLen, offset; + LINE *lp, *nlp; + int globalFlag, printFlag, didSub, needPrint; + + if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) { + bb_error_msg("bad line range for substitute"); + return; + } + + globalFlag = FALSE; + printFlag = FALSE; + didSub = FALSE; + needPrint = FALSE; + + /* + * Copy the command so we can modify it. + */ + strcpy(buf, cmd); + cp = buf; + + if (isblank(*cp) || (*cp == '\0')) { + bb_error_msg("bad delimiter for substitute"); + return; + } + + delim = *cp++; + oldStr = cp; + + cp = strchr(cp, delim); + + if (cp == NULL) { + bb_error_msg("missing 2nd delimiter for substitute"); + return; + } + + *cp++ = '\0'; + + newStr = cp; + cp = strchr(cp, delim); + + if (cp) + *cp++ = '\0'; + else + cp = ""; + + while (*cp) switch (*cp++) { + case 'g': + globalFlag = TRUE; + break; + + case 'p': + printFlag = TRUE; + break; + + default: + bb_error_msg("unknown option for substitute"); + return; + } + + if (*oldStr == '\0') { + if (searchString[0] == '\0') { + bb_error_msg("no previous search string"); + return; + } + + oldStr = searchString; + } + + if (oldStr != searchString) + strcpy(searchString, oldStr); + + lp = findLine(num1); + + if (lp == NULL) + return; + + oldLen = strlen(oldStr); + newLen = strlen(newStr); + deltaLen = newLen - oldLen; + offset = 0; + nlp = NULL; + + while (num1 <= num2) { + offset = findString(lp, oldStr, oldLen, offset); + + if (offset < 0) { + if (needPrint) { + printLines(num1, num1, FALSE); + needPrint = FALSE; + } + + offset = 0; + lp = lp->next; + num1++; + + continue; + } + + needPrint = printFlag; + didSub = TRUE; + dirty = TRUE; + + /* + * If the replacement string is the same size or shorter + * than the old string, then the substitution is easy. + */ + if (deltaLen <= 0) { + memcpy(&lp->data[offset], newStr, newLen); + + if (deltaLen) { + memcpy(&lp->data[offset + newLen], + &lp->data[offset + oldLen], + lp->len - offset - oldLen); + + lp->len += deltaLen; + } + + offset += newLen; + + if (globalFlag) + continue; + + if (needPrint) { + printLines(num1, num1, FALSE); + needPrint = FALSE; + } + + lp = lp->next; + num1++; + + continue; + } + + /* + * The new string is larger, so allocate a new line + * structure and use that. Link it in in place of + * the old line structure. + */ + nlp = (LINE *) malloc(sizeof(LINE) + lp->len + deltaLen); + + if (nlp == NULL) { + bb_error_msg("cannot get memory for line"); + return; + } + + nlp->len = lp->len + deltaLen; + + memcpy(nlp->data, lp->data, offset); + + memcpy(&nlp->data[offset], newStr, newLen); + + memcpy(&nlp->data[offset + newLen], + &lp->data[offset + oldLen], + lp->len - offset - oldLen); + + nlp->next = lp->next; + nlp->prev = lp->prev; + nlp->prev->next = nlp; + nlp->next->prev = nlp; + + if (curLine == lp) + curLine = nlp; + + free(lp); + lp = nlp; + + offset += newLen; + + if (globalFlag) + continue; + + if (needPrint) { + printLines(num1, num1, FALSE); + needPrint = FALSE; + } + + lp = lp->next; + num1++; + } + + if (!didSub) + bb_error_msg("no substitutions found for \"%s\"", oldStr); +} + + +/* + * Search a line for the specified string starting at the specified + * offset in the line. Returns the offset of the found string, or -1. + */ +static int findString( const LINE * lp, const char * str, int len, int offset) +{ + int left; + const char *cp, *ncp; + + cp = &lp->data[offset]; + left = lp->len - offset; + + while (left >= len) { + ncp = memchr(cp, *str, left); + + if (ncp == NULL) + return -1; + + left -= (ncp - cp); + + if (left < len) + return -1; + + cp = ncp; + + if (memcmp(cp, str, len) == 0) + return (cp - lp->data); + + cp++; + left--; + } + + return -1; +} + + +/* + * Add lines which are typed in by the user. + * The lines are inserted just before the specified line number. + * The lines are terminated by a line containing a single dot (ugly!), + * or by an end of file. + */ +static void addLines(int num) +{ + int len; + char buf[USERSIZE + 1]; + + while (fgets(buf, sizeof(buf), stdin)) { + if ((buf[0] == '.') && (buf[1] == '\n') && (buf[2] == '\0')) + return; + + len = strlen(buf); + + if (len == 0) + return; + + if (buf[len - 1] != '\n') { + bb_error_msg("line too long"); + do { + len = fgetc(stdin); + } while ((len != EOF) && (len != '\n')); + return; + } + + if (!insertLine(num++, buf, len)) + return; + } +} + + +/* + * Parse a line number argument if it is present. This is a sum + * or difference of numbers, '.', '$', 'x, or a search string. + * Returns TRUE if successful (whether or not there was a number). + * Returns FALSE if there was a parsing error, with a message output. + * Whether there was a number is returned indirectly, as is the number. + * The character pointer which stopped the scan is also returned. + */ +static int getNum(const char **retcp, int *retHaveNum, int *retNum) +{ + const char *cp; + char *endStr, str[USERSIZE]; + int haveNum, value, num, sign; + + cp = *retcp; + haveNum = FALSE; + value = 0; + sign = 1; + + while (TRUE) { + while (isblank(*cp)) + cp++; + + switch (*cp) { + case '.': + haveNum = TRUE; + num = curNum; + cp++; + break; + + case '$': + haveNum = TRUE; + num = lastNum; + cp++; + break; + + case '\'': + cp++; + + if ((*cp < 'a') || (*cp > 'z')) { + bb_error_msg("bad mark name"); + return FALSE; + } + + haveNum = TRUE; + num = marks[*cp++ - 'a']; + break; + + case '/': + strcpy(str, ++cp); + endStr = strchr(str, '/'); + + if (endStr) { + *endStr++ = '\0'; + cp += (endStr - str); + } + else + cp = ""; + + num = searchLines(str, curNum, lastNum); + + if (num == 0) + return FALSE; + + haveNum = TRUE; + break; + + default: + if (!isdigit(*cp)) { + *retcp = cp; + *retHaveNum = haveNum; + *retNum = value; + return TRUE; + } + + num = 0; + + while (isdigit(*cp)) + num = num * 10 + *cp++ - '0'; + + haveNum = TRUE; + break; + } + + value += num * sign; + + while (isblank(*cp)) + cp++; + + switch (*cp) { + case '-': + sign = -1; + cp++; + break; + + case '+': + sign = 1; + cp++; + break; + + default: + *retcp = cp; + *retHaveNum = haveNum; + *retNum = value; + return TRUE; + } + } +} + + +/* + * Initialize everything for editing. + */ +static int initEdit(void) +{ + int i; + + bufSize = INITBUF_SIZE; + bufBase = malloc(bufSize); + + if (bufBase == NULL) { + bb_error_msg("no memory for buffer"); + return FALSE; + } + + bufPtr = bufBase; + bufUsed = 0; + + lines.next = &lines; + lines.prev = &lines; + + curLine = NULL; + curNum = 0; + lastNum = 0; + dirty = FALSE; + fileName = NULL; + searchString[0] = '\0'; + + for (i = 0; i < 26; i++) + marks[i] = 0; + + return TRUE; +} + + +/* + * Finish editing. + */ +static void termEdit(void) +{ + if (bufBase) + free(bufBase); + + bufBase = NULL; + bufPtr = NULL; + bufSize = 0; + bufUsed = 0; + + if (fileName) + free(fileName); + + fileName = NULL; + + searchString[0] = '\0'; + + if (lastNum) + deleteLines(1, lastNum); + + lastNum = 0; + curNum = 0; + curLine = NULL; +} + + +/* + * Read lines from a file at the specified line number. + * Returns TRUE if the file was successfully read. + */ +static int readLines(const char * file, int num) +{ + int fd, cc; + int len, lineCount, charCount; + char *cp; + + if ((num < 1) || (num > lastNum + 1)) { + bb_error_msg("bad line for read"); + return FALSE; + } + + fd = open(file, 0); + + if (fd < 0) { + perror(file); + return FALSE; + } + + bufPtr = bufBase; + bufUsed = 0; + lineCount = 0; + charCount = 0; + cc = 0; + + printf("\"%s\", ", file); + fflush(stdout); + + do { + cp = memchr(bufPtr, '\n', bufUsed); + + if (cp) { + len = (cp - bufPtr) + 1; + + if (!insertLine(num, bufPtr, len)) { + close(fd); + return FALSE; + } + + bufPtr += len; + bufUsed -= len; + charCount += len; + lineCount++; + num++; + + continue; + } + + if (bufPtr != bufBase) { + memcpy(bufBase, bufPtr, bufUsed); + bufPtr = bufBase + bufUsed; + } + + if (bufUsed >= bufSize) { + len = (bufSize * 3) / 2; + cp = realloc(bufBase, len); + + if (cp == NULL) { + bb_error_msg("no memory for buffer"); + close(fd); + return FALSE; + } + + bufBase = cp; + bufPtr = bufBase + bufUsed; + bufSize = len; + } + + cc = read(fd, bufPtr, bufSize - bufUsed); + bufUsed += cc; + bufPtr = bufBase; + + } while (cc > 0); + + if (cc < 0) { + perror(file); + close(fd); + return FALSE; + } + + if (bufUsed) { + if (!insertLine(num, bufPtr, bufUsed)) { + close(fd); + return -1; + } + + lineCount++; + charCount += bufUsed; + } + + close(fd); + + printf("%d lines%s, %d chars\n", lineCount, + (bufUsed ? " (incomplete)" : ""), charCount); + + return TRUE; +} + + +/* + * Write the specified lines out to the specified file. + * Returns TRUE if successful, or FALSE on an error with a message output. + */ +static int writeLines(const char * file, int num1, int num2) +{ + LINE *lp; + int fd, lineCount, charCount; + + if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) { + bb_error_msg("bad line range for write"); + return FALSE; + } + + lineCount = 0; + charCount = 0; + + fd = creat(file, 0666); + + if (fd < 0) { + perror(file); + return FALSE; + } + + printf("\"%s\", ", file); + fflush(stdout); + + lp = findLine(num1); + + if (lp == NULL) { + close(fd); + return FALSE; + } + + while (num1++ <= num2) { + if (write(fd, lp->data, lp->len) != lp->len) { + perror(file); + close(fd); + return FALSE; + } + + charCount += lp->len; + lineCount++; + lp = lp->next; + } + + if (close(fd) < 0) { + perror(file); + return FALSE; + } + + printf("%d lines, %d chars\n", lineCount, charCount); + return TRUE; +} + + +/* + * Print lines in a specified range. + * The last line printed becomes the current line. + * If expandFlag is TRUE, then the line is printed specially to + * show magic characters. + */ +static int printLines(int num1, int num2, int expandFlag) +{ + const LINE *lp; + const char *cp; + int ch, count; + + if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) { + bb_error_msg("bad line range for print"); + return FALSE; + } + + lp = findLine(num1); + + if (lp == NULL) + return FALSE; + + while (num1 <= num2) { + if (!expandFlag) { + write(1, lp->data, lp->len); + setCurNum(num1++); + lp = lp->next; + + continue; + } + + /* + * Show control characters and characters with the + * high bit set specially. + */ + cp = lp->data; + count = lp->len; + + if ((count > 0) && (cp[count - 1] == '\n')) + count--; + + while (count-- > 0) { + ch = *cp++; + + if (ch & 0x80) { + fputs("M-", stdout); + ch &= 0x7f; + } + + if (ch < ' ') { + fputc('^', stdout); + ch += '@'; + } + + if (ch == 0x7f) { + fputc('^', stdout); + ch = '?'; + } + + fputc(ch, stdout); + } + + fputs("$\n", stdout); + + setCurNum(num1++); + lp = lp->next; + } + + return TRUE; +} + + +/* + * Insert a new line with the specified text. + * The line is inserted so as to become the specified line, + * thus pushing any existing and further lines down one. + * The inserted line is also set to become the current line. + * Returns TRUE if successful. + */ +static int insertLine(int num, const char * data, int len) +{ + LINE *newLp, *lp; + + if ((num < 1) || (num > lastNum + 1)) { + bb_error_msg("inserting at bad line number"); + return FALSE; + } + + newLp = malloc(sizeof(LINE) + len - 1); + + if (newLp == NULL) { + bb_error_msg("failed to allocate memory for line"); + return FALSE; + } + + memcpy(newLp->data, data, len); + newLp->len = len; + + if (num > lastNum) + lp = &lines; + else { + lp = findLine(num); + + if (lp == NULL) { + free((char *) newLp); + return FALSE; + } + } + + newLp->next = lp; + newLp->prev = lp->prev; + lp->prev->next = newLp; + lp->prev = newLp; + + lastNum++; + dirty = TRUE; + return setCurNum(num); +} + + +/* + * Delete lines from the given range. + */ +static int deleteLines(int num1, int num2) +{ + LINE *lp, *nlp, *plp; + int count; + + if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) { + bb_error_msg("bad line numbers for delete"); + return FALSE; + } + + lp = findLine(num1); + + if (lp == NULL) + return FALSE; + + if ((curNum >= num1) && (curNum <= num2)) { + if (num2 < lastNum) + setCurNum(num2 + 1); + else if (num1 > 1) + setCurNum(num1 - 1); + else + curNum = 0; + } + + count = num2 - num1 + 1; + + if (curNum > num2) + curNum -= count; + + lastNum -= count; + + while (count-- > 0) { + nlp = lp->next; + plp = lp->prev; + plp->next = nlp; + nlp->prev = plp; + lp->next = NULL; + lp->prev = NULL; + lp->len = 0; + free(lp); + lp = nlp; + } + + dirty = TRUE; + + return TRUE; +} + + +/* + * Search for a line which contains the specified string. + * If the string is NULL, then the previously searched for string + * is used. The currently searched for string is saved for future use. + * Returns the line number which matches, or 0 if there was no match + * with an error printed. + */ +static int searchLines(const char *str, int num1, int num2) +{ + const LINE *lp; + int len; + + if ((num1 < 1) || (num2 > lastNum) || (num1 > num2)) { + bb_error_msg("bad line numbers for search"); + return 0; + } + + if (*str == '\0') { + if (searchString[0] == '\0') { + bb_error_msg("no previous search string"); + return 0; + } + + str = searchString; + } + + if (str != searchString) + strcpy(searchString, str); + + len = strlen(str); + + lp = findLine(num1); + + if (lp == NULL) + return 0; + + while (num1 <= num2) { + if (findString(lp, str, len, 0) >= 0) + return num1; + + num1++; + lp = lp->next; + } + + bb_error_msg("cannot find string \"%s\"", str); + return 0; +} + + +/* + * Return a pointer to the specified line number. + */ +static LINE *findLine(int num) +{ + LINE *lp; + int lnum; + + if ((num < 1) || (num > lastNum)) { + bb_error_msg("line number %d does not exist", num); + return NULL; + } + + if (curNum <= 0) { + curNum = 1; + curLine = lines.next; + } + + if (num == curNum) + return curLine; + + lp = curLine; + lnum = curNum; + + if (num < (curNum / 2)) { + lp = lines.next; + lnum = 1; + } + else if (num > ((curNum + lastNum) / 2)) { + lp = lines.prev; + lnum = lastNum; + } + + while (lnum < num) { + lp = lp->next; + lnum++; + } + + while (lnum > num) { + lp = lp->prev; + lnum--; + } + return lp; +} + + +/* + * Set the current line number. + * Returns TRUE if successful. + */ +static int setCurNum(int num) +{ + LINE *lp; + + lp = findLine(num); + + if (lp == NULL) + return FALSE; + + curNum = num; + curLine = lp; + return TRUE; +} diff --git a/editors/patch.c b/editors/patch.c new file mode 100644 index 000000000..8e885d06e --- /dev/null +++ b/editors/patch.c @@ -0,0 +1,275 @@ +/* vi: set sw=4 ts=4: */ +/* + * busybox patch applet to handle the unified diff format. + * Copyright (C) 2003 Glenn McGrath + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * This applet is written to work with patches generated by GNU diff, + * where there is equivalent functionality busybox patch shall behave + * as per GNU patch. + * + * There is a SUSv3 specification for patch, however it looks to be + * incomplete, it doesnt even mention unified diff format. + * http://www.opengroup.org/onlinepubs/007904975/utilities/patch.html + * + * Issues + * - Non-interactive + * - Patches must apply cleanly or the hunk will fail. + * - Reject file isnt saved + * - + */ + +#include +#include +#include +#include +#include "busybox.h" + +static unsigned int copy_lines(FILE *src_stream, FILE *dest_stream, const unsigned int lines_count) +{ + unsigned int i = 0; + + while (src_stream && (i < lines_count)) { + char *line; + line = xmalloc_fgets(src_stream); + if (line == NULL) { + break; + } + if (fputs(line, dest_stream) == EOF) { + bb_perror_msg_and_die("error writing to new file"); + } + free(line); + + i++; + } + return i; +} + +/* If patch_level is -1 it will remove all directory names + * char *line must be greater than 4 chars + * returns NULL if the file doesnt exist or error + * returns malloc'ed filename + */ + +static char *extract_filename(char *line, int patch_level) +{ + char *temp, *filename_start_ptr = line + 4; + int i; + + /* Terminate string at end of source filename */ + temp = strchr(filename_start_ptr, '\t'); + if (temp) *temp = 0; + + /* skip over (patch_level) number of leading directories */ + for (i = 0; i < patch_level; i++) { + if(!(temp = strchr(filename_start_ptr, '/'))) break; + filename_start_ptr = temp + 1; + } + + return xstrdup(filename_start_ptr); +} + +static int file_doesnt_exist(const char *filename) +{ + struct stat statbuf; + return stat(filename, &statbuf); +} + +int patch_main(int argc, char **argv) +{ + int patch_level = -1; + char *patch_line; + int ret; + FILE *patch_file = NULL; + + { + char *p, *i; + ret = getopt32(argc, argv, "p:i:", &p, &i); + if (ret & 1) + patch_level = xatol_range(p, -1, USHRT_MAX); + if (ret & 2) { + patch_file = xfopen(i, "r"); + } else { + patch_file = stdin; + } + ret = 0; + } + + patch_line = xmalloc_fgets(patch_file); + while (patch_line) { + FILE *src_stream; + FILE *dst_stream; + char *original_filename; + char *new_filename; + char *backup_filename; + unsigned int src_cur_line = 1; + unsigned int dest_cur_line = 0; + unsigned int dest_beg_line; + unsigned int bad_hunk_count = 0; + unsigned int hunk_count = 0; + char copy_trailing_lines_flag = 0; + + /* Skip everything upto the "---" marker + * No need to parse the lines "Only in ", and "diff " + */ + while (patch_line && strncmp(patch_line, "--- ", 4) != 0) { + free(patch_line); + patch_line = xmalloc_fgets(patch_file); + } + /* FIXME: patch_line NULL check?? */ + + /* Extract the filename used before the patch was generated */ + original_filename = extract_filename(patch_line, patch_level); + free(patch_line); + + patch_line = xmalloc_fgets(patch_file); + /* FIXME: NULL check?? */ + if (strncmp(patch_line, "+++ ", 4) != 0) { + ret = 2; + bb_error_msg("invalid patch"); + continue; + } + new_filename = extract_filename(patch_line, patch_level); + free(patch_line); + + if (file_doesnt_exist(new_filename)) { + char *line_ptr; + /* Create leading directories */ + line_ptr = strrchr(new_filename, '/'); + if (line_ptr) { + *line_ptr = '\0'; + bb_make_directory(new_filename, -1, FILEUTILS_RECUR); + *line_ptr = '/'; + } + dst_stream = xfopen(new_filename, "w+"); + backup_filename = NULL; + } else { + backup_filename = xmalloc(strlen(new_filename) + 6); + strcpy(backup_filename, new_filename); + strcat(backup_filename, ".orig"); + if (rename(new_filename, backup_filename) == -1) { + bb_perror_msg_and_die("cannot create file %s", + backup_filename); + } + dst_stream = xfopen(new_filename, "w"); + } + + if ((backup_filename == NULL) || file_doesnt_exist(original_filename)) { + src_stream = NULL; + } else { + if (strcmp(original_filename, new_filename) == 0) { + src_stream = xfopen(backup_filename, "r"); + } else { + src_stream = xfopen(original_filename, "r"); + } + } + + printf("patching file %s\n", new_filename); + + /* Handle each hunk */ + patch_line = xmalloc_fgets(patch_file); + while (patch_line) { + unsigned int count; + unsigned int src_beg_line; + unsigned int unused; + unsigned int hunk_offset_start = 0; + int hunk_error = 0; + + /* This bit should be improved */ + if ((sscanf(patch_line, "@@ -%d,%d +%d,%d @@", &src_beg_line, &unused, &dest_beg_line, &unused) != 4) && + (sscanf(patch_line, "@@ -%d,%d +%d @@", &src_beg_line, &unused, &dest_beg_line) != 3) && + (sscanf(patch_line, "@@ -%d +%d,%d @@", &src_beg_line, &dest_beg_line, &unused) != 3)) { + /* No more hunks for this file */ + break; + } + free(patch_line); + hunk_count++; + + if (src_beg_line && dest_beg_line) { + /* Copy unmodified lines upto start of hunk */ + /* src_beg_line will be 0 if its a new file */ + count = src_beg_line - src_cur_line; + if (copy_lines(src_stream, dst_stream, count) != count) { + bb_error_msg_and_die("bad src file"); + } + src_cur_line += count; + dest_cur_line += count; + copy_trailing_lines_flag = 1; + } + hunk_offset_start = src_cur_line; + + while ((patch_line = xmalloc_fgets(patch_file)) != NULL) { + if ((*patch_line == '-') || (*patch_line == ' ')) { + char *src_line = NULL; + if (src_stream) { + src_line = xmalloc_fgets(src_stream); + if (!src_line) { + hunk_error++; + break; + } else { + src_cur_line++; + } + if (strcmp(src_line, patch_line + 1) != 0) { + bb_error_msg("hunk #%d FAILED at %d", hunk_count, hunk_offset_start); + hunk_error++; + free(patch_line); + break; + } + free(src_line); + } + if (*patch_line == ' ') { + fputs(patch_line + 1, dst_stream); + dest_cur_line++; + } + } else if (*patch_line == '+') { + fputs(patch_line + 1, dst_stream); + dest_cur_line++; + } else { + break; + } + free(patch_line); + } + if (hunk_error) { + bad_hunk_count++; + } + } + + /* Cleanup last patched file */ + if (copy_trailing_lines_flag) { + copy_lines(src_stream, dst_stream, -1); + } + if (src_stream) { + fclose(src_stream); + } + if (dst_stream) { + fclose(dst_stream); + } + if (bad_hunk_count) { + if (!ret) { + ret = 1; + } + bb_error_msg("%d out of %d hunk FAILED", bad_hunk_count, hunk_count); + } else { + /* It worked, we can remove the backup */ + if (backup_filename) { + unlink(backup_filename); + } + if ((dest_cur_line == 0) || (dest_beg_line == 0)) { + /* The new patched file is empty, remove it */ + if (unlink(new_filename) == -1) { + bb_perror_msg_and_die("cannot remove file %s", new_filename); + } + if (unlink(original_filename) == -1) { + bb_perror_msg_and_die("cannot remove original file %s", new_filename); + } + } + } + } + + /* 0 = SUCCESS + * 1 = Some hunks failed + * 2 = More serious problems + */ + return ret; +} diff --git a/editors/sed.c b/editors/sed.c new file mode 100644 index 000000000..ac3d8d463 --- /dev/null +++ b/editors/sed.c @@ -0,0 +1,1281 @@ +/* vi: set sw=4 ts=4: */ +/* + * sed.c - very minimalist version of sed + * + * Copyright (C) 1999,2000,2001 by Lineo, inc. and Mark Whitley + * Copyright (C) 1999,2000,2001 by Mark Whitley + * Copyright (C) 2002 Matt Kraai + * Copyright (C) 2003 by Glenn McGrath + * Copyright (C) 2003,2004 by Rob Landley + * + * MAINTAINER: Rob Landley + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +/* Code overview. + + Files are laid out to avoid unnecessary function declarations. So for + example, every function add_cmd calls occurs before add_cmd in this file. + + add_cmd() is called on each line of sed command text (from a file or from + the command line). It calls get_address() and parse_cmd_args(). The + resulting sed_cmd_t structures are appended to a linked list + (bbg.sed_cmd_head/bbg.sed_cmd_tail). + + add_input_file() adds a FILE * to the list of input files. We need to + know all input sources ahead of time to find the last line for the $ match. + + process_files() does actual sedding, reading data lines from each input FILE * + (which could be stdin) and applying the sed command list (sed_cmd_head) to + each of the resulting lines. + + sed_main() is where external code calls into this, with a command line. +*/ + + +/* + Supported features and commands in this version of sed: + + - comments ('#') + - address matching: num|/matchstr/[,num|/matchstr/|$]command + - commands: (p)rint, (d)elete, (s)ubstitue (with g & I flags) + - edit commands: (a)ppend, (i)nsert, (c)hange + - file commands: (r)ead + - backreferences in substitution expressions (\0, \1, \2...\9) + - grouped commands: {cmd1;cmd2} + - transliteration (y/source-chars/dest-chars/) + - pattern space hold space storing / swapping (g, h, x) + - labels / branching (: label, b, t, T) + + (Note: Specifying an address (range) to match is *optional*; commands + default to the whole pattern space if no specific address match was + requested.) + + Todo: + - Create a wrapper around regex to make libc's regex conform with sed + + Reference http://www.opengroup.org/onlinepubs/007904975/utilities/sed.html +*/ + +#include "busybox.h" +#include "xregex.h" + +/* Each sed command turns into one of these structures. */ +typedef struct sed_cmd_s { + /* Ordered by alignment requirements: currently 36 bytes on x86 */ + + /* address storage */ + regex_t *beg_match; /* sed -e '/match/cmd' */ + regex_t *end_match; /* sed -e '/match/,/end_match/cmd' */ + regex_t *sub_match; /* For 's/sub_match/string/' */ + int beg_line; /* 'sed 1p' 0 == apply commands to all lines */ + int end_line; /* 'sed 1,3p' 0 == one line only. -1 = last line ($) */ + + FILE *file; /* File (sw) command writes to, -1 for none. */ + char *string; /* Data string for (saicytb) commands. */ + + unsigned short which_match; /* (s) Which match to replace (0 for all) */ + + /* Bitfields (gcc won't group them if we don't) */ + unsigned int invert:1; /* the '!' after the address */ + unsigned int in_match:1; /* Next line also included in match? */ + unsigned int sub_p:1; /* (s) print option */ + + int last_char; /* Last line written by (sw) had no '\n' */ + + /* GENERAL FIELDS */ + char cmd; /* The command char: abcdDgGhHilnNpPqrstwxy:={} */ + struct sed_cmd_s *next; /* Next command (linked list, NULL terminated) */ +} sed_cmd_t; + +static const char *const semicolon_whitespace = "; \n\r\t\v"; + +struct sed_globals { + /* options */ + int be_quiet, regex_type; + FILE *nonstdout; + char *outname, *hold_space; + + /* List of input files */ + int input_file_count, current_input_file; + FILE **input_file_list; + + regmatch_t regmatch[10]; + regex_t *previous_regex_ptr; + + /* linked list of sed commands */ + sed_cmd_t sed_cmd_head, *sed_cmd_tail; + + /* Linked list of append lines */ + llist_t *append_head; + + char *add_cmd_line; + + struct pipeline { + char *buf; /* Space to hold string */ + int idx; /* Space used */ + int len; /* Space allocated */ + } pipeline; +} bbg; + + +void sed_free_and_close_stuff(void); +#if ENABLE_FEATURE_CLEAN_UP +static void sed_free_and_close_stuff(void) +{ + sed_cmd_t *sed_cmd = bbg.sed_cmd_head.next; + + llist_free(bbg.append_head, free); + + while (sed_cmd) { + sed_cmd_t *sed_cmd_next = sed_cmd->next; + + if (sed_cmd->file) + xprint_and_close_file(sed_cmd->file); + + if (sed_cmd->beg_match) { + regfree(sed_cmd->beg_match); + free(sed_cmd->beg_match); + } + if (sed_cmd->end_match) { + regfree(sed_cmd->end_match); + free(sed_cmd->end_match); + } + if (sed_cmd->sub_match) { + regfree(sed_cmd->sub_match); + free(sed_cmd->sub_match); + } + free(sed_cmd->string); + free(sed_cmd); + sed_cmd = sed_cmd_next; + } + + if (bbg.hold_space) free(bbg.hold_space); + + while (bbg.current_input_file < bbg.input_file_count) + fclose(bbg.input_file_list[bbg.current_input_file++]); +} +#endif + +/* If something bad happens during -i operation, delete temp file */ + +static void cleanup_outname(void) +{ + if (bbg.outname) unlink(bbg.outname); +} + +/* strdup, replacing "\n" with '\n', and "\delimiter" with 'delimiter' */ + +static void parse_escapes(char *dest, char *string, int len, char from, char to) +{ + int i = 0; + + while (i < len) { + if (string[i] == '\\') { + if (!to || string[i+1] == from) { + *(dest++) = to ? to : string[i+1]; + i += 2; + continue; + } else *(dest++) = string[i++]; + } + *(dest++) = string[i++]; + } + *dest = 0; +} + +static char *copy_parsing_escapes(char *string, int len) +{ + char *dest = xmalloc(len + 1); + + parse_escapes(dest, string, len, 'n', '\n'); + return dest; +} + + +/* + * index_of_next_unescaped_regexp_delim - walks left to right through a string + * beginning at a specified index and returns the index of the next regular + * expression delimiter (typically a forward * slash ('/')) not preceded by + * a backslash ('\'). A negative delimiter disables square bracket checking. + */ +static int index_of_next_unescaped_regexp_delim(int delimiter, char *str) +{ + int bracket = -1; + int escaped = 0; + int idx = 0; + char ch; + + if (delimiter < 0) { + bracket--; + delimiter = -delimiter; + } + + for (; (ch = str[idx]); idx++) { + if (bracket >= 0) { + if (ch == ']' && !(bracket == idx - 1 || (bracket == idx - 2 + && str[idx - 1] == '^'))) + bracket = -1; + } else if (escaped) + escaped = 0; + else if (ch == '\\') + escaped = 1; + else if (bracket == -1 && ch == '[') + bracket = idx; + else if (ch == delimiter) + return idx; + } + + /* if we make it to here, we've hit the end of the string */ + bb_error_msg_and_die("unmatched '%c'", delimiter); +} + +/* + * Returns the index of the third delimiter + */ +static int parse_regex_delim(char *cmdstr, char **match, char **replace) +{ + char *cmdstr_ptr = cmdstr; + char delimiter; + int idx = 0; + + /* verify that the 's' or 'y' is followed by something. That something + * (typically a 'slash') is now our regexp delimiter... */ + if (*cmdstr == '\0') + bb_error_msg_and_die("bad format in substitution expression"); + delimiter = *cmdstr_ptr++; + + /* save the match string */ + idx = index_of_next_unescaped_regexp_delim(delimiter, cmdstr_ptr); + *match = copy_parsing_escapes(cmdstr_ptr, idx); + + /* save the replacement string */ + cmdstr_ptr += idx + 1; + idx = index_of_next_unescaped_regexp_delim(-delimiter, cmdstr_ptr); + *replace = copy_parsing_escapes(cmdstr_ptr, idx); + + return ((cmdstr_ptr - cmdstr) + idx); +} + +/* + * returns the index in the string just past where the address ends. + */ +static int get_address(char *my_str, int *linenum, regex_t ** regex) +{ + char *pos = my_str; + + if (isdigit(*my_str)) { + *linenum = strtol(my_str, &pos, 10); + /* endstr shouldnt ever equal NULL */ + } else if (*my_str == '$') { + *linenum = -1; + pos++; + } else if (*my_str == '/' || *my_str == '\\') { + int next; + char delimiter; + char *temp; + + delimiter = '/'; + if (*my_str == '\\') delimiter = *++pos; + next = index_of_next_unescaped_regexp_delim(delimiter, ++pos); + temp = copy_parsing_escapes(pos, next); + *regex = xmalloc(sizeof(regex_t)); + xregcomp(*regex, temp, bbg.regex_type|REG_NEWLINE); + free(temp); + /* Move position to next character after last delimiter */ + pos += (next+1); + } + return pos - my_str; +} + +/* Grab a filename. Whitespace at start is skipped, then goes to EOL. */ +static int parse_file_cmd(sed_cmd_t *sed_cmd, char *filecmdstr, char **retval) +{ + int start = 0, idx, hack = 0; + + /* Skip whitespace, then grab filename to end of line */ + while (isspace(filecmdstr[start])) start++; + idx = start; + while (filecmdstr[idx] && filecmdstr[idx] != '\n') idx++; + + /* If lines glued together, put backslash back. */ + if (filecmdstr[idx] == '\n') hack = 1; + if (idx == start) + bb_error_msg_and_die("empty filename"); + *retval = xstrndup(filecmdstr+start, idx-start+hack+1); + if (hack) (*retval)[idx] = '\\'; + + return idx; +} + +static int parse_subst_cmd(sed_cmd_t *sed_cmd, char *substr) +{ + int cflags = bbg.regex_type; + char *match; + int idx = 0; + + /* + * A substitution command should look something like this: + * s/match/replace/ #gIpw + * || | ||| + * mandatory optional + */ + idx = parse_regex_delim(substr, &match, &sed_cmd->string); + + /* determine the number of back references in the match string */ + /* Note: we compute this here rather than in the do_subst_command() + * function to save processor time, at the expense of a little more memory + * (4 bits) per sed_cmd */ + + /* process the flags */ + + sed_cmd->which_match = 1; + while (substr[++idx]) { + /* Parse match number */ + if (isdigit(substr[idx])) { + if (match[0] != '^') { + /* Match 0 treated as all, multiple matches we take the last one. */ + char *pos = substr + idx; + /* FIXME: error check? */ + sed_cmd->which_match = (unsigned short)strtol(substr+idx, &pos, 10); + idx = pos - substr; + } + continue; + } + /* Skip spaces */ + if (isspace(substr[idx])) continue; + + switch (substr[idx]) { + /* Replace all occurrences */ + case 'g': + if (match[0] != '^') sed_cmd->which_match = 0; + break; + /* Print pattern space */ + case 'p': + sed_cmd->sub_p = 1; + break; + /* Write to file */ + case 'w': + { + char *temp; + idx += parse_file_cmd(sed_cmd, substr+idx, &temp); + + break; + } + /* Ignore case (gnu exension) */ + case 'I': + cflags |= REG_ICASE; + break; + /* Comment */ + case '#': + while (substr[++idx]) /*skip all*/; + /* Fall through */ + /* End of command */ + case ';': + case '}': + goto out; + default: + bb_error_msg_and_die("bad option in substitution expression"); + } + } +out: + /* compile the match string into a regex */ + if (*match != '\0') { + /* If match is empty, we use last regex used at runtime */ + sed_cmd->sub_match = (regex_t *) xmalloc(sizeof(regex_t)); + xregcomp(sed_cmd->sub_match, match, cflags); + } + free(match); + + return idx; +} + +/* + * Process the commands arguments + */ +static char *parse_cmd_args(sed_cmd_t *sed_cmd, char *cmdstr) +{ + /* handle (s)ubstitution command */ + if (sed_cmd->cmd == 's') + cmdstr += parse_subst_cmd(sed_cmd, cmdstr); + /* handle edit cmds: (a)ppend, (i)nsert, and (c)hange */ + else if (strchr("aic", sed_cmd->cmd)) { + if ((sed_cmd->end_line || sed_cmd->end_match) && sed_cmd->cmd != 'c') + bb_error_msg_and_die + ("only a beginning address can be specified for edit commands"); + for (;;) { + if (*cmdstr == '\n' || *cmdstr == '\\') { + cmdstr++; + break; + } else if (isspace(*cmdstr)) + cmdstr++; + else + break; + } + sed_cmd->string = xstrdup(cmdstr); + parse_escapes(sed_cmd->string, sed_cmd->string, strlen(cmdstr), 0, 0); + cmdstr += strlen(cmdstr); + /* handle file cmds: (r)ead */ + } else if (strchr("rw", sed_cmd->cmd)) { + if (sed_cmd->end_line || sed_cmd->end_match) + bb_error_msg_and_die("command only uses one address"); + cmdstr += parse_file_cmd(sed_cmd, cmdstr, &sed_cmd->string); + if (sed_cmd->cmd == 'w') + sed_cmd->file = xfopen(sed_cmd->string, "w"); + /* handle branch commands */ + } else if (strchr(":btT", sed_cmd->cmd)) { + int length; + + cmdstr = skip_whitespace(cmdstr); + length = strcspn(cmdstr, semicolon_whitespace); + if (length) { + sed_cmd->string = xstrndup(cmdstr, length); + cmdstr += length; + } + } + /* translation command */ + else if (sed_cmd->cmd == 'y') { + char *match, *replace; + int i = cmdstr[0]; + + cmdstr += parse_regex_delim(cmdstr, &match, &replace)+1; + /* \n already parsed, but \delimiter needs unescaping. */ + parse_escapes(match, match, strlen(match), i, i); + parse_escapes(replace, replace, strlen(replace), i, i); + + sed_cmd->string = xzalloc((strlen(match) + 1) * 2); + for (i = 0; match[i] && replace[i]; i++) { + sed_cmd->string[i*2] = match[i]; + sed_cmd->string[i*2+1] = replace[i]; + } + free(match); + free(replace); + } + /* if it wasnt a single-letter command that takes no arguments + * then it must be an invalid command. + */ + else if (strchr("dDgGhHlnNpPqx={}", sed_cmd->cmd) == 0) { + bb_error_msg_and_die("unsupported command %c", sed_cmd->cmd); + } + + /* give back whatever's left over */ + return cmdstr; +} + + +/* Parse address+command sets, skipping comment lines. */ + +static void add_cmd(char *cmdstr) +{ + sed_cmd_t *sed_cmd; + int temp; + + /* Append this line to any unfinished line from last time. */ + if (bbg.add_cmd_line) { + cmdstr = xasprintf("%s\n%s", bbg.add_cmd_line, cmdstr); + free(bbg.add_cmd_line); + bbg.add_cmd_line = cmdstr; + } + + /* If this line ends with backslash, request next line. */ + temp = strlen(cmdstr); + if (temp && cmdstr[temp-1] == '\\') { + if (!bbg.add_cmd_line) + bbg.add_cmd_line = xstrdup(cmdstr); + bbg.add_cmd_line[temp-1] = 0; + return; + } + + /* Loop parsing all commands in this line. */ + while (*cmdstr) { + /* Skip leading whitespace and semicolons */ + cmdstr += strspn(cmdstr, semicolon_whitespace); + + /* If no more commands, exit. */ + if (!*cmdstr) break; + + /* if this is a comment, jump past it and keep going */ + if (*cmdstr == '#') { + /* "#n" is the same as using -n on the command line */ + if (cmdstr[1] == 'n') + bbg.be_quiet++; + cmdstr = strpbrk(cmdstr, "\n\r"); + if (!cmdstr) break; + continue; + } + + /* parse the command + * format is: [addr][,addr][!]cmd + * |----||-----||-| + * part1 part2 part3 + */ + + sed_cmd = xzalloc(sizeof(sed_cmd_t)); + + /* first part (if present) is an address: either a '$', a number or a /regex/ */ + cmdstr += get_address(cmdstr, &sed_cmd->beg_line, &sed_cmd->beg_match); + + /* second part (if present) will begin with a comma */ + if (*cmdstr == ',') { + int idx; + + cmdstr++; + idx = get_address(cmdstr, &sed_cmd->end_line, &sed_cmd->end_match); + if (!idx) + bb_error_msg_and_die("no address after comma"); + cmdstr += idx; + } + + /* skip whitespace before the command */ + cmdstr = skip_whitespace(cmdstr); + + /* Check for inversion flag */ + if (*cmdstr == '!') { + sed_cmd->invert = 1; + cmdstr++; + + /* skip whitespace before the command */ + cmdstr = skip_whitespace(cmdstr); + } + + /* last part (mandatory) will be a command */ + if (!*cmdstr) + bb_error_msg_and_die("missing command"); + sed_cmd->cmd = *(cmdstr++); + cmdstr = parse_cmd_args(sed_cmd, cmdstr); + + /* Add the command to the command array */ + bbg.sed_cmd_tail->next = sed_cmd; + bbg.sed_cmd_tail = bbg.sed_cmd_tail->next; + } + + /* If we glued multiple lines together, free the memory. */ + free(bbg.add_cmd_line); + bbg.add_cmd_line = NULL; +} + +/* Append to a string, reallocating memory as necessary. */ + +#define PIPE_GROW 64 + +static void pipe_putc(char c) +{ + if (bbg.pipeline.idx == bbg.pipeline.len) { + bbg.pipeline.buf = xrealloc(bbg.pipeline.buf, + bbg.pipeline.len + PIPE_GROW); + bbg.pipeline.len += PIPE_GROW; + } + bbg.pipeline.buf[bbg.pipeline.idx++] = c; +} + +static void do_subst_w_backrefs(char *line, char *replace) +{ + int i,j; + + /* go through the replacement string */ + for (i = 0; replace[i]; i++) { + /* if we find a backreference (\1, \2, etc.) print the backref'ed * text */ + if (replace[i] == '\\' && replace[i+1] >= '0' && replace[i+1] <= '9') { + int backref = replace[++i]-'0'; + + /* print out the text held in bbg.regmatch[backref] */ + if (bbg.regmatch[backref].rm_so != -1) { + j = bbg.regmatch[backref].rm_so; + while (j < bbg.regmatch[backref].rm_eo) + pipe_putc(line[j++]); + } + } + + /* if we find a backslash escaped character, print the character */ + else if (replace[i] == '\\') pipe_putc(replace[++i]); + + /* if we find an unescaped '&' print out the whole matched text. */ + else if (replace[i] == '&') { + j = bbg.regmatch[0].rm_so; + while (j < bbg.regmatch[0].rm_eo) + pipe_putc(line[j++]); + } + /* Otherwise just output the character. */ + else pipe_putc(replace[i]); + } +} + +static int do_subst_command(sed_cmd_t *sed_cmd, char **line) +{ + char *oldline = *line; + int altered = 0; + int match_count = 0; + regex_t *current_regex; + + /* Handle empty regex. */ + if (sed_cmd->sub_match == NULL) { + current_regex = bbg.previous_regex_ptr; + if (!current_regex) + bb_error_msg_and_die("no previous regexp"); + } else + bbg.previous_regex_ptr = current_regex = sed_cmd->sub_match; + + /* Find the first match */ + if (REG_NOMATCH == regexec(current_regex, oldline, 10, bbg.regmatch, 0)) + return 0; + + /* Initialize temporary output buffer. */ + bbg.pipeline.buf = xmalloc(PIPE_GROW); + bbg.pipeline.len = PIPE_GROW; + bbg.pipeline.idx = 0; + + /* Now loop through, substituting for matches */ + do { + int i; + + /* Work around bug in glibc regexec, demonstrated by: + echo " a.b" | busybox sed 's [^ .]* x g' + The match_count check is so not to break + echo "hi" | busybox sed 's/^/!/g' */ + if (!bbg.regmatch[0].rm_so && !bbg.regmatch[0].rm_eo && match_count) { + pipe_putc(*oldline++); + continue; + } + + match_count++; + + /* If we aren't interested in this match, output old line to + end of match and continue */ + if (sed_cmd->which_match && sed_cmd->which_match != match_count) { + for (i = 0; i < bbg.regmatch[0].rm_eo; i++) + pipe_putc(*oldline++); + continue; + } + + /* print everything before the match */ + for (i = 0; i < bbg.regmatch[0].rm_so; i++) + pipe_putc(oldline[i]); + + /* then print the substitution string */ + do_subst_w_backrefs(oldline, sed_cmd->string); + + /* advance past the match */ + oldline += bbg.regmatch[0].rm_eo; + /* flag that something has changed */ + altered++; + + /* if we're not doing this globally, get out now */ + if (sed_cmd->which_match) break; + } while (*oldline && (regexec(current_regex, oldline, 10, bbg.regmatch, 0) != REG_NOMATCH)); + + /* Copy rest of string into output pipeline */ + + while (*oldline) + pipe_putc(*oldline++); + pipe_putc(0); + + free(*line); + *line = bbg.pipeline.buf; + return altered; +} + +/* Set command pointer to point to this label. (Does not handle null label.) */ +static sed_cmd_t *branch_to(char *label) +{ + sed_cmd_t *sed_cmd; + + for (sed_cmd = bbg.sed_cmd_head.next; sed_cmd; sed_cmd = sed_cmd->next) { + if (sed_cmd->cmd == ':' && sed_cmd->string && !strcmp(sed_cmd->string, label)) { + return sed_cmd; + } + } + bb_error_msg_and_die("can't find label for jump to '%s'", label); +} + +static void append(char *s) +{ + llist_add_to_end(&bbg.append_head, xstrdup(s)); +} + +static void flush_append(void) +{ + char *data; + + /* Output appended lines. */ + while ((data = (char *)llist_pop(&bbg.append_head))) { + fprintf(bbg.nonstdout, "%s\n", data); + free(data); + } +} + +static void add_input_file(FILE *file) +{ + bbg.input_file_list = xrealloc(bbg.input_file_list, + (bbg.input_file_count + 1) * sizeof(FILE *)); + bbg.input_file_list[bbg.input_file_count++] = file; +} + +/* Get next line of input from bbg.input_file_list, flushing append buffer and + * noting if we ran out of files without a newline on the last line we read. + */ +static char *get_next_line(int *last_char) +{ + char *temp = NULL; + int len, lc; + + lc = 0; + flush_append(); + while (bbg.current_input_file < bbg.input_file_count) { + temp = bb_get_chunk_from_file( + bbg.input_file_list[bbg.current_input_file], &len); + if (temp) { + /* len > 0 here, it's ok to do temp[len-1] */ + char c = temp[len-1]; + if (c == '\n' || c == '\0') { + temp[len-1] = '\0'; + lc |= (unsigned char)c; + break; + } + /* will be returned if last line in the file + * doesn't end with either '\n' or '\0' */ + lc |= 0x100; + break; + } + /* Close this file and advance to next one */ + fclose(bbg.input_file_list[bbg.current_input_file++]); + /* "this is the first line from new input file" */ + lc |= 0x200; + } + *last_char = lc; + return temp; +} + +/* Output line of text. */ +/* Note: + * The tricks with 0x200 and last_puts_char are there to emulate gnu sed. + * Without them, we had this: + * echo -n thingy >z1 + * echo -n again >z2 + * >znull + * sed "s/i/z/" z1 z2 znull | hexdump -vC output: + * gnu sed 4.1.5: + * 00000000 74 68 7a 6e 67 79 0a 61 67 61 7a 6e |thzngy.agazn| + * bbox: + * 00000000 74 68 7a 6e 67 79 61 67 61 7a 6e |thzngyagazn| + */ + +static int puts_maybe_newline(char *s, FILE *file, int prev_last_char, int last_char) +{ + static char last_puts_char; + + /* Is this a first line from new file + * and old file didn't end with '\n'? */ + if ((last_char & 0x200) && last_puts_char != '\n') { + fputc('\n', file); + last_puts_char = '\n'; + } + fputs(s, file); + /* 'x': we don't care what is it, but we know it isn't '\n' */ + if (s[0]) last_puts_char = 'x'; + if (!(last_char & 0x100)) { /* had trailing '\n' or '\0'? */ + last_char &= 0xff; + fputc(last_char, file); + last_puts_char = last_char; + } + + if (ferror(file)) { + xfunc_error_retval = 4; /* It's what gnu sed exits with... */ + bb_error_msg_and_die(bb_msg_write_error); + } + + return last_char; +} + +#define sed_puts(s, n) \ + (prev_last_char = puts_maybe_newline(s, bbg.nonstdout, prev_last_char, n)) + +/* Process all the lines in all the files */ + +static void process_files(void) +{ + char *pattern_space, *next_line; + int linenum = 0, prev_last_char = 0; + int last_char, next_last_char = 0; + sed_cmd_t *sed_cmd; + int substituted; + + /* Prime the pump */ + next_line = get_next_line(&next_last_char); + + /* go through every line in each file */ +again: + substituted = 0; + + /* Advance to next line. Stop if out of lines. */ + pattern_space = next_line; + if (!pattern_space) return; + last_char = next_last_char; + + /* Read one line in advance so we can act on the last line, + * the '$' address */ + next_line = get_next_line(&next_last_char); + linenum++; +restart: + /* for every line, go through all the commands */ + for (sed_cmd = bbg.sed_cmd_head.next; sed_cmd; sed_cmd = sed_cmd->next) { + int old_matched, matched; + + old_matched = sed_cmd->in_match; + + /* Determine if this command matches this line: */ + + /* Are we continuing a previous multi-line match? */ + sed_cmd->in_match = sed_cmd->in_match + /* Or is no range necessary? */ + || (!sed_cmd->beg_line && !sed_cmd->end_line + && !sed_cmd->beg_match && !sed_cmd->end_match) + /* Or did we match the start of a numerical range? */ + || (sed_cmd->beg_line > 0 && (sed_cmd->beg_line == linenum)) + /* Or does this line match our begin address regex? */ + || (sed_cmd->beg_match && + !regexec(sed_cmd->beg_match, pattern_space, 0, NULL, 0)) + /* Or did we match last line of input? */ + || (sed_cmd->beg_line == -1 && next_line == NULL); + + /* Snapshot the value */ + + matched = sed_cmd->in_match; + + /* Is this line the end of the current match? */ + + if (matched) { + sed_cmd->in_match = !( + /* has the ending line come, or is this a single address command? */ + (sed_cmd->end_line ? + sed_cmd->end_line == -1 ? + !next_line + : (sed_cmd->end_line <= linenum) + : !sed_cmd->end_match + ) + /* or does this line matches our last address regex */ + || (sed_cmd->end_match && old_matched + && (regexec(sed_cmd->end_match, + pattern_space, 0, NULL, 0) == 0)) + ); + } + + /* Skip blocks of commands we didn't match. */ + if (sed_cmd->cmd == '{') { + if (sed_cmd->invert ? matched : !matched) + while (sed_cmd && sed_cmd->cmd != '}') + sed_cmd = sed_cmd->next; + if (!sed_cmd) bb_error_msg_and_die("unterminated {"); + continue; + } + + /* Okay, so did this line match? */ + if (sed_cmd->invert ? !matched : matched) { + /* Update last used regex in case a blank substitute BRE is found */ + if (sed_cmd->beg_match) { + bbg.previous_regex_ptr = sed_cmd->beg_match; + } + + /* actual sedding */ + switch (sed_cmd->cmd) { + + /* Print line number */ + case '=': + fprintf(bbg.nonstdout, "%d\n", linenum); + break; + + /* Write the current pattern space up to the first newline */ + case 'P': + { + char *tmp = strchr(pattern_space, '\n'); + + if (tmp) { + *tmp = '\0'; + sed_puts(pattern_space, 1); + *tmp = '\n'; + break; + } + /* Fall Through */ + } + + /* Write the current pattern space to output */ + case 'p': + sed_puts(pattern_space, last_char); + break; + /* Delete up through first newline */ + case 'D': + { + char *tmp = strchr(pattern_space, '\n'); + + if (tmp) { + tmp = xstrdup(tmp+1); + free(pattern_space); + pattern_space = tmp; + goto restart; + } + } + /* discard this line. */ + case 'd': + goto discard_line; + + /* Substitute with regex */ + case 's': + if (!do_subst_command(sed_cmd, &pattern_space)) + break; + substituted |= 1; + + /* handle p option */ + if (sed_cmd->sub_p) + sed_puts(pattern_space, last_char); + /* handle w option */ + if (sed_cmd->file) + sed_cmd->last_char = puts_maybe_newline( + pattern_space, sed_cmd->file, + sed_cmd->last_char, last_char); + break; + + /* Append line to linked list to be printed later */ + case 'a': + append(sed_cmd->string); + break; + + /* Insert text before this line */ + case 'i': + sed_puts(sed_cmd->string, 1); + break; + + /* Cut and paste text (replace) */ + case 'c': + /* Only triggers on last line of a matching range. */ + if (!sed_cmd->in_match) + sed_puts(sed_cmd->string, 0); + goto discard_line; + + /* Read file, append contents to output */ + case 'r': + { + FILE *rfile; + + rfile = fopen(sed_cmd->string, "r"); + if (rfile) { + char *line; + + while ((line = xmalloc_getline(rfile)) + != NULL) + append(line); + xprint_and_close_file(rfile); + } + + break; + } + + /* Write pattern space to file. */ + case 'w': + sed_cmd->last_char = puts_maybe_newline( + pattern_space, sed_cmd->file, + sed_cmd->last_char, last_char); + break; + + /* Read next line from input */ + case 'n': + if (!bbg.be_quiet) + sed_puts(pattern_space, last_char); + if (next_line) { + free(pattern_space); + pattern_space = next_line; + last_char = next_last_char; + next_line = get_next_line(&next_last_char); + linenum++; + break; + } + /* fall through */ + + /* Quit. End of script, end of input. */ + case 'q': + /* Exit the outer while loop */ + free(next_line); + next_line = NULL; + goto discard_commands; + + /* Append the next line to the current line */ + case 'N': + { + int len; + /* If no next line, jump to end of script and exit. */ + if (next_line == NULL) { + /* Jump to end of script and exit */ + free(next_line); + next_line = NULL; + goto discard_line; + /* append next_line, read new next_line. */ + } + len = strlen(pattern_space); + pattern_space = realloc(pattern_space, len + strlen(next_line) + 2); + pattern_space[len] = '\n'; + strcpy(pattern_space + len+1, next_line); + last_char = next_last_char; + next_line = get_next_line(&next_last_char); + linenum++; + break; + } + + /* Test/branch if substitution occurred */ + case 't': + if (!substituted) break; + substituted = 0; + /* Fall through */ + /* Test/branch if substitution didn't occur */ + case 'T': + if (substituted) break; + /* Fall through */ + /* Branch to label */ + case 'b': + if (!sed_cmd->string) goto discard_commands; + else sed_cmd = branch_to(sed_cmd->string); + break; + /* Transliterate characters */ + case 'y': + { + int i, j; + + for (i = 0; pattern_space[i]; i++) { + for (j = 0; sed_cmd->string[j]; j += 2) { + if (pattern_space[i] == sed_cmd->string[j]) { + pattern_space[i] = sed_cmd->string[j + 1]; + break; + } + } + } + + break; + } + case 'g': /* Replace pattern space with hold space */ + free(pattern_space); + pattern_space = xstrdup(bbg.hold_space ? bbg.hold_space : ""); + break; + case 'G': /* Append newline and hold space to pattern space */ + { + int pattern_space_size = 2; + int hold_space_size = 0; + + if (pattern_space) + pattern_space_size += strlen(pattern_space); + if (bbg.hold_space) + hold_space_size = strlen(bbg.hold_space); + pattern_space = xrealloc(pattern_space, + pattern_space_size + hold_space_size); + if (pattern_space_size == 2) + pattern_space[0] = 0; + strcat(pattern_space, "\n"); + if (bbg.hold_space) + strcat(pattern_space, bbg.hold_space); + last_char = '\n'; + + break; + } + case 'h': /* Replace hold space with pattern space */ + free(bbg.hold_space); + bbg.hold_space = xstrdup(pattern_space); + break; + case 'H': /* Append newline and pattern space to hold space */ + { + int hold_space_size = 2; + int pattern_space_size = 0; + + if (bbg.hold_space) + hold_space_size += strlen(bbg.hold_space); + if (pattern_space) + pattern_space_size = strlen(pattern_space); + bbg.hold_space = xrealloc(bbg.hold_space, + hold_space_size + pattern_space_size); + + if (hold_space_size == 2) + *bbg.hold_space = 0; + strcat(bbg.hold_space, "\n"); + if (pattern_space) + strcat(bbg.hold_space, pattern_space); + + break; + } + case 'x': /* Exchange hold and pattern space */ + { + char *tmp = pattern_space; + pattern_space = bbg.hold_space ? : xzalloc(1); + last_char = '\n'; + bbg.hold_space = tmp; + break; + } + } + } + } + + /* + * exit point from sedding... + */ +discard_commands: + /* we will print the line unless we were told to be quiet ('-n') + or if the line was suppressed (ala 'd'elete) */ + if (!bbg.be_quiet) sed_puts(pattern_space, last_char); + + /* Delete and such jump here. */ +discard_line: + flush_append(); + free(pattern_space); + + goto again; +} + +/* It is possible to have a command line argument with embedded + newlines. This counts as multiple command lines. */ + +static void add_cmd_block(char *cmdstr) +{ + int go = 1; + char *temp = xstrdup(cmdstr), *temp2 = temp; + + while (go) { + int len = strcspn(temp2, "\n"); + if (!temp2[len]) go = 0; + else temp2[len] = 0; + add_cmd(temp2); + temp2 += len+1; + } + free(temp); +} + +static void add_cmds_link(llist_t *opt_e) +{ + if (!opt_e) return; + add_cmds_link(opt_e->link); + add_cmd_block(opt_e->data); + free(opt_e); +} + +static void add_files_link(llist_t *opt_f) +{ + char *line; + FILE *cmdfile; + if (!opt_f) return; + add_files_link(opt_f->link); + cmdfile = xfopen(opt_f->data, "r"); + while ((line = xmalloc_getline(cmdfile)) != NULL) { + add_cmd(line); + free(line); + } + xprint_and_close_file(cmdfile); + free(opt_f); +} + +int sed_main(int argc, char **argv) +{ + enum { + OPT_in_place = 1 << 0, + }; + unsigned opt; + llist_t *opt_e, *opt_f; + int status = EXIT_SUCCESS; + + bbg.sed_cmd_tail = &bbg.sed_cmd_head; + + /* destroy command strings on exit */ + if (ENABLE_FEATURE_CLEAN_UP) atexit(sed_free_and_close_stuff); + + /* Lie to autoconf when it starts asking stupid questions. */ + if (argc == 2 && !strcmp(argv[1], "--version")) { + puts("This is not GNU sed version 4.0"); + return 0; + } + + /* do normal option parsing */ + opt_e = opt_f = NULL; + opt_complementary = "e::f::" /* can occur multiple times */ + "nn"; /* count -n */ + opt = getopt32(argc, argv, "irne:f:", &opt_e, &opt_f, + &bbg.be_quiet); /* counter for -n */ + argc -= optind; + argv += optind; + if (opt & OPT_in_place) { // -i + atexit(cleanup_outname); + } + if (opt & 0x2) bbg.regex_type |= REG_EXTENDED; // -r + //if (opt & 0x4) bbg.be_quiet++; // -n + if (opt & 0x8) { // -e + /* getopt32 reverses order of arguments, handle it */ + add_cmds_link(opt_e); + } + if (opt & 0x10) { // -f + /* getopt32 reverses order of arguments, handle it */ + add_files_link(opt_f); + } + /* if we didn't get a pattern from -e or -f, use argv[0] */ + if (!(opt & 0x18)) { + if (!argc) + bb_show_usage(); + add_cmd_block(*argv++); + argc--; + } + /* Flush any unfinished commands. */ + add_cmd(""); + + /* By default, we write to stdout */ + bbg.nonstdout = stdout; + + /* argv[0..(argc-1)] should be names of file to process. If no + * files were specified or '-' was specified, take input from stdin. + * Otherwise, we process all the files specified. */ + if (argv[0] == NULL) { + if (opt & OPT_in_place) + bb_error_msg_and_die(bb_msg_requires_arg, "-i"); + add_input_file(stdin); + process_files(); + } else { + int i; + FILE *file; + + for (i = 0; i < argc; i++) { + struct stat statbuf; + int nonstdoutfd; + + if (argv[i][0] == '-' && !argv[i][1] + && !(opt & OPT_in_place) + ) { + add_input_file(stdin); + process_files(); + continue; + } + file = fopen_or_warn(argv[i], "r"); + if (!file) { + status = EXIT_FAILURE; + continue; + } + if (!(opt & OPT_in_place)) { + add_input_file(file); + continue; + } + + bbg.outname = xasprintf("%sXXXXXX", argv[i]); + nonstdoutfd = mkstemp(bbg.outname); + if (-1 == nonstdoutfd) + bb_error_msg_and_die("no temp file"); + bbg.nonstdout = fdopen(nonstdoutfd, "w"); + + /* Set permissions of output file */ + + fstat(fileno(file), &statbuf); + fchmod(nonstdoutfd, statbuf.st_mode); + add_input_file(file); + process_files(); + fclose(bbg.nonstdout); + + bbg.nonstdout = stdout; + /* unlink(argv[i]); */ + // FIXME: error check / message? + rename(bbg.outname, argv[i]); + free(bbg.outname); + bbg.outname = 0; + } + if (bbg.input_file_count > bbg.current_input_file) + process_files(); + } + + return status; +} diff --git a/editors/vi.c b/editors/vi.c new file mode 100644 index 000000000..eef895c53 --- /dev/null +++ b/editors/vi.c @@ -0,0 +1,3926 @@ +/* vi: set sw=4 ts=4: */ +/* + * tiny vi.c: A small 'vi' clone + * Copyright (C) 2000, 2001 Sterling Huxley + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +/* + * Things To Do: + * EXINIT + * $HOME/.exrc and ./.exrc + * add magic to search /foo.*bar + * add :help command + * :map macros + * if mark[] values were line numbers rather than pointers + * it would be easier to change the mark when add/delete lines + * More intelligence in refresh() + * ":r !cmd" and "!cmd" to filter text through an external command + * A true "undo" facility + * An "ex" line oriented mode- maybe using "cmdedit" + */ + + +#include "busybox.h" + +#ifdef CONFIG_LOCALE_SUPPORT +#define Isprint(c) isprint((c)) +#else +#define Isprint(c) ( (c) >= ' ' && (c) != 127 && (c) != ((unsigned char)'\233') ) +#endif + +#define MAX_SCR_COLS BUFSIZ + +// Misc. non-Ascii keys that report an escape sequence +#define VI_K_UP 128 // cursor key Up +#define VI_K_DOWN 129 // cursor key Down +#define VI_K_RIGHT 130 // Cursor Key Right +#define VI_K_LEFT 131 // cursor key Left +#define VI_K_HOME 132 // Cursor Key Home +#define VI_K_END 133 // Cursor Key End +#define VI_K_INSERT 134 // Cursor Key Insert +#define VI_K_PAGEUP 135 // Cursor Key Page Up +#define VI_K_PAGEDOWN 136 // Cursor Key Page Down +#define VI_K_FUN1 137 // Function Key F1 +#define VI_K_FUN2 138 // Function Key F2 +#define VI_K_FUN3 139 // Function Key F3 +#define VI_K_FUN4 140 // Function Key F4 +#define VI_K_FUN5 141 // Function Key F5 +#define VI_K_FUN6 142 // Function Key F6 +#define VI_K_FUN7 143 // Function Key F7 +#define VI_K_FUN8 144 // Function Key F8 +#define VI_K_FUN9 145 // Function Key F9 +#define VI_K_FUN10 146 // Function Key F10 +#define VI_K_FUN11 147 // Function Key F11 +#define VI_K_FUN12 148 // Function Key F12 + +/* vt102 typical ESC sequence */ +/* terminal standout start/normal ESC sequence */ +static const char SOs[] = "\033[7m"; +static const char SOn[] = "\033[0m"; +/* terminal bell sequence */ +static const char bell[] = "\007"; +/* Clear-end-of-line and Clear-end-of-screen ESC sequence */ +static const char Ceol[] = "\033[0K"; +static const char Ceos [] = "\033[0J"; +/* Cursor motion arbitrary destination ESC sequence */ +static const char CMrc[] = "\033[%d;%dH"; +/* Cursor motion up and down ESC sequence */ +static const char CMup[] = "\033[A"; +static const char CMdown[] = "\n"; + + +enum { + YANKONLY = FALSE, + YANKDEL = TRUE, + FORWARD = 1, // code depends on "1" for array index + BACK = -1, // code depends on "-1" for array index + LIMITED = 0, // how much of text[] in char_search + FULL = 1, // how much of text[] in char_search + + S_BEFORE_WS = 1, // used in skip_thing() for moving "dot" + S_TO_WS = 2, // used in skip_thing() for moving "dot" + S_OVER_WS = 3, // used in skip_thing() for moving "dot" + S_END_PUNCT = 4, // used in skip_thing() for moving "dot" + S_END_ALNUM = 5 // used in skip_thing() for moving "dot" +}; + +typedef unsigned char Byte; + +static int vi_setops; +#define VI_AUTOINDENT 1 +#define VI_SHOWMATCH 2 +#define VI_IGNORECASE 4 +#define VI_ERR_METHOD 8 +#define autoindent (vi_setops & VI_AUTOINDENT) +#define showmatch (vi_setops & VI_SHOWMATCH ) +#define ignorecase (vi_setops & VI_IGNORECASE) +/* indicate error with beep or flash */ +#define err_method (vi_setops & VI_ERR_METHOD) + + +static int editing; // >0 while we are editing a file +static int cmd_mode; // 0=command 1=insert 2=replace +static int file_modified; // buffer contents changed +static int last_file_modified = -1; +static int fn_start; // index of first cmd line file name +static int save_argc; // how many file names on cmd line +static int cmdcnt; // repetition count +static fd_set rfds; // use select() for small sleeps +static struct timeval tv; // use select() for small sleeps +static int rows, columns; // the terminal screen is this size +static int crow, ccol, offset; // cursor is on Crow x Ccol with Horz Ofset +static Byte *status_buffer; // mesages to the user +#define STATUS_BUFFER_LEN 200 +static int have_status_msg; // is default edit status needed? +static int last_status_cksum; // hash of current status line +static Byte *cfn; // previous, current, and next file name +static Byte *text, *end; // pointers to the user data in memory +static Byte *screen; // pointer to the virtual screen buffer +static int screensize; // and its size +static Byte *screenbegin; // index into text[], of top line on the screen +static Byte *dot; // where all the action takes place +static int tabstop; +static struct termios term_orig, term_vi; // remember what the cooked mode was +static Byte erase_char; // the users erase character +static Byte last_input_char; // last char read from user +static Byte last_forward_char; // last char searched for with 'f' + +#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR +static int last_row; // where the cursor was last moved to +#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ +#ifdef CONFIG_FEATURE_VI_USE_SIGNALS +static jmp_buf restart; // catch_sig() +#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */ +#if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME) +static int my_pid; +#endif +#ifdef CONFIG_FEATURE_VI_DOT_CMD +static int adding2q; // are we currently adding user input to q +static Byte *last_modifying_cmd; // last modifying cmd for "." +static Byte *ioq, *ioq_start; // pointer to string for get_one_char to "read" +#endif /* CONFIG_FEATURE_VI_DOT_CMD */ +#if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK) +static Byte *modifying_cmds; // cmds that modify text[] +#endif /* CONFIG_FEATURE_VI_DOT_CMD || CONFIG_FEATURE_VI_YANKMARK */ +#ifdef CONFIG_FEATURE_VI_READONLY +static int vi_readonly, readonly; +#endif /* CONFIG_FEATURE_VI_READONLY */ +#ifdef CONFIG_FEATURE_VI_YANKMARK +static Byte *reg[28]; // named register a-z, "D", and "U" 0-25,26,27 +static int YDreg, Ureg; // default delete register and orig line for "U" +static Byte *mark[28]; // user marks points somewhere in text[]- a-z and previous context '' +static Byte *context_start, *context_end; +#endif /* CONFIG_FEATURE_VI_YANKMARK */ +#ifdef CONFIG_FEATURE_VI_SEARCH +static Byte *last_search_pattern; // last pattern from a '/' or '?' search +#endif /* CONFIG_FEATURE_VI_SEARCH */ + + +static void edit_file(Byte *); // edit one file +static void do_cmd(Byte); // execute a command +static void sync_cursor(Byte *, int *, int *); // synchronize the screen cursor to dot +static Byte *begin_line(Byte *); // return pointer to cur line B-o-l +static Byte *end_line(Byte *); // return pointer to cur line E-o-l +static Byte *prev_line(Byte *); // return pointer to prev line B-o-l +static Byte *next_line(Byte *); // return pointer to next line B-o-l +static Byte *end_screen(void); // get pointer to last char on screen +static int count_lines(Byte *, Byte *); // count line from start to stop +static Byte *find_line(int); // find begining of line #li +static Byte *move_to_col(Byte *, int); // move "p" to column l +static int isblnk(Byte); // is the char a blank or tab +static void dot_left(void); // move dot left- dont leave line +static void dot_right(void); // move dot right- dont leave line +static void dot_begin(void); // move dot to B-o-l +static void dot_end(void); // move dot to E-o-l +static void dot_next(void); // move dot to next line B-o-l +static void dot_prev(void); // move dot to prev line B-o-l +static void dot_scroll(int, int); // move the screen up or down +static void dot_skip_over_ws(void); // move dot pat WS +static void dot_delete(void); // delete the char at 'dot' +static Byte *bound_dot(Byte *); // make sure text[0] <= P < "end" +static Byte *new_screen(int, int); // malloc virtual screen memory +static Byte *new_text(int); // malloc memory for text[] buffer +static Byte *char_insert(Byte *, Byte); // insert the char c at 'p' +static Byte *stupid_insert(Byte *, Byte); // stupidly insert the char c at 'p' +static Byte find_range(Byte **, Byte **, Byte); // return pointers for an object +static int st_test(Byte *, int, int, Byte *); // helper for skip_thing() +static Byte *skip_thing(Byte *, int, int, int); // skip some object +static Byte *find_pair(Byte *, Byte); // find matching pair () [] {} +static Byte *text_hole_delete(Byte *, Byte *); // at "p", delete a 'size' byte hole +static Byte *text_hole_make(Byte *, int); // at "p", make a 'size' byte hole +static Byte *yank_delete(Byte *, Byte *, int, int); // yank text[] into register then delete +static void show_help(void); // display some help info +static void rawmode(void); // set "raw" mode on tty +static void cookmode(void); // return to "cooked" mode on tty +static int mysleep(int); // sleep for 'h' 1/100 seconds +static Byte readit(void); // read (maybe cursor) key from stdin +static Byte get_one_char(void); // read 1 char from stdin +static int file_size(const Byte *); // what is the byte size of "fn" +static int file_insert(Byte *, Byte *, int); +static int file_write(Byte *, Byte *, Byte *); +static void place_cursor(int, int, int); +static void screen_erase(void); +static void clear_to_eol(void); +static void clear_to_eos(void); +static void standout_start(void); // send "start reverse video" sequence +static void standout_end(void); // send "end reverse video" sequence +static void flash(int); // flash the terminal screen +static void show_status_line(void); // put a message on the bottom line +static void psb(const char *, ...); // Print Status Buf +static void psbs(const char *, ...); // Print Status Buf in standout mode +static void ni(Byte *); // display messages +static int format_edit_status(void); // format file status on status line +static void redraw(int); // force a full screen refresh +static void format_line(Byte*, Byte*, int); +static void refresh(int); // update the terminal from screen[] + +static void Indicate_Error(void); // use flash or beep to indicate error +#define indicate_error(c) Indicate_Error() +static void Hit_Return(void); + +#ifdef CONFIG_FEATURE_VI_SEARCH +static Byte *char_search(Byte *, Byte *, int, int); // search for pattern starting at p +static int mycmp(Byte *, Byte *, int); // string cmp based in "ignorecase" +#endif /* CONFIG_FEATURE_VI_SEARCH */ +#ifdef CONFIG_FEATURE_VI_COLON +static Byte *get_one_address(Byte *, int *); // get colon addr, if present +static Byte *get_address(Byte *, int *, int *); // get two colon addrs, if present +static void colon(Byte *); // execute the "colon" mode cmds +#endif /* CONFIG_FEATURE_VI_COLON */ +#ifdef CONFIG_FEATURE_VI_USE_SIGNALS +static void winch_sig(int); // catch window size changes +static void suspend_sig(int); // catch ctrl-Z +static void catch_sig(int); // catch ctrl-C and alarm time-outs +#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */ +#ifdef CONFIG_FEATURE_VI_DOT_CMD +static void start_new_cmd_q(Byte); // new queue for command +static void end_cmd_q(void); // stop saving input chars +#else /* CONFIG_FEATURE_VI_DOT_CMD */ +#define end_cmd_q() +#endif /* CONFIG_FEATURE_VI_DOT_CMD */ +#ifdef CONFIG_FEATURE_VI_SETOPTS +static void showmatching(Byte *); // show the matching pair () [] {} +#endif /* CONFIG_FEATURE_VI_SETOPTS */ +#if defined(CONFIG_FEATURE_VI_YANKMARK) || (defined(CONFIG_FEATURE_VI_COLON) && defined(CONFIG_FEATURE_VI_SEARCH)) || defined(CONFIG_FEATURE_VI_CRASHME) +static Byte *string_insert(Byte *, Byte *); // insert the string at 'p' +#endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_SEARCH || CONFIG_FEATURE_VI_CRASHME */ +#ifdef CONFIG_FEATURE_VI_YANKMARK +static Byte *text_yank(Byte *, Byte *, int); // save copy of "p" into a register +static Byte what_reg(void); // what is letter of current YDreg +static void check_context(Byte); // remember context for '' command +#endif /* CONFIG_FEATURE_VI_YANKMARK */ +#ifdef CONFIG_FEATURE_VI_CRASHME +static void crash_dummy(); +static void crash_test(); +static int crashme = 0; +#endif /* CONFIG_FEATURE_VI_CRASHME */ + + +static void write1(const char *out) +{ + fputs(out, stdout); +} + +int vi_main(int argc, char **argv) +{ + int c; + RESERVE_CONFIG_BUFFER(STATUS_BUFFER, STATUS_BUFFER_LEN); + +#ifdef CONFIG_FEATURE_VI_YANKMARK + int i; +#endif /* CONFIG_FEATURE_VI_YANKMARK */ +#if defined(CONFIG_FEATURE_VI_USE_SIGNALS) || defined(CONFIG_FEATURE_VI_CRASHME) + my_pid = getpid(); +#endif +#ifdef CONFIG_FEATURE_VI_CRASHME + (void) srand((long) my_pid); +#endif /* CONFIG_FEATURE_VI_CRASHME */ + + status_buffer = (Byte *)STATUS_BUFFER; + last_status_cksum = 0; + +#ifdef CONFIG_FEATURE_VI_READONLY + vi_readonly = readonly = FALSE; + if (strncmp(argv[0], "view", 4) == 0) { + readonly = TRUE; + vi_readonly = TRUE; + } +#endif /* CONFIG_FEATURE_VI_READONLY */ + vi_setops = VI_AUTOINDENT | VI_SHOWMATCH | VI_IGNORECASE | VI_ERR_METHOD; +#ifdef CONFIG_FEATURE_VI_YANKMARK + for (i = 0; i < 28; i++) { + reg[i] = 0; + } // init the yank regs +#endif /* CONFIG_FEATURE_VI_YANKMARK */ +#if defined(CONFIG_FEATURE_VI_DOT_CMD) || defined(CONFIG_FEATURE_VI_YANKMARK) + modifying_cmds = (Byte *) "aAcCdDiIJoOpPrRsxX<>~"; // cmds modifying text[] +#endif /* CONFIG_FEATURE_VI_DOT_CMD */ + + // 1- process $HOME/.exrc file + // 2- process EXINIT variable from environment + // 3- process command line args + while ((c = getopt(argc, argv, "hCR")) != -1) { + switch (c) { +#ifdef CONFIG_FEATURE_VI_CRASHME + case 'C': + crashme = 1; + break; +#endif /* CONFIG_FEATURE_VI_CRASHME */ +#ifdef CONFIG_FEATURE_VI_READONLY + case 'R': // Read-only flag + readonly = TRUE; + vi_readonly = TRUE; + break; +#endif /* CONFIG_FEATURE_VI_READONLY */ + //case 'r': // recover flag- ignore- we don't use tmp file + //case 'x': // encryption flag- ignore + //case 'c': // execute command first + //case 'h': // help -- just use default + default: + show_help(); + return 1; + } + } + + // The argv array can be used by the ":next" and ":rewind" commands + // save optind. + fn_start = optind; // remember first file name for :next and :rew + save_argc = argc; + + //----- This is the main file handling loop -------------- + if (optind >= argc) { + editing = 1; // 0= exit, 1= one file, 2= multiple files + edit_file(0); + } else { + for (; optind < argc; optind++) { + editing = 1; // 0=exit, 1=one file, 2+ =many files + free(cfn); + cfn = (Byte *) xstrdup(argv[optind]); + edit_file(cfn); + } + } + //----------------------------------------------------------- + + return 0; +} + +static void edit_file(Byte * fn) +{ + Byte c; + int cnt, size, ch; + +#ifdef CONFIG_FEATURE_VI_USE_SIGNALS + int sig; +#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */ +#ifdef CONFIG_FEATURE_VI_YANKMARK + static Byte *cur_line; +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + + rawmode(); + rows = 24; + columns = 80; + ch= -1; + if (ENABLE_FEATURE_VI_WIN_RESIZE) + get_terminal_width_height(0, &columns, &rows); + new_screen(rows, columns); // get memory for virtual screen + + cnt = file_size(fn); // file size + size = 2 * cnt; // 200% of file size + new_text(size); // get a text[] buffer + screenbegin = dot = end = text; + if (fn != 0) { + ch= file_insert(fn, text, cnt); + } + if (ch < 1) { + (void) char_insert(text, '\n'); // start empty buf with dummy line + } + file_modified = 0; + last_file_modified = -1; +#ifdef CONFIG_FEATURE_VI_YANKMARK + YDreg = 26; // default Yank/Delete reg + Ureg = 27; // hold orig line for "U" cmd + for (cnt = 0; cnt < 28; cnt++) { + mark[cnt] = 0; + } // init the marks + mark[26] = mark[27] = text; // init "previous context" +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + + last_forward_char = last_input_char = '\0'; + crow = 0; + ccol = 0; + +#ifdef CONFIG_FEATURE_VI_USE_SIGNALS + catch_sig(0); + signal(SIGWINCH, winch_sig); + signal(SIGTSTP, suspend_sig); + sig = setjmp(restart); + if (sig != 0) { + screenbegin = dot = text; + } +#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */ + + editing = 1; + cmd_mode = 0; // 0=command 1=insert 2='R'eplace + cmdcnt = 0; + tabstop = 8; + offset = 0; // no horizontal offset + c = '\0'; +#ifdef CONFIG_FEATURE_VI_DOT_CMD + free(last_modifying_cmd); + free(ioq_start); + ioq = ioq_start = last_modifying_cmd = 0; + adding2q = 0; +#endif /* CONFIG_FEATURE_VI_DOT_CMD */ + redraw(FALSE); // dont force every col re-draw + show_status_line(); + + //------This is the main Vi cmd handling loop ----------------------- + while (editing > 0) { +#ifdef CONFIG_FEATURE_VI_CRASHME + if (crashme > 0) { + if ((end - text) > 1) { + crash_dummy(); // generate a random command + } else { + crashme = 0; + dot = + string_insert(text, (Byte *) "\n\n##### Ran out of text to work on. #####\n\n"); // insert the string + refresh(FALSE); + } + } +#endif /* CONFIG_FEATURE_VI_CRASHME */ + last_input_char = c = get_one_char(); // get a cmd from user +#ifdef CONFIG_FEATURE_VI_YANKMARK + // save a copy of the current line- for the 'U" command + if (begin_line(dot) != cur_line) { + cur_line = begin_line(dot); + text_yank(begin_line(dot), end_line(dot), Ureg); + } +#endif /* CONFIG_FEATURE_VI_YANKMARK */ +#ifdef CONFIG_FEATURE_VI_DOT_CMD + // These are commands that change text[]. + // Remember the input for the "." command + if (!adding2q && ioq_start == 0 + && strchr((char *) modifying_cmds, c) != NULL) { + start_new_cmd_q(c); + } +#endif /* CONFIG_FEATURE_VI_DOT_CMD */ + do_cmd(c); // execute the user command + // + // poll to see if there is input already waiting. if we are + // not able to display output fast enough to keep up, skip + // the display update until we catch up with input. + if (mysleep(0) == 0) { + // no input pending- so update output + refresh(FALSE); + show_status_line(); + } +#ifdef CONFIG_FEATURE_VI_CRASHME + if (crashme > 0) + crash_test(); // test editor variables +#endif /* CONFIG_FEATURE_VI_CRASHME */ + } + //------------------------------------------------------------------- + + place_cursor(rows, 0, FALSE); // go to bottom of screen + clear_to_eol(); // Erase to end of line + cookmode(); +} + +//----- The Colon commands ------------------------------------- +#ifdef CONFIG_FEATURE_VI_COLON +static Byte *get_one_address(Byte * p, int *addr) // get colon addr, if present +{ + int st; + Byte *q; + +#ifdef CONFIG_FEATURE_VI_YANKMARK + Byte c; +#endif /* CONFIG_FEATURE_VI_YANKMARK */ +#ifdef CONFIG_FEATURE_VI_SEARCH + Byte *pat, buf[BUFSIZ]; +#endif /* CONFIG_FEATURE_VI_SEARCH */ + + *addr = -1; // assume no addr + if (*p == '.') { // the current line + p++; + q = begin_line(dot); + *addr = count_lines(text, q); +#ifdef CONFIG_FEATURE_VI_YANKMARK + } else if (*p == '\'') { // is this a mark addr + p++; + c = tolower(*p); + p++; + if (c >= 'a' && c <= 'z') { + // we have a mark + c = c - 'a'; + q = mark[(int) c]; + if (q != NULL) { // is mark valid + *addr = count_lines(text, q); // count lines + } + } +#endif /* CONFIG_FEATURE_VI_YANKMARK */ +#ifdef CONFIG_FEATURE_VI_SEARCH + } else if (*p == '/') { // a search pattern + q = buf; + for (p++; *p; p++) { + if (*p == '/') + break; + *q++ = *p; + *q = '\0'; + } + pat = (Byte *) xstrdup((char *) buf); // save copy of pattern + if (*p == '/') + p++; + q = char_search(dot, pat, FORWARD, FULL); + if (q != NULL) { + *addr = count_lines(text, q); + } + free(pat); +#endif /* CONFIG_FEATURE_VI_SEARCH */ + } else if (*p == '$') { // the last line in file + p++; + q = begin_line(end - 1); + *addr = count_lines(text, q); + } else if (isdigit(*p)) { // specific line number + sscanf((char *) p, "%d%n", addr, &st); + p += st; + } else { // I don't reconise this + // unrecognised address- assume -1 + *addr = -1; + } + return p; +} + +static Byte *get_address(Byte *p, int *b, int *e) // get two colon addrs, if present +{ + //----- get the address' i.e., 1,3 'a,'b ----- + // get FIRST addr, if present + while (isblnk(*p)) + p++; // skip over leading spaces + if (*p == '%') { // alias for 1,$ + p++; + *b = 1; + *e = count_lines(text, end-1); + goto ga0; + } + p = get_one_address(p, b); + while (isblnk(*p)) + p++; + if (*p == ',') { // is there a address separator + p++; + while (isblnk(*p)) + p++; + // get SECOND addr, if present + p = get_one_address(p, e); + } +ga0: + while (isblnk(*p)) + p++; // skip over trailing spaces + return p; +} + +#ifdef CONFIG_FEATURE_VI_SETOPTS +static void setops(const Byte *args, const char *opname, int flg_no, + const char *short_opname, int opt) +{ + const char *a = (char *) args + flg_no; + int l = strlen(opname) - 1; /* opname have + ' ' */ + + if (strncasecmp(a, opname, l) == 0 || + strncasecmp(a, short_opname, 2) == 0) { + if(flg_no) + vi_setops &= ~opt; + else + vi_setops |= opt; + } +} +#endif + +static void colon(Byte * buf) +{ + Byte c, *orig_buf, *buf1, *q, *r; + Byte *fn, cmd[BUFSIZ], args[BUFSIZ]; + int i, l, li, ch, b, e; + int useforce = FALSE, forced = FALSE; + struct stat st_buf; + + // :3154 // if (-e line 3154) goto it else stay put + // :4,33w! foo // write a portion of buffer to file "foo" + // :w // write all of buffer to current file + // :q // quit + // :q! // quit- dont care about modified file + // :'a,'z!sort -u // filter block through sort + // :'f // goto mark "f" + // :'fl // list literal the mark "f" line + // :.r bar // read file "bar" into buffer before dot + // :/123/,/abc/d // delete lines from "123" line to "abc" line + // :/xyz/ // goto the "xyz" line + // :s/find/replace/ // substitute pattern "find" with "replace" + // :! // run then return + // + + if (strlen((char *) buf) <= 0) + goto vc1; + if (*buf == ':') + buf++; // move past the ':' + + li = ch = i = 0; + b = e = -1; + q = text; // assume 1,$ for the range + r = end - 1; + li = count_lines(text, end - 1); + fn = cfn; // default to current file + memset(cmd, '\0', BUFSIZ); // clear cmd[] + memset(args, '\0', BUFSIZ); // clear args[] + + // look for optional address(es) :. :1 :1,9 :'q,'a :% + buf = get_address(buf, &b, &e); + + // remember orig command line + orig_buf = buf; + + // get the COMMAND into cmd[] + buf1 = cmd; + while (*buf != '\0') { + if (isspace(*buf)) + break; + *buf1++ = *buf++; + } + // get any ARGuments + while (isblnk(*buf)) + buf++; + strcpy((char *) args, (char *) buf); + buf1 = (Byte*)last_char_is((char *)cmd, '!'); + if (buf1) { + useforce = TRUE; + *buf1 = '\0'; // get rid of ! + } + if (b >= 0) { + // if there is only one addr, then the addr + // is the line number of the single line the + // user wants. So, reset the end + // pointer to point at end of the "b" line + q = find_line(b); // what line is #b + r = end_line(q); + li = 1; + } + if (e >= 0) { + // we were given two addrs. change the + // end pointer to the addr given by user. + r = find_line(e); // what line is #e + r = end_line(r); + li = e - b + 1; + } + // ------------ now look for the command ------------ + i = strlen((char *) cmd); + if (i == 0) { // :123CR goto line #123 + if (b >= 0) { + dot = find_line(b); // what line is #b + dot_skip_over_ws(); + } + } else if (strncmp((char *) cmd, "!", 1) == 0) { // run a cmd + // :!ls run the + (void) alarm(0); // wait for input- no alarms + place_cursor(rows - 1, 0, FALSE); // go to Status line + clear_to_eol(); // clear the line + cookmode(); + system((char*)(orig_buf+1)); // run the cmd + rawmode(); + Hit_Return(); // let user see results + (void) alarm(3); // done waiting for input + } else if (strncmp((char *) cmd, "=", i) == 0) { // where is the address + if (b < 0) { // no addr given- use defaults + b = e = count_lines(text, dot); + } + psb("%d", b); + } else if (strncasecmp((char *) cmd, "delete", i) == 0) { // delete lines + if (b < 0) { // no addr given- use defaults + q = begin_line(dot); // assume .,. for the range + r = end_line(dot); + } + dot = yank_delete(q, r, 1, YANKDEL); // save, then delete lines + dot_skip_over_ws(); + } else if (strncasecmp((char *) cmd, "edit", i) == 0) { // Edit a file + int sr; + sr= 0; + // don't edit, if the current file has been modified + if (file_modified && ! useforce) { + psbs("No write since last change (:edit! overrides)"); + goto vc1; + } + if (strlen((char*)args) > 0) { + // the user supplied a file name + fn= args; + } else if (cfn != 0 && strlen((char*)cfn) > 0) { + // no user supplied name- use the current filename + fn= cfn; + goto vc5; + } else { + // no user file name, no current name- punt + psbs("No current filename"); + goto vc1; + } + + // see if file exists- if not, its just a new file request + if ((sr=stat((char*)fn, &st_buf)) < 0) { + // This is just a request for a new file creation. + // The file_insert below will fail but we get + // an empty buffer with a file name. Then the "write" + // command can do the create. + } else { + if ((st_buf.st_mode & (S_IFREG)) == 0) { + // This is not a regular file + psbs("\"%s\" is not a regular file", fn); + goto vc1; + } + if ((st_buf.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == 0) { + // dont have any read permissions + psbs("\"%s\" is not readable", fn); + goto vc1; + } + } + + // There is a read-able regular file + // make this the current file + q = (Byte *) xstrdup((char *) fn); // save the cfn + free(cfn); // free the old name + cfn = q; // remember new cfn + + vc5: + // delete all the contents of text[] + new_text(2 * file_size(fn)); + screenbegin = dot = end = text; + + // insert new file + ch = file_insert(fn, text, file_size(fn)); + + if (ch < 1) { + // start empty buf with dummy line + (void) char_insert(text, '\n'); + ch= 1; + } + file_modified = 0; + last_file_modified = -1; +#ifdef CONFIG_FEATURE_VI_YANKMARK + if (Ureg >= 0 && Ureg < 28 && reg[Ureg] != 0) { + free(reg[Ureg]); // free orig line reg- for 'U' + reg[Ureg]= 0; + } + if (YDreg >= 0 && YDreg < 28 && reg[YDreg] != 0) { + free(reg[YDreg]); // free default yank/delete register + reg[YDreg]= 0; + } + for (li = 0; li < 28; li++) { + mark[li] = 0; + } // init the marks +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + // how many lines in text[]? + li = count_lines(text, end - 1); + psb("\"%s\"%s" +#ifdef CONFIG_FEATURE_VI_READONLY + "%s" +#endif /* CONFIG_FEATURE_VI_READONLY */ + " %dL, %dC", cfn, + (sr < 0 ? " [New file]" : ""), +#ifdef CONFIG_FEATURE_VI_READONLY + ((vi_readonly || readonly) ? " [Read only]" : ""), +#endif /* CONFIG_FEATURE_VI_READONLY */ + li, ch); + } else if (strncasecmp((char *) cmd, "file", i) == 0) { // what File is this + if (b != -1 || e != -1) { + ni((Byte *) "No address allowed on this command"); + goto vc1; + } + if (strlen((char *) args) > 0) { + // user wants a new filename + free(cfn); + cfn = (Byte *) xstrdup((char *) args); + } else { + // user wants file status info + last_status_cksum = 0; // force status update + } + } else if (strncasecmp((char *) cmd, "features", i) == 0) { // what features are available + // print out values of all features + place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen + clear_to_eol(); // clear the line + cookmode(); + show_help(); + rawmode(); + Hit_Return(); + } else if (strncasecmp((char *) cmd, "list", i) == 0) { // literal print line + if (b < 0) { // no addr given- use defaults + q = begin_line(dot); // assume .,. for the range + r = end_line(dot); + } + place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen + clear_to_eol(); // clear the line + puts("\r"); + for (; q <= r; q++) { + int c_is_no_print; + + c = *q; + c_is_no_print = c > 127 && !Isprint(c); + if (c_is_no_print) { + c = '.'; + standout_start(); + } + if (c == '\n') { + write1("$\r"); + } else if (c < ' ' || c == 127) { + putchar('^'); + if(c == 127) + c = '?'; + else + c += '@'; + } + putchar(c); + if (c_is_no_print) + standout_end(); + } +#ifdef CONFIG_FEATURE_VI_SET + vc2: +#endif /* CONFIG_FEATURE_VI_SET */ + Hit_Return(); + } else if ((strncasecmp((char *) cmd, "quit", i) == 0) || // Quit + (strncasecmp((char *) cmd, "next", i) == 0)) { // edit next file + if (useforce) { + // force end of argv list + if (*cmd == 'q') { + optind = save_argc; + } + editing = 0; + goto vc1; + } + // don't exit if the file been modified + if (file_modified) { + psbs("No write since last change (:%s! overrides)", + (*cmd == 'q' ? "quit" : "next")); + goto vc1; + } + // are there other file to edit + if (*cmd == 'q' && optind < save_argc - 1) { + psbs("%d more file to edit", (save_argc - optind - 1)); + goto vc1; + } + if (*cmd == 'n' && optind >= save_argc - 1) { + psbs("No more files to edit"); + goto vc1; + } + editing = 0; + } else if (strncasecmp((char *) cmd, "read", i) == 0) { // read file into text[] + fn = args; + if (strlen((char *) fn) <= 0) { + psbs("No filename given"); + goto vc1; + } + if (b < 0) { // no addr given- use defaults + q = begin_line(dot); // assume "dot" + } + // read after current line- unless user said ":0r foo" + if (b != 0) + q = next_line(q); +#ifdef CONFIG_FEATURE_VI_READONLY + l= readonly; // remember current files' status +#endif + ch = file_insert(fn, q, file_size(fn)); +#ifdef CONFIG_FEATURE_VI_READONLY + readonly= l; +#endif + if (ch < 0) + goto vc1; // nothing was inserted + // how many lines in text[]? + li = count_lines(q, q + ch - 1); + psb("\"%s\"" +#ifdef CONFIG_FEATURE_VI_READONLY + "%s" +#endif /* CONFIG_FEATURE_VI_READONLY */ + " %dL, %dC", fn, +#ifdef CONFIG_FEATURE_VI_READONLY + ((vi_readonly || readonly) ? " [Read only]" : ""), +#endif /* CONFIG_FEATURE_VI_READONLY */ + li, ch); + if (ch > 0) { + // if the insert is before "dot" then we need to update + if (q <= dot) + dot += ch; + file_modified++; + } + } else if (strncasecmp((char *) cmd, "rewind", i) == 0) { // rewind cmd line args + if (file_modified && ! useforce) { + psbs("No write since last change (:rewind! overrides)"); + } else { + // reset the filenames to edit + optind = fn_start - 1; + editing = 0; + } +#ifdef CONFIG_FEATURE_VI_SET + } else if (strncasecmp((char *) cmd, "set", i) == 0) { // set or clear features + i = 0; // offset into args + if (strlen((char *) args) == 0) { + // print out values of all options + place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen + clear_to_eol(); // clear the line + printf("----------------------------------------\r\n"); +#ifdef CONFIG_FEATURE_VI_SETOPTS + if (!autoindent) + printf("no"); + printf("autoindent "); + if (!err_method) + printf("no"); + printf("flash "); + if (!ignorecase) + printf("no"); + printf("ignorecase "); + if (!showmatch) + printf("no"); + printf("showmatch "); + printf("tabstop=%d ", tabstop); +#endif /* CONFIG_FEATURE_VI_SETOPTS */ + printf("\r\n"); + goto vc2; + } + if (strncasecmp((char *) args, "no", 2) == 0) + i = 2; // ":set noautoindent" +#ifdef CONFIG_FEATURE_VI_SETOPTS + setops(args, "autoindent ", i, "ai", VI_AUTOINDENT); + setops(args, "flash ", i, "fl", VI_ERR_METHOD); + setops(args, "ignorecase ", i, "ic", VI_IGNORECASE); + setops(args, "showmatch ", i, "ic", VI_SHOWMATCH); + if (strncasecmp((char *) args + i, "tabstop=%d ", 7) == 0) { + sscanf(strchr((char *) args + i, '='), "=%d", &ch); + if (ch > 0 && ch < columns - 1) + tabstop = ch; + } +#endif /* CONFIG_FEATURE_VI_SETOPTS */ +#endif /* CONFIG_FEATURE_VI_SET */ +#ifdef CONFIG_FEATURE_VI_SEARCH + } else if (strncasecmp((char *) cmd, "s", 1) == 0) { // substitute a pattern with a replacement pattern + Byte *ls, *F, *R; + int gflag; + + // F points to the "find" pattern + // R points to the "replace" pattern + // replace the cmd line delimiters "/" with NULLs + gflag = 0; // global replace flag + c = orig_buf[1]; // what is the delimiter + F = orig_buf + 2; // start of "find" + R = (Byte *) strchr((char *) F, c); // middle delimiter + if (!R) goto colon_s_fail; + *R++ = '\0'; // terminate "find" + buf1 = (Byte *) strchr((char *) R, c); + if (!buf1) goto colon_s_fail; + *buf1++ = '\0'; // terminate "replace" + if (*buf1 == 'g') { // :s/foo/bar/g + buf1++; + gflag++; // turn on gflag + } + q = begin_line(q); + if (b < 0) { // maybe :s/foo/bar/ + q = begin_line(dot); // start with cur line + b = count_lines(text, q); // cur line number + } + if (e < 0) + e = b; // maybe :.s/foo/bar/ + for (i = b; i <= e; i++) { // so, :20,23 s \0 find \0 replace \0 + ls = q; // orig line start + vc4: + buf1 = char_search(q, F, FORWARD, LIMITED); // search cur line only for "find" + if (buf1 != NULL) { + // we found the "find" pattern- delete it + (void) text_hole_delete(buf1, buf1 + strlen((char *) F) - 1); + // inset the "replace" patern + (void) string_insert(buf1, R); // insert the string + // check for "global" :s/foo/bar/g + if (gflag == 1) { + if ((buf1 + strlen((char *) R)) < end_line(ls)) { + q = buf1 + strlen((char *) R); + goto vc4; // don't let q move past cur line + } + } + } + q = next_line(ls); + } +#endif /* CONFIG_FEATURE_VI_SEARCH */ + } else if (strncasecmp((char *) cmd, "version", i) == 0) { // show software version + psb("%s", BB_VER " " BB_BT); + } else if (strncasecmp((char *) cmd, "write", i) == 0 // write text to file + || strncasecmp((char *) cmd, "wq", i) == 0 + || strncasecmp((char *) cmd, "wn", i) == 0 + || strncasecmp((char *) cmd, "x", i) == 0) { + // is there a file name to write to? + if (strlen((char *) args) > 0) { + fn = args; + } +#ifdef CONFIG_FEATURE_VI_READONLY + if ((vi_readonly || readonly) && ! useforce) { + psbs("\"%s\" File is read only", fn); + goto vc3; + } +#endif /* CONFIG_FEATURE_VI_READONLY */ + // how many lines in text[]? + li = count_lines(q, r); + ch = r - q + 1; + // see if file exists- if not, its just a new file request + if (useforce) { + // if "fn" is not write-able, chmod u+w + // sprintf(syscmd, "chmod u+w %s", fn); + // system(syscmd); + forced = TRUE; + } + l = file_write(fn, q, r); + if (useforce && forced) { + // chmod u-w + // sprintf(syscmd, "chmod u-w %s", fn); + // system(syscmd); + forced = FALSE; + } + if (l < 0) { + if (l == -1) + psbs("Write error: %s", strerror(errno)); + } else { + psb("\"%s\" %dL, %dC", fn, li, l); + if (q == text && r == end - 1 && l == ch) { + file_modified = 0; + last_file_modified = -1; + } + if ((cmd[0] == 'x' || cmd[1] == 'q' || cmd[1] == 'n' || + cmd[0] == 'X' || cmd[1] == 'Q' || cmd[1] == 'N') + && l == ch) { + editing = 0; + } + } +#ifdef CONFIG_FEATURE_VI_READONLY + vc3:; +#endif /* CONFIG_FEATURE_VI_READONLY */ +#ifdef CONFIG_FEATURE_VI_YANKMARK + } else if (strncasecmp((char *) cmd, "yank", i) == 0) { // yank lines + if (b < 0) { // no addr given- use defaults + q = begin_line(dot); // assume .,. for the range + r = end_line(dot); + } + text_yank(q, r, YDreg); + li = count_lines(q, r); + psb("Yank %d lines (%d chars) into [%c]", + li, strlen((char *) reg[YDreg]), what_reg()); +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + } else { + // cmd unknown + ni((Byte *) cmd); + } + vc1: + dot = bound_dot(dot); // make sure "dot" is valid + return; +#ifdef CONFIG_FEATURE_VI_SEARCH +colon_s_fail: + psb(":s expression missing delimiters"); +#endif +} + +#endif /* CONFIG_FEATURE_VI_COLON */ + +static void Hit_Return(void) +{ + char c; + + standout_start(); // start reverse video + write1("[Hit return to continue]"); + standout_end(); // end reverse video + while ((c = get_one_char()) != '\n' && c != '\r') /*do nothing */ + ; + redraw(TRUE); // force redraw all +} + +//----- Synchronize the cursor to Dot -------------------------- +static void sync_cursor(Byte * d, int *row, int *col) +{ + Byte *beg_cur; // begin and end of "d" line + Byte *end_scr; // begin and end of screen + Byte *tp; + int cnt, ro, co; + + beg_cur = begin_line(d); // first char of cur line + + end_scr = end_screen(); // last char of screen + + if (beg_cur < screenbegin) { + // "d" is before top line on screen + // how many lines do we have to move + cnt = count_lines(beg_cur, screenbegin); + sc1: + screenbegin = beg_cur; + if (cnt > (rows - 1) / 2) { + // we moved too many lines. put "dot" in middle of screen + for (cnt = 0; cnt < (rows - 1) / 2; cnt++) { + screenbegin = prev_line(screenbegin); + } + } + } else if (beg_cur > end_scr) { + // "d" is after bottom line on screen + // how many lines do we have to move + cnt = count_lines(end_scr, beg_cur); + if (cnt > (rows - 1) / 2) + goto sc1; // too many lines + for (ro = 0; ro < cnt - 1; ro++) { + // move screen begin the same amount + screenbegin = next_line(screenbegin); + // now, move the end of screen + end_scr = next_line(end_scr); + end_scr = end_line(end_scr); + } + } + // "d" is on screen- find out which row + tp = screenbegin; + for (ro = 0; ro < rows - 1; ro++) { // drive "ro" to correct row + if (tp == beg_cur) + break; + tp = next_line(tp); + } + + // find out what col "d" is on + co = 0; + do { // drive "co" to correct column + if (*tp == '\n' || *tp == '\0') + break; + if (*tp == '\t') { + // 7 - (co % 8 ) + co += ((tabstop - 1) - (co % tabstop)); + } else if (*tp < ' ' || *tp == 127) { + co++; // display as ^X, use 2 columns + } + } while (tp++ < d && ++co); + + // "co" is the column where "dot" is. + // The screen has "columns" columns. + // The currently displayed columns are 0+offset -- columns+ofset + // |-------------------------------------------------------------| + // ^ ^ ^ + // offset | |------- columns ----------------| + // + // If "co" is already in this range then we do not have to adjust offset + // but, we do have to subtract the "offset" bias from "co". + // If "co" is outside this range then we have to change "offset". + // If the first char of a line is a tab the cursor will try to stay + // in column 7, but we have to set offset to 0. + + if (co < 0 + offset) { + offset = co; + } + if (co >= columns + offset) { + offset = co - columns + 1; + } + // if the first char of the line is a tab, and "dot" is sitting on it + // force offset to 0. + if (d == beg_cur && *d == '\t') { + offset = 0; + } + co -= offset; + + *row = ro; + *col = co; +} + +//----- Text Movement Routines --------------------------------- +static Byte *begin_line(Byte * p) // return pointer to first char cur line +{ + while (p > text && p[-1] != '\n') + p--; // go to cur line B-o-l + return p; +} + +static Byte *end_line(Byte * p) // return pointer to NL of cur line line +{ + while (p < end - 1 && *p != '\n') + p++; // go to cur line E-o-l + return p; +} + +static inline Byte *dollar_line(Byte * p) // return pointer to just before NL line +{ + while (p < end - 1 && *p != '\n') + p++; // go to cur line E-o-l + // Try to stay off of the Newline + if (*p == '\n' && (p - begin_line(p)) > 0) + p--; + return p; +} + +static Byte *prev_line(Byte * p) // return pointer first char prev line +{ + p = begin_line(p); // goto begining of cur line + if (p[-1] == '\n' && p > text) + p--; // step to prev line + p = begin_line(p); // goto begining of prev line + return p; +} + +static Byte *next_line(Byte * p) // return pointer first char next line +{ + p = end_line(p); + if (*p == '\n' && p < end - 1) + p++; // step to next line + return p; +} + +//----- Text Information Routines ------------------------------ +static Byte *end_screen(void) +{ + Byte *q; + int cnt; + + // find new bottom line + q = screenbegin; + for (cnt = 0; cnt < rows - 2; cnt++) + q = next_line(q); + q = end_line(q); + return q; +} + +static int count_lines(Byte * start, Byte * stop) // count line from start to stop +{ + Byte *q; + int cnt; + + if (stop < start) { // start and stop are backwards- reverse them + q = start; + start = stop; + stop = q; + } + cnt = 0; + stop = end_line(stop); // get to end of this line + for (q = start; q <= stop && q <= end - 1; q++) { + if (*q == '\n') + cnt++; + } + return cnt; +} + +static Byte *find_line(int li) // find begining of line #li +{ + Byte *q; + + for (q = text; li > 1; li--) { + q = next_line(q); + } + return q; +} + +//----- Dot Movement Routines ---------------------------------- +static void dot_left(void) +{ + if (dot > text && dot[-1] != '\n') + dot--; +} + +static void dot_right(void) +{ + if (dot < end - 1 && *dot != '\n') + dot++; +} + +static void dot_begin(void) +{ + dot = begin_line(dot); // return pointer to first char cur line +} + +static void dot_end(void) +{ + dot = end_line(dot); // return pointer to last char cur line +} + +static Byte *move_to_col(Byte * p, int l) +{ + int co; + + p = begin_line(p); + co = 0; + do { + if (*p == '\n' || *p == '\0') + break; + if (*p == '\t') { + // 7 - (co % 8 ) + co += ((tabstop - 1) - (co % tabstop)); + } else if (*p < ' ' || *p == 127) { + co++; // display as ^X, use 2 columns + } + } while (++co <= l && p++ < end); + return p; +} + +static void dot_next(void) +{ + dot = next_line(dot); +} + +static void dot_prev(void) +{ + dot = prev_line(dot); +} + +static void dot_scroll(int cnt, int dir) +{ + Byte *q; + + for (; cnt > 0; cnt--) { + if (dir < 0) { + // scroll Backwards + // ctrl-Y scroll up one line + screenbegin = prev_line(screenbegin); + } else { + // scroll Forwards + // ctrl-E scroll down one line + screenbegin = next_line(screenbegin); + } + } + // make sure "dot" stays on the screen so we dont scroll off + if (dot < screenbegin) + dot = screenbegin; + q = end_screen(); // find new bottom line + if (dot > q) + dot = begin_line(q); // is dot is below bottom line? + dot_skip_over_ws(); +} + +static void dot_skip_over_ws(void) +{ + // skip WS + while (isspace(*dot) && *dot != '\n' && dot < end - 1) + dot++; +} + +static void dot_delete(void) // delete the char at 'dot' +{ + (void) text_hole_delete(dot, dot); +} + +static Byte *bound_dot(Byte * p) // make sure text[0] <= P < "end" +{ + if (p >= end && end > text) { + p = end - 1; + indicate_error('1'); + } + if (p < text) { + p = text; + indicate_error('2'); + } + return p; +} + +//----- Helper Utility Routines -------------------------------- + +//---------------------------------------------------------------- +//----- Char Routines -------------------------------------------- +/* Chars that are part of a word- + * 0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz + * Chars that are Not part of a word (stoppers) + * !"#$%&'()*+,-./:;<=>?@[\]^`{|}~ + * Chars that are WhiteSpace + * TAB NEWLINE VT FF RETURN SPACE + * DO NOT COUNT NEWLINE AS WHITESPACE + */ + +static Byte *new_screen(int ro, int co) +{ + int li; + + free(screen); + screensize = ro * co + 8; + screen = (Byte *) xmalloc(screensize); + // initialize the new screen. assume this will be a empty file. + screen_erase(); + // non-existent text[] lines start with a tilde (~). + for (li = 1; li < ro - 1; li++) { + screen[(li * co) + 0] = '~'; + } + return screen; +} + +static Byte *new_text(int size) +{ + if (size < 10240) + size = 10240; // have a minimum size for new files + free(text); + text = (Byte *) xmalloc(size + 8); + memset(text, '\0', size); // clear new text[] + //text += 4; // leave some room for "oops" + return text; +} + +#ifdef CONFIG_FEATURE_VI_SEARCH +static int mycmp(Byte * s1, Byte * s2, int len) +{ + int i; + + i = strncmp((char *) s1, (char *) s2, len); +#ifdef CONFIG_FEATURE_VI_SETOPTS + if (ignorecase) { + i = strncasecmp((char *) s1, (char *) s2, len); + } +#endif /* CONFIG_FEATURE_VI_SETOPTS */ + return i; +} + +static Byte *char_search(Byte * p, Byte * pat, int dir, int range) // search for pattern starting at p +{ +#ifndef REGEX_SEARCH + Byte *start, *stop; + int len; + + len = strlen((char *) pat); + if (dir == FORWARD) { + stop = end - 1; // assume range is p - end-1 + if (range == LIMITED) + stop = next_line(p); // range is to next line + for (start = p; start < stop; start++) { + if (mycmp(start, pat, len) == 0) { + return start; + } + } + } else if (dir == BACK) { + stop = text; // assume range is text - p + if (range == LIMITED) + stop = prev_line(p); // range is to prev line + for (start = p - len; start >= stop; start--) { + if (mycmp(start, pat, len) == 0) { + return start; + } + } + } + // pattern not found + return NULL; +#else /*REGEX_SEARCH */ + char *q; + struct re_pattern_buffer preg; + int i; + int size, range; + + re_syntax_options = RE_SYNTAX_POSIX_EXTENDED; + preg.translate = 0; + preg.fastmap = 0; + preg.buffer = 0; + preg.allocated = 0; + + // assume a LIMITED forward search + q = next_line(p); + q = end_line(q); + q = end - 1; + if (dir == BACK) { + q = prev_line(p); + q = text; + } + // count the number of chars to search over, forward or backward + size = q - p; + if (size < 0) + size = p - q; + // RANGE could be negative if we are searching backwards + range = q - p; + + q = (char *) re_compile_pattern(pat, strlen((char *) pat), &preg); + if (q != 0) { + // The pattern was not compiled + psbs("bad search pattern: \"%s\": %s", pat, q); + i = 0; // return p if pattern not compiled + goto cs1; + } + + q = p; + if (range < 0) { + q = p - size; + if (q < text) + q = text; + } + // search for the compiled pattern, preg, in p[] + // range < 0- search backward + // range > 0- search forward + // 0 < start < size + // re_search() < 0 not found or error + // re_search() > 0 index of found pattern + // struct pattern char int int int struct reg + // re_search (*pattern_buffer, *string, size, start, range, *regs) + i = re_search(&preg, q, size, 0, range, 0); + if (i == -1) { + p = 0; + i = 0; // return NULL if pattern not found + } + cs1: + if (dir == FORWARD) { + p = p + i; + } else { + p = p - i; + } + return p; +#endif /*REGEX_SEARCH */ +} +#endif /* CONFIG_FEATURE_VI_SEARCH */ + +static Byte *char_insert(Byte * p, Byte c) // insert the char c at 'p' +{ + if (c == 22) { // Is this an ctrl-V? + p = stupid_insert(p, '^'); // use ^ to indicate literal next + p--; // backup onto ^ + refresh(FALSE); // show the ^ + c = get_one_char(); + *p = c; + p++; + file_modified++; // has the file been modified + } else if (c == 27) { // Is this an ESC? + cmd_mode = 0; + cmdcnt = 0; + end_cmd_q(); // stop adding to q + last_status_cksum = 0; // force status update + if ((p[-1] != '\n') && (dot>text)) { + p--; + } + } else if (c == erase_char || c == 8 || c == 127) { // Is this a BS + // 123456789 + if ((p[-1] != '\n') && (dot>text)) { + p--; + p = text_hole_delete(p, p); // shrink buffer 1 char + } + } else { + // insert a char into text[] + Byte *sp; // "save p" + + if (c == 13) + c = '\n'; // translate \r to \n + sp = p; // remember addr of insert + p = stupid_insert(p, c); // insert the char +#ifdef CONFIG_FEATURE_VI_SETOPTS + if (showmatch && strchr(")]}", *sp) != NULL) { + showmatching(sp); + } + if (autoindent && c == '\n') { // auto indent the new line + Byte *q; + + q = prev_line(p); // use prev line as templet + for (; isblnk(*q); q++) { + p = stupid_insert(p, *q); // insert the char + } + } +#endif /* CONFIG_FEATURE_VI_SETOPTS */ + } + return p; +} + +static Byte *stupid_insert(Byte * p, Byte c) // stupidly insert the char c at 'p' +{ + p = text_hole_make(p, 1); + if (p != 0) { + *p = c; + file_modified++; // has the file been modified + p++; + } + return p; +} + +static Byte find_range(Byte ** start, Byte ** stop, Byte c) +{ + Byte *save_dot, *p, *q; + int cnt; + + save_dot = dot; + p = q = dot; + + if (strchr("cdy><", c)) { + // these cmds operate on whole lines + p = q = begin_line(p); + for (cnt = 1; cnt < cmdcnt; cnt++) { + q = next_line(q); + } + q = end_line(q); + } else if (strchr("^%$0bBeEft", c)) { + // These cmds operate on char positions + do_cmd(c); // execute movement cmd + q = dot; + } else if (strchr("wW", c)) { + do_cmd(c); // execute movement cmd + // if we are at the next word's first char + // step back one char + // but check the possibilities when it is true + if (dot > text && ((isspace(dot[-1]) && !isspace(dot[0])) + || (ispunct(dot[-1]) && !ispunct(dot[0])) + || (isalnum(dot[-1]) && !isalnum(dot[0])))) + dot--; // move back off of next word + if (dot > text && *dot == '\n') + dot--; // stay off NL + q = dot; + } else if (strchr("H-k{", c)) { + // these operate on multi-lines backwards + q = end_line(dot); // find NL + do_cmd(c); // execute movement cmd + dot_begin(); + p = dot; + } else if (strchr("L+j}\r\n", c)) { + // these operate on multi-lines forwards + p = begin_line(dot); + do_cmd(c); // execute movement cmd + dot_end(); // find NL + q = dot; + } else { + c = 27; // error- return an ESC char + //break; + } + *start = p; + *stop = q; + if (q < p) { + *start = q; + *stop = p; + } + dot = save_dot; + return c; +} + +static int st_test(Byte * p, int type, int dir, Byte * tested) +{ + Byte c, c0, ci; + int test, inc; + + inc = dir; + c = c0 = p[0]; + ci = p[inc]; + test = 0; + + if (type == S_BEFORE_WS) { + c = ci; + test = ((!isspace(c)) || c == '\n'); + } + if (type == S_TO_WS) { + c = c0; + test = ((!isspace(c)) || c == '\n'); + } + if (type == S_OVER_WS) { + c = c0; + test = ((isspace(c))); + } + if (type == S_END_PUNCT) { + c = ci; + test = ((ispunct(c))); + } + if (type == S_END_ALNUM) { + c = ci; + test = ((isalnum(c)) || c == '_'); + } + *tested = c; + return test; +} + +static Byte *skip_thing(Byte * p, int linecnt, int dir, int type) +{ + Byte c; + + while (st_test(p, type, dir, &c)) { + // make sure we limit search to correct number of lines + if (c == '\n' && --linecnt < 1) + break; + if (dir >= 0 && p >= end - 1) + break; + if (dir < 0 && p <= text) + break; + p += dir; // move to next char + } + return p; +} + +// find matching char of pair () [] {} +static Byte *find_pair(Byte * p, Byte c) +{ + Byte match, *q; + int dir, level; + + match = ')'; + level = 1; + dir = 1; // assume forward + switch (c) { + case '(': + match = ')'; + break; + case '[': + match = ']'; + break; + case '{': + match = '}'; + break; + case ')': + match = '('; + dir = -1; + break; + case ']': + match = '['; + dir = -1; + break; + case '}': + match = '{'; + dir = -1; + break; + } + for (q = p + dir; text <= q && q < end; q += dir) { + // look for match, count levels of pairs (( )) + if (*q == c) + level++; // increase pair levels + if (*q == match) + level--; // reduce pair level + if (level == 0) + break; // found matching pair + } + if (level != 0) + q = NULL; // indicate no match + return q; +} + +#ifdef CONFIG_FEATURE_VI_SETOPTS +// show the matching char of a pair, () [] {} +static void showmatching(Byte * p) +{ + Byte *q, *save_dot; + + // we found half of a pair + q = find_pair(p, *p); // get loc of matching char + if (q == NULL) { + indicate_error('3'); // no matching char + } else { + // "q" now points to matching pair + save_dot = dot; // remember where we are + dot = q; // go to new loc + refresh(FALSE); // let the user see it + (void) mysleep(40); // give user some time + dot = save_dot; // go back to old loc + refresh(FALSE); + } +} +#endif /* CONFIG_FEATURE_VI_SETOPTS */ + +// open a hole in text[] +static Byte *text_hole_make(Byte * p, int size) // at "p", make a 'size' byte hole +{ + Byte *src, *dest; + int cnt; + + if (size <= 0) + goto thm0; + src = p; + dest = p + size; + cnt = end - src; // the rest of buffer + if (memmove(dest, src, cnt) != dest) { + psbs("can't create room for new characters"); + } + memset(p, ' ', size); // clear new hole + end = end + size; // adjust the new END + file_modified++; // has the file been modified + thm0: + return p; +} + +// close a hole in text[] +static Byte *text_hole_delete(Byte * p, Byte * q) // delete "p" thru "q", inclusive +{ + Byte *src, *dest; + int cnt, hole_size; + + // move forwards, from beginning + // assume p <= q + src = q + 1; + dest = p; + if (q < p) { // they are backward- swap them + src = p + 1; + dest = q; + } + hole_size = q - p + 1; + cnt = end - src; + if (src < text || src > end) + goto thd0; + if (dest < text || dest >= end) + goto thd0; + if (src >= end) + goto thd_atend; // just delete the end of the buffer + if (memmove(dest, src, cnt) != dest) { + psbs("can't delete the character"); + } + thd_atend: + end = end - hole_size; // adjust the new END + if (dest >= end) + dest = end - 1; // make sure dest in below end-1 + if (end <= text) + dest = end = text; // keep pointers valid + file_modified++; // has the file been modified + thd0: + return dest; +} + +// copy text into register, then delete text. +// if dist <= 0, do not include, or go past, a NewLine +// +static Byte *yank_delete(Byte * start, Byte * stop, int dist, int yf) +{ + Byte *p; + + // make sure start <= stop + if (start > stop) { + // they are backwards, reverse them + p = start; + start = stop; + stop = p; + } + if (dist <= 0) { + // we cannot cross NL boundaries + p = start; + if (*p == '\n') + return p; + // dont go past a NewLine + for (; p + 1 <= stop; p++) { + if (p[1] == '\n') { + stop = p; // "stop" just before NewLine + break; + } + } + } + p = start; +#ifdef CONFIG_FEATURE_VI_YANKMARK + text_yank(start, stop, YDreg); +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + if (yf == YANKDEL) { + p = text_hole_delete(start, stop); + } // delete lines + return p; +} + +static void show_help(void) +{ + puts("These features are available:" +#ifdef CONFIG_FEATURE_VI_SEARCH + "\n\tPattern searches with / and ?" +#endif /* CONFIG_FEATURE_VI_SEARCH */ +#ifdef CONFIG_FEATURE_VI_DOT_CMD + "\n\tLast command repeat with \'.\'" +#endif /* CONFIG_FEATURE_VI_DOT_CMD */ +#ifdef CONFIG_FEATURE_VI_YANKMARK + "\n\tLine marking with 'x" + "\n\tNamed buffers with \"x" +#endif /* CONFIG_FEATURE_VI_YANKMARK */ +#ifdef CONFIG_FEATURE_VI_READONLY + "\n\tReadonly if vi is called as \"view\"" + "\n\tReadonly with -R command line arg" +#endif /* CONFIG_FEATURE_VI_READONLY */ +#ifdef CONFIG_FEATURE_VI_SET + "\n\tSome colon mode commands with \':\'" +#endif /* CONFIG_FEATURE_VI_SET */ +#ifdef CONFIG_FEATURE_VI_SETOPTS + "\n\tSettable options with \":set\"" +#endif /* CONFIG_FEATURE_VI_SETOPTS */ +#ifdef CONFIG_FEATURE_VI_USE_SIGNALS + "\n\tSignal catching- ^C" + "\n\tJob suspend and resume with ^Z" +#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */ +#ifdef CONFIG_FEATURE_VI_WIN_RESIZE + "\n\tAdapt to window re-sizes" +#endif /* CONFIG_FEATURE_VI_WIN_RESIZE */ + ); +} + +static inline void print_literal(Byte * buf, Byte * s) // copy s to buf, convert unprintable +{ + Byte c, b[2]; + + b[1] = '\0'; + strcpy((char *) buf, ""); // init buf + if (strlen((char *) s) <= 0) + s = (Byte *) "(NULL)"; + for (; *s > '\0'; s++) { + int c_is_no_print; + + c = *s; + c_is_no_print = c > 127 && !Isprint(c); + if (c_is_no_print) { + strcat((char *) buf, SOn); + c = '.'; + } + if (c < ' ' || c == 127) { + strcat((char *) buf, "^"); + if(c == 127) + c = '?'; + else + c += '@'; + } + b[0] = c; + strcat((char *) buf, (char *) b); + if (c_is_no_print) + strcat((char *) buf, SOs); + if (*s == '\n') { + strcat((char *) buf, "$"); + } + } +} + +#ifdef CONFIG_FEATURE_VI_DOT_CMD +static void start_new_cmd_q(Byte c) +{ + // release old cmd + free(last_modifying_cmd); + // get buffer for new cmd + last_modifying_cmd = (Byte *) xmalloc(BUFSIZ); + memset(last_modifying_cmd, '\0', BUFSIZ); // clear new cmd queue + // if there is a current cmd count put it in the buffer first + if (cmdcnt > 0) + sprintf((char *) last_modifying_cmd, "%d%c", cmdcnt, c); + else // just save char c onto queue + last_modifying_cmd[0] = c; + adding2q = 1; +} + +static void end_cmd_q(void) +{ +#ifdef CONFIG_FEATURE_VI_YANKMARK + YDreg = 26; // go back to default Yank/Delete reg +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + adding2q = 0; + return; +} +#endif /* CONFIG_FEATURE_VI_DOT_CMD */ + +#if defined(CONFIG_FEATURE_VI_YANKMARK) || (defined(CONFIG_FEATURE_VI_COLON) && defined(CONFIG_FEATURE_VI_SEARCH)) || defined(CONFIG_FEATURE_VI_CRASHME) +static Byte *string_insert(Byte * p, Byte * s) // insert the string at 'p' +{ + int cnt, i; + + i = strlen((char *) s); + p = text_hole_make(p, i); + strncpy((char *) p, (char *) s, i); + for (cnt = 0; *s != '\0'; s++) { + if (*s == '\n') + cnt++; + } +#ifdef CONFIG_FEATURE_VI_YANKMARK + psb("Put %d lines (%d chars) from [%c]", cnt, i, what_reg()); +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + return p; +} +#endif /* CONFIG_FEATURE_VI_YANKMARK || CONFIG_FEATURE_VI_COLON || CONFIG_FEATURE_VI_CRASHME */ + +#ifdef CONFIG_FEATURE_VI_YANKMARK +static Byte *text_yank(Byte * p, Byte * q, int dest) // copy text into a register +{ + Byte *t; + int cnt; + + if (q < p) { // they are backwards- reverse them + t = q; + q = p; + p = t; + } + cnt = q - p + 1; + t = reg[dest]; + free(t); // if already a yank register, free it + t = (Byte *) xmalloc(cnt + 1); // get a new register + memset(t, '\0', cnt + 1); // clear new text[] + strncpy((char *) t, (char *) p, cnt); // copy text[] into bufer + reg[dest] = t; + return p; +} + +static Byte what_reg(void) +{ + Byte c; + + c = 'D'; // default to D-reg + if (0 <= YDreg && YDreg <= 25) + c = 'a' + (Byte) YDreg; + if (YDreg == 26) + c = 'D'; + if (YDreg == 27) + c = 'U'; + return c; +} + +static void check_context(Byte cmd) +{ + // A context is defined to be "modifying text" + // Any modifying command establishes a new context. + + if (dot < context_start || dot > context_end) { + if (strchr((char *) modifying_cmds, cmd) != NULL) { + // we are trying to modify text[]- make this the current context + mark[27] = mark[26]; // move cur to prev + mark[26] = dot; // move local to cur + context_start = prev_line(prev_line(dot)); + context_end = next_line(next_line(dot)); + //loiter= start_loiter= now; + } + } + return; +} + +static inline Byte *swap_context(Byte * p) // goto new context for '' command make this the current context +{ + Byte *tmp; + + // the current context is in mark[26] + // the previous context is in mark[27] + // only swap context if other context is valid + if (text <= mark[27] && mark[27] <= end - 1) { + tmp = mark[27]; + mark[27] = mark[26]; + mark[26] = tmp; + p = mark[26]; // where we are going- previous context + context_start = prev_line(prev_line(prev_line(p))); + context_end = next_line(next_line(next_line(p))); + } + return p; +} +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + +static int isblnk(Byte c) // is the char a blank or tab +{ + return (c == ' ' || c == '\t'); +} + +//----- Set terminal attributes -------------------------------- +static void rawmode(void) +{ + tcgetattr(0, &term_orig); + term_vi = term_orig; + term_vi.c_lflag &= (~ICANON & ~ECHO); // leave ISIG ON- allow intr's + term_vi.c_iflag &= (~IXON & ~ICRNL); + term_vi.c_oflag &= (~ONLCR); + term_vi.c_cc[VMIN] = 1; + term_vi.c_cc[VTIME] = 0; + erase_char = term_vi.c_cc[VERASE]; + tcsetattr(0, TCSANOW, &term_vi); +} + +static void cookmode(void) +{ + fflush(stdout); + tcsetattr(0, TCSANOW, &term_orig); +} + +//----- Come here when we get a window resize signal --------- +#ifdef CONFIG_FEATURE_VI_USE_SIGNALS +static void winch_sig(int sig ATTRIBUTE_UNUSED) +{ + signal(SIGWINCH, winch_sig); + if (ENABLE_FEATURE_VI_WIN_RESIZE) + get_terminal_width_height(0, &columns, &rows); + new_screen(rows, columns); // get memory for virtual screen + redraw(TRUE); // re-draw the screen +} + +//----- Come here when we get a continue signal ------------------- +static void cont_sig(int sig ATTRIBUTE_UNUSED) +{ + rawmode(); // terminal to "raw" + last_status_cksum = 0; // force status update + redraw(TRUE); // re-draw the screen + + signal(SIGTSTP, suspend_sig); + signal(SIGCONT, SIG_DFL); + kill(my_pid, SIGCONT); +} + +//----- Come here when we get a Suspend signal ------------------- +static void suspend_sig(int sig ATTRIBUTE_UNUSED) +{ + place_cursor(rows - 1, 0, FALSE); // go to bottom of screen + clear_to_eol(); // Erase to end of line + cookmode(); // terminal to "cooked" + + signal(SIGCONT, cont_sig); + signal(SIGTSTP, SIG_DFL); + kill(my_pid, SIGTSTP); +} + +//----- Come here when we get a signal --------------------------- +static void catch_sig(int sig) +{ + signal(SIGINT, catch_sig); + if(sig) + longjmp(restart, sig); +} +#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */ + +static int mysleep(int hund) // sleep for 'h' 1/100 seconds +{ + // Don't hang- Wait 5/100 seconds- 1 Sec= 1000000 + fflush(stdout); + FD_ZERO(&rfds); + FD_SET(0, &rfds); + tv.tv_sec = 0; + tv.tv_usec = hund * 10000; + select(1, &rfds, NULL, NULL, &tv); + return FD_ISSET(0, &rfds); +} + +#define readbuffer bb_common_bufsiz1 + +static int readed_for_parse; + +//----- IO Routines -------------------------------------------- +static Byte readit(void) // read (maybe cursor) key from stdin +{ + Byte c; + int n; + struct esc_cmds { + const char *seq; + Byte val; + }; + + static const struct esc_cmds esccmds[] = { + {"OA", (Byte) VI_K_UP}, // cursor key Up + {"OB", (Byte) VI_K_DOWN}, // cursor key Down + {"OC", (Byte) VI_K_RIGHT}, // Cursor Key Right + {"OD", (Byte) VI_K_LEFT}, // cursor key Left + {"OH", (Byte) VI_K_HOME}, // Cursor Key Home + {"OF", (Byte) VI_K_END}, // Cursor Key End + {"[A", (Byte) VI_K_UP}, // cursor key Up + {"[B", (Byte) VI_K_DOWN}, // cursor key Down + {"[C", (Byte) VI_K_RIGHT}, // Cursor Key Right + {"[D", (Byte) VI_K_LEFT}, // cursor key Left + {"[H", (Byte) VI_K_HOME}, // Cursor Key Home + {"[F", (Byte) VI_K_END}, // Cursor Key End + {"[1~", (Byte) VI_K_HOME}, // Cursor Key Home + {"[2~", (Byte) VI_K_INSERT}, // Cursor Key Insert + {"[4~", (Byte) VI_K_END}, // Cursor Key End + {"[5~", (Byte) VI_K_PAGEUP}, // Cursor Key Page Up + {"[6~", (Byte) VI_K_PAGEDOWN}, // Cursor Key Page Down + {"OP", (Byte) VI_K_FUN1}, // Function Key F1 + {"OQ", (Byte) VI_K_FUN2}, // Function Key F2 + {"OR", (Byte) VI_K_FUN3}, // Function Key F3 + {"OS", (Byte) VI_K_FUN4}, // Function Key F4 + {"[15~", (Byte) VI_K_FUN5}, // Function Key F5 + {"[17~", (Byte) VI_K_FUN6}, // Function Key F6 + {"[18~", (Byte) VI_K_FUN7}, // Function Key F7 + {"[19~", (Byte) VI_K_FUN8}, // Function Key F8 + {"[20~", (Byte) VI_K_FUN9}, // Function Key F9 + {"[21~", (Byte) VI_K_FUN10}, // Function Key F10 + {"[23~", (Byte) VI_K_FUN11}, // Function Key F11 + {"[24~", (Byte) VI_K_FUN12}, // Function Key F12 + {"[11~", (Byte) VI_K_FUN1}, // Function Key F1 + {"[12~", (Byte) VI_K_FUN2}, // Function Key F2 + {"[13~", (Byte) VI_K_FUN3}, // Function Key F3 + {"[14~", (Byte) VI_K_FUN4}, // Function Key F4 + }; + +#define ESCCMDS_COUNT (sizeof(esccmds)/sizeof(struct esc_cmds)) + + (void) alarm(0); // turn alarm OFF while we wait for input + fflush(stdout); + n = readed_for_parse; + // get input from User- are there already input chars in Q? + if (n <= 0) { + ri0: + // the Q is empty, wait for a typed char + n = read(0, readbuffer, BUFSIZ - 1); + if (n < 0) { + if (errno == EINTR) + goto ri0; // interrupted sys call + if (errno == EBADF) + editing = 0; + if (errno == EFAULT) + editing = 0; + if (errno == EINVAL) + editing = 0; + if (errno == EIO) + editing = 0; + errno = 0; + } + if(n <= 0) + return 0; // error + if (readbuffer[0] == 27) { + // This is an ESC char. Is this Esc sequence? + // Could be bare Esc key. See if there are any + // more chars to read after the ESC. This would + // be a Function or Cursor Key sequence. + FD_ZERO(&rfds); + FD_SET(0, &rfds); + tv.tv_sec = 0; + tv.tv_usec = 50000; // Wait 5/100 seconds- 1 Sec=1000000 + + // keep reading while there are input chars and room in buffer + while (select(1, &rfds, NULL, NULL, &tv) > 0 && n <= (BUFSIZ - 5)) { + // read the rest of the ESC string + int r = read(0, (void *) (readbuffer + n), BUFSIZ - n); + if (r > 0) { + n += r; + } + } + } + readed_for_parse = n; + } + c = readbuffer[0]; + if(c == 27 && n > 1) { + // Maybe cursor or function key? + const struct esc_cmds *eindex; + + for (eindex = esccmds; eindex < &esccmds[ESCCMDS_COUNT]; eindex++) { + int cnt = strlen(eindex->seq); + + if(n <= cnt) + continue; + if(strncmp(eindex->seq, (char *) readbuffer + 1, cnt)) + continue; + // is a Cursor key- put derived value back into Q + c = eindex->val; + // for squeeze out the ESC sequence + n = cnt + 1; + break; + } + if(eindex == &esccmds[ESCCMDS_COUNT]) { + /* defined ESC sequence not found, set only one ESC */ + n = 1; + } + } else { + n = 1; + } + // remove key sequence from Q + readed_for_parse -= n; + memmove(readbuffer, readbuffer + n, BUFSIZ - n); + (void) alarm(3); // we are done waiting for input, turn alarm ON + return c; +} + +//----- IO Routines -------------------------------------------- +static Byte get_one_char(void) +{ + static Byte c; + +#ifdef CONFIG_FEATURE_VI_DOT_CMD + // ! adding2q && ioq == 0 read() + // ! adding2q && ioq != 0 *ioq + // adding2q *last_modifying_cmd= read() + if (!adding2q) { + // we are not adding to the q. + // but, we may be reading from a q + if (ioq == 0) { + // there is no current q, read from STDIN + c = readit(); // get the users input + } else { + // there is a queue to get chars from first + c = *ioq++; + if (c == '\0') { + // the end of the q, read from STDIN + free(ioq_start); + ioq_start = ioq = 0; + c = readit(); // get the users input + } + } + } else { + // adding STDIN chars to q + c = readit(); // get the users input + if (last_modifying_cmd != 0) { + int len = strlen((char *) last_modifying_cmd); + if (len + 1 >= BUFSIZ) { + psbs("last_modifying_cmd overrun"); + } else { + // add new char to q + last_modifying_cmd[len] = c; + } + } + } +#else /* CONFIG_FEATURE_VI_DOT_CMD */ + c = readit(); // get the users input +#endif /* CONFIG_FEATURE_VI_DOT_CMD */ + return c; // return the char, where ever it came from +} + +static Byte *get_input_line(Byte * prompt) // get input line- use "status line" +{ + Byte buf[BUFSIZ]; + Byte c; + int i; + static Byte *obufp = NULL; + + strcpy((char *) buf, (char *) prompt); + last_status_cksum = 0; // force status update + place_cursor(rows - 1, 0, FALSE); // go to Status line, bottom of screen + clear_to_eol(); // clear the line + write1((char *) prompt); // write out the :, /, or ? prompt + + for (i = strlen((char *) buf); i < BUFSIZ;) { + c = get_one_char(); // read user input + if (c == '\n' || c == '\r' || c == 27) + break; // is this end of input + if (c == erase_char || c == 8 || c == 127) { + // user wants to erase prev char + i--; // backup to prev char + buf[i] = '\0'; // erase the char + buf[i + 1] = '\0'; // null terminate buffer + write1("\b \b"); // erase char on screen + if (i <= 0) { // user backs up before b-o-l, exit + break; + } + } else { + buf[i] = c; // save char in buffer + buf[i + 1] = '\0'; // make sure buffer is null terminated + putchar(c); // echo the char back to user + i++; + } + } + refresh(FALSE); + free(obufp); + obufp = (Byte *) xstrdup((char *) buf); + return obufp; +} + +static int file_size(const Byte * fn) // what is the byte size of "fn" +{ + struct stat st_buf; + int cnt, sr; + + if (fn == 0 || strlen((char *)fn) <= 0) + return -1; + cnt = -1; + sr = stat((char *) fn, &st_buf); // see if file exists + if (sr >= 0) { + cnt = (int) st_buf.st_size; + } + return cnt; +} + +static int file_insert(Byte * fn, Byte * p, int size) +{ + int fd, cnt; + + cnt = -1; +#ifdef CONFIG_FEATURE_VI_READONLY + readonly = FALSE; +#endif /* CONFIG_FEATURE_VI_READONLY */ + if (fn == 0 || strlen((char*) fn) <= 0) { + psbs("No filename given"); + goto fi0; + } + if (size == 0) { + // OK- this is just a no-op + cnt = 0; + goto fi0; + } + if (size < 0) { + psbs("Trying to insert a negative number (%d) of characters", size); + goto fi0; + } + if (p < text || p > end) { + psbs("Trying to insert file outside of memory"); + goto fi0; + } + + // see if we can open the file +#ifdef CONFIG_FEATURE_VI_READONLY + if (vi_readonly) goto fi1; // do not try write-mode +#endif + fd = open((char *) fn, O_RDWR); // assume read & write + if (fd < 0) { + // could not open for writing- maybe file is read only +#ifdef CONFIG_FEATURE_VI_READONLY + fi1: +#endif + fd = open((char *) fn, O_RDONLY); // try read-only + if (fd < 0) { + psbs("\"%s\" %s", fn, "cannot open file"); + goto fi0; + } +#ifdef CONFIG_FEATURE_VI_READONLY + // got the file- read-only + readonly = TRUE; +#endif /* CONFIG_FEATURE_VI_READONLY */ + } + p = text_hole_make(p, size); + cnt = read(fd, p, size); + close(fd); + if (cnt < 0) { + cnt = -1; + p = text_hole_delete(p, p + size - 1); // un-do buffer insert + psbs("cannot read file \"%s\"", fn); + } else if (cnt < size) { + // There was a partial read, shrink unused space text[] + p = text_hole_delete(p + cnt, p + (size - cnt) - 1); // un-do buffer insert + psbs("cannot read all of file \"%s\"", fn); + } + if (cnt >= size) + file_modified++; + fi0: + return cnt; +} + +static int file_write(Byte * fn, Byte * first, Byte * last) +{ + int fd, cnt, charcnt; + + if (fn == 0) { + psbs("No current filename"); + return -2; + } + charcnt = 0; + // FIXIT- use the correct umask() + fd = open((char *) fn, (O_WRONLY | O_CREAT | O_TRUNC), 0664); + if (fd < 0) + return -1; + cnt = last - first + 1; + charcnt = write(fd, first, cnt); + if (charcnt == cnt) { + // good write + //file_modified= FALSE; // the file has not been modified + } else { + charcnt = 0; + } + close(fd); + return charcnt; +} + +//----- Terminal Drawing --------------------------------------- +// The terminal is made up of 'rows' line of 'columns' columns. +// classically this would be 24 x 80. +// screen coordinates +// 0,0 ... 0,79 +// 1,0 ... 1,79 +// . ... . +// . ... . +// 22,0 ... 22,79 +// 23,0 ... 23,79 status line +// + +//----- Move the cursor to row x col (count from 0, not 1) ------- +static void place_cursor(int row, int col, int opti) +{ + char cm1[BUFSIZ]; + char *cm; +#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR + char cm2[BUFSIZ]; + Byte *screenp; + // char cm3[BUFSIZ]; + int Rrow= last_row; +#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ + + memset(cm1, '\0', BUFSIZ - 1); // clear the buffer + + if (row < 0) row = 0; + if (row >= rows) row = rows - 1; + if (col < 0) col = 0; + if (col >= columns) col = columns - 1; + + //----- 1. Try the standard terminal ESC sequence + sprintf((char *) cm1, CMrc, row + 1, col + 1); + cm= cm1; + if (! opti) goto pc0; + +#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR + //----- find the minimum # of chars to move cursor ------------- + //----- 2. Try moving with discreet chars (Newline, [back]space, ...) + memset(cm2, '\0', BUFSIZ - 1); // clear the buffer + + // move to the correct row + while (row < Rrow) { + // the cursor has to move up + strcat(cm2, CMup); + Rrow--; + } + while (row > Rrow) { + // the cursor has to move down + strcat(cm2, CMdown); + Rrow++; + } + + // now move to the correct column + strcat(cm2, "\r"); // start at col 0 + // just send out orignal source char to get to correct place + screenp = &screen[row * columns]; // start of screen line + strncat(cm2, (char* )screenp, col); + + //----- 3. Try some other way of moving cursor + //--------------------------------------------- + + // pick the shortest cursor motion to send out + cm= cm1; + if (strlen(cm2) < strlen(cm)) { + cm= cm2; + } /* else if (strlen(cm3) < strlen(cm)) { + cm= cm3; + } */ +#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ + pc0: + write1(cm); // move the cursor +} + +//----- Erase from cursor to end of line ----------------------- +static void clear_to_eol(void) +{ + write1(Ceol); // Erase from cursor to end of line +} + +//----- Erase from cursor to end of screen ----------------------- +static void clear_to_eos(void) +{ + write1(Ceos); // Erase from cursor to end of screen +} + +//----- Start standout mode ------------------------------------ +static void standout_start(void) // send "start reverse video" sequence +{ + write1(SOs); // Start reverse video mode +} + +//----- End standout mode -------------------------------------- +static void standout_end(void) // send "end reverse video" sequence +{ + write1(SOn); // End reverse video mode +} + +//----- Flash the screen -------------------------------------- +static void flash(int h) +{ + standout_start(); // send "start reverse video" sequence + redraw(TRUE); + (void) mysleep(h); + standout_end(); // send "end reverse video" sequence + redraw(TRUE); +} + +static void Indicate_Error(void) +{ +#ifdef CONFIG_FEATURE_VI_CRASHME + if (crashme > 0) + return; // generate a random command +#endif /* CONFIG_FEATURE_VI_CRASHME */ + if (!err_method) { + write1(bell); // send out a bell character + } else { + flash(10); + } +} + +//----- Screen[] Routines -------------------------------------- +//----- Erase the Screen[] memory ------------------------------ +static void screen_erase(void) +{ + memset(screen, ' ', screensize); // clear new screen +} + +static int bufsum(unsigned char *buf, int count) +{ + int sum = 0; + unsigned char *e = buf + count; + while (buf < e) + sum += *buf++; + return sum; +} + +//----- Draw the status line at bottom of the screen ------------- +static void show_status_line(void) +{ + int cnt = 0, cksum = 0; + + // either we already have an error or status message, or we + // create one. + if (!have_status_msg) { + cnt = format_edit_status(); + cksum = bufsum(status_buffer, cnt); + } + if (have_status_msg || ((cnt > 0 && last_status_cksum != cksum))) { + last_status_cksum= cksum; // remember if we have seen this line + place_cursor(rows - 1, 0, FALSE); // put cursor on status line + write1((char*)status_buffer); + clear_to_eol(); + if (have_status_msg) { + if (((int)strlen((char*)status_buffer) - (have_status_msg - 1)) > + (columns - 1) ) { + have_status_msg = 0; + Hit_Return(); + } + have_status_msg = 0; + } + place_cursor(crow, ccol, FALSE); // put cursor back in correct place + } + fflush(stdout); +} + +//----- format the status buffer, the bottom line of screen ------ +// format status buffer, with STANDOUT mode +static void psbs(const char *format, ...) +{ + va_list args; + + va_start(args, format); + strcpy((char *) status_buffer, SOs); // Terminal standout mode on + vsprintf((char *) status_buffer + strlen((char *) status_buffer), format, args); + strcat((char *) status_buffer, SOn); // Terminal standout mode off + va_end(args); + + have_status_msg = 1 + sizeof(SOs) + sizeof(SOn) - 2; + + return; +} + +// format status buffer +static void psb(const char *format, ...) +{ + va_list args; + + va_start(args, format); + vsprintf((char *) status_buffer, format, args); + va_end(args); + + have_status_msg = 1; + + return; +} + +static void ni(Byte * s) // display messages +{ + Byte buf[BUFSIZ]; + + print_literal(buf, s); + psbs("\'%s\' is not implemented", buf); +} + +static int format_edit_status(void) // show file status on status line +{ + int cur, percent, ret, trunc_at; + static int tot; + + // file_modified is now a counter rather than a flag. this + // helps reduce the amount of line counting we need to do. + // (this will cause a mis-reporting of modified status + // once every MAXINT editing operations.) + + // it would be nice to do a similar optimization here -- if + // we haven't done a motion that could have changed which line + // we're on, then we shouldn't have to do this count_lines() + cur = count_lines(text, dot); + + // reduce counting -- the total lines can't have + // changed if we haven't done any edits. + if (file_modified != last_file_modified) { + tot = cur + count_lines(dot, end - 1) - 1; + last_file_modified = file_modified; + } + + // current line percent + // ------------- ~~ ---------- + // total lines 100 + if (tot > 0) { + percent = (100 * cur) / tot; + } else { + cur = tot = 0; + percent = 100; + } + + trunc_at = columns < STATUS_BUFFER_LEN-1 ? + columns : STATUS_BUFFER_LEN-1; + + ret = snprintf((char *) status_buffer, trunc_at+1, +#ifdef CONFIG_FEATURE_VI_READONLY + "%c %s%s%s %d/%d %d%%", +#else + "%c %s%s %d/%d %d%%", +#endif + (cmd_mode ? (cmd_mode == 2 ? 'R':'I'):'-'), + (cfn != 0 ? (char *) cfn : "No file"), +#ifdef CONFIG_FEATURE_VI_READONLY + ((vi_readonly || readonly) ? " [Read-only]" : ""), +#endif + (file_modified ? " [modified]" : ""), + cur, tot, percent); + + if (ret >= 0 && ret < trunc_at) + return ret; /* it all fit */ + + return trunc_at; /* had to truncate */ +} + +//----- Force refresh of all Lines ----------------------------- +static void redraw(int full_screen) +{ + place_cursor(0, 0, FALSE); // put cursor in correct place + clear_to_eos(); // tel terminal to erase display + screen_erase(); // erase the internal screen buffer + last_status_cksum = 0; // force status update + refresh(full_screen); // this will redraw the entire display + show_status_line(); +} + +//----- Format a text[] line into a buffer --------------------- +static void format_line(Byte *dest, Byte *src, int li) +{ + int co; + Byte c; + + for (co= 0; co < MAX_SCR_COLS; co++) { + c= ' '; // assume blank + if (li > 0 && co == 0) { + c = '~'; // not first line, assume Tilde + } + // are there chars in text[] and have we gone past the end + if (text < end && src < end) { + c = *src++; + } + if (c == '\n') + break; + if (c > 127 && !Isprint(c)) { + c = '.'; + } + if (c < ' ' || c == 127) { + if (c == '\t') { + c = ' '; + // co % 8 != 7 + for (; (co % tabstop) != (tabstop - 1); co++) { + dest[co] = c; + } + } else { + dest[co++] = '^'; + if(c == 127) + c = '?'; + else + c += '@'; // make it visible + } + } + // the co++ is done here so that the column will + // not be overwritten when we blank-out the rest of line + dest[co] = c; + if (src >= end) + break; + } +} + +//----- Refresh the changed screen lines ----------------------- +// Copy the source line from text[] into the buffer and note +// if the current screenline is different from the new buffer. +// If they differ then that line needs redrawing on the terminal. +// +static void refresh(int full_screen) +{ + static int old_offset; + int li, changed; + Byte buf[MAX_SCR_COLS]; + Byte *tp, *sp; // pointer into text[] and screen[] +#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR + int last_li= -2; // last line that changed- for optimizing cursor movement +#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ + + if (ENABLE_FEATURE_VI_WIN_RESIZE) + get_terminal_width_height(0, &columns, &rows); + sync_cursor(dot, &crow, &ccol); // where cursor will be (on "dot") + tp = screenbegin; // index into text[] of top line + + // compare text[] to screen[] and mark screen[] lines that need updating + for (li = 0; li < rows - 1; li++) { + int cs, ce; // column start & end + memset(buf, ' ', MAX_SCR_COLS); // blank-out the buffer + buf[MAX_SCR_COLS-1] = 0; // NULL terminate the buffer + // format current text line into buf + format_line(buf, tp, li); + + // skip to the end of the current text[] line + while (tp < end && *tp++ != '\n') /*no-op*/ ; + + // see if there are any changes between vitual screen and buf + changed = FALSE; // assume no change + cs= 0; + ce= columns-1; + sp = &screen[li * columns]; // start of screen line + if (full_screen) { + // force re-draw of every single column from 0 - columns-1 + goto re0; + } + // compare newly formatted buffer with virtual screen + // look forward for first difference between buf and screen + for ( ; cs <= ce; cs++) { + if (buf[cs + offset] != sp[cs]) { + changed = TRUE; // mark for redraw + break; + } + } + + // look backward for last difference between buf and screen + for ( ; ce >= cs; ce--) { + if (buf[ce + offset] != sp[ce]) { + changed = TRUE; // mark for redraw + break; + } + } + // now, cs is index of first diff, and ce is index of last diff + + // if horz offset has changed, force a redraw + if (offset != old_offset) { + re0: + changed = TRUE; + } + + // make a sanity check of columns indexes + if (cs < 0) cs= 0; + if (ce > columns-1) ce= columns-1; + if (cs > ce) { cs= 0; ce= columns-1; } + // is there a change between vitual screen and buf + if (changed) { + // copy changed part of buffer to virtual screen + memmove(sp+cs, buf+(cs+offset), ce-cs+1); + + // move cursor to column of first change + if (offset != old_offset) { + // opti_cur_move is still too stupid + // to handle offsets correctly + place_cursor(li, cs, FALSE); + } else { +#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR + // if this just the next line + // try to optimize cursor movement + // otherwise, use standard ESC sequence + place_cursor(li, cs, li == (last_li+1) ? TRUE : FALSE); + last_li= li; +#else /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ + place_cursor(li, cs, FALSE); // use standard ESC sequence +#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ + } + + // write line out to terminal + { + int nic = ce-cs+1; + char *out = (char*)sp+cs; + + while(nic-- > 0) { + putchar(*out); + out++; + } + } +#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR + last_row = li; +#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ + } + } + +#ifdef CONFIG_FEATURE_VI_OPTIMIZE_CURSOR + place_cursor(crow, ccol, (crow == last_row) ? TRUE : FALSE); + last_row = crow; +#else + place_cursor(crow, ccol, FALSE); +#endif /* CONFIG_FEATURE_VI_OPTIMIZE_CURSOR */ + + if (offset != old_offset) + old_offset = offset; +} + +//--------------------------------------------------------------------- +//----- the Ascii Chart ----------------------------------------------- +// +// 00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel +// 08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si +// 10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb +// 18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us +// 20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 ' +// 28 ( 29 ) 2a * 2b + 2c , 2d - 2e . 2f / +// 30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7 +// 38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ? +// 40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G +// 48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O +// 50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W +// 58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _ +// 60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g +// 68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o +// 70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w +// 78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del +//--------------------------------------------------------------------- + +//----- Execute a Vi Command ----------------------------------- +static void do_cmd(Byte c) +{ + Byte c1, *p, *q, *msg, buf[9], *save_dot; + int cnt, i, j, dir, yf; + + c1 = c; // quiet the compiler + cnt = yf = dir = 0; // quiet the compiler + p = q = save_dot = msg = buf; // quiet the compiler + memset(buf, '\0', 9); // clear buf + + show_status_line(); + + /* if this is a cursor key, skip these checks */ + switch (c) { + case VI_K_UP: + case VI_K_DOWN: + case VI_K_LEFT: + case VI_K_RIGHT: + case VI_K_HOME: + case VI_K_END: + case VI_K_PAGEUP: + case VI_K_PAGEDOWN: + goto key_cmd_mode; + } + + if (cmd_mode == 2) { + // flip-flop Insert/Replace mode + if (c == VI_K_INSERT) goto dc_i; + // we are 'R'eplacing the current *dot with new char + if (*dot == '\n') { + // don't Replace past E-o-l + cmd_mode = 1; // convert to insert + } else { + if (1 <= c || Isprint(c)) { + if (c != 27) + dot = yank_delete(dot, dot, 0, YANKDEL); // delete char + dot = char_insert(dot, c); // insert new char + } + goto dc1; + } + } + if (cmd_mode == 1) { + // hitting "Insert" twice means "R" replace mode + if (c == VI_K_INSERT) goto dc5; + // insert the char c at "dot" + if (1 <= c || Isprint(c)) { + dot = char_insert(dot, c); + } + goto dc1; + } + +key_cmd_mode: + switch (c) { + //case 0x01: // soh + //case 0x09: // ht + //case 0x0b: // vt + //case 0x0e: // so + //case 0x0f: // si + //case 0x10: // dle + //case 0x11: // dc1 + //case 0x13: // dc3 +#ifdef CONFIG_FEATURE_VI_CRASHME + case 0x14: // dc4 ctrl-T + crashme = (crashme == 0) ? 1 : 0; + break; +#endif /* CONFIG_FEATURE_VI_CRASHME */ + //case 0x16: // syn + //case 0x17: // etb + //case 0x18: // can + //case 0x1c: // fs + //case 0x1d: // gs + //case 0x1e: // rs + //case 0x1f: // us + //case '!': // !- + //case '#': // #- + //case '&': // &- + //case '(': // (- + //case ')': // )- + //case '*': // *- + //case ',': // ,- + //case '=': // =- + //case '@': // @- + //case 'F': // F- + //case 'K': // K- + //case 'Q': // Q- + //case 'S': // S- + //case 'T': // T- + //case 'V': // V- + //case '[': // [- + //case '\\': // \- + //case ']': // ]- + //case '_': // _- + //case '`': // `- + //case 'g': // g- + //case 'u': // u- FIXME- there is no undo + //case 'v': // v- + default: // unrecognised command + buf[0] = c; + buf[1] = '\0'; + if (c < ' ') { + buf[0] = '^'; + buf[1] = c + '@'; + buf[2] = '\0'; + } + ni((Byte *) buf); + end_cmd_q(); // stop adding to q + case 0x00: // nul- ignore + break; + case 2: // ctrl-B scroll up full screen + case VI_K_PAGEUP: // Cursor Key Page Up + dot_scroll(rows - 2, -1); + break; +#ifdef CONFIG_FEATURE_VI_USE_SIGNALS + case 0x03: // ctrl-C interrupt + longjmp(restart, 1); + break; + case 26: // ctrl-Z suspend + suspend_sig(SIGTSTP); + break; +#endif /* CONFIG_FEATURE_VI_USE_SIGNALS */ + case 4: // ctrl-D scroll down half screen + dot_scroll((rows - 2) / 2, 1); + break; + case 5: // ctrl-E scroll down one line + dot_scroll(1, 1); + break; + case 6: // ctrl-F scroll down full screen + case VI_K_PAGEDOWN: // Cursor Key Page Down + dot_scroll(rows - 2, 1); + break; + case 7: // ctrl-G show current status + last_status_cksum = 0; // force status update + break; + case 'h': // h- move left + case VI_K_LEFT: // cursor key Left + case 8: // ctrl-H- move left (This may be ERASE char) + case 127: // DEL- move left (This may be ERASE char) + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dot_left(); + break; + case 10: // Newline ^J + case 'j': // j- goto next line, same col + case VI_K_DOWN: // cursor key Down + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dot_next(); // go to next B-o-l + dot = move_to_col(dot, ccol + offset); // try stay in same col + break; + case 12: // ctrl-L force redraw whole screen + case 18: // ctrl-R force redraw + place_cursor(0, 0, FALSE); // put cursor in correct place + clear_to_eos(); // tel terminal to erase display + (void) mysleep(10); + screen_erase(); // erase the internal screen buffer + last_status_cksum = 0; // force status update + refresh(TRUE); // this will redraw the entire display + break; + case 13: // Carriage Return ^M + case '+': // +- goto next line + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dot_next(); + dot_skip_over_ws(); + break; + case 21: // ctrl-U scroll up half screen + dot_scroll((rows - 2) / 2, -1); + break; + case 25: // ctrl-Y scroll up one line + dot_scroll(1, -1); + break; + case 27: // esc + if (cmd_mode == 0) + indicate_error(c); + cmd_mode = 0; // stop insrting + end_cmd_q(); + last_status_cksum = 0; // force status update + break; + case ' ': // move right + case 'l': // move right + case VI_K_RIGHT: // Cursor Key Right + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dot_right(); + break; +#ifdef CONFIG_FEATURE_VI_YANKMARK + case '"': // "- name a register to use for Delete/Yank + c1 = get_one_char(); + c1 = tolower(c1); + if (islower(c1)) { + YDreg = c1 - 'a'; + } else { + indicate_error(c); + } + break; + case '\'': // '- goto a specific mark + c1 = get_one_char(); + c1 = tolower(c1); + if (islower(c1)) { + c1 = c1 - 'a'; + // get the b-o-l + q = mark[(int) c1]; + if (text <= q && q < end) { + dot = q; + dot_begin(); // go to B-o-l + dot_skip_over_ws(); + } + } else if (c1 == '\'') { // goto previous context + dot = swap_context(dot); // swap current and previous context + dot_begin(); // go to B-o-l + dot_skip_over_ws(); + } else { + indicate_error(c); + } + break; + case 'm': // m- Mark a line + // this is really stupid. If there are any inserts or deletes + // between text[0] and dot then this mark will not point to the + // correct location! It could be off by many lines! + // Well..., at least its quick and dirty. + c1 = get_one_char(); + c1 = tolower(c1); + if (islower(c1)) { + c1 = c1 - 'a'; + // remember the line + mark[(int) c1] = dot; + } else { + indicate_error(c); + } + break; + case 'P': // P- Put register before + case 'p': // p- put register after + p = reg[YDreg]; + if (p == 0) { + psbs("Nothing in register %c", what_reg()); + break; + } + // are we putting whole lines or strings + if (strchr((char *) p, '\n') != NULL) { + if (c == 'P') { + dot_begin(); // putting lines- Put above + } + if (c == 'p') { + // are we putting after very last line? + if (end_line(dot) == (end - 1)) { + dot = end; // force dot to end of text[] + } else { + dot_next(); // next line, then put before + } + } + } else { + if (c == 'p') + dot_right(); // move to right, can move to NL + } + dot = string_insert(dot, p); // insert the string + end_cmd_q(); // stop adding to q + break; + case 'U': // U- Undo; replace current line with original version + if (reg[Ureg] != 0) { + p = begin_line(dot); + q = end_line(dot); + p = text_hole_delete(p, q); // delete cur line + p = string_insert(p, reg[Ureg]); // insert orig line + dot = p; + dot_skip_over_ws(); + } + break; +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + case '$': // $- goto end of line + case VI_K_END: // Cursor Key End + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dot = end_line(dot); + break; + case '%': // %- find matching char of pair () [] {} + for (q = dot; q < end && *q != '\n'; q++) { + if (strchr("()[]{}", *q) != NULL) { + // we found half of a pair + p = find_pair(q, *q); + if (p == NULL) { + indicate_error(c); + } else { + dot = p; + } + break; + } + } + if (*q == '\n') + indicate_error(c); + break; + case 'f': // f- forward to a user specified char + last_forward_char = get_one_char(); // get the search char + // + // dont separate these two commands. 'f' depends on ';' + // + //**** fall thru to ... ';' + case ';': // ;- look at rest of line for last forward char + if (cmdcnt-- > 1) { + do_cmd(';'); + } // repeat cnt + if (last_forward_char == 0) break; + q = dot + 1; + while (q < end - 1 && *q != '\n' && *q != last_forward_char) { + q++; + } + if (*q == last_forward_char) + dot = q; + break; + case '-': // -- goto prev line + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dot_prev(); + dot_skip_over_ws(); + break; +#ifdef CONFIG_FEATURE_VI_DOT_CMD + case '.': // .- repeat the last modifying command + // Stuff the last_modifying_cmd back into stdin + // and let it be re-executed. + if (last_modifying_cmd != 0) { + ioq = ioq_start = (Byte *) xstrdup((char *) last_modifying_cmd); + } + break; +#endif /* CONFIG_FEATURE_VI_DOT_CMD */ +#ifdef CONFIG_FEATURE_VI_SEARCH + case '?': // /- search for a pattern + case '/': // /- search for a pattern + buf[0] = c; + buf[1] = '\0'; + q = get_input_line(buf); // get input line- use "status line" + if (strlen((char *) q) == 1) + goto dc3; // if no pat re-use old pat + if (strlen((char *) q) > 1) { // new pat- save it and find + // there is a new pat + free(last_search_pattern); + last_search_pattern = (Byte *) xstrdup((char *) q); + goto dc3; // now find the pattern + } + // user changed mind and erased the "/"- do nothing + break; + case 'N': // N- backward search for last pattern + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dir = BACK; // assume BACKWARD search + p = dot - 1; + if (last_search_pattern[0] == '?') { + dir = FORWARD; + p = dot + 1; + } + goto dc4; // now search for pattern + break; + case 'n': // n- repeat search for last pattern + // search rest of text[] starting at next char + // if search fails return orignal "p" not the "p+1" address + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dc3: + if (last_search_pattern == 0) { + msg = (Byte *) "No previous regular expression"; + goto dc2; + } + if (last_search_pattern[0] == '/') { + dir = FORWARD; // assume FORWARD search + p = dot + 1; + } + if (last_search_pattern[0] == '?') { + dir = BACK; + p = dot - 1; + } + dc4: + q = char_search(p, last_search_pattern + 1, dir, FULL); + if (q != NULL) { + dot = q; // good search, update "dot" + msg = (Byte *) ""; + goto dc2; + } + // no pattern found between "dot" and "end"- continue at top + p = text; + if (dir == BACK) { + p = end - 1; + } + q = char_search(p, last_search_pattern + 1, dir, FULL); + if (q != NULL) { // found something + dot = q; // found new pattern- goto it + msg = (Byte *) "search hit BOTTOM, continuing at TOP"; + if (dir == BACK) { + msg = (Byte *) "search hit TOP, continuing at BOTTOM"; + } + } else { + msg = (Byte *) "Pattern not found"; + } + dc2: + if (*msg) psbs("%s", msg); + break; + case '{': // {- move backward paragraph + q = char_search(dot, (Byte *) "\n\n", BACK, FULL); + if (q != NULL) { // found blank line + dot = next_line(q); // move to next blank line + } + break; + case '}': // }- move forward paragraph + q = char_search(dot, (Byte *) "\n\n", FORWARD, FULL); + if (q != NULL) { // found blank line + dot = next_line(q); // move to next blank line + } + break; +#endif /* CONFIG_FEATURE_VI_SEARCH */ + case '0': // 0- goto begining of line + case '1': // 1- + case '2': // 2- + case '3': // 3- + case '4': // 4- + case '5': // 5- + case '6': // 6- + case '7': // 7- + case '8': // 8- + case '9': // 9- + if (c == '0' && cmdcnt < 1) { + dot_begin(); // this was a standalone zero + } else { + cmdcnt = cmdcnt * 10 + (c - '0'); // this 0 is part of a number + } + break; + case ':': // :- the colon mode commands + p = get_input_line((Byte *) ":"); // get input line- use "status line" +#ifdef CONFIG_FEATURE_VI_COLON + colon(p); // execute the command +#else /* CONFIG_FEATURE_VI_COLON */ + if (*p == ':') + p++; // move past the ':' + cnt = strlen((char *) p); + if (cnt <= 0) + break; + if (strncasecmp((char *) p, "quit", cnt) == 0 || + strncasecmp((char *) p, "q!", cnt) == 0) { // delete lines + if (file_modified && p[1] != '!') { + psbs("No write since last change (:quit! overrides)"); + } else { + editing = 0; + } + } else if (strncasecmp((char *) p, "write", cnt) == 0 + || strncasecmp((char *) p, "wq", cnt) == 0 + || strncasecmp((char *) p, "wn", cnt) == 0 + || strncasecmp((char *) p, "x", cnt) == 0) { + cnt = file_write(cfn, text, end - 1); + if (cnt < 0) { + if (cnt == -1) + psbs("Write error: %s", strerror(errno)); + } else { + file_modified = 0; + last_file_modified = -1; + psb("\"%s\" %dL, %dC", cfn, count_lines(text, end - 1), cnt); + if (p[0] == 'x' || p[1] == 'q' || p[1] == 'n' || + p[0] == 'X' || p[1] == 'Q' || p[1] == 'N') { + editing = 0; + } + } + } else if (strncasecmp((char *) p, "file", cnt) == 0 ) { + last_status_cksum = 0; // force status update + } else if (sscanf((char *) p, "%d", &j) > 0) { + dot = find_line(j); // go to line # j + dot_skip_over_ws(); + } else { // unrecognised cmd + ni((Byte *) p); + } +#endif /* CONFIG_FEATURE_VI_COLON */ + break; + case '<': // <- Left shift something + case '>': // >- Right shift something + cnt = count_lines(text, dot); // remember what line we are on + c1 = get_one_char(); // get the type of thing to delete + find_range(&p, &q, c1); + (void) yank_delete(p, q, 1, YANKONLY); // save copy before change + p = begin_line(p); + q = end_line(q); + i = count_lines(p, q); // # of lines we are shifting + for ( ; i > 0; i--, p = next_line(p)) { + if (c == '<') { + // shift left- remove tab or 8 spaces + if (*p == '\t') { + // shrink buffer 1 char + (void) text_hole_delete(p, p); + } else if (*p == ' ') { + // we should be calculating columns, not just SPACE + for (j = 0; *p == ' ' && j < tabstop; j++) { + (void) text_hole_delete(p, p); + } + } + } else if (c == '>') { + // shift right -- add tab or 8 spaces + (void) char_insert(p, '\t'); + } + } + dot = find_line(cnt); // what line were we on + dot_skip_over_ws(); + end_cmd_q(); // stop adding to q + break; + case 'A': // A- append at e-o-l + dot_end(); // go to e-o-l + //**** fall thru to ... 'a' + case 'a': // a- append after current char + if (*dot != '\n') + dot++; + goto dc_i; + break; + case 'B': // B- back a blank-delimited Word + case 'E': // E- end of a blank-delimited word + case 'W': // W- forward a blank-delimited word + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dir = FORWARD; + if (c == 'B') + dir = BACK; + if (c == 'W' || isspace(dot[dir])) { + dot = skip_thing(dot, 1, dir, S_TO_WS); + dot = skip_thing(dot, 2, dir, S_OVER_WS); + } + if (c != 'W') + dot = skip_thing(dot, 1, dir, S_BEFORE_WS); + break; + case 'C': // C- Change to e-o-l + case 'D': // D- delete to e-o-l + save_dot = dot; + dot = dollar_line(dot); // move to before NL + // copy text into a register and delete + dot = yank_delete(save_dot, dot, 0, YANKDEL); // delete to e-o-l + if (c == 'C') + goto dc_i; // start inserting +#ifdef CONFIG_FEATURE_VI_DOT_CMD + if (c == 'D') + end_cmd_q(); // stop adding to q +#endif /* CONFIG_FEATURE_VI_DOT_CMD */ + break; + case 'G': // G- goto to a line number (default= E-O-F) + dot = end - 1; // assume E-O-F + if (cmdcnt > 0) { + dot = find_line(cmdcnt); // what line is #cmdcnt + } + dot_skip_over_ws(); + break; + case 'H': // H- goto top line on screen + dot = screenbegin; + if (cmdcnt > (rows - 1)) { + cmdcnt = (rows - 1); + } + if (cmdcnt-- > 1) { + do_cmd('+'); + } // repeat cnt + dot_skip_over_ws(); + break; + case 'I': // I- insert before first non-blank + dot_begin(); // 0 + dot_skip_over_ws(); + //**** fall thru to ... 'i' + case 'i': // i- insert before current char + case VI_K_INSERT: // Cursor Key Insert + dc_i: + cmd_mode = 1; // start insrting + break; + case 'J': // J- join current and next lines together + if (cmdcnt-- > 2) { + do_cmd(c); + } // repeat cnt + dot_end(); // move to NL + if (dot < end - 1) { // make sure not last char in text[] + *dot++ = ' '; // replace NL with space + file_modified++; + while (isblnk(*dot)) { // delete leading WS + dot_delete(); + } + } + end_cmd_q(); // stop adding to q + break; + case 'L': // L- goto bottom line on screen + dot = end_screen(); + if (cmdcnt > (rows - 1)) { + cmdcnt = (rows - 1); + } + if (cmdcnt-- > 1) { + do_cmd('-'); + } // repeat cnt + dot_begin(); + dot_skip_over_ws(); + break; + case 'M': // M- goto middle line on screen + dot = screenbegin; + for (cnt = 0; cnt < (rows-1) / 2; cnt++) + dot = next_line(dot); + break; + case 'O': // O- open a empty line above + // 0i\n ESC -i + p = begin_line(dot); + if (p[-1] == '\n') { + dot_prev(); + case 'o': // o- open a empty line below; Yes, I know it is in the middle of the "if (..." + dot_end(); + dot = char_insert(dot, '\n'); + } else { + dot_begin(); // 0 + dot = char_insert(dot, '\n'); // i\n ESC + dot_prev(); // - + } + goto dc_i; + break; + case 'R': // R- continuous Replace char + dc5: + cmd_mode = 2; + break; + case 'X': // X- delete char before dot + case 'x': // x- delete the current char + case 's': // s- substitute the current char + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dir = 0; + if (c == 'X') + dir = -1; + if (dot[dir] != '\n') { + if (c == 'X') + dot--; // delete prev char + dot = yank_delete(dot, dot, 0, YANKDEL); // delete char + } + if (c == 's') + goto dc_i; // start insrting + end_cmd_q(); // stop adding to q + break; + case 'Z': // Z- if modified, {write}; exit + // ZZ means to save file (if necessary), then exit + c1 = get_one_char(); + if (c1 != 'Z') { + indicate_error(c); + break; + } + if (file_modified) { +#ifdef CONFIG_FEATURE_VI_READONLY + if (vi_readonly || readonly) { + psbs("\"%s\" File is read only", cfn); + break; + } +#endif /* CONFIG_FEATURE_VI_READONLY */ + cnt = file_write(cfn, text, end - 1); + if (cnt < 0) { + if (cnt == -1) + psbs("Write error: %s", strerror(errno)); + } else if (cnt == (end - 1 - text + 1)) { + editing = 0; + } + } else { + editing = 0; + } + break; + case '^': // ^- move to first non-blank on line + dot_begin(); + dot_skip_over_ws(); + break; + case 'b': // b- back a word + case 'e': // e- end of word + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dir = FORWARD; + if (c == 'b') + dir = BACK; + if ((dot + dir) < text || (dot + dir) > end - 1) + break; + dot += dir; + if (isspace(*dot)) { + dot = skip_thing(dot, (c == 'e') ? 2 : 1, dir, S_OVER_WS); + } + if (isalnum(*dot) || *dot == '_') { + dot = skip_thing(dot, 1, dir, S_END_ALNUM); + } else if (ispunct(*dot)) { + dot = skip_thing(dot, 1, dir, S_END_PUNCT); + } + break; + case 'c': // c- change something + case 'd': // d- delete something +#ifdef CONFIG_FEATURE_VI_YANKMARK + case 'y': // y- yank something + case 'Y': // Y- Yank a line +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + yf = YANKDEL; // assume either "c" or "d" +#ifdef CONFIG_FEATURE_VI_YANKMARK + if (c == 'y' || c == 'Y') + yf = YANKONLY; +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + c1 = 'y'; + if (c != 'Y') + c1 = get_one_char(); // get the type of thing to delete + find_range(&p, &q, c1); + if (c1 == 27) { // ESC- user changed mind and wants out + c = c1 = 27; // Escape- do nothing + } else if (strchr("wW", c1)) { + if (c == 'c') { + // don't include trailing WS as part of word + while (isblnk(*q)) { + if (q <= text || q[-1] == '\n') + break; + q--; + } + } + dot = yank_delete(p, q, 0, yf); // delete word + } else if (strchr("^0bBeEft$", c1)) { + // single line copy text into a register and delete + dot = yank_delete(p, q, 0, yf); // delete word + } else if (strchr("cdykjHL%+-{}\r\n", c1)) { + // multiple line copy text into a register and delete + dot = yank_delete(p, q, 1, yf); // delete lines + if (c == 'c') { + dot = char_insert(dot, '\n'); + // on the last line of file don't move to prev line + if (dot != (end-1)) { + dot_prev(); + } + } else if (c == 'd') { + dot_begin(); + dot_skip_over_ws(); + } + } else { + // could not recognize object + c = c1 = 27; // error- + indicate_error(c); + } + if (c1 != 27) { + // if CHANGING, not deleting, start inserting after the delete + if (c == 'c') { + strcpy((char *) buf, "Change"); + goto dc_i; // start inserting + } + if (c == 'd') { + strcpy((char *) buf, "Delete"); + } +#ifdef CONFIG_FEATURE_VI_YANKMARK + if (c == 'y' || c == 'Y') { + strcpy((char *) buf, "Yank"); + } + p = reg[YDreg]; + q = p + strlen((char *) p); + for (cnt = 0; p <= q; p++) { + if (*p == '\n') + cnt++; + } + psb("%s %d lines (%d chars) using [%c]", + buf, cnt, strlen((char *) reg[YDreg]), what_reg()); +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + end_cmd_q(); // stop adding to q + } + break; + case 'k': // k- goto prev line, same col + case VI_K_UP: // cursor key Up + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + dot_prev(); + dot = move_to_col(dot, ccol + offset); // try stay in same col + break; + case 'r': // r- replace the current char with user input + c1 = get_one_char(); // get the replacement char + if (*dot != '\n') { + *dot = c1; + file_modified++; // has the file been modified + } + end_cmd_q(); // stop adding to q + break; + case 't': // t- move to char prior to next x + last_forward_char = get_one_char(); + do_cmd(';'); + if (*dot == last_forward_char) + dot_left(); + last_forward_char= 0; + break; + case 'w': // w- forward a word + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + if (isalnum(*dot) || *dot == '_') { // we are on ALNUM + dot = skip_thing(dot, 1, FORWARD, S_END_ALNUM); + } else if (ispunct(*dot)) { // we are on PUNCT + dot = skip_thing(dot, 1, FORWARD, S_END_PUNCT); + } + if (dot < end - 1) + dot++; // move over word + if (isspace(*dot)) { + dot = skip_thing(dot, 2, FORWARD, S_OVER_WS); + } + break; + case 'z': // z- + c1 = get_one_char(); // get the replacement char + cnt = 0; + if (c1 == '.') + cnt = (rows - 2) / 2; // put dot at center + if (c1 == '-') + cnt = rows - 2; // put dot at bottom + screenbegin = begin_line(dot); // start dot at top + dot_scroll(cnt, -1); + break; + case '|': // |- move to column "cmdcnt" + dot = move_to_col(dot, cmdcnt - 1); // try to move to column + break; + case '~': // ~- flip the case of letters a-z -> A-Z + if (cmdcnt-- > 1) { + do_cmd(c); + } // repeat cnt + if (islower(*dot)) { + *dot = toupper(*dot); + file_modified++; // has the file been modified + } else if (isupper(*dot)) { + *dot = tolower(*dot); + file_modified++; // has the file been modified + } + dot_right(); + end_cmd_q(); // stop adding to q + break; + //----- The Cursor and Function Keys ----------------------------- + case VI_K_HOME: // Cursor Key Home + dot_begin(); + break; + // The Fn keys could point to do_macro which could translate them + case VI_K_FUN1: // Function Key F1 + case VI_K_FUN2: // Function Key F2 + case VI_K_FUN3: // Function Key F3 + case VI_K_FUN4: // Function Key F4 + case VI_K_FUN5: // Function Key F5 + case VI_K_FUN6: // Function Key F6 + case VI_K_FUN7: // Function Key F7 + case VI_K_FUN8: // Function Key F8 + case VI_K_FUN9: // Function Key F9 + case VI_K_FUN10: // Function Key F10 + case VI_K_FUN11: // Function Key F11 + case VI_K_FUN12: // Function Key F12 + break; + } + + dc1: + // if text[] just became empty, add back an empty line + if (end == text) { + (void) char_insert(text, '\n'); // start empty buf with dummy line + dot = text; + } + // it is OK for dot to exactly equal to end, otherwise check dot validity + if (dot != end) { + dot = bound_dot(dot); // make sure "dot" is valid + } +#ifdef CONFIG_FEATURE_VI_YANKMARK + check_context(c); // update the current context +#endif /* CONFIG_FEATURE_VI_YANKMARK */ + + if (!isdigit(c)) + cmdcnt = 0; // cmd was not a number, reset cmdcnt + cnt = dot - begin_line(dot); + // Try to stay off of the Newline + if (*dot == '\n' && cnt > 0 && cmd_mode == 0) + dot--; +} + +#ifdef CONFIG_FEATURE_VI_CRASHME +static int totalcmds = 0; +static int Mp = 85; // Movement command Probability +static int Np = 90; // Non-movement command Probability +static int Dp = 96; // Delete command Probability +static int Ip = 97; // Insert command Probability +static int Yp = 98; // Yank command Probability +static int Pp = 99; // Put command Probability +static int M = 0, N = 0, I = 0, D = 0, Y = 0, P = 0, U = 0; +char chars[20] = "\t012345 abcdABCD-=.$"; +char *words[20] = { "this", "is", "a", "test", + "broadcast", "the", "emergency", "of", + "system", "quick", "brown", "fox", + "jumped", "over", "lazy", "dogs", + "back", "January", "Febuary", "March" +}; +char *lines[20] = { + "You should have received a copy of the GNU General Public License\n", + "char c, cm, *cmd, *cmd1;\n", + "generate a command by percentages\n", + "Numbers may be typed as a prefix to some commands.\n", + "Quit, discarding changes!\n", + "Forced write, if permission originally not valid.\n", + "In general, any ex or ed command (such as substitute or delete).\n", + "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n", + "Please get w/ me and I will go over it with you.\n", + "The following is a list of scheduled, committed changes.\n", + "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n", + "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n", + "Any question about transactions please contact Sterling Huxley.\n", + "I will try to get back to you by Friday, December 31.\n", + "This Change will be implemented on Friday.\n", + "Let me know if you have problems accessing this;\n", + "Sterling Huxley recently added you to the access list.\n", + "Would you like to go to lunch?\n", + "The last command will be automatically run.\n", + "This is too much english for a computer geek.\n", +}; +char *multilines[20] = { + "You should have received a copy of the GNU General Public License\n", + "char c, cm, *cmd, *cmd1;\n", + "generate a command by percentages\n", + "Numbers may be typed as a prefix to some commands.\n", + "Quit, discarding changes!\n", + "Forced write, if permission originally not valid.\n", + "In general, any ex or ed command (such as substitute or delete).\n", + "I have tickets available for the Blazers vs LA Clippers for Monday, Janurary 1 at 1:00pm.\n", + "Please get w/ me and I will go over it with you.\n", + "The following is a list of scheduled, committed changes.\n", + "1. Launch Norton Antivirus (Start, Programs, Norton Antivirus)\n", + "Reminder....Town Meeting in Central Perk cafe today at 3:00pm.\n", + "Any question about transactions please contact Sterling Huxley.\n", + "I will try to get back to you by Friday, December 31.\n", + "This Change will be implemented on Friday.\n", + "Let me know if you have problems accessing this;\n", + "Sterling Huxley recently added you to the access list.\n", + "Would you like to go to lunch?\n", + "The last command will be automatically run.\n", + "This is too much english for a computer geek.\n", +}; + +// create a random command to execute +static void crash_dummy() +{ + static int sleeptime; // how long to pause between commands + char c, cm, *cmd, *cmd1; + int i, cnt, thing, rbi, startrbi, percent; + + // "dot" movement commands + cmd1 = " \n\r\002\004\005\006\025\0310^$-+wWeEbBhjklHL"; + + // is there already a command running? + if (readed_for_parse > 0) + goto cd1; + cd0: + startrbi = rbi = 0; + sleeptime = 0; // how long to pause between commands + memset(readbuffer, '\0', BUFSIZ); // clear the read buffer + // generate a command by percentages + percent = (int) lrand48() % 100; // get a number from 0-99 + if (percent < Mp) { // Movement commands + // available commands + cmd = cmd1; + M++; + } else if (percent < Np) { // non-movement commands + cmd = "mz<>\'\""; // available commands + N++; + } else if (percent < Dp) { // Delete commands + cmd = "dx"; // available commands + D++; + } else if (percent < Ip) { // Inset commands + cmd = "iIaAsrJ"; // available commands + I++; + } else if (percent < Yp) { // Yank commands + cmd = "yY"; // available commands + Y++; + } else if (percent < Pp) { // Put commands + cmd = "pP"; // available commands + P++; + } else { + // We do not know how to handle this command, try again + U++; + goto cd0; + } + // randomly pick one of the available cmds from "cmd[]" + i = (int) lrand48() % strlen(cmd); + cm = cmd[i]; + if (strchr(":\024", cm)) + goto cd0; // dont allow colon or ctrl-T commands + readbuffer[rbi++] = cm; // put cmd into input buffer + + // now we have the command- + // there are 1, 2, and multi char commands + // find out which and generate the rest of command as necessary + if (strchr("dmryz<>\'\"", cm)) { // 2-char commands + cmd1 = " \n\r0$^-+wWeEbBhjklHL"; + if (cm == 'm' || cm == '\'' || cm == '\"') { // pick a reg[] + cmd1 = "abcdefghijklmnopqrstuvwxyz"; + } + thing = (int) lrand48() % strlen(cmd1); // pick a movement command + c = cmd1[thing]; + readbuffer[rbi++] = c; // add movement to input buffer + } + if (strchr("iIaAsc", cm)) { // multi-char commands + if (cm == 'c') { + // change some thing + thing = (int) lrand48() % strlen(cmd1); // pick a movement command + c = cmd1[thing]; + readbuffer[rbi++] = c; // add movement to input buffer + } + thing = (int) lrand48() % 4; // what thing to insert + cnt = (int) lrand48() % 10; // how many to insert + for (i = 0; i < cnt; i++) { + if (thing == 0) { // insert chars + readbuffer[rbi++] = chars[((int) lrand48() % strlen(chars))]; + } else if (thing == 1) { // insert words + strcat((char *) readbuffer, words[(int) lrand48() % 20]); + strcat((char *) readbuffer, " "); + sleeptime = 0; // how fast to type + } else if (thing == 2) { // insert lines + strcat((char *) readbuffer, lines[(int) lrand48() % 20]); + sleeptime = 0; // how fast to type + } else { // insert multi-lines + strcat((char *) readbuffer, multilines[(int) lrand48() % 20]); + sleeptime = 0; // how fast to type + } + } + strcat((char *) readbuffer, "\033"); + } + readed_for_parse = strlen(readbuffer); + cd1: + totalcmds++; + if (sleeptime > 0) + (void) mysleep(sleeptime); // sleep 1/100 sec +} + +// test to see if there are any errors +static void crash_test() +{ + static time_t oldtim; + time_t tim; + char d[2], msg[BUFSIZ]; + + msg[0] = '\0'; + if (end < text) { + strcat((char *) msg, "end textend) { + strcat((char *) msg, "end>textend "); + } + if (dot < text) { + strcat((char *) msg, "dot end) { + strcat((char *) msg, "dot>end "); + } + if (screenbegin < text) { + strcat((char *) msg, "screenbegin end - 1) { + strcat((char *) msg, "screenbegin>end-1 "); + } + + if (strlen(msg) > 0) { + alarm(0); + printf("\n\n%d: \'%c\' %s\n\n\n%s[Hit return to continue]%s", + totalcmds, last_input_char, msg, SOs, SOn); + fflush(stdout); + while (read(0, d, 1) > 0) { + if (d[0] == '\n' || d[0] == '\r') + break; + } + alarm(3); + } + tim = (time_t) time((time_t *) 0); + if (tim >= (oldtim + 3)) { + sprintf((char *) status_buffer, + "Tot=%d: M=%d N=%d I=%d D=%d Y=%d P=%d U=%d size=%d", + totalcmds, M, N, I, D, Y, P, U, end - text + 1); + oldtim = tim; + } + return; +} +#endif /* CONFIG_FEATURE_VI_CRASHME */ diff --git a/examples/bootfloppy/bootfloppy.txt b/examples/bootfloppy/bootfloppy.txt new file mode 100644 index 000000000..b514e6305 --- /dev/null +++ b/examples/bootfloppy/bootfloppy.txt @@ -0,0 +1,180 @@ +Building a Busybox Boot Floppy +============================== + +This document describes how to buid a boot floppy using the following +components: + + - Linux Kernel (http://www.kernel.org) + - uClibc: C library (http://www.uclibc.org/) + - Busybox: Unix utilities (http://busybox.net/) + - Syslinux: bootloader (http://syslinux.zytor.com) + +It is based heavily on a paper presented by Erik Andersen at the 2001 Embedded +Systems Conference. + + + +Building The Software Components +-------------------------------- + +Detailed instructions on how to build Busybox, uClibc, or a working Linux +kernel are beyond the scope of this document. The following guidelines will +help though: + + - Stock Busybox from CVS or a tarball will work with no modifications to + any files. Just extract and go. + - Ditto uClibc. + - Your Linux kernel must include support for initrd or else the floppy + won't be able to mount it's root file system. + +If you require further information on building Busybox uClibc or Linux, please +refer to the web pages and documentation for those individual programs. + + + +Making a Root File System +------------------------- + +The following steps will create a root file system. + + - Create an empty file that you can format as a filesystem: + + dd if=/dev/zero of=rootfs bs=1k count=4000 + + - Set up the rootfs file we just created to be used as a loop device (may not + be necessary) + + losetup /dev/loop0 rootfs + + - Format the rootfs file with a filesystem: + + mkfs.ext2 -F -i 2000 rootfs + + - Mount the file on a mountpoint so we can place files in it: + + mkdir loop + mount -o loop rootfs loop/ + + (you will probably need to be root to do this) + + - Copy on the C library, the dynamic linking library, and other necessary + libraries. For this example, we copy the following files from the uClibc + tree: + + mkdir loop/lib + (chdir to uClibc directory) + cp -a libc.so* uClibc*.so \ + ld.so-1/d-link/ld-linux-uclibc.so* \ + ld.so-1/libdl/libdl.so* \ + crypt/libcrypt.so* \ + (path to)loop/lib + + - Install the Busybox binary and accompanying symlinks: + + (chdir to busybox directory) + make PREFIX=(path to)loop/ install + + - Make device files in /dev: + + This can be done by running the 'mkdevs.sh' script. If you want the gory + details, you can read the script. + + - Make necessary files in /etc: + + For this, just cp -a the etc/ directory onto rootfs. Again, if you want + all the details, you can just look at the files in the dir. + + - Unmount the rootfs from the mountpoint: + + umount loop + + - Compress it: + + gzip -9 rootfs + + +Making a SYSLINUX boot floppy +----------------------------- + +The following steps will create the boot floppy. + +Note: You will need to have the mtools package installed beforehand. + + - Insert a floppy in the drive and format it with an MSDOS filesystem: + + mformat a: + + (if the system doesn't know what device 'a:' is, look at /etc/mtools.conf) + + - Run syslinux on the floppy: + + syslinux -s /dev/fd0 + + (the -s stands for "safe, slow, and stupid" and should work better with + buggy BIOSes; it can be omitted) + + - Put on a syslinux.cfg file: + + mcopy syslinux.cfg a: + + (more on syslinux.cfg below) + + - Copy the root file system you made onto the MSDOS formatted floppy + + mcopy rootfs.gz a: + + - Build a linux kernel and copy it onto the disk with the filename 'linux' + + mcopy bzImage a:linux + + +Sample syslinux.cfg +~~~~~~~~~~~~~~~~~~~ + +The following simple syslinux.cfg file should work. You can tweak it if you +like. + +----begin-syslinux.cfg--------------- +DEFAULT linux +APPEND initrd=rootfs.gz root=/dev/ram0 +TIMEOUT 10 +PROMPT 1 +----end-syslinux.cfg--------------- + +Some changes you could make to syslinux.cfg: + + - This value is the number seconds it will wait before booting. You can set + the timeout to 0 (or omit) to boot instantly, or you can set it as high as + 10 to wait awhile. + + - PROMPT can be set to 0 to disable the 'boot:' prompt. + + - you can add this line to display the contents of a file as a welcome + message: + + DISPLAY display.txt + + + +Additional Resources +-------------------- + +Other useful information on making a Linux bootfloppy is available at the +following URLs: + +http://www.linuxdoc.org/HOWTO/Bootdisk-HOWTO/index.html +http://www.linux-embedded.com/howto/Embedded-Linux-Howto.html +http://linux-embedded.org/howto/LFS-HOWTO.html +http://linux-embedded.org/pmhowto.html +http://recycle.lbl.gov/~ldoolitt/embedded/ (Larry Doolittle's stuff) + + + +Possible TODOs +-------------- + +The following features that we might want to add later: + + - support for additional filesystems besides ext2, i.e. minix + - different libc, static vs dynamic loading + - maybe using an alternate bootloader diff --git a/examples/bootfloppy/display.txt b/examples/bootfloppy/display.txt new file mode 100644 index 000000000..399d326d9 --- /dev/null +++ b/examples/bootfloppy/display.txt @@ -0,0 +1,4 @@ + +This boot floppy is made with Busybox, uClibc, and the Linux kernel. +Hit RETURN to boot or enter boot parameters at the prompt below. + diff --git a/examples/bootfloppy/etc/fstab b/examples/bootfloppy/etc/fstab new file mode 100644 index 000000000..ef14ca2cc --- /dev/null +++ b/examples/bootfloppy/etc/fstab @@ -0,0 +1,2 @@ +proc /proc proc defaults 0 0 + diff --git a/examples/bootfloppy/etc/init.d/rcS b/examples/bootfloppy/etc/init.d/rcS new file mode 100755 index 000000000..4f29b923f --- /dev/null +++ b/examples/bootfloppy/etc/init.d/rcS @@ -0,0 +1,3 @@ +#! /bin/sh + +/bin/mount -a diff --git a/examples/bootfloppy/etc/inittab b/examples/bootfloppy/etc/inittab new file mode 100644 index 000000000..eb3e979ce --- /dev/null +++ b/examples/bootfloppy/etc/inittab @@ -0,0 +1,5 @@ +::sysinit:/etc/init.d/rcS +::respawn:-/bin/sh +tty2::askfirst:-/bin/sh +::ctrlaltdel:/bin/umount -a -r + diff --git a/examples/bootfloppy/etc/profile b/examples/bootfloppy/etc/profile new file mode 100644 index 000000000..8a7c77d78 --- /dev/null +++ b/examples/bootfloppy/etc/profile @@ -0,0 +1,8 @@ +# /etc/profile: system-wide .profile file for the Bourne shells + +echo +echo -n "Processing /etc/profile... " +# no-op +echo "Done" +echo + diff --git a/examples/bootfloppy/mkdevs.sh b/examples/bootfloppy/mkdevs.sh new file mode 100755 index 000000000..03a1a8550 --- /dev/null +++ b/examples/bootfloppy/mkdevs.sh @@ -0,0 +1,62 @@ +#!/bin/sh +# +# makedev.sh - creates device files for a busybox boot floppy image + + +# we do our work in the dev/ directory +if [ -z "$1" ]; then + echo "usage: `basename $0` path/to/dev/dir" + exit 1 +fi + +cd $1 + + +# miscellaneous one-of-a-kind stuff +mknod console c 5 1 +mknod full c 1 7 +mknod kmem c 1 2 +mknod mem c 1 1 +mknod null c 1 3 +mknod port c 1 4 +mknod random c 1 8 +mknod urandom c 1 9 +mknod zero c 1 5 +ln -s /proc/kcore core + +# IDE HD devs +# note: not going to bother creating all concievable partitions; you can do +# that yourself as you need 'em. +mknod hda b 3 0 +mknod hdb b 3 64 +mknod hdc b 22 0 +mknod hdd b 22 64 + +# loop devs +for i in `seq 0 7`; do + mknod loop$i b 7 $i +done + +# ram devs +for i in `seq 0 9`; do + mknod ram$i b 1 $i +done +ln -s ram1 ram + +# ttys +mknod tty c 5 0 +for i in `seq 0 9`; do + mknod tty$i c 4 $i +done + +# virtual console screen devs +for i in `seq 0 9`; do + mknod vcs$i b 7 $i +done +ln -s vcs0 vcs + +# virtual console screen w/ attributes devs +for i in `seq 0 9`; do + mknod vcsa$i b 7 $i +done +ln -s vcsa0 vcsa diff --git a/examples/bootfloppy/mkrootfs.sh b/examples/bootfloppy/mkrootfs.sh new file mode 100755 index 000000000..e79ed418e --- /dev/null +++ b/examples/bootfloppy/mkrootfs.sh @@ -0,0 +1,105 @@ +#!/bin/bash +# +# mkrootfs.sh - creates a root file system +# + +# TODO: need to add checks here to verify that busybox, uClibc and bzImage +# exist + + +# command-line settable variables +BUSYBOX_DIR=.. +UCLIBC_DIR=../../uClibc +TARGET_DIR=./loop +FSSIZE=4000 +CLEANUP=1 +MKFS='mkfs.ext2 -F' + +# don't-touch variables +BASE_DIR=`pwd` + + +while getopts 'b:u:s:t:Cm' opt +do + case $opt in + b) BUSYBOX_DIR=$OPTARG ;; + u) UCLIBC_DIR=$OPTARG ;; + t) TARGET_DIR=$OPTARG ;; + s) FSSIZE=$OPTARG ;; + C) CLEANUP=0 ;; + m) MKFS='mkfs.minix' ;; + *) + echo "usage: `basename $0` [-bu]" + echo " -b DIR path to busybox direcory (default ..)" + echo " -u DIR path to uClibc direcory (default ../../uClibc)" + echo " -t DIR path to target direcory (default ./loop)" + echo " -s SIZE size of root filesystem in Kbytes (default 4000)" + echo " -C don't perform cleanup (umount target dir, gzip rootfs, etc.)" + echo " (this allows you to 'chroot loop/ /bin/sh' to test it)" + echo " -m use minix filesystem (default is ext2)" + exit 1 + ;; + esac +done + + + + +# clean up from any previous work +mount | grep -q loop +[ $? -eq 0 ] && umount $TARGET_DIR +[ -d $TARGET_DIR ] && rm -rf $TARGET_DIR/ +[ -f rootfs ] && rm -f rootfs +[ -f rootfs.gz ] && rm -f rootfs.gz + + +# prepare root file system and mount as loopback +dd if=/dev/zero of=rootfs bs=1k count=$FSSIZE +$MKFS -i 2000 rootfs +mkdir $TARGET_DIR +mount -o loop,exec rootfs $TARGET_DIR # must be root + + +# install uClibc +mkdir -p $TARGET_DIR/lib +cd $UCLIBC_DIR +make INSTALL_DIR= +cp -a libc.so* $BASE_DIR/$TARGET_DIR/lib +cp -a uClibc*.so $BASE_DIR/$TARGET_DIR/lib +cp -a ld.so-1/d-link/ld-linux-uclibc.so* $BASE_DIR/$TARGET_DIR/lib +cp -a ld.so-1/libdl/libdl.so* $BASE_DIR/$TARGET_DIR/lib +cp -a crypt/libcrypt.so* $BASE_DIR/$TARGET_DIR/lib +cd $BASE_DIR + + +# install busybox and components +cd $BUSYBOX_DIR +make distclean +make CC=$BASE_DIR/$UCLIBC_DIR/extra/gcc-uClibc/i386-uclibc-gcc +make PREFIX=$BASE_DIR/$TARGET_DIR install +cd $BASE_DIR + + +# make files in /dev +mkdir $TARGET_DIR/dev +./mkdevs.sh $TARGET_DIR/dev + + +# make files in /etc +cp -a etc $TARGET_DIR +ln -s /proc/mounts $TARGET_DIR/etc/mtab + + +# other miscellaneous setup +mkdir $TARGET_DIR/initrd +mkdir $TARGET_DIR/proc + + +# Done. Maybe do cleanup. +if [ $CLEANUP -eq 1 ] +then + umount $TARGET_DIR + rmdir $TARGET_DIR + gzip -9 rootfs +fi + diff --git a/examples/bootfloppy/mksyslinux.sh b/examples/bootfloppy/mksyslinux.sh new file mode 100755 index 000000000..e25417353 --- /dev/null +++ b/examples/bootfloppy/mksyslinux.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# +# Formats a floppy to use Syslinux + +dummy="" + + +# need to have mtools installed +if [ -z `which mformat` -o -z `which mcopy` ]; then + echo "You must have the mtools package installed to run this script" + exit 1 +fi + + +# need an arg for the location of the kernel +if [ -z "$1" ]; then + echo "usage: `basename $0` path/to/linux/kernel" + exit 1 +fi + + +# need to have a root file system built +if [ ! -f rootfs.gz ]; then + echo "You need to have a rootfs built first." + echo "Hit RETURN to make one now or Control-C to quit." + read dummy + ./mkrootfs.sh +fi + + +# prepare the floppy +echo "Please insert a blank floppy in the drive and press RETURN to format" +echo "(WARNING: All data will be erased! Hit Control-C to abort)" +read dummy + +echo "Formatting the floppy..." +mformat a: +echo "Making it bootable with Syslinux..." +syslinux -s /dev/fd0 +echo "Copying Syslinux configuration files..." +mcopy syslinux.cfg display.txt a: +echo "Copying root filesystem file..." +mcopy rootfs.gz a: +# XXX: maybe check for "no space on device" errors here +echo "Copying linux kernel..." +mcopy $1 a:linux +# XXX: maybe check for "no space on device" errors here too +echo "Finished: boot floppy created" diff --git a/examples/bootfloppy/quickstart.txt b/examples/bootfloppy/quickstart.txt new file mode 100644 index 000000000..0d8090824 --- /dev/null +++ b/examples/bootfloppy/quickstart.txt @@ -0,0 +1,15 @@ +Quickstart on making the Busybox boot-floppy: + + 1) Download Busybox and uClibc from CVS or tarballs. Make sure they share a + common parent directory. (i.e. busybox/ and uclibc/ are both right off of + /tmp, or wherever.) + + 2) Build a Linux kernel. Make sure you include support for initrd. + + 3) Put a floppy in the drive. Make sure it is a floppy you don't care about + because the contents will be overwritten. + + 4) As root, type ./mksyslinux.sh path/to/linux/kernel from this directory. + Wait patiently while the magic happens. + + 5) Boot up on the floppy. diff --git a/examples/bootfloppy/syslinux.cfg b/examples/bootfloppy/syslinux.cfg new file mode 100644 index 000000000..fa2677ca8 --- /dev/null +++ b/examples/bootfloppy/syslinux.cfg @@ -0,0 +1,7 @@ +display display.txt +default linux +timeout 10 +prompt 1 +label linux + kernel linux + append initrd=rootfs.gz root=/dev/ram0 diff --git a/examples/busybox.spec b/examples/busybox.spec new file mode 100644 index 000000000..3986436c1 --- /dev/null +++ b/examples/busybox.spec @@ -0,0 +1,44 @@ +%define name busybox +%define epoch 0 +%define version 0.61.pre +%define release %(date -I | sed -e 's/-/_/g') +%define serial 1 + +Name: %{name} +#Epoch: %{epoch} +Version: %{version} +Release: %{release} +Serial: %{serial} +Copyright: GPL +Group: System/Utilities +Summary: BusyBox is a tiny suite of Unix utilities in a multi-call binary. +URL: http://busybox.net/ +Source: ftp://busybox.net/busybox/%{name}-%{version}.tar.gz +Buildroot: /var/tmp/%{name}-%{version} +Packager : Erik Andersen + +%Description +BusyBox combines tiny versions of many common UNIX utilities into a single +small executable. It provides minimalist replacements for most of the utilities +you usually find in fileutils, shellutils, findutils, textutils, grep, gzip, +tar, etc. BusyBox provides a fairly complete POSIX environment for any small +or emdedded system. The utilities in BusyBox generally have fewer options then +their full featured GNU cousins; however, the options that are provided behave +very much like their GNU counterparts. + +%Prep +%setup -q -n %{name}-%{version} + +%Build +make + +%Install +rm -rf $RPM_BUILD_ROOT +make PREFIX=$RPM_BUILD_ROOT install + +%Clean +rm -rf $RPM_BUILD_ROOT + +%Files +%defattr(-,root,root) +/ diff --git a/examples/depmod.pl b/examples/depmod.pl new file mode 100755 index 000000000..b2bf54713 --- /dev/null +++ b/examples/depmod.pl @@ -0,0 +1,289 @@ +#!/usr/bin/perl -w +# vi: set sw=4 ts=4: +# Copyright (c) 2001 David Schleef +# Copyright (c) 2001 Erik Andersen +# Copyright (c) 2001 Stuart Hughes +# Copyright (c) 2002 Steven J. Hill +# Copyright (c) 2006 Freescale Semiconductor, Inc +# +# History: +# March 2006: Stuart Hughes . +# Significant updates, including implementing the '-F' option +# and adding support for 2.6 kernels. + +# This program is free software; you can redistribute it and/or modify it +# under the same terms as Perl itself. +use Getopt::Long; +use File::Find; +use strict; + +# Set up some default values +my $kdir=""; +my $basedir=""; +my $kernel=""; +my $kernelsyms=""; +my $stdout=0; +my $verbose=0; +my $help=0; +my $nm = $ENV{'NM'} || "nm"; + +# more globals +my (@liblist) = (); +my $exp = {}; +my $dep = {}; +my $mod = {}; + +my $usage = < | -F } [options]... + Where: + -h --help : Show this help screen + -b --basedir : Modules base directory (e.g /lib/modules/<2.x.y>) + -k --kernel : Kernel binary for the target (e.g. vmlinux) + -F --kernelsyms : Kernel symbol file (e.g. System.map) + -n --stdout : Write to stdout instead of /modules.dep + -v --verbose : Print out lots of debugging stuff +TXT + +# get command-line options +GetOptions( + "help|h" => \$help, + "basedir|b=s" => \$basedir, + "kernel|k=s" => \$kernel, + "kernelsyms|F=s" => \$kernelsyms, + "stdout|n" => \$stdout, + "verbose|v" => \$verbose, +); + +die $usage if $help; +die $usage unless $basedir && ( $kernel || $kernelsyms ); +die "can't use both -k and -F\n\n$usage" if $kernel && $kernelsyms; + +# Strip any trailing or multiple slashes from basedir +$basedir =~ s-(/)\1+-/-g; + +# The base directory should contain /lib/modules somewhere +if($basedir !~ m-/lib/modules-) { + warn "WARNING: base directory does not match ..../lib/modules\n"; +} + +# if no kernel version is contained in the basedir, try to find one +if($basedir !~ m-/lib/modules/\d\.\d-) { + opendir(BD, $basedir) or die "can't open basedir $basedir : $!\n"; + foreach ( readdir(BD) ) { + next if /^\.\.?$/; + next unless -d "$basedir/$_"; + warn "dir = $_\n" if $verbose; + if( /^\d\.\d/ ) { + $kdir = $_; + warn("Guessed module directory as $basedir/$kdir\n"); + last; + } + } + closedir(BD); + die "Cannot find a kernel version under $basedir\n" unless $kdir; + $basedir = "$basedir/$kdir"; +} + +# Find the list of .o or .ko files living under $basedir +warn "**** Locating all modules\n" if $verbose; +find sub { + my $file; + if ( -f $_ && ! -d $_ ) { + $file = $File::Find::name; + if ( $file =~ /\.k?o$/ ) { + push(@liblist, $file); + warn "$file\n" if $verbose; + } + } +}, $basedir; +warn "**** Finished locating modules\n" if $verbose; + +foreach my $obj ( @liblist ){ + # turn the input file name into a target tag name + my ($tgtname) = $obj =~ m-(/lib/modules/.*)$-; + + warn "\nMODULE = $tgtname\n" if $verbose; + + # get a list of symbols + my @output=`$nm $obj`; + + build_ref_tables($tgtname, \@output, $exp, $dep); +} + + +# vmlinux is a special name that is only used to resolve symbols +my $tgtname = 'vmlinux'; +my @output = $kernelsyms ? `cat $kernelsyms` : `$nm $kernel`; +warn "\nMODULE = $tgtname\n" if $verbose; +build_ref_tables($tgtname, \@output, $exp, $dep); + +# resolve the dependencies for each module +# reduce dependencies: remove unresolvable and resolved from vmlinux/System.map +# remove duplicates +foreach my $module (keys %$dep) { + warn "reducing module: $module\n" if $verbose; + $mod->{$module} = {}; + foreach (@{$dep->{$module}}) { + if( $exp->{$_} ) { + warn "resolved symbol $_ in file $exp->{$_}\n" if $verbose; + next if $exp->{$_} =~ /vmlinux/; + $mod->{$module}{$exp->{$_}} = 1; + } else { + warn "unresolved symbol $_ in file $module\n"; + } + } +} + +# figure out where the output should go +if ($stdout == 0) { + open(STDOUT, ">$basedir/modules.dep") + or die "cannot open $basedir/modules.dep: $!"; +} +my $kseries = $basedir =~ m,/2\.6\.[^/]*, ? '2.6' : '2.4'; + +foreach my $module ( keys %$mod ) { + if($kseries eq '2.4') { + print "$module:\t"; + my @sorted = sort bydep keys %{$mod->{$module}}; + print join(" \\\n\t",@sorted); + print "\n\n"; + } else { + print "$module: "; + my @sorted = sort bydep keys %{$mod->{$module}}; + print join(" ",@sorted); + print "\n"; + } +} + + +sub build_ref_tables +{ + my ($name, $sym_ar, $exp, $dep) = @_; + + my $ksymtab = grep m/ __ksymtab/, @$sym_ar; + + # gather the exported symbols + if($ksymtab){ + # explicitly exported + foreach ( @$sym_ar ) { + / __ksymtab_(.*)$/ and do { + warn "sym = $1\n" if $verbose; + $exp->{$1} = $name; + }; + } + } else { + # exporting all symbols + foreach ( @$sym_ar ) { + / [ABCDGRST] (.*)$/ and do { + warn "syma = $1\n" if $verbose; + $exp->{$1} = $name; + }; + } + } + + # this takes makes sure modules with no dependencies get listed + push @{$dep->{$name}}, 'printk' unless $name eq 'vmlinux'; + + # gather the unresolved symbols + foreach ( @$sym_ar ) { + !/ __this_module/ && / U (.*)$/ and do { + warn "und = $1\n" if $verbose; + push @{$dep->{$name}}, $1; + }; + } +} + +sub bydep +{ + foreach my $f ( keys %{$mod->{$b}} ) { + if($f eq $a) { + return 1; + } + } + return -1; +} + + + +__END__ + +=head1 NAME + +depmod.pl - a cross platform script to generate kernel module +dependency lists (modules.conf) which can then be used by modprobe +on the target platform. + +It supports Linux 2.4 and 2.6 styles of modules.conf (auto-detected) + +=head1 SYNOPSIS + +depmod.pl [OPTION]... [basedir]... + +Example: + + depmod.pl -F linux/System.map -b target/lib/modules/2.6.11 + +=head1 DESCRIPTION + +The purpose of this script is to automagically generate a list of of kernel +module dependencies. This script produces dependency lists that should be +identical to the depmod program from the modutils package. Unlike the depmod +binary, however, depmod.pl is designed to be run on your host system, not +on your target system. + +This script was written by David Schleef to be used in +conjunction with the BusyBox modprobe applet. + +=head1 OPTIONS + +=over 4 + +=item B<-h --help> + +This displays the help message. + +=item B<-b --basedir> + +The base directory uner which the target's modules will be found. This +defaults to the /lib/modules directory. + +If you don't specify the kernel version, this script will search for +one under the specified based directory and use the first thing that +looks like a kernel version. + +=item B<-k --kernel> + +Kernel binary for the target (vmlinux). You must either supply a kernel binary +or a kernel symbol file (using the -F option). + +=item B<-F --kernelsyms> + +Kernel symbol file for the target (System.map). + +=item B<-n --stdout> + +Write to stdout instead of modules.dep +kernel binary for the target (using the -k option). + +=item B<--verbose> + +Verbose (debug) output + +=back + +=head1 COPYRIGHT AND LICENSE + + Copyright (c) 2001 David Schleef + Copyright (c) 2001 Erik Andersen + Copyright (c) 2001 Stuart Hughes + Copyright (c) 2002 Steven J. Hill + Copyright (c) 2006 Freescale Semiconductor, Inc + +This program is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 AUTHOR + +David Schleef + +=cut diff --git a/examples/devfsd.conf b/examples/devfsd.conf new file mode 100644 index 000000000..e90e7102b --- /dev/null +++ b/examples/devfsd.conf @@ -0,0 +1,133 @@ +# Sample /etc/devfsd.conf configuration file. +# Richard Gooch 17-FEB-2002 +# +# adapted for busybox devfsd implementation by Tito +# +# Enable full compatibility mode for old device names. You may comment these +# out if you don't use the old device names. Make sure you know what you're +# doing! +REGISTER .* MKOLDCOMPAT +UNREGISTER .* RMOLDCOMPAT + +# You may comment out the above and uncomment the following if you've +# configured your system to use the original "new" devfs names or the really +# new names +#REGISTER ^vc/ MKOLDCOMPAT +#UNREGISTER ^vc/ RMOLDCOMPAT +#REGISTER ^pty/ MKOLDCOMPAT +#UNREGISTER ^pty/ RMOLDCOMPAT +#REGISTER ^misc/ MKOLDCOMPAT +#UNREGISTER ^misc/ RMOLDCOMPAT + +# You may comment these out if you don't use the original "new" names +REGISTER .* MKNEWCOMPAT +UNREGISTER .* RMNEWCOMPAT + +# Enable module autoloading. You may comment this out if you don't use +# autoloading +# Supported by busybox when CONFIG_DEVFSD_MODLOAD is set. +# This actually doesn't work with busybox modutils but needs +# the real modutils' modprobe +LOOKUP .* MODLOAD + +# Uncomment the following if you want to set the group to "tty" for the +# pseudo-tty devices. This is necessary so that mesg(1) can later be used to +# enable/disable talk requests and wall(1) messages. +REGISTER ^pty/s.* PERMISSIONS -1.tty 0600 +#REGISTER ^pts/.* PERMISSIONS -1.tty 0600 + +# Restoring /dev/log on startup would trigger the minilogd/initlog deadlock +# (minilogd falsely assuming syslogd has been started). +REGISTER ^log$ IGNORE +CREATE ^log$ IGNORE +CHANGE ^log$ IGNORE +DELETE ^log$ IGNORE + +# +# Uncomment this if you want permissions to be saved and restored +# Do not do this for pseudo-terminal devices +REGISTER ^pt[sy] IGNORE +CREATE ^pt[sy] IGNORE +CHANGE ^pt[sy] IGNORE +DELETE ^pt[sy] IGNORE +REGISTER .* COPY /lib/dev-state/$devname $devpath +CREATE .* COPY $devpath /lib/dev-state/$devname +CHANGE .* COPY $devpath /lib/dev-state/$devname +#DELETE .* CFUNCTION GLOBAL unlink /lib/dev-state/$devname +# Busybox +DELETE .* EXECUTE /bin/rm -f /lib/dev-state/$devname + +RESTORE /lib/dev-state + +# +# Uncomment this if you want the old /dev/cdrom symlink +#REGISTER ^cdroms/cdrom0$ CFUNCTION GLOBAL mksymlink $devname cdrom +#UNREGISTER ^cdroms/cdrom0$ CFUNCTION GLOBAL unlink cdrom +# busybox +REGISTER ^cdroms/cdrom0$ EXECUTE /bin/ln -sf $devname cdrom +UNREGISTER ^cdroms/cdrom0$ EXECUTE /bin/rm -f cdrom + +#REGISTER ^v4l/video0$ CFUNCTION GLOBAL mksymlink v4l/video0 video +#UNREGISTER ^v4l/video0$ CFUNCTION GLOBAL unlink video +#REGISTER ^radio0$ CFUNCTION GLOBAL mksymlink radio0 radio +#UNREGISTER ^radio0$ CFUNCTION GLOBAL unlink radio +# Busybox +REGISTER ^v4l/video0$ EXECUTE /bin/ln -sf v4l/video0 video +UNREGISTER ^v4l/video0$ EXECUTE /bin/rm -f video +REGISTER ^radio0$ EXECUTE /bin/ln -sf radio0 radio +UNREGISTER ^radio0$ EXECUTE /bin/rm -f radio + +# ALSA stuff +#LOOKUP snd MODLOAD ACTION snd + +# Uncomment this to let PAM manage devfs +# Not supported by busybox +#REGISTER .* CFUNCTION /lib/security/pam_console_apply_devfsd.so pam_console_apply_single $devpath + +# Uncomment this to manage USB mouse +# Not supported by busybox +#REGISTER ^input/mouse0$ CFUNCTION GLOBAL mksymlink $devname usbmouse +#UNREGISTER ^input/mouse0$ CFUNCTION GLOBAL unlink usbmouse +# Busybox +#REGISTER ^input/mouse0$ EXECUTE /bin/ln -sf $devname usbmouse +#UNREGISTER ^input/mouse0$ EXECUTE /bin/rm -f usbmouse +# Not supported by busybox +#REGISTER ^input/mice$ CFUNCTION GLOBAL mksymlink $devname usbmouse +#UNREGISTER ^input/mice$ CFUNCTION GLOBAL unlink usbmouse +# Busybox +REGISTER ^input/mice$ EXECUTE /bin/ln -sf $devname usbmouse +UNREGISTER ^input/mice$ EXECUTE /bin/rm -f usbmouse + +# If you have removable media and want to force media revalidation when looking +# up new or old compatibility names, uncomment the following lines +# SCSI NEWCOMPAT /dev/sd/* names +LOOKUP ^(sd/c[0-9]+b[0-9]+t[0-9]+u[0-9]+)p[0-9]+$ EXECUTE /bin/dd if=$mntpnt/\1 of=/dev/null count=1 +# SCSI OLDCOMPAT /dev/sd?? names +LOOKUP ^(sd[a-z]+)[0-9]+$ EXECUTE /bin/dd if=$mntpnt/\1 of=/dev/null count=1 +# IDE NEWCOMPAT /dev/ide/hd/* names +LOOKUP ^(ide/hd/c[0-9]+b[0-9]+t[0-9]+u[0-9]+)p[0-9]+$ EXECUTE /bin/dd if=$mntpnt/\1 of=/dev/null count=1 +# IDE OLDCOMPAT /dev/hd?? names +LOOKUP ^(hd[a-z])[0-9]+$ EXECUTE /bin/dd if=$mntpnt/\1 of=/dev/null count=1 +# IDE-SCSI NEWCOMPAT /dev/sd/* names +#LOOKUP ^(sd/c[0-9]+b[0-9]+t[0-9]+u[0-9]+)p[0-9]+$ EXECUTE /bin/dd if=$mntpnt/\1 of=/dev/null count=1 +#SCSI OLDCOMPAT /dev/scd? names +LOOKUP ^(scd+)[0-9]+$ EXECUTE /bin/dd if=$mntpnt/\1 of=/dev/null count=1 + + +REGISTER ^dvb/card[0-9]+/[^/]+$ PERMISSIONS root.video 0660 +# Not supported by busybox +#REGISTER ^dvb/card([0-9]+)/([^/0-9]*)[0-9]+$ CFUNCTION GLOBAL mksymlink /dev/$devname ost/\2\1 +#UNREGISTER ^dvb/card([0-9]+)/([^/0-9]*)[0-9]+$ CFUNCTION GLOBAL unlink ost/\2\1 +# Busybox +REGISTER ^dvb/card([0-9]+)/([^/0-9]*)[0-9]+$ EXECUTE /bin/ln -sf /dev/$devname ost/\2\1 +UNREGISTER ^dvb/card([0-9]+)/([^/0-9]*)[0-9]+$ EXECUTE /bin/rm -f ost/\2\1 + +# Include package-generated files from /etc/devfs/conf.d +# Supported by busybox +# INCLUDE /etc/devfs/conf.d/ +INCLUDE /etc/devfs/busybox/ +# Busybox: just for testing +#INCLUDE /etc/devfs/nothing/ +#INCLUDE /etc/devfs/nothing/nothing +#OPTIONAL_INCLUDE /etc/devfs/nothing/ +#OPTIONAL_INCLUDE /etc/devfs/nothing/nothing diff --git a/examples/dnsd.conf b/examples/dnsd.conf new file mode 100644 index 000000000..8af622b02 --- /dev/null +++ b/examples/dnsd.conf @@ -0,0 +1 @@ +thebox 192.168.1.5 diff --git a/examples/inetd.conf b/examples/inetd.conf new file mode 100644 index 000000000..ca7e3d8e1 --- /dev/null +++ b/examples/inetd.conf @@ -0,0 +1,73 @@ +# /etc/inetd.conf: see inetd(8) for further informations. +# +# Internet server configuration database +# +# +# If you want to disable an entry so it isn't touched during +# package updates just comment it out with a single '#' character. +# +# If you make changes to this file, either reboot your machine or +# send the inetd process a HUP signal: +# Do a "ps x" as root and look up the pid of inetd. Then do a +# kill -HUP +# inetd will re-read this file whenever it gets that signal. +# +# +#:INTERNAL: Internal services +# It is generally considered safer to keep these off. +echo stream tcp nowait root internal +echo dgram udp wait root internal +#discard stream tcp nowait root internal +#discard dgram udp wait root internal +daytime stream tcp nowait root internal +daytime dgram udp wait root internal +#chargen stream tcp nowait root internal +#chargen dgram udp wait root internal +time stream tcp nowait root internal +time dgram udp wait root internal + +# These are standard services. +# +#ftp stream tcp nowait root /usr/sbin/tcpd in.ftpd +#telnet stream tcp nowait root /sbin/telnetd /sbin/telnetd +#nntp stream tcp nowait root tcpd in.nntpd +#smtp stream tcp nowait root tcpd sendmail -v +# +# Shell, login, exec and talk are BSD protocols. +# +# If you run an ntalk daemon (such as netkit-ntalk) on the old talk +# port, that is, "talk" as opposed to "ntalk", it won't work and may +# cause certain broken talk clients to malfunction. +# +# The talkd from netkit-ntalk 0.12 and higher, however, can speak the +# old talk protocol and can be used safely. +# +#shell stream tcp nowait root /usr/sbin/tcpd in.rshd -L +#login stream tcp nowait root /usr/sbin/tcpd in.rlogind -L +#exec stream tcp nowait root /usr/sbin/tcpd in.rexecd +#talk dgram udp wait root /usr/sbin/tcpd in.talkd +#ntalk dgram udp wait root /usr/sbin/tcpd in.talkd +# +# Pop et al +# Leave these off unless you're using them. +#pop2 stream tcp nowait root /usr/sbin/tcpd in.pop2d +#pop3 stream tcp nowait root /usr/sbin/tcpd in.pop3d +# +# The Internet UUCP service. +# uucp stream tcp nowait uucp /usr/sbin/tcpd /usr/lib/uucp/uucico -l +# +# Tftp service is provided primarily for booting. Most sites +# run this only on machines acting as "boot servers." If you don't +# need it, don't use it. +# +#tftp dgram udp wait nobody /usr/sbin/tcpd in.tftpd +#bootps dgram udp wait root /usr/sbin/in.bootpd in.bootpd +# +# Finger, systat and netstat give out user information which may be +# valuable to potential "system crackers." Many sites choose to disable +# some or all of these services to improve security. +# +#finger stream tcp nowait nobody /usr/sbin/tcpd in.fingerd -w +#systat stream tcp nowait nobody /usr/sbin/tcpd /bin/ps -auwwx +#netstat stream tcp nowait root /bin/netstat /bin/netstat -a +#ident stream tcp nowait root /usr/sbin/in.identd in.identd diff --git a/examples/inittab b/examples/inittab new file mode 100644 index 000000000..5f2af8724 --- /dev/null +++ b/examples/inittab @@ -0,0 +1,90 @@ +# /etc/inittab init(8) configuration for BusyBox +# +# Copyright (C) 1999-2004 by Erik Andersen +# +# +# Note, BusyBox init doesn't support runlevels. The runlevels field is +# completely ignored by BusyBox init. If you want runlevels, use sysvinit. +# +# +# Format for each entry: ::: +# +# : WARNING: This field has a non-traditional meaning for BusyBox init! +# +# The id field is used by BusyBox init to specify the controlling tty for +# the specified process to run on. The contents of this field are +# appended to "/dev/" and used as-is. There is no need for this field to +# be unique, although if it isn't you may have strange results. If this +# field is left blank, it is completely ignored. Also note that if +# BusyBox detects that a serial console is in use, then all entries +# containing non-empty id fields will be ignored. BusyBox init does +# nothing with utmp. We don't need no stinkin' utmp. +# +# : The runlevels field is completely ignored. +# +# : Valid actions include: sysinit, respawn, askfirst, wait, once, +# restart, ctrlaltdel, and shutdown. +# +# Note: askfirst acts just like respawn, but before running the specified +# process it displays the line "Please press Enter to activate this +# console." and then waits for the user to press enter before starting +# the specified process. +# +# Note: unrecognised actions (like initdefault) will cause init to emit +# an error message, and then go along with its business. +# +# : Specifies the process to be executed and it's command line. +# +# Note: BusyBox init works just fine without an inittab. If no inittab is +# found, it has the following default behavior: +# ::sysinit:/etc/init.d/rcS +# ::askfirst:/bin/sh +# ::ctrlaltdel:/sbin/reboot +# ::shutdown:/sbin/swapoff -a +# ::shutdown:/bin/umount -a -r +# ::restart:/sbin/init +# +# if it detects that /dev/console is _not_ a serial console, it will +# also run: +# tty2::askfirst:/bin/sh +# tty3::askfirst:/bin/sh +# tty4::askfirst:/bin/sh +# +# Boot-time system configuration/initialization script. +# This is run first except when booting in single-user mode. +# +::sysinit:/etc/init.d/rcS + +# /bin/sh invocations on selected ttys +# +# Note below that we prefix the shell commands with a "-" to indicate to the +# shell that it is supposed to be a login shell. Normally this is handled by +# login, but since we are bypassing login in this case, BusyBox lets you do +# this yourself... +# +# Start an "askfirst" shell on the console (whatever that may be) +::askfirst:-/bin/sh +# Start an "askfirst" shell on /dev/tty2-4 +tty2::askfirst:-/bin/sh +tty3::askfirst:-/bin/sh +tty4::askfirst:-/bin/sh + +# /sbin/getty invocations for selected ttys +tty4::respawn:/sbin/getty 38400 tty5 +tty5::respawn:/sbin/getty 38400 tty6 + +# Example of how to put a getty on a serial line (for a terminal) +#::respawn:/sbin/getty -L ttyS0 9600 vt100 +#::respawn:/sbin/getty -L ttyS1 9600 vt100 +# +# Example how to put a getty on a modem line. +#::respawn:/sbin/getty 57600 ttyS2 + +# Stuff to do when restarting the init process +::restart:/sbin/init + +# Stuff to do before rebooting +::ctrlaltdel:/sbin/reboot +::shutdown:/bin/umount -a -r +::shutdown:/sbin/swapoff -a + diff --git a/examples/mk2knr.pl b/examples/mk2knr.pl new file mode 100755 index 000000000..1259b8436 --- /dev/null +++ b/examples/mk2knr.pl @@ -0,0 +1,84 @@ +#!/usr/bin/perl -w +# +# @(#) mk2knr.pl - generates a perl script that converts lexemes to K&R-style +# +# How to use this script: +# - In the busybox directory type 'examples/mk2knr.pl files-to-convert' +# - Review the 'convertme.pl' script generated and remove / edit any of the +# substitutions in there (please especially check for false positives) +# - Type './convertme.pl same-files-as-before' +# - Compile and see if it works +# +# BUGS: This script does not ignore strings inside comments or strings inside +# quotes (it probably should). + +# set this to something else if you want +$convertme = 'convertme.pl'; + +# internal-use variables (don't touch) +$convert = 0; +%converted = (); + +# if no files were specified, print usage +die "usage: $0 file.c | file.h\n" if scalar(@ARGV) == 0; + +# prepare the "convert me" file +open(CM, ">$convertme") or die "convertme.pl $!"; +print CM "#!/usr/bin/perl -p -i\n\n"; + +# process each file passed on the cmd line +while (<>) { + + # if the line says "getopt" in it anywhere, we don't want to muck with it + # because option lists tend to include strings like "cxtzvOf:" which get + # matched by the "check for mixed case" regexps below + next if /getopt/; + + # tokenize the string into just the variables + while (/([a-zA-Z_][a-zA-Z0-9_]*)/g) { + $var = $1; + + # ignore the word "BusyBox" + next if ($var =~ /BusyBox/); + + # this checks for javaStyle or szHungarianNotation + $convert++ if ($var =~ /^[a-z]+[A-Z][a-z]+/); + + # this checks for PascalStyle + $convert++ if ($var =~ /^[A-Z][a-z]+[A-Z][a-z]+/); + + # if we want to add more checks, we can add 'em here, but the above + # checks catch "just enough" and not too much, so prolly not. + + if ($convert) { + $convert = 0; + + # skip ahead if we've already dealt with this one + next if ($converted{$var}); + + # record that we've dealt with this var + $converted{$var} = 1; + + print CM "s/\\b$var\\b/"; # more to come in just a minute + + # change the first letter to lower-case + $var = lcfirst($var); + + # put underscores before all remaining upper-case letters + $var =~ s/([A-Z])/_$1/g; + + # now change the remaining characters to lower-case + $var = lc($var); + + print CM "$var/g;\n"; + } + } +} + +# tidy up and make the $convertme script executable +close(CM); +chmod 0755, $convertme; + +# print a helpful help message +print "Done. Scheduled name changes are in $convertme.\n"; +print "Please review/modify it and then type ./$convertme to do the search & replace.\n"; diff --git a/examples/udhcp/sample.bound b/examples/udhcp/sample.bound new file mode 100755 index 000000000..2a95d8b7d --- /dev/null +++ b/examples/udhcp/sample.bound @@ -0,0 +1,31 @@ +#!/bin/sh +# Sample udhcpc renew script + +RESOLV_CONF="/etc/udhcpc/resolv.conf" + +[ -n "$broadcast" ] && BROADCAST="broadcast $broadcast" +[ -n "$subnet" ] && NETMASK="netmask $subnet" + +/sbin/ifconfig $interface $ip $BROADCAST $NETMASK + +if [ -n "$router" ] +then + echo "deleting routers" + while /sbin/route del default gw 0.0.0.0 dev $interface + do : + done + + metric=0 + for i in $router + do + /sbin/route add default gw $i dev $interface metric $((metric++)) + done +fi + +echo -n > $RESOLV_CONF +[ -n "$domain" ] && echo domain $domain >> $RESOLV_CONF +for i in $dns +do + echo adding dns $i + echo nameserver $i >> $RESOLV_CONF +done \ No newline at end of file diff --git a/examples/udhcp/sample.deconfig b/examples/udhcp/sample.deconfig new file mode 100755 index 000000000..b221bcf12 --- /dev/null +++ b/examples/udhcp/sample.deconfig @@ -0,0 +1,4 @@ +#!/bin/sh +# Sample udhcpc deconfig script + +/sbin/ifconfig $interface 0.0.0.0 diff --git a/examples/udhcp/sample.nak b/examples/udhcp/sample.nak new file mode 100755 index 000000000..f4d08e669 --- /dev/null +++ b/examples/udhcp/sample.nak @@ -0,0 +1,4 @@ +#!/bin/sh +# Sample udhcpc nak script + +echo Received a NAK: $message diff --git a/examples/udhcp/sample.renew b/examples/udhcp/sample.renew new file mode 100755 index 000000000..842bafe91 --- /dev/null +++ b/examples/udhcp/sample.renew @@ -0,0 +1,31 @@ +#!/bin/sh +# Sample udhcpc bound script + +RESOLV_CONF="/etc/udhcpc/resolv.conf" + +[ -n "$broadcast" ] && BROADCAST="broadcast $broadcast" +[ -n "$subnet" ] && NETMASK="netmask $subnet" + +/sbin/ifconfig $interface $ip $BROADCAST $NETMASK + +if [ -n "$router" ] +then + echo "deleting routers" + while /sbin/route del default gw 0.0.0.0 dev $interface + do : + done + + metric=0 + for i in $router + do + /sbin/route add default gw $i dev $interface metric $((metric++)) + done +fi + +echo -n > $RESOLV_CONF +[ -n "$domain" ] && echo domain $domain >> $RESOLV_CONF +for i in $dns +do + echo adding dns $i + echo nameserver $i >> $RESOLV_CONF +done \ No newline at end of file diff --git a/examples/udhcp/sample.script b/examples/udhcp/sample.script new file mode 100644 index 000000000..9b717ac3c --- /dev/null +++ b/examples/udhcp/sample.script @@ -0,0 +1,7 @@ +#!/bin/sh +# Currently, we only dispatch according to command. However, a more +# elaborate system might dispatch by command and interface or do some +# common initialization first, especially if more dhcp event notifications +# are added. + +exec /usr/share/udhcpc/sample.$1 diff --git a/examples/udhcp/simple.script b/examples/udhcp/simple.script new file mode 100644 index 000000000..98ebc159f --- /dev/null +++ b/examples/udhcp/simple.script @@ -0,0 +1,40 @@ +#!/bin/sh + +# udhcpc script edited by Tim Riker + +[ -z "$1" ] && echo "Error: should be called from udhcpc" && exit 1 + +RESOLV_CONF="/etc/resolv.conf" +[ -n "$broadcast" ] && BROADCAST="broadcast $broadcast" +[ -n "$subnet" ] && NETMASK="netmask $subnet" + +case "$1" in + deconfig) + /sbin/ifconfig $interface 0.0.0.0 + ;; + + renew|bound) + /sbin/ifconfig $interface $ip $BROADCAST $NETMASK + + if [ -n "$router" ] ; then + echo "deleting routers" + while route del default gw 0.0.0.0 dev $interface ; do + : + done + + metric=0 + for i in $router ; do + route add default gw $i dev $interface metric $((metric++)) + done + fi + + echo -n > $RESOLV_CONF + [ -n "$domain" ] && echo search $domain >> $RESOLV_CONF + for i in $dns ; do + echo adding dns $i + echo nameserver $i >> $RESOLV_CONF + done + ;; +esac + +exit 0 diff --git a/examples/udhcp/udhcpd.conf b/examples/udhcp/udhcpd.conf new file mode 100644 index 000000000..f91fddebf --- /dev/null +++ b/examples/udhcp/udhcpd.conf @@ -0,0 +1,123 @@ +# Sample udhcpd configuration file (/etc/udhcpd.conf) + +# The start and end of the IP lease block + +start 192.168.0.20 #default: 192.168.0.20 +end 192.168.0.254 #default: 192.168.0.254 + + +# The interface that udhcpd will use + +interface eth0 #default: eth0 + + +# The maximim number of leases (includes addressesd reserved +# by OFFER's, DECLINE's, and ARP conficts + +#max_leases 254 #default: 254 + + +# If remaining is true (default), udhcpd will store the time +# remaining for each lease in the udhcpd leases file. This is +# for embedded systems that cannot keep time between reboots. +# If you set remaining to no, the absolute time that the lease +# expires at will be stored in the dhcpd.leases file. + +#remaining yes #default: yes + + +# The time period at which udhcpd will write out a dhcpd.leases +# file. If this is 0, udhcpd will never automatically write a +# lease file. (specified in seconds) + +#auto_time 7200 #default: 7200 (2 hours) + + +# The amount of time that an IP will be reserved (leased) for if a +# DHCP decline message is received (seconds). + +#decline_time 3600 #default: 3600 (1 hour) + + +# The amount of time that an IP will be reserved (leased) for if an +# ARP conflct occurs. (seconds + +#conflict_time 3600 #default: 3600 (1 hour) + + +# How long an offered address is reserved (leased) in seconds + +#offer_time 60 #default: 60 (1 minute) + +# If a lease to be given is below this value, the full lease time is +# instead used (seconds). + +#min_lease 60 #defult: 60 + + +# The location of the leases file + +#lease_file /var/lib/misc/udhcpd.leases #defualt: /var/lib/misc/udhcpd.leases + +# The location of the pid file +#pidfile /var/run/udhcpd.pid #default: /var/run/udhcpd.pid + +# Everytime udhcpd writes a leases file, the below script will be called. +# Useful for writing the lease file to flash every few hours. + +#notify_file #default: (no script) + +#notify_file dumpleases # <--- usefull for debugging + +# The following are bootp specific options, setable by udhcpd. + +#siaddr 192.168.0.22 #default: 0.0.0.0 + +#sname zorak #default: (none) + +#boot_file /var/nfs_root #default: (none) + +# The remainer of options are DHCP options and can be specifed with the +# keyword 'opt' or 'option'. If an option can take multiple items, such +# as the dns option, they can be listed on the same line, or multiple +# lines. The only option with a default is 'lease'. + +#Examles +opt dns 192.168.10.2 192.168.10.10 +option subnet 255.255.255.0 +opt router 192.168.10.2 +opt wins 192.168.10.10 +option dns 129.219.13.81 # appened to above DNS servers for a total of 3 +option domain local +option lease 864000 # 10 days of seconds + + +# Currently supported options, for more info, see options.c +#opt subnet +#opt timezone +#opt router +#opt timesvr +#opt namesvr +#opt dns +#opt logsvr +#opt cookiesvr +#opt lprsvr +#opt bootsize +#opt domain +#opt swapsvr +#opt rootpath +#opt ipttl +#opt mtu +#opt broadcast +#opt wins +#opt lease +#opt ntpsrv +#opt tftp +#opt bootfile + + +# Static leases map +#static_lease 00:60:08:11:CE:4E 192.168.0.54 +#static_lease 00:60:08:11:CE:3E 192.168.0.44 + + diff --git a/examples/undeb b/examples/undeb new file mode 100644 index 000000000..37104e9d8 --- /dev/null +++ b/examples/undeb @@ -0,0 +1,53 @@ +#!/bin/sh +# +# This should work with the GNU version of tar and gzip! +# This should work with the bash or ash shell! +# Requires the programs (ar, tar, gzip, and the pager more or less). +# +usage() { +echo "Usage: undeb -c package.deb " +echo " undeb -l package.deb " +echo " undeb -x package.deb /foo/boo " +exit +} + +deb=$2 + +exist() { +if [ "$deb" = "" ]; then +usage +elif [ ! -s "$deb" ]; then +echo "Can't find $deb!" +exit +fi +} + +if [ "$1" = "" ]; then +usage +elif [ "$1" = "-l" ]; then +exist +type more >/dev/null 2>&1 && pager=more +type less >/dev/null 2>&1 && pager=less +[ "$pager" = "" ] && echo "No pager found!" && exit +(ar -p $deb control.tar.gz | tar -xzO *control ; echo -e "\nPress enter to scroll, q to Quit!\n" ; ar -p $deb data.tar.gz | tar -tzv) | $pager +exit +elif [ "$1" = "-c" ]; then +exist +ar -p $deb control.tar.gz | tar -xzO *control +exit +elif [ "$1" = "-x" ]; then +exist +if [ "$3" = "" ]; then +usage +elif [ ! -d "$3" ]; then +echo "No such directory $3!" +exit +fi +ar -p $deb data.tar.gz | tar -xzvpf - -C $3 || exit +echo +echo "Extracted $deb to $3!" +exit +else +usage +fi diff --git a/examples/unrpm b/examples/unrpm new file mode 100644 index 000000000..7fd3676f6 --- /dev/null +++ b/examples/unrpm @@ -0,0 +1,48 @@ +#!/bin/sh +# +# This should work with the GNU version of cpio and gzip! +# This should work with the bash or ash shell! +# Requires the programs (cpio, gzip, and the pager more or less). +# +usage() { +echo "Usage: unrpm -l package.rpm " +echo " unrpm -x package.rpm /foo/boo " +exit +} + +rpm=$2 + +exist() { +if [ "$rpm" = "" ]; then +usage +elif [ ! -s "$rpm" ]; then +echo "Can't find $rpm!" +exit +fi +} + +if [ "$1" = "" ]; then +usage +elif [ "$1" = "-l" ]; then +exist +type more >/dev/null 2>&1 && pager=more +type less >/dev/null 2>&1 && pager=less +[ "$pager" = "" ] && echo "No pager found!" && exit +(echo -e "\nPress enter to scroll, q to Quit!\n" ; rpm2cpio $rpm | cpio -tv --quiet) | $pager +exit +elif [ "$1" = "-x" ]; then +exist +if [ "$3" = "" ]; then +usage +elif [ ! -d "$3" ]; then +echo "No such directory $3!" +exit +fi +rpm2cpio $rpm | (umask 0 ; cd $3 ; cpio -idmuv) || exit +echo +echo "Extracted $rpm to $3!" +exit +else +usage +fi diff --git a/examples/zcip.script b/examples/zcip.script new file mode 100644 index 000000000..988e542a4 --- /dev/null +++ b/examples/zcip.script @@ -0,0 +1,38 @@ +#!/bin/sh + +# only for use as a "zcip" callback script +if [ "x$interface" = x ] +then + exit 1 +fi + +# zcip should start on boot/resume and various media changes +case "$1" in +init) + # for now, zcip requires the link to be already up, + # and it drops links when they go down. that isn't + # the most robust model... + exit 0 + ;; +config) + if [ "x$ip" = x ] + then + exit 1 + fi + # remember $ip for $interface, to use on restart + if [ "x$IP" != x -a -w "$IP.$interface" ] + then + echo $ip > "$IP.$interface" + fi + exec ip address add dev $interface \ + scope link local "$ip/16" broadcast + + ;; +deconfig) + if [ x$ip = x ] + then + exit 1 + fi + exec ip address del dev $interface local $ip + ;; +esac +exit 1 diff --git a/findutils/Config.in b/findutils/Config.in new file mode 100644 index 000000000..5a4476a98 --- /dev/null +++ b/findutils/Config.in @@ -0,0 +1,159 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Finding Utilities" + +config FIND + bool "find" + default n + help + find is used to search your system to find specified files. + +config FEATURE_FIND_PRINT0 + bool "Enable -print0 option" + default y + depends on FIND + help + Causes output names to be separated by a null character + rather than a newline. This allows names that contain + newlines and other whitespace to be more easily + interpreted by other programs. + +config FEATURE_FIND_MTIME + bool "Enable modified time matching (-mtime) option" + default y + depends on FIND + help + Allow searching based on the modification time of + files, in days. + +config FEATURE_FIND_MMIN + bool "Enable modified time matching (-min) option" + default y + depends on FIND + help + Allow searching based on the modification time of + files, in minutes. + +config FEATURE_FIND_PERM + bool "Enable permissions matching (-perm) option" + default y + depends on FIND + help + Enable searching based on file permissions. + +config FEATURE_FIND_TYPE + bool "Enable filetype matching (-type) option" + default y + depends on FIND + help + Enable searching based on file type (file, + directory, socket, device, etc.). + +config FEATURE_FIND_XDEV + bool "Enable stay in filesystem (-xdev) option" + default y + depends on FIND + help + This option will allow find to restrict searches to a single + filesystem. + +config FEATURE_FIND_NEWER + bool "Enable -newer option for comparing file mtimes" + default y + depends on FIND + help + Support the 'find -newer' option for finding any files which have + a modified time that is more recent than the specified FILE. + +config FEATURE_FIND_INUM + bool "Enable inode number matching (-inum) option" + default y + depends on FIND + help + Support the 'find -inum' option for searching by inode number. + +config FEATURE_FIND_EXEC + bool "Enable (-exec) option allowing execution of commands" + default y + depends on FIND + help + Support the 'find -exec' option for executing commands based upon + the files matched. + +config GREP + bool "grep" + default n + help + grep is used to search files for a specified pattern. + +config FEATURE_GREP_EGREP_ALIAS + bool "Support extended regular expressions (egrep & grep -E)" + default y + depends on GREP + help + Enabled support for extended regular expressions. Extended + regular expressions allow for alternation (foo|bar), grouping, + and various repetition operators. + +config FEATURE_GREP_FGREP_ALIAS + bool "Alias fgrep to grep -F" + default y + depends on GREP + help + fgrep sees the search pattern as a normal string rather than + regular expressions. + grep -F is always builtin, this just creates the fgrep alias. + +config FEATURE_GREP_CONTEXT + bool "Enable before and after context flags (-A, -B and -C)" + default y + depends on GREP + help + Print the specified number of leading (-B) and/or trailing (-A) + context surrounding our matching lines. + Print the specified number of context lines (-C). + +config XARGS + bool "xargs" + default n + help + xargs is used to execute a specified command on + every item from standard input. + +config FEATURE_XARGS_SUPPORT_CONFIRMATION + bool "Enable prompt and confirmation option -p" + default n + depends on XARGS + help + Support prompt the user about whether to run each command + line and read a line from the terminal. + +config FEATURE_XARGS_SUPPORT_QUOTES + bool "Enable support single and double quotes and backslash" + default n + depends on XARGS + help + Default xargs unsupport single and double quotes + and backslash for can use aruments with spaces. + +config FEATURE_XARGS_SUPPORT_TERMOPT + bool "Enable support options -x" + default n + depends on XARGS + help + Enable support exit if the size (see the -s or -n option) + is exceeded. + +config FEATURE_XARGS_SUPPORT_ZERO_TERM + bool "Enable null terminated option -0" + default n + depends on XARGS + help + Enable input filenames are terminated by a null character + instead of by whitespace, and the quotes and backslash + are not special. + +endmenu diff --git a/findutils/Kbuild b/findutils/Kbuild new file mode 100644 index 000000000..7b504bacf --- /dev/null +++ b/findutils/Kbuild @@ -0,0 +1,10 @@ +# 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_FIND) += find.o +lib-$(CONFIG_GREP) += grep.o +lib-$(CONFIG_XARGS) += xargs.o diff --git a/findutils/find.c b/findutils/find.c new file mode 100644 index 000000000..bf6b71a83 --- /dev/null +++ b/findutils/find.c @@ -0,0 +1,582 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini find implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Reworked by David Douthitt and + * Matt Kraai . + * + * Licensed under the GPL version 2, see the file LICENSE in this tarball. + */ + +/* findutils-4.1.20: + * + * # find file.txt -exec 'echo {}' '{} {}' ';' + * find: echo file.txt: No such file or directory + * # find file.txt -exec 'echo' '{} {}' '; ' + * find: missing argument to `-exec' + * # find file.txt -exec 'echo {}' '{} {}' ';' junk + * find: paths must precede expression + * # find file.txt -exec 'echo {}' '{} {}' ';' junk ';' + * find: paths must precede expression + * # find file.txt -exec 'echo' '{} {}' ';' + * file.txt file.txt + * (strace: execve("/bin/echo", ["echo", "file.txt file.txt"], [ 30 vars ])) + * # find file.txt -exec 'echo' '{} {}' ';' -print -exec pwd ';' + * file.txt file.txt + * file.txt + * /tmp + * # find -name '*.c' -o -name '*.h' + * [shows files, *.c and *.h intermixed] + * # find file.txt -name '*f*' -o -name '*t*' + * file.txt + * # find file.txt -name '*z*' -o -name '*t*' + * file.txt + * # find file.txt -name '*f*' -o -name '*z*' + * file.txt + * + * # find t z -name '*t*' -print -o -name '*z*' + * t + * # find t z t z -name '*t*' -o -name '*z*' -print + * z + * z + * # find t z t z '(' -name '*t*' -o -name '*z*' ')' -o -print + * (no output) + */ + +#include "busybox.h" +#include + +USE_FEATURE_FIND_XDEV(static dev_t *xdev_dev;) +USE_FEATURE_FIND_XDEV(static int xdev_count;) + +typedef int (*action_fp)(const char *fileName, struct stat *statbuf, void *); + +typedef struct { + action_fp f; +} action; +#define ACTS(name, arg...) typedef struct { action a; arg; } action_##name; +#define ACTF(name) static int func_##name(const char *fileName, struct stat *statbuf, action_##name* ap) + ACTS(print) + ACTS(name, char *pattern;) +USE_FEATURE_FIND_PRINT0(ACTS(print0)) +USE_FEATURE_FIND_TYPE( ACTS(type, int type_mask;)) +USE_FEATURE_FIND_PERM( ACTS(perm, char perm_char; int perm_mask;)) +USE_FEATURE_FIND_MTIME( ACTS(mtime, char mtime_char; int mtime_days;)) +USE_FEATURE_FIND_MMIN( ACTS(mmin, char mmin_char; int mmin_mins;)) +USE_FEATURE_FIND_NEWER( ACTS(newer, time_t newer_mtime;)) +USE_FEATURE_FIND_INUM( ACTS(inum, ino_t inode_num;)) +USE_FEATURE_FIND_EXEC( ACTS(exec, char **exec_argv; int *subst_count; int exec_argc;)) +USE_DESKTOP( ACTS(paren, action ***subexpr;)) +USE_DESKTOP( ACTS(size, off_t size;)) +USE_DESKTOP( ACTS(prune)) + +static action ***actions; +static int need_print = 1; + +static inline int one_char(const char* str, char c) +{ + return (str[0] == c && str[1] == '\0'); +} + + +static int count_subst(const char *str) +{ + int count = 0; + while ((str = strstr(str, "{}"))) { + count++; + str++; + } + return count; +} + + +static char* subst(const char *src, int count, const char* filename) +{ + char *buf, *dst, *end; + int flen = strlen(filename); + /* we replace each '{}' with filename: growth by strlen-2 */ + buf = dst = xmalloc(strlen(src) + count*(flen-2) + 1); + while ((end = strstr(src, "{}"))) { + memcpy(dst, src, end - src); + dst += end - src; + src = end + 2; + memcpy(dst, filename, flen); + dst += flen; + } + strcpy(dst, src); + return buf; +} + + +static int exec_actions(action ***appp, const char *fileName, struct stat *statbuf) +{ + int cur_group; + int cur_action; + int rc = TRUE; + action **app, *ap; + + cur_group = -1; + while ((app = appp[++cur_group])) { + cur_action = -1; + do { + ap = app[++cur_action]; + } while (ap && (rc = ap->f(fileName, statbuf, ap))); + if (!ap) { + /* all actions in group were successful */ + break; + } + } + return rc; +} + + +ACTF(name) +{ + const char *tmp = strrchr(fileName, '/'); + if (tmp == NULL) + tmp = fileName; + else + tmp++; + return fnmatch(ap->pattern, tmp, FNM_PERIOD) == 0; +} +#if ENABLE_FEATURE_FIND_TYPE +ACTF(type) +{ + return ((statbuf->st_mode & S_IFMT) == ap->type_mask); +} +#endif +#if ENABLE_FEATURE_FIND_PERM +ACTF(perm) +{ + return !((isdigit(ap->perm_char) && (statbuf->st_mode & 07777) == ap->perm_mask) + || (ap->perm_char == '-' && (statbuf->st_mode & ap->perm_mask) == ap->perm_mask) + || (ap->perm_char == '+' && (statbuf->st_mode & ap->perm_mask) != 0)); +} +#endif +#if ENABLE_FEATURE_FIND_MTIME +ACTF(mtime) +{ + time_t file_age = time(NULL) - statbuf->st_mtime; + time_t mtime_secs = ap->mtime_days * 24 * 60 * 60; + return !((isdigit(ap->mtime_char) && file_age >= mtime_secs + && file_age < mtime_secs + 24 * 60 * 60) + || (ap->mtime_char == '+' && file_age >= mtime_secs + 24 * 60 * 60) + || (ap->mtime_char == '-' && file_age < mtime_secs)); +} +#endif +#if ENABLE_FEATURE_FIND_MMIN +ACTF(mmin) +{ + time_t file_age = time(NULL) - statbuf->st_mtime; + time_t mmin_secs = ap->mmin_mins * 60; + return !((isdigit(ap->mmin_char) && file_age >= mmin_secs + && file_age < mmin_secs + 60) + || (ap->mmin_char == '+' && file_age >= mmin_secs + 60) + || (ap->mmin_char == '-' && file_age < mmin_secs)); +} +#endif +#if ENABLE_FEATURE_FIND_NEWER +ACTF(newer) +{ + return (ap->newer_mtime >= statbuf->st_mtime); +} +#endif +#if ENABLE_FEATURE_FIND_INUM +ACTF(inum) +{ + return (statbuf->st_ino != ap->inode_num); +} +#endif +#if ENABLE_FEATURE_FIND_EXEC +ACTF(exec) +{ + int i, rc; + char *argv[ap->exec_argc+1]; + for (i = 0; i < ap->exec_argc; i++) + argv[i] = subst(ap->exec_argv[i], ap->subst_count[i], fileName); + argv[i] = NULL; /* terminate the list */ + errno = 0; + rc = wait4pid(spawn(argv)); + if (errno) + bb_perror_msg("%s", argv[0]); + for (i = 0; i < ap->exec_argc; i++) + free(argv[i]); + return rc == 0; /* return 1 if success */ +} +#endif + +#if ENABLE_FEATURE_FIND_PRINT0 +ACTF(print0) +{ + printf("%s%c", fileName, '\0'); + return TRUE; +} +#endif + +ACTF(print) +{ + puts(fileName); + return TRUE; +} + +#if ENABLE_DESKTOP +ACTF(paren) +{ + return exec_actions(ap->subexpr, fileName, statbuf); +} + +/* + * -prune: if -depth is not given, return true and do not descend + * current dir; if -depth is given, return false with no effect. + * Example: + * find dir -name 'asm-*' -prune -o -name '*.[chS]' -print + */ +ACTF(prune) +{ + return SKIP; +} + +ACTF(size) +{ + return statbuf->st_size == ap->size; +} +#endif + + +static int fileAction(const char *fileName, struct stat *statbuf, void* junk, int depth) +{ + int rc; +#ifdef CONFIG_FEATURE_FIND_XDEV + if (S_ISDIR(statbuf->st_mode) && xdev_count) { + int i; + for (i = 0; i < xdev_count; i++) { + if (xdev_dev[i] != statbuf->st_dev) + return SKIP; + } + } +#endif + rc = exec_actions(actions, fileName, statbuf); + /* Had no explicit -print[0] or -exec? then print */ + if (rc && need_print) + puts(fileName); + /* Cannot return 0: our caller, recursive_action(), + * will perror() and skip dirs (if called on dir) */ + return rc == 0 ? TRUE : rc; +} + + +#if ENABLE_FEATURE_FIND_TYPE +static int find_type(char *type) +{ + int mask = 0; + + switch (type[0]) { + case 'b': + mask = S_IFBLK; + break; + case 'c': + mask = S_IFCHR; + break; + case 'd': + mask = S_IFDIR; + break; + case 'p': + mask = S_IFIFO; + break; + case 'f': + mask = S_IFREG; + break; + case 'l': + mask = S_IFLNK; + break; + case 's': + mask = S_IFSOCK; + break; + } + + if (mask == 0 || type[1] != '\0') + bb_error_msg_and_die(bb_msg_invalid_arg, type, "-type"); + + return mask; +} +#endif + +action*** parse_params(char **argv) +{ + action*** appp; + int cur_group = 0; + int cur_action = 0; + + action* alloc_action(int sizeof_struct, action_fp f) + { + action *ap; + appp[cur_group] = xrealloc(appp[cur_group], (cur_action+2) * sizeof(*appp)); + appp[cur_group][cur_action++] = ap = xmalloc(sizeof_struct); + appp[cur_group][cur_action] = NULL; + ap->f = f; + return ap; + } +#define ALLOC_ACTION(name) (action_##name*)alloc_action(sizeof(action_##name), (action_fp) func_##name) + + appp = xzalloc(2 * sizeof(*appp)); /* appp[0],[1] == NULL */ + +// Actions have side effects and return a true or false value +// We implement: -print, -print0, -exec + +// The rest are tests. + +// Tests and actions are grouped by operators +// ( expr ) Force precedence +// ! expr True if expr is false +// -not expr Same as ! expr +// expr1 [-a[nd]] expr2 And; expr2 is not evaluated if expr1 is false +// expr1 -o[r] expr2 Or; expr2 is not evaluated if expr1 is true +// expr1 , expr2 List; both expr1 and expr2 are always evaluated +// We implement: (), -a, -o + + while (*argv) { + char *arg = argv[0]; + char *arg1 = argv[1]; + /* --- Operators --- */ + if (strcmp(arg, "-a") == 0 + USE_DESKTOP(|| strcmp(arg, "-and") == 0) + ) { + /* no special handling required */ + } + else if (strcmp(arg, "-o") == 0 + USE_DESKTOP(|| strcmp(arg, "-or") == 0) + ) { + /* start new OR group */ + cur_group++; + appp = xrealloc(appp, (cur_group+2) * sizeof(*appp)); + appp[cur_group] = NULL; + appp[cur_group+1] = NULL; + cur_action = 0; + } + + /* --- Tests and actions --- */ + else if (strcmp(arg, "-print") == 0) { + need_print = 0; + (void) ALLOC_ACTION(print); + } +#if ENABLE_FEATURE_FIND_PRINT0 + else if (strcmp(arg, "-print0") == 0) { + need_print = 0; + (void) ALLOC_ACTION(print0); + } +#endif + else if (strcmp(arg, "-name") == 0) { + action_name *ap; + if (!*++argv) + bb_error_msg_and_die(bb_msg_requires_arg, arg); + ap = ALLOC_ACTION(name); + ap->pattern = arg1; + } +#if ENABLE_FEATURE_FIND_TYPE + else if (strcmp(arg, "-type") == 0) { + action_type *ap; + if (!*++argv) + bb_error_msg_and_die(bb_msg_requires_arg, arg); + ap = ALLOC_ACTION(type); + ap->type_mask = find_type(arg1); + } +#endif +#if ENABLE_FEATURE_FIND_PERM +/* TODO: + * -perm mode File's permission bits are exactly mode (octal or symbolic). + * Symbolic modes use mode 0 as a point of departure. + * -perm -mode All of the permission bits mode are set for the file. + * -perm +mode Any of the permission bits mode are set for the file. + */ + else if (strcmp(arg, "-perm") == 0) { + action_perm *ap; + if (!*++argv) + bb_error_msg_and_die(bb_msg_requires_arg, arg); + ap = ALLOC_ACTION(perm); + ap->perm_mask = xstrtol_range(arg1, 8, 0, 07777); + ap->perm_char = arg1[0]; + if (ap->perm_char == '-') + ap->perm_mask = -ap->perm_mask; + } +#endif +#if ENABLE_FEATURE_FIND_MTIME + else if (strcmp(arg, "-mtime") == 0) { + action_mtime *ap; + if (!*++argv) + bb_error_msg_and_die(bb_msg_requires_arg, arg); + ap = ALLOC_ACTION(mtime); + ap->mtime_days = xatol(arg1); + ap->mtime_char = arg1[0]; + if (ap->mtime_char == '-') + ap->mtime_days = -ap->mtime_days; + } +#endif +#if ENABLE_FEATURE_FIND_MMIN + else if (strcmp(arg, "-mmin") == 0) { + action_mmin *ap; + if (!*++argv) + bb_error_msg_and_die(bb_msg_requires_arg, arg); + ap = ALLOC_ACTION(mmin); + ap->mmin_mins = xatol(arg1); + ap->mmin_char = arg1[0]; + if (ap->mmin_char == '-') + ap->mmin_mins = -ap->mmin_mins; + } +#endif +#if ENABLE_FEATURE_FIND_NEWER + else if (strcmp(arg, "-newer") == 0) { + action_newer *ap; + struct stat stat_newer; + if (!*++argv) + bb_error_msg_and_die(bb_msg_requires_arg, arg); + xstat(arg1, &stat_newer); + ap = ALLOC_ACTION(newer); + ap->newer_mtime = stat_newer.st_mtime; + } +#endif +#if ENABLE_FEATURE_FIND_INUM + else if (strcmp(arg, "-inum") == 0) { + action_inum *ap; + if (!*++argv) + bb_error_msg_and_die(bb_msg_requires_arg, arg); + ap = ALLOC_ACTION(inum); + ap->inode_num = xatoul(arg1); + } +#endif +#if ENABLE_FEATURE_FIND_EXEC + else if (strcmp(arg, "-exec") == 0) { + int i; + action_exec *ap; + need_print = 0; + ap = ALLOC_ACTION(exec); + ap->exec_argv = ++argv; /* first arg after -exec */ + ap->exec_argc = 0; + while (1) { + if (!*argv) /* did not see ';' till end */ + bb_error_msg_and_die(bb_msg_requires_arg, arg); + if (one_char(argv[0], ';')) + break; + argv++; + ap->exec_argc++; + } + if (ap->exec_argc == 0) + bb_error_msg_and_die(bb_msg_requires_arg, arg); + ap->subst_count = xmalloc(ap->exec_argc * sizeof(int)); + i = ap->exec_argc; + while (i--) + ap->subst_count[i] = count_subst(ap->exec_argv[i]); + } +#endif +#if ENABLE_DESKTOP + else if (one_char(arg, '(')) { + action_paren *ap; + char **endarg; + int nested = 1; + + endarg = argv; + while (1) { + if (!*++endarg) + bb_error_msg_and_die("unpaired '('"); + if (one_char(*endarg, '(')) + nested++; + else if (one_char(*endarg, ')') && !--nested) { + *endarg = NULL; + break; + } + } + ap = ALLOC_ACTION(paren); + ap->subexpr = parse_params(argv + 1); + *endarg = ")"; /* restore NULLed parameter */ + argv = endarg; + } + else if (strcmp(arg, "-prune") == 0) { + (void) ALLOC_ACTION(prune); + } + else if (strcmp(arg, "-size") == 0) { + action_size *ap; + if (!*++argv) + bb_error_msg_and_die(bb_msg_requires_arg, arg); + ap = ALLOC_ACTION(size); + ap->size = XATOOFF(arg1); + } +#endif + else + bb_show_usage(); + argv++; + } + + return appp; +#undef ALLOC_ACTION +} + + +int find_main(int argc, char **argv) +{ + int dereference = FALSE; + char *arg; + char **argp; + int i, firstopt, status = EXIT_SUCCESS; + + for (firstopt = 1; firstopt < argc; firstopt++) { + if (argv[firstopt][0] == '-') + break; +#if ENABLE_DESKTOP + if (one_char(argv[firstopt], '(')) + break; +#endif + } + if (firstopt == 1) { + argv[0] = "."; + argv--; + firstopt++; + } + +// All options always return true. They always take effect, +// rather than being processed only when their place in the +// expression is reached +// We implement: -follow, -xdev + + /* Process options, and replace then with -a */ + /* (-a will be ignored by recursive parser later) */ + argp = &argv[firstopt]; + while ((arg = argp[0])) { + if (strcmp(arg, "-follow") == 0) { + dereference = TRUE; + argp[0] = "-a"; + } +#if ENABLE_FEATURE_FIND_XDEV + else if (strcmp(arg, "-xdev") == 0) { + struct stat stbuf; + if (!xdev_count) { + xdev_count = firstopt - 1; + xdev_dev = xmalloc(xdev_count * sizeof(dev_t)); + for (i = 1; i < firstopt; i++) { + /* not xstat(): shouldn't bomb out on + * "find not_exist exist -xdev" */ + if (stat(argv[i], &stbuf)) stbuf.st_dev = -1L; + xdev_dev[i-1] = stbuf.st_dev; + } + } + argp[0] = "-a"; + } + argp++; + } +#endif + + actions = parse_params(&argv[firstopt]); + + for (i = 1; i < firstopt; i++) { + if (!recursive_action(argv[i], + TRUE, // recurse + dereference, // follow links + FALSE, // depth first + fileAction, // file action + fileAction, // dir action + NULL, // user data + 0)) // depth + status = EXIT_FAILURE; + } + return status; +} diff --git a/findutils/grep.c b/findutils/grep.c new file mode 100644 index 000000000..8bb38f95c --- /dev/null +++ b/findutils/grep.c @@ -0,0 +1,483 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini grep implementation for busybox using libc regex. + * + * Copyright (C) 1999,2000,2001 by Lineo, inc. and Mark Whitley + * Copyright (C) 1999,2000,2001 by Mark Whitley + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ +/* BB_AUDIT SUSv3 defects - unsupported option -x. */ +/* BB_AUDIT GNU defects - always acts as -a. */ +/* http://www.opengroup.org/onlinepubs/007904975/utilities/grep.html */ +/* + * 2004,2006 (C) Vladimir Oleynik - + * correction "-e pattern1 -e pattern2" logic and more optimizations. + * precompiled regex + */ +/* + * (C) 2006 Jac Goudsmit added -o option + */ + +#include "busybox.h" +#include "xregex.h" + + +/* options */ +#define GREP_OPTS "lnqvscFiHhe:f:Lor" +#define GREP_OPT_l (1<<0) +#define PRINT_FILES_WITH_MATCHES (option_mask32 & GREP_OPT_l) +#define GREP_OPT_n (1<<1) +#define PRINT_LINE_NUM (option_mask32 & GREP_OPT_n) +#define GREP_OPT_q (1<<2) +#define BE_QUIET (option_mask32 & GREP_OPT_q) +#define GREP_OPT_v (1<<3) +#define GREP_OPT_s (1<<4) +#define SUPPRESS_ERR_MSGS (option_mask32 & GREP_OPT_s) +#define GREP_OPT_c (1<<5) +#define PRINT_MATCH_COUNTS (option_mask32 & GREP_OPT_c) +#define GREP_OPT_F (1<<6) +#define FGREP_FLAG (option_mask32 & GREP_OPT_F) +#define GREP_OPT_i (1<<7) +#define GREP_OPT_H (1<<8) +#define GREP_OPT_h (1<<9) +#define GREP_OPT_e (1<<10) +#define GREP_OPT_f (1<<11) +#define GREP_OPT_L (1<<12) +#define PRINT_FILES_WITHOUT_MATCHES (option_mask32 & GREP_OPT_L) +#define GREP_OPT_o (1<<13) +#define GREP_OPT_r (1<<14) +#if ENABLE_FEATURE_GREP_CONTEXT +# define GREP_OPT_CONTEXT "A:B:C:" +# define GREP_OPT_A (1<<15) +# define GREP_OPT_B (1<<16) +# define GREP_OPT_C (1<<17) +# define GREP_OPT_E (1<<18) +#else +# define GREP_OPT_CONTEXT "" +# define GREP_OPT_A 0 +# define GREP_OPT_B 0 +# define GREP_OPT_C 0 +# define GREP_OPT_E (1<<15) +#endif +#if ENABLE_FEATURE_GREP_EGREP_ALIAS +# define OPT_EGREP "E" +#else +# define OPT_EGREP "" +#endif + +typedef unsigned char byte_t; + +static int reflags; +static byte_t invert_search; +static byte_t print_filename; +static byte_t open_errors; + +#if ENABLE_FEATURE_GREP_CONTEXT +static int lines_before; +static int lines_after; +static char **before_buf; +static int last_line_printed; +#endif /* ENABLE_FEATURE_GREP_CONTEXT */ + +/* globals used internally */ +static llist_t *pattern_head; /* growable list of patterns to match */ +static const char *cur_file; /* the current file we are reading */ + +typedef struct GREP_LIST_DATA { + char *pattern; + regex_t preg; +#define PATTERN_MEM_A 1 +#define COMPILED 2 + int flg_mem_alocated_compiled; +} grep_list_data_t; + +static void print_line(const char *line, int linenum, char decoration) +{ +#if ENABLE_FEATURE_GREP_CONTEXT + /* possibly print the little '--' separator */ + if ((lines_before || lines_after) && last_line_printed && + last_line_printed < linenum - 1) { + puts("--"); + } + last_line_printed = linenum; +#endif + if (print_filename) + printf("%s%c", cur_file, decoration); + if (PRINT_LINE_NUM) + printf("%i%c", linenum, decoration); + /* Emulate weird GNU grep behavior with -ov */ + if ((option_mask32 & (GREP_OPT_v+GREP_OPT_o)) != (GREP_OPT_v+GREP_OPT_o)) + puts(line); +} + + +static int grep_file(FILE *file) +{ + char *line; + byte_t ret; + int linenum = 0; + int nmatches = 0; + regmatch_t regmatch; +#if ENABLE_FEATURE_GREP_CONTEXT + int print_n_lines_after = 0; + int curpos = 0; /* track where we are in the circular 'before' buffer */ + int idx = 0; /* used for iteration through the circular buffer */ +#endif /* ENABLE_FEATURE_GREP_CONTEXT */ + + while ((line = xmalloc_getline(file)) != NULL) { + llist_t *pattern_ptr = pattern_head; + grep_list_data_t * gl; + + linenum++; + ret = 0; + while (pattern_ptr) { + gl = (grep_list_data_t *)pattern_ptr->data; + if (FGREP_FLAG) { + ret = strstr(line, gl->pattern) != NULL; + } else { + /* + * test for a postitive-assertion match (regexec returns success (0) + * and the user did not specify invert search), or a negative-assertion + * match (regexec returns failure (REG_NOMATCH) and the user specified + * invert search) + */ + if (!(gl->flg_mem_alocated_compiled & COMPILED)) { + gl->flg_mem_alocated_compiled |= COMPILED; + xregcomp(&(gl->preg), gl->pattern, reflags); + } + regmatch.rm_so = 0; + regmatch.rm_eo = 0; + ret |= regexec(&(gl->preg), line, 1, ®match, 0) == 0; + } + pattern_ptr = pattern_ptr->link; + } /* while (pattern_ptr) */ + + if (ret ^ invert_search) { + + if (PRINT_FILES_WITH_MATCHES || BE_QUIET) + free(line); + + /* if we found a match but were told to be quiet, stop here */ + if (BE_QUIET || PRINT_FILES_WITHOUT_MATCHES) + return -1; + + /* keep track of matches */ + nmatches++; + + /* if we're just printing filenames, we stop after the first match */ + if (PRINT_FILES_WITH_MATCHES) + break; + + /* print the matched line */ + if (PRINT_MATCH_COUNTS == 0) { +#if ENABLE_FEATURE_GREP_CONTEXT + int prevpos = (curpos == 0) ? lines_before - 1 : curpos - 1; + + /* if we were told to print 'before' lines and there is at least + * one line in the circular buffer, print them */ + if (lines_before && before_buf[prevpos] != NULL) { + int first_buf_entry_line_num = linenum - lines_before; + + /* advance to the first entry in the circular buffer, and + * figure out the line number is of the first line in the + * buffer */ + idx = curpos; + while (before_buf[idx] == NULL) { + idx = (idx + 1) % lines_before; + first_buf_entry_line_num++; + } + + /* now print each line in the buffer, clearing them as we go */ + while (before_buf[idx] != NULL) { + print_line(before_buf[idx], first_buf_entry_line_num, '-'); + free(before_buf[idx]); + before_buf[idx] = NULL; + idx = (idx + 1) % lines_before; + first_buf_entry_line_num++; + } + } + + /* make a note that we need to print 'after' lines */ + print_n_lines_after = lines_after; +#endif + if (option_mask32 & GREP_OPT_o) { + line[regmatch.rm_eo] = '\0'; + print_line(line + regmatch.rm_so, linenum, ':'); + } else { + print_line(line, linenum, ':'); + } + } + } +#if ENABLE_FEATURE_GREP_CONTEXT + else { /* no match */ + /* Add the line to the circular 'before' buffer */ + if (lines_before) { + free(before_buf[curpos]); + before_buf[curpos] = xstrdup(line); + curpos = (curpos + 1) % lines_before; + } + } + + /* if we need to print some context lines after the last match, do so */ + if (print_n_lines_after && (last_line_printed != linenum)) { + print_line(line, linenum, '-'); + print_n_lines_after--; + } +#endif /* ENABLE_FEATURE_GREP_CONTEXT */ + free(line); + } + + /* special-case file post-processing for options where we don't print line + * matches, just filenames and possibly match counts */ + + /* grep -c: print [filename:]count, even if count is zero */ + if (PRINT_MATCH_COUNTS) { + if (print_filename) + printf("%s:", cur_file); + printf("%d\n", nmatches); + } + + /* grep -l: print just the filename, but only if we grepped the line in the file */ + if (PRINT_FILES_WITH_MATCHES && nmatches > 0) { + puts(cur_file); + } + + /* grep -L: print just the filename, but only if we didn't grep the line in the file */ + if (PRINT_FILES_WITHOUT_MATCHES && nmatches == 0) { + puts(cur_file); + } + + return nmatches; +} + +#if ENABLE_FEATURE_CLEAN_UP +#define new_grep_list_data(p, m) add_grep_list_data(p, m) +static char * add_grep_list_data(char *pattern, int flg_used_mem) +#else +#define new_grep_list_data(p, m) add_grep_list_data(p) +static char * add_grep_list_data(char *pattern) +#endif +{ + grep_list_data_t *gl = xmalloc(sizeof(grep_list_data_t)); + gl->pattern = pattern; +#if ENABLE_FEATURE_CLEAN_UP + gl->flg_mem_alocated_compiled = flg_used_mem; +#else + gl->flg_mem_alocated_compiled = 0; +#endif + return (char *)gl; +} + + +static void load_regexes_from_file(llist_t *fopt) +{ + char *line; + FILE *f; + + while (fopt) { + llist_t *cur = fopt; + char *ffile = cur->data; + + fopt = cur->link; + free(cur); + f = xfopen(ffile, "r"); + while ((line = xmalloc_getline(f)) != NULL) { + llist_add_to(&pattern_head, + new_grep_list_data(line, PATTERN_MEM_A)); + } + } +} + + +static int file_action_grep(const char *filename, struct stat *statbuf, void* matched, int depth) +{ + FILE *file = fopen(filename, "r"); + if (file == NULL) { + if (!SUPPRESS_ERR_MSGS) + bb_perror_msg("%s", cur_file); + open_errors = 1; + return 0; + } + cur_file = filename; + *(int*)matched += grep_file(file); + fclose(file); + return 1; +} + + +static int grep_dir(const char *dir) +{ + int matched = 0; + recursive_action(dir, + /* recurse= */ 1, + /* followLinks= */ 0, + /* depthFirst= */ 1, + /* fileAction= */ file_action_grep, + /* dirAction= */ NULL, + /* userData= */ &matched, + /* depth= */ 0); + return matched; +} + + +int grep_main(int argc, char **argv) +{ + FILE *file; + int matched; + llist_t *fopt = NULL; + + /* do normal option parsing */ +#if ENABLE_FEATURE_GREP_CONTEXT + char *slines_after; + char *slines_before; + char *Copt; + + opt_complementary = "H-h:e::f::C-AB"; + getopt32(argc, argv, + GREP_OPTS GREP_OPT_CONTEXT OPT_EGREP, + &pattern_head, &fopt, + &slines_after, &slines_before, &Copt); + + if (option_mask32 & GREP_OPT_C) { + /* -C unsets prev -A and -B, but following -A or -B + may override it */ + if (!(option_mask32 & GREP_OPT_A)) /* not overridden */ + slines_after = Copt; + if (!(option_mask32 & GREP_OPT_B)) /* not overridden */ + slines_before = Copt; + option_mask32 |= GREP_OPT_A|GREP_OPT_B; /* for parser */ + } + if (option_mask32 & GREP_OPT_A) { + lines_after = xatoi_u(slines_after); + } + if (option_mask32 & GREP_OPT_B) { + lines_before = xatoi_u(slines_before); + } + /* sanity checks */ + if (option_mask32 & (GREP_OPT_c|GREP_OPT_q|GREP_OPT_l|GREP_OPT_L)) { + option_mask32 &= ~GREP_OPT_n; + lines_before = 0; + lines_after = 0; + } else if (lines_before > 0) + before_buf = (char **)xzalloc(lines_before * sizeof(char *)); +#else + /* with auto sanity checks */ + opt_complementary = "H-h:e::f::c-n:q-n:l-n"; + getopt32(argc, argv, GREP_OPTS OPT_EGREP, + &pattern_head, &fopt); +#endif + invert_search = ((option_mask32 & GREP_OPT_v) != 0); /* 0 | 1 */ + + if (pattern_head != NULL) { + /* convert char *argv[] to grep_list_data_t */ + llist_t *cur; + + for (cur = pattern_head; cur; cur = cur->link) + cur->data = new_grep_list_data(cur->data, 0); + } + if (option_mask32 & GREP_OPT_f) + load_regexes_from_file(fopt); + + if (ENABLE_FEATURE_GREP_FGREP_ALIAS && applet_name[0] == 'f') + option_mask32 |= GREP_OPT_F; + + if (!(option_mask32 & GREP_OPT_o)) + reflags = REG_NOSUB; + + if (ENABLE_FEATURE_GREP_EGREP_ALIAS && + (applet_name[0] == 'e' || (option_mask32 & GREP_OPT_E))) + reflags |= REG_EXTENDED; + + if (option_mask32 & GREP_OPT_i) + reflags |= REG_ICASE; + + argv += optind; + argc -= optind; + + /* if we didn't get a pattern from a -e and no command file was specified, + * argv[optind] should be the pattern. no pattern, no worky */ + if (pattern_head == NULL) { + if (*argv == NULL) + bb_show_usage(); + else { + char *pattern = new_grep_list_data(*argv++, 0); + + llist_add_to(&pattern_head, pattern); + argc--; + } + } + + /* argv[(optind)..(argc-1)] should be names of file to grep through. If + * there is more than one file to grep, we will print the filenames. */ + if (argc > 1) + print_filename = 1; + /* -H / -h of course override */ + if (option_mask32 & GREP_OPT_H) + print_filename = 1; + if (option_mask32 & GREP_OPT_h) + print_filename = 0; + + /* If no files were specified, or '-' was specified, take input from + * stdin. Otherwise, we grep through all the files specified. */ + if (argc == 0) + argc++; + matched = 0; + while (argc--) { + cur_file = *argv++; + file = stdin; + if (!cur_file || (*cur_file == '-' && !cur_file[1])) { + cur_file = "(standard input)"; + } else { + if (option_mask32 & GREP_OPT_r) { + struct stat st; + if (stat(cur_file, &st) == 0 && S_ISDIR(st.st_mode)) { + if (!(option_mask32 & GREP_OPT_h)) + print_filename = 1; + matched += grep_dir(cur_file); + goto grep_done; + } + } + /* else: fopen(dir) will succeed, but reading won't */ + file = fopen(cur_file, "r"); + if (file == NULL) { + if (!SUPPRESS_ERR_MSGS) + bb_perror_msg("%s", cur_file); + open_errors = 1; + continue; + } + } + matched += grep_file(file); + fclose_if_not_stdin(file); + grep_done: + if (matched < 0) { + /* we found a match but were told to be quiet, stop here and + * return success */ + break; + } + } + + /* destroy all the elments in the pattern list */ + if (ENABLE_FEATURE_CLEAN_UP) { + while (pattern_head) { + llist_t *pattern_head_ptr = pattern_head; + grep_list_data_t *gl = + (grep_list_data_t *)pattern_head_ptr->data; + + pattern_head = pattern_head->link; + if ((gl->flg_mem_alocated_compiled & PATTERN_MEM_A)) + free(gl->pattern); + if ((gl->flg_mem_alocated_compiled & COMPILED)) + regfree(&(gl->preg)); + free(pattern_head_ptr); + } + } + /* 0 = success, 1 = failed, 2 = error */ + /* If the -q option is specified, the exit status shall be zero + * if an input line is selected, even if an error was detected. */ + if (BE_QUIET && matched) + return 0; + if (open_errors) + return 2; + return !matched; /* invert return value 0 = success, 1 = failed */ +} diff --git a/findutils/xargs.c b/findutils/xargs.c new file mode 100644 index 000000000..029d4077d --- /dev/null +++ b/findutils/xargs.c @@ -0,0 +1,539 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini xargs implementation for busybox + * Options are supported: "-prtx -n max_arg -s max_chars -e[ouf_str]" + * + * (C) 2002,2003 by Vladimir Oleynik + * + * Special thanks + * - Mark Whitley and Glenn McGrath for stimulus to rewrite :) + * - Mike Rendell + * and David MacKenzie . + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * xargs is described in the Single Unix Specification v3 at + * http://www.opengroup.org/onlinepubs/007904975/utilities/xargs.html + * + */ + +#include "busybox.h" + +/* COMPAT: SYSV version defaults size (and has a max value of) to 470. + We try to make it as large as possible. */ +#if !defined(ARG_MAX) && defined(_SC_ARG_MAX) +#define ARG_MAX sysconf (_SC_ARG_MAX) +#endif +#ifndef ARG_MAX +#define ARG_MAX 470 +#endif + + +#ifdef TEST +# ifndef CONFIG_FEATURE_XARGS_SUPPORT_CONFIRMATION +# define CONFIG_FEATURE_XARGS_SUPPORT_CONFIRMATION +# endif +# ifndef CONFIG_FEATURE_XARGS_SUPPORT_QUOTES +# define CONFIG_FEATURE_XARGS_SUPPORT_QUOTES +# endif +# ifndef CONFIG_FEATURE_XARGS_SUPPORT_TERMOPT +# define CONFIG_FEATURE_XARGS_SUPPORT_TERMOPT +# endif +# ifndef CONFIG_FEATURE_XARGS_SUPPORT_ZERO_TERM +# define CONFIG_FEATURE_XARGS_SUPPORT_ZERO_TERM +# endif +#endif + +/* + This function has special algorithm. + Don't use fork and include to main! +*/ +static int xargs_exec(char *const *args) +{ + pid_t p; + volatile int exec_errno = 0; /* shared vfork stack */ + int status; + + p = vfork(); + if (p < 0) + bb_perror_msg_and_die("vfork"); + + if (p == 0) { + /* vfork -- child */ + execvp(args[0], args); + exec_errno = errno; /* set error to shared stack */ + _exit(1); + } + + /* vfork -- parent */ + while (wait(&status) == (pid_t) -1) + if (errno != EINTR) + break; + if (exec_errno) { + errno = exec_errno; + bb_perror_msg("%s", args[0]); + return exec_errno == ENOENT ? 127 : 126; + } + if (WEXITSTATUS(status) == 255) { + bb_error_msg("%s: exited with status 255; aborting", args[0]); + return 124; + } + if (WIFSTOPPED(status)) { + bb_error_msg("%s: stopped by signal %d", + args[0], WSTOPSIG(status)); + return 125; + } + if (WIFSIGNALED(status)) { + bb_error_msg("%s: terminated by signal %d", + args[0], WTERMSIG(status)); + return 125; + } + if (WEXITSTATUS(status)) + return 123; + return 0; +} + + +typedef struct xlist_s { + char *data; + size_t lenght; + struct xlist_s *link; +} xlist_t; + +static int eof_stdin_detected; + +#define ISBLANK(c) ((c) == ' ' || (c) == '\t') +#define ISSPACE(c) (ISBLANK(c) || (c) == '\n' || (c) == '\r' \ + || (c) == '\f' || (c) == '\v') + +#ifdef CONFIG_FEATURE_XARGS_SUPPORT_QUOTES +static xlist_t *process_stdin(xlist_t * list_arg, + const char *eof_str, size_t mc, char *buf) +{ +#define NORM 0 +#define QUOTE 1 +#define BACKSLASH 2 +#define SPACE 4 + + char *s = NULL; /* start word */ + char *p = NULL; /* pointer to end word */ + char q = 0; /* quote char */ + char state = NORM; + char eof_str_detected = 0; + size_t line_l = 0; /* size loaded args line */ + int c; /* current char */ + xlist_t *cur; + xlist_t *prev; + + for (prev = cur = list_arg; cur; cur = cur->link) { + line_l += cur->lenght; /* previous allocated */ + if (prev != cur) + prev = prev->link; + } + + while (!eof_stdin_detected) { + c = getchar(); + if (c == EOF) { + eof_stdin_detected++; + if (s) + goto unexpected_eof; + break; + } + if (eof_str_detected) + continue; + if (state == BACKSLASH) { + state = NORM; + goto set; + } else if (state == QUOTE) { + if (c == q) { + q = 0; + state = NORM; + } else { + goto set; + } + } else { /* if(state == NORM) */ + + if (ISSPACE(c)) { + if (s) { +unexpected_eof: + state = SPACE; + c = 0; + goto set; + } + } else { + if (s == NULL) + s = p = buf; + if (c == '\\') { + state = BACKSLASH; + } else if (c == '\'' || c == '"') { + q = c; + state = QUOTE; + } else { +set: + if ((size_t)(p - buf) >= mc) + bb_error_msg_and_die("argument line too long"); + *p++ = c; + } + } + } + if (state == SPACE) { /* word's delimiter or EOF detected */ + if (q) { + bb_error_msg_and_die("unmatched %s quote", + q == '\'' ? "single" : "double"); + } + /* word loaded */ + if (eof_str) { + eof_str_detected = strcmp(s, eof_str) == 0; + } + if (!eof_str_detected) { + size_t lenght = (p - buf); + + cur = xmalloc(sizeof(xlist_t) + lenght); + cur->data = memcpy(cur + 1, s, lenght); + cur->lenght = lenght; + cur->link = NULL; + if (prev == NULL) { + list_arg = cur; + } else { + prev->link = cur; + } + prev = cur; + line_l += lenght; + if (line_l > mc) { + /* stop memory usage :-) */ + break; + } + } + s = NULL; + state = NORM; + } + } + return list_arg; +} +#else +/* The variant does not support single quotes, double quotes or backslash */ +static xlist_t *process_stdin(xlist_t * list_arg, + const char *eof_str, size_t mc, char *buf) +{ + + int c; /* current char */ + int eof_str_detected = 0; + char *s = NULL; /* start word */ + char *p = NULL; /* pointer to end word */ + size_t line_l = 0; /* size loaded args line */ + xlist_t *cur; + xlist_t *prev; + + for (prev = cur = list_arg; cur; cur = cur->link) { + line_l += cur->lenght; /* previous allocated */ + if (prev != cur) + prev = prev->link; + } + + while (!eof_stdin_detected) { + c = getchar(); + if (c == EOF) { + eof_stdin_detected++; + } + if (eof_str_detected) + continue; + if (c == EOF || ISSPACE(c)) { + if (s == NULL) + continue; + c = EOF; + } + if (s == NULL) + s = p = buf; + if ((p - buf) >= mc) + bb_error_msg_and_die("argument line too long"); + *p++ = c == EOF ? 0 : c; + if (c == EOF) { /* word's delimiter or EOF detected */ + /* word loaded */ + if (eof_str) { + eof_str_detected = strcmp(s, eof_str) == 0; + } + if (!eof_str_detected) { + size_t lenght = (p - buf); + + cur = xmalloc(sizeof(xlist_t) + lenght); + cur->data = memcpy(cur + 1, s, lenght); + cur->lenght = lenght; + cur->link = NULL; + if (prev == NULL) { + list_arg = cur; + } else { + prev->link = cur; + } + prev = cur; + line_l += lenght; + if (line_l > mc) { + /* stop memory usage :-) */ + break; + } + s = NULL; + } + } + } + return list_arg; +} +#endif /* CONFIG_FEATURE_XARGS_SUPPORT_QUOTES */ + + +#ifdef CONFIG_FEATURE_XARGS_SUPPORT_CONFIRMATION +/* Prompt the user for a response, and + if the user responds affirmatively, return true; + otherwise, return false. Used "/dev/tty", not stdin. */ +static int xargs_ask_confirmation(void) +{ + static FILE *tty_stream; + int c, savec; + + if (!tty_stream) { + tty_stream = xfopen(CURRENT_TTY, "r"); + /* pranoidal security by vodz */ + fcntl(fileno(tty_stream), F_SETFD, FD_CLOEXEC); + } + fputs(" ?...", stderr); + fflush(stderr); + c = savec = getc(tty_stream); + while (c != EOF && c != '\n') + c = getc(tty_stream); + if (savec == 'y' || savec == 'Y') + return 1; + return 0; +} +#else +# define xargs_ask_confirmation() 1 +#endif /* CONFIG_FEATURE_XARGS_SUPPORT_CONFIRMATION */ + +#ifdef CONFIG_FEATURE_XARGS_SUPPORT_ZERO_TERM +static xlist_t *process0_stdin(xlist_t * list_arg, const char *eof_str ATTRIBUTE_UNUSED, + size_t mc, char *buf) +{ + int c; /* current char */ + char *s = NULL; /* start word */ + char *p = NULL; /* pointer to end word */ + size_t line_l = 0; /* size loaded args line */ + xlist_t *cur; + xlist_t *prev; + + for (prev = cur = list_arg; cur; cur = cur->link) { + line_l += cur->lenght; /* previous allocated */ + if (prev != cur) + prev = prev->link; + } + + while (!eof_stdin_detected) { + c = getchar(); + if (c == EOF) { + eof_stdin_detected++; + if (s == NULL) + break; + c = 0; + } + if (s == NULL) + s = p = buf; + if ((size_t)(p - buf) >= mc) + bb_error_msg_and_die("argument line too long"); + *p++ = c; + if (c == 0) { /* word's delimiter or EOF detected */ + /* word loaded */ + size_t lenght = (p - buf); + + cur = xmalloc(sizeof(xlist_t) + lenght); + cur->data = memcpy(cur + 1, s, lenght); + cur->lenght = lenght; + cur->link = NULL; + if (prev == NULL) { + list_arg = cur; + } else { + prev->link = cur; + } + prev = cur; + line_l += lenght; + if (line_l > mc) { + /* stop memory usage :-) */ + break; + } + s = NULL; + } + } + return list_arg; +} +#endif /* CONFIG_FEATURE_XARGS_SUPPORT_ZERO_TERM */ + +/* Correct regardless of combination of CONFIG_xxx */ +enum { + OPTBIT_VERBOSE = 0, + OPTBIT_NO_EMPTY, + OPTBIT_UPTO_NUMBER, + OPTBIT_UPTO_SIZE, + OPTBIT_EOF_STRING, + USE_FEATURE_XARGS_SUPPORT_CONFIRMATION(OPTBIT_INTERACTIVE,) + USE_FEATURE_XARGS_SUPPORT_TERMOPT( OPTBIT_TERMINATE ,) + USE_FEATURE_XARGS_SUPPORT_ZERO_TERM( OPTBIT_ZEROTERM ,) + + OPT_VERBOSE = 1< 20 * 1024) + orig_arg_max = 20 * 1024; + n_max_chars = orig_arg_max; + } + max_chars = xmalloc(n_max_chars); + + if (opt & OPT_UPTO_NUMBER) { + n_max_arg = xatoul_range(max_args, 1, INT_MAX); + } else { + n_max_arg = n_max_chars; + } + + while ((list = read_args(list, eof_str, n_max_chars, max_chars)) != NULL || + !(opt & OPT_NO_EMPTY)) + { + opt |= OPT_NO_EMPTY; + n = 0; + n_chars = 0; +#ifdef CONFIG_FEATURE_XARGS_SUPPORT_TERMOPT + for (cur = list; cur;) { + n_chars += cur->lenght; + n++; + cur = cur->link; + if (n_chars > n_max_chars || (n == n_max_arg && cur)) { + if (opt & OPT_TERMINATE) + bb_error_msg_and_die("argument list too long"); + break; + } + } +#else + for (cur = list; cur; cur = cur->link) { + n_chars += cur->lenght; + n++; + if (n_chars > n_max_chars || n == n_max_arg) { + break; + } + } +#endif /* CONFIG_FEATURE_XARGS_SUPPORT_TERMOPT */ + + /* allocate pointers for execvp: + argc*arg, n*arg from stdin, NULL */ + args = xzalloc((n + argc + 1) * sizeof(char *)); + + /* store the command to be executed + (taken from the command line) */ + for (i = 0; i < argc; i++) + args[i] = argv[i]; + /* (taken from stdin) */ + for (cur = list; n; cur = cur->link) { + args[i++] = cur->data; + n--; + } + + if (opt & (OPT_INTERACTIVE | OPT_VERBOSE)) { + for (i = 0; args[i]; i++) { + if (i) + fputc(' ', stderr); + fputs(args[i], stderr); + } + if (!(opt & OPT_INTERACTIVE)) + fputc('\n', stderr); + } + if (!(opt & OPT_INTERACTIVE) || xargs_ask_confirmation()) { + child_error = xargs_exec(args); + } + + /* clean up */ + for (i = argc; args[i]; i++) { + cur = list; + list = list->link; + free(cur); + } + free(args); + if (child_error > 0 && child_error != 123) { + break; + } + } + if (ENABLE_FEATURE_CLEAN_UP) free(max_chars); + return child_error; +} + + +#ifdef TEST + +const char *applet_name = "debug stuff usage"; + +void bb_show_usage(void) +{ + fprintf(stderr, "Usage: %s [-p] [-r] [-t] -[x] [-n max_arg] [-s max_chars]\n", + applet_name); + exit(1); +} + +int main(int argc, char **argv) +{ + return xargs_main(argc, argv); +} +#endif /* TEST */ diff --git a/include/applets.h b/include/applets.h new file mode 100644 index 000000000..d3e38d813 --- /dev/null +++ b/include/applets.h @@ -0,0 +1,333 @@ +/* vi: set sw=4 ts=4: */ +/* + * applets.h - a listing of all busybox applets. + * + * If you write a new applet, you need to add an entry to this list to make + * busybox aware of it. + * + * It is CRUCIAL that this listing be kept in ascii order, otherwise the binary + * search lookup contributed by Gaute B Strokkenes stops working. If you value + * your kneecaps, you'll be sure to *make sure* that any changes made to this + * file result in the listing remaining in ascii order. You have been warned. + */ + +#undef APPLET +#undef APPLET_ODDNAME +#undef APPLET_NOUSAGE + + +#if defined(PROTOTYPES) +# define APPLET(a,b,c) extern int a##_main(int argc, char **argv); +# define APPLET_NOUSAGE(a,b,c,d) extern int b##_main(int argc, char **argv); +# define APPLET_ODDNAME(a,b,c,d,e) extern int b##_main(int argc, char **argv); +#elif defined(MAKE_USAGE) +# ifdef CONFIG_FEATURE_VERBOSE_USAGE +# define APPLET(a,b,c) a##_trivial_usage "\n\n" a##_full_usage "\0" +# define APPLET_NOUSAGE(a,b,c,d) "\b\0" +# define APPLET_ODDNAME(a,b,c,d,e) e##_trivial_usage "\n\n" e##_full_usage "\0" +# else +# define APPLET(a,b,c) a##_trivial_usage "\0" +# define APPLET_NOUSAGE(a,b,c,d) "\b\0" +# define APPLET_ODDNAME(a,b,c,d,e) e##_trivial_usage "\0" +# endif +#elif defined(MAKE_LINKS) +# define APPLET(a,b,c) LINK b a +# define APPLET_NOUSAGE(a,b,c,d) LINK c a +# define APPLET_ODDNAME(a,b,c,d,e) LINK c a +#else + const struct BB_applet applets[] = { +# define APPLET(a,b,c) {#a,a##_main,b,c}, +# define APPLET_NOUSAGE(a,b,c,d) {#a,b##_main,c,d}, +# define APPLET_ODDNAME(a,b,c,d,e) {#a,b##_main,c,d}, +#endif + +#ifdef CONFIG_INSTALL_NO_USR +# define _BB_DIR_USR_BIN _BB_DIR_BIN +# define _BB_DIR_USR_SBIN _BB_DIR_SBIN +#endif + +// _BB_SUID_ALWAYS: will complain if busybox isn't suid +// and is run by non-root (applet_main() will not be called at all) +// _BB_SUID_NEVER: will drop suid prior to applet_main() +// _BB_SUID_MAYBE: neither of the above + +USE_TEST(APPLET_NOUSAGE([, test, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_TEST(APPLET_NOUSAGE([[, test, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_ADDGROUP(APPLET(addgroup, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_ADDUSER(APPLET(adduser, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_ADJTIMEX(APPLET(adjtimex, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_AR(APPLET(ar, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_ARPING(APPLET(arping, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_ASH(APPLET_NOUSAGE(ash, ash, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_AWK(APPLET(awk, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_BASENAME(APPLET(basename, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_BBCONFIG(APPLET(bbconfig, _BB_DIR_BIN, _BB_SUID_NEVER)) +//USE_BBSH(APPLET(bbsh, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_BUNZIP2(APPLET(bunzip2, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +/* Always enabled. */ +APPLET_NOUSAGE(busybox, busybox, _BB_DIR_BIN, _BB_SUID_MAYBE) +USE_BUNZIP2(APPLET_ODDNAME(bzcat, bunzip2, _BB_DIR_USR_BIN, _BB_SUID_NEVER, bzcat)) +USE_CAL(APPLET(cal, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_CAT(APPLET(cat, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_CATV(APPLET(catv, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_CHATTR(APPLET(chattr, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_CHGRP(APPLET(chgrp, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_CHMOD(APPLET(chmod, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_CHOWN(APPLET(chown, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_CHPST(APPLET(chpst, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_CHROOT(APPLET(chroot, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_CHVT(APPLET(chvt, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_CKSUM(APPLET(cksum, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_CLEAR(APPLET(clear, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_CMP(APPLET(cmp, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_COMM(APPLET(comm, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_CP(APPLET(cp, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_CPIO(APPLET(cpio, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_CROND(APPLET(crond, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_CRONTAB(APPLET(crontab, _BB_DIR_USR_BIN, _BB_SUID_ALWAYS)) +USE_CUT(APPLET(cut, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_DATE(APPLET(date, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_DC(APPLET(dc, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_DD(APPLET(dd, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_DEALLOCVT(APPLET(deallocvt, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_DELGROUP(APPLET_ODDNAME(delgroup, deluser, _BB_DIR_BIN, _BB_SUID_NEVER, delgroup)) +USE_DELUSER(APPLET(deluser, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_DEVFSD(APPLET(devfsd, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_DF(APPLET(df, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_APP_DHCPRELAY(APPLET(dhcprelay, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_DIFF(APPLET(diff, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_DIRNAME(APPLET(dirname, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_DMESG(APPLET(dmesg, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_DNSD(APPLET(dnsd, _BB_DIR_USR_SBIN, _BB_SUID_ALWAYS)) +USE_DOS2UNIX(APPLET(dos2unix, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_DPKG(APPLET(dpkg, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_DPKG_DEB(APPLET_ODDNAME(dpkg-deb, dpkg_deb, _BB_DIR_USR_BIN, _BB_SUID_NEVER, dpkg_deb)) +USE_DU(APPLET(du, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_DUMPKMAP(APPLET(dumpkmap, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_APP_DUMPLEASES(APPLET(dumpleases, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_E2FSCK(APPLET(e2fsck, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_E2LABEL(APPLET_NOUSAGE(e2label, tune2fs, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_ECHO(APPLET(echo, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_ED(APPLET(ed, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_FEATURE_GREP_EGREP_ALIAS(APPLET_NOUSAGE(egrep, grep, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_EJECT(APPLET(eject, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_ENV(APPLET(env, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_ENVDIR(APPLET_ODDNAME(envdir, chpst, _BB_DIR_USR_BIN, _BB_SUID_NEVER, envdir)) +USE_ENVUIDGID(APPLET_ODDNAME(envuidgid, chpst, _BB_DIR_USR_BIN, _BB_SUID_NEVER, envuidgid)) +USE_ETHER_WAKE(APPLET_ODDNAME(ether-wake, ether_wake, _BB_DIR_USR_BIN, _BB_SUID_NEVER, ether_wake)) +USE_EXPR(APPLET(expr, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_FAKEIDENTD(APPLET(fakeidentd, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_FALSE(APPLET(false, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_FBSET(APPLET(fbset, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_FDFLUSH(APPLET_ODDNAME(fdflush, freeramdisk, _BB_DIR_BIN, _BB_SUID_NEVER, fdflush)) +USE_FDFORMAT(APPLET(fdformat, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_FDISK(APPLET(fdisk, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_FEATURE_GREP_FGREP_ALIAS(APPLET_NOUSAGE(fgrep, grep, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_FIND(APPLET(find, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_FINDFS(APPLET_NOUSAGE(findfs, tune2fs, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_FOLD(APPLET(fold, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_FREE(APPLET(free, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_FREERAMDISK(APPLET(freeramdisk, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_FSCK(APPLET(fsck, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_E2FSCK(APPLET_NOUSAGE(fsck.ext2, e2fsck, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_E2FSCK(APPLET_NOUSAGE(fsck.ext3, e2fsck, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_FSCK_MINIX(APPLET_ODDNAME(fsck.minix, fsck_minix, _BB_DIR_SBIN, _BB_SUID_NEVER, fsck_minix)) +USE_FTPGET(APPLET_ODDNAME(ftpget, ftpgetput, _BB_DIR_USR_BIN, _BB_SUID_NEVER,ftpget)) +USE_FTPPUT(APPLET_ODDNAME(ftpput, ftpgetput, _BB_DIR_USR_BIN, _BB_SUID_NEVER,ftpput)) +USE_FUSER(APPLET(fuser, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_GETOPT(APPLET(getopt, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_GETTY(APPLET(getty, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_GREP(APPLET(grep, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_GUNZIP(APPLET(gunzip, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_GZIP(APPLET(gzip, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_HALT(APPLET(halt, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_HDPARM(APPLET(hdparm, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_HEAD(APPLET(head, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_HEXDUMP(APPLET(hexdump, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_HOSTID(APPLET(hostid, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_HOSTNAME(APPLET(hostname, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_HTTPD(APPLET(httpd, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_HUSH(APPLET_NOUSAGE(hush, hush, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_HWCLOCK(APPLET(hwclock, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_ID(APPLET(id, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_IFCONFIG(APPLET(ifconfig, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_IFUPDOWN(APPLET_ODDNAME(ifdown, ifupdown, _BB_DIR_SBIN, _BB_SUID_NEVER, ifdown)) +USE_IFUPDOWN(APPLET_ODDNAME(ifup, ifupdown, _BB_DIR_SBIN, _BB_SUID_NEVER, ifup)) +USE_INETD(APPLET(inetd, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_INIT(APPLET(init, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_INSMOD(APPLET(insmod, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_INSTALL(APPLET(install, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_IP(APPLET(ip, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_IPADDR(APPLET(ipaddr, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_IPCALC(APPLET(ipcalc, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_IPCRM(APPLET(ipcrm, _BB_DIR_USR_BIN, _BB_SUID_ALWAYS)) +USE_IPCS(APPLET(ipcs, _BB_DIR_USR_BIN, _BB_SUID_ALWAYS)) +USE_IPLINK(APPLET(iplink, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_IPROUTE(APPLET(iproute, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_IPRULE(APPLET(iprule, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_IPTUNNEL(APPLET(iptunnel, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_KILL(APPLET(kill, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_KILLALL(APPLET_ODDNAME(killall, kill, _BB_DIR_USR_BIN, _BB_SUID_NEVER, killall)) +USE_KILLALL5(APPLET_ODDNAME(killall5, kill, _BB_DIR_USR_BIN, _BB_SUID_NEVER, killall5)) +USE_KLOGD(APPLET(klogd, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_LASH(APPLET(lash, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_LAST(APPLET(last, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_LENGTH(APPLET(length, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_LESS(APPLET(less, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_SETARCH(APPLET_NOUSAGE(linux32, setarch, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_SETARCH(APPLET_NOUSAGE(linux64, setarch, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_FEATURE_INITRD(APPLET_NOUSAGE(linuxrc, init, _BB_DIR_ROOT, _BB_SUID_NEVER)) +USE_LN(APPLET(ln, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_LOADFONT(APPLET(loadfont, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_LOADKMAP(APPLET(loadkmap, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_LOGGER(APPLET(logger, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_LOGIN(APPLET(login, _BB_DIR_BIN, _BB_SUID_ALWAYS)) +USE_LOGNAME(APPLET(logname, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_LOGREAD(APPLET(logread, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_LOSETUP(APPLET(losetup, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_LS(APPLET(ls, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_LSATTR(APPLET(lsattr, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_LSMOD(APPLET(lsmod, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_UNLZMA(APPLET_ODDNAME(lzmacat, unlzma, _BB_DIR_USR_BIN, _BB_SUID_NEVER, lzmacat)) +USE_MAKEDEVS(APPLET(makedevs, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_MD5SUM(APPLET_ODDNAME(md5sum, md5_sha1_sum, _BB_DIR_USR_BIN, _BB_SUID_NEVER, md5sum)) +USE_MDEV(APPLET(mdev, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_MESG(APPLET(mesg, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_MKDIR(APPLET(mkdir, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_MKE2FS(APPLET(mke2fs, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_MKFIFO(APPLET(mkfifo, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_MKE2FS(APPLET_NOUSAGE(mkfs.ext2, mke2fs, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_MKE2FS(APPLET_NOUSAGE(mkfs.ext3, mke2fs, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_MKFS_MINIX(APPLET_ODDNAME(mkfs.minix, mkfs_minix, _BB_DIR_SBIN, _BB_SUID_NEVER, mkfs_minix)) +USE_MKNOD(APPLET(mknod, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_MKSWAP(APPLET(mkswap, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_MKTEMP(APPLET(mktemp, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_MODPROBE(APPLET(modprobe, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_MORE(APPLET(more, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_MOUNT(APPLET(mount, _BB_DIR_BIN, USE_DESKTOP(_BB_SUID_MAYBE) SKIP_DESKTOP(_BB_SUID_NEVER))) +USE_MOUNTPOINT(APPLET(mountpoint, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_MSH(APPLET_NOUSAGE(msh, msh, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_MT(APPLET(mt, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_MV(APPLET(mv, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_NAMEIF(APPLET(nameif, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_NC(APPLET(nc, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_NETSTAT(APPLET(netstat, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_NICE(APPLET(nice, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_NMETER(APPLET(nmeter, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_NOHUP(APPLET(nohup, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_NSLOOKUP(APPLET(nslookup, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_OD(APPLET(od, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_OPENVT(APPLET(openvt, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_PASSWD(APPLET(passwd, _BB_DIR_USR_BIN, _BB_SUID_ALWAYS)) +USE_PATCH(APPLET(patch, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_PIDOF(APPLET(pidof, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_PING(APPLET(ping, _BB_DIR_BIN, _BB_SUID_MAYBE)) +USE_PING6(APPLET(ping6, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_PIPE_PROGRESS(APPLET_NOUSAGE(pipe_progress, pipe_progress, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_PIVOT_ROOT(APPLET(pivot_root, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_HALT(APPLET_ODDNAME(poweroff, halt, _BB_DIR_SBIN, _BB_SUID_NEVER, poweroff)) +USE_PRINTENV(APPLET(printenv, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_PRINTF(APPLET(printf, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_PS(APPLET(ps, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_PWD(APPLET(pwd, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_RAIDAUTORUN(APPLET(raidautorun, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_RDATE(APPLET(rdate, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_READAHEAD(APPLET(readahead, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_READLINK(APPLET(readlink, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_READPROFILE(APPLET(readprofile, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_REALPATH(APPLET(realpath, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_HALT(APPLET_ODDNAME(reboot, halt, _BB_DIR_SBIN, _BB_SUID_NEVER, reboot)) +USE_RENICE(APPLET(renice, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_RESET(APPLET(reset, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_RESIZE(APPLET(resize, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_RM(APPLET(rm, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_RMDIR(APPLET(rmdir, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_RMMOD(APPLET(rmmod, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_ROUTE(APPLET(route, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_RPM(APPLET(rpm, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_RPM2CPIO(APPLET(rpm2cpio, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_RUN_PARTS(APPLET_ODDNAME(run-parts, run_parts, _BB_DIR_BIN, _BB_SUID_NEVER, run_parts)) +USE_RUNLEVEL(APPLET(runlevel, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_RUNSV(APPLET(runsv, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_RUNSVDIR(APPLET(runsvdir, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_RX(APPLET(rx, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_SED(APPLET(sed, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_SEQ(APPLET(seq, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_SETARCH(APPLET(setarch, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_SETCONSOLE(APPLET(setconsole, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_SETKEYCODES(APPLET(setkeycodes, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_SETLOGCONS(APPLET(setlogcons, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_SETSID(APPLET(setsid, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_SETUIDGID(APPLET_ODDNAME(setuidgid, chpst, _BB_DIR_USR_BIN, _BB_SUID_NEVER, setuidgid)) +USE_FEATURE_SH_IS_ASH(APPLET_NOUSAGE(sh, ash, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_FEATURE_SH_IS_HUSH(APPLET_NOUSAGE(sh, hush, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_FEATURE_SH_IS_LASH(APPLET_NOUSAGE(sh, lash, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_FEATURE_SH_IS_MSH(APPLET_NOUSAGE(sh, msh, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_SHA1SUM(APPLET_ODDNAME(sha1sum, md5_sha1_sum, _BB_DIR_USR_BIN, _BB_SUID_NEVER, sha1sum)) +USE_SLEEP(APPLET(sleep, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_SOFTLIMIT(APPLET_ODDNAME(softlimit, chpst, _BB_DIR_USR_BIN, _BB_SUID_NEVER, softlimit)) +USE_SORT(APPLET(sort, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_START_STOP_DAEMON(APPLET_ODDNAME(start-stop-daemon, start_stop_daemon, _BB_DIR_SBIN, _BB_SUID_NEVER, start_stop_daemon)) +USE_STAT(APPLET(stat, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_STRINGS(APPLET(strings, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_STTY(APPLET(stty, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_SU(APPLET(su, _BB_DIR_BIN, _BB_SUID_ALWAYS)) +USE_SULOGIN(APPLET(sulogin, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_SUM(APPLET(sum, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_SV(APPLET(sv, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_SVLOGD(APPLET(svlogd, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_SWAPONOFF(APPLET_ODDNAME(swapoff, swap_on_off, _BB_DIR_SBIN, _BB_SUID_NEVER,swapoff)) +USE_SWAPONOFF(APPLET_ODDNAME(swapon, swap_on_off, _BB_DIR_SBIN, _BB_SUID_NEVER, swapon)) +USE_SWITCH_ROOT(APPLET(switch_root, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_SYNC(APPLET(sync, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_BB_SYSCTL(APPLET(sysctl, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_SYSLOGD(APPLET(syslogd, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_TAIL(APPLET(tail, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_TAR(APPLET(tar, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_TASKSET(APPLET(taskset, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_TEE(APPLET(tee, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_TELNET(APPLET(telnet, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_TELNETD(APPLET(telnetd, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_TEST(APPLET(test, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_TFTP(APPLET(tftp, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_TIME(APPLET(time, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_TOP(APPLET(top, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_TOUCH(APPLET(touch, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_TR(APPLET(tr, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_TRACEROUTE(APPLET(traceroute, _BB_DIR_USR_BIN, _BB_SUID_MAYBE)) +USE_TRUE(APPLET(true, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_TTY(APPLET(tty, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_TUNE2FS(APPLET(tune2fs, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_APP_UDHCPC(APPLET(udhcpc, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_APP_UDHCPD(APPLET(udhcpd, _BB_DIR_USR_SBIN, _BB_SUID_NEVER)) +USE_UMOUNT(APPLET(umount, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_UNAME(APPLET(uname, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_UNCOMPRESS(APPLET(uncompress, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_UNIQ(APPLET(uniq, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_UNIX2DOS(APPLET_ODDNAME(unix2dos, dos2unix, _BB_DIR_USR_BIN, _BB_SUID_NEVER, unix2dos)) +USE_UNLZMA(APPLET(unlzma, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_UNZIP(APPLET(unzip, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_UPTIME(APPLET(uptime, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_USLEEP(APPLET(usleep, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_UUDECODE(APPLET(uudecode, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_UUENCODE(APPLET(uuencode, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_VCONFIG(APPLET(vconfig, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_VI(APPLET(vi, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_VLOCK(APPLET(vlock, _BB_DIR_USR_BIN, _BB_SUID_ALWAYS)) +USE_WATCH(APPLET(watch, _BB_DIR_BIN, _BB_SUID_NEVER)) +USE_WATCHDOG(APPLET(watchdog, _BB_DIR_SBIN, _BB_SUID_NEVER)) +USE_WC(APPLET(wc, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_WGET(APPLET(wget, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_WHICH(APPLET(which, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_WHO(APPLET(who, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_WHOAMI(APPLET(whoami, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_XARGS(APPLET(xargs, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_YES(APPLET(yes, _BB_DIR_USR_BIN, _BB_SUID_NEVER)) +USE_GUNZIP(APPLET_ODDNAME(zcat, gunzip, _BB_DIR_BIN, _BB_SUID_NEVER, zcat)) +USE_ZCIP(APPLET(zcip, _BB_DIR_SBIN, _BB_SUID_NEVER)) + +#if !defined(PROTOTYPES) && !defined(MAKE_USAGE) + { 0,NULL,0,0 } +}; + +#endif diff --git a/include/busybox.h b/include/busybox.h new file mode 100644 index 000000000..d20337ff1 --- /dev/null +++ b/include/busybox.h @@ -0,0 +1,47 @@ +/* vi: set sw=4 ts=4: */ +/* + * Busybox main internal header file + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ +#ifndef _BB_INTERNAL_H_ +#define _BB_INTERNAL_H_ 1 + +#include "libbb.h" + +/* order matters: used as index into "install_dir[]" in busybox.c */ +enum Location { + _BB_DIR_ROOT = 0, + _BB_DIR_BIN, + _BB_DIR_SBIN, + _BB_DIR_USR_BIN, + _BB_DIR_USR_SBIN +}; + +enum SUIDRoot { + _BB_SUID_NEVER = 0, + _BB_SUID_MAYBE, + _BB_SUID_ALWAYS +}; + +struct BB_applet { + const char *name; + int (*main) (int argc, char **argv); + __extension__ enum Location location:4; + __extension__ enum SUIDRoot need_suid:4; +}; + +/* From busybox.c */ +extern const struct BB_applet applets[]; + +/* Automagically pull in all the applet function prototypes and + * applet usage strings. These are all of the form: + * extern int foo_main(int argc, char **argv); + * extern const char foo_usage[]; + * These are all autogenerated from the set of currently defined applets. + */ +#define PROTOTYPES +#include "applets.h" +#undef PROTOTYPES + +#endif /* _BB_INTERNAL_H_ */ diff --git a/include/dump.h b/include/dump.h new file mode 100644 index 000000000..7e1715430 --- /dev/null +++ b/include/dump.h @@ -0,0 +1,50 @@ +/* vi: set sw=4 ts=4: */ +#define F_IGNORE 0x01 /* %_A */ +#define F_SETREP 0x02 /* rep count set, not default */ +#define F_ADDRESS 0x001 /* print offset */ +#define F_BPAD 0x002 /* blank pad */ +#define F_C 0x004 /* %_c */ +#define F_CHAR 0x008 /* %c */ +#define F_DBL 0x010 /* %[EefGf] */ +#define F_INT 0x020 /* %[di] */ +#define F_P 0x040 /* %_p */ +#define F_STR 0x080 /* %s */ +#define F_U 0x100 /* %_u */ +#define F_UINT 0x200 /* %[ouXx] */ +#define F_TEXT 0x400 /* no conversions */ + +enum _vflag { ALL, DUP, FIRST, WAIT }; /* -v values */ + +typedef struct _pr { + struct _pr *nextpr; /* next print unit */ + unsigned int flags; /* flag values */ + int bcnt; /* byte count */ + char *cchar; /* conversion character */ + char *fmt; /* printf format */ + char *nospace; /* no whitespace version */ +} PR; + +typedef struct _fu { + struct _fu *nextfu; /* next format unit */ + struct _pr *nextpr; /* next print unit */ + unsigned int flags; /* flag values */ + int reps; /* repetition count */ + int bcnt; /* byte count */ + char *fmt; /* format string */ +} FU; + +typedef struct _fs { /* format strings */ + struct _fs *nextfs; /* linked list of format strings */ + struct _fu *nextfu; /* linked list of format units */ + int bcnt; +} FS; + +extern void bb_dump_add(const char *fmt); +extern int bb_dump_dump(char **argv); +extern int bb_dump_size(FS * fs); + +extern FS *bb_dump_fshead; /* head of format strings */ +extern int bb_dump_blocksize; /* data block size */ +extern int bb_dump_length; /* max bytes to read */ +extern enum _vflag bb_dump_vflag; +extern off_t bb_dump_skip; /* bytes to skip */ diff --git a/include/grp_.h b/include/grp_.h new file mode 100644 index 000000000..b34addfa2 --- /dev/null +++ b/include/grp_.h @@ -0,0 +1,115 @@ +/* vi: set sw=4 ts=4: */ +/* Copyright (C) 1991,92,95,96,97,98,99,2000,01 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +/* + * POSIX Standard: 9.2.1 Group Database Access + */ + + +#if !defined CONFIG_USE_BB_PWD_GRP +#include + +#else + +#ifndef _GRP_H +#define _GRP_H 1 + +#include +#include +#include + +/* The group structure. */ +struct group +{ + char *gr_name; /* Group name. */ + char *gr_passwd; /* Password. */ + gid_t gr_gid; /* Group ID. */ + char **gr_mem; /* Member list. */ +}; + + +/* Rewind the group-file stream. */ +extern void setgrent (void); + +/* Close the group-file stream. */ +extern void endgrent (void); + +/* Read an entry from the group-file stream, opening it if necessary. */ +extern struct group *getgrent (void); + +/* Read a group entry from STREAM. */ +extern struct group *fgetgrent (FILE *__stream); + +/* Write the given entry onto the given stream. */ +extern int putgrent (__const struct group *__restrict __p, + FILE *__restrict __f); + +/* Search for an entry with a matching group ID. */ +extern struct group *getgrgid (gid_t __gid); + +/* Search for an entry with a matching group name. */ +extern struct group *getgrnam (__const char *__name); + +/* Reentrant versions of some of the functions above. + + PLEASE NOTE: the `getgrent_r' function is not (yet) standardized. + The interface may change in later versions of this library. But + the interface is designed following the principals used for the + other reentrant functions so the chances are good this is what the + POSIX people would choose. */ + +extern int getgrent_r (struct group *__restrict __resultbuf, + char *__restrict __buffer, size_t __buflen, + struct group **__restrict __result); + +/* Search for an entry with a matching group ID. */ +extern int getgrgid_r (gid_t __gid, struct group *__restrict __resultbuf, + char *__restrict __buffer, size_t __buflen, + struct group **__restrict __result); + +/* Search for an entry with a matching group name. */ +extern int getgrnam_r (__const char *__restrict __name, + struct group *__restrict __resultbuf, + char *__restrict __buffer, size_t __buflen, + struct group **__restrict __result); + +/* Read a group entry from STREAM. This function is not standardized + an probably never will. */ +extern int fgetgrent_r (FILE *__restrict __stream, + struct group *__restrict __resultbuf, + char *__restrict __buffer, size_t __buflen, + struct group **__restrict __result); + +/* Set the group set for the current user to GROUPS (N of them). */ +extern int setgroups (size_t __n, __const gid_t *__groups); + +/* Store at most *NGROUPS members of the group set for USER into + *GROUPS. Also include GROUP. The actual number of groups found is + returned in *NGROUPS. Return -1 if the if *NGROUPS is too small. */ +extern int getgrouplist (__const char *__user, gid_t __group, + gid_t *__groups, int *__ngroups); + +/* Initialize the group set for the current user + by reading the group database and using all groups + of which USER is a member. Also include GROUP. */ +extern int initgroups (__const char *__user, gid_t __group); + + +#endif /* grp.h */ +#endif diff --git a/include/inet_common.h b/include/inet_common.h new file mode 100644 index 000000000..1c16f6ca2 --- /dev/null +++ b/include/inet_common.h @@ -0,0 +1,30 @@ +/* vi: set sw=4 ts=4: */ +/* + * stolen from net-tools-1.59 and stripped down for busybox by + * Erik Andersen + * + * Heavily modified by Manuel Novoa III Mar 12, 2001 + * + * Version: $Id: inet_common.h,v 1.4 2004/03/10 07:42:37 mjn3 Exp $ + * + */ + +#include +#include +#include +#include "platform.h" + +/* hostfirst!=0 If we expect this to be a hostname, + try hostname database first + */ +extern int INET_resolve(const char *name, struct sockaddr_in *s_in, int hostfirst); + +/* numeric: & 0x8000: "default" instead of "*", + * & 0x4000: host instead of net, + * & 0x0fff: don't resolve + */ +extern int INET_rresolve(char *name, size_t len, struct sockaddr_in *s_in, + int numeric, unsigned int netmask); + +extern int INET6_resolve(const char *name, struct sockaddr_in6 *sin6); +extern int INET6_rresolve(char *name, size_t len, struct sockaddr_in6 *sin6, int numeric); diff --git a/include/libbb.h b/include/libbb.h new file mode 100644 index 000000000..1d91a0a72 --- /dev/null +++ b/include/libbb.h @@ -0,0 +1,689 @@ +/* vi: set sw=4 ts=4: */ +/* + * Busybox main internal header file + * + * Based in part on code from sash, Copyright (c) 1999 by David I. Bell + * Permission has been granted to redistribute this code under the GPL. + * + * Licensed under the GPL version 2, see the file LICENSE in this tarball. + */ +#ifndef __LIBBUSYBOX_H__ +#define __LIBBUSYBOX_H__ 1 + +#include "platform.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_SELINUX +#include +#endif + +#ifdef CONFIG_LOCALE_SUPPORT +#include +#else +#define setlocale(x,y) +#endif + +#include "pwd_.h" +#include "grp_.h" +#include "shadow_.h" + +/* Try to pull in PATH_MAX */ +#include +#include +#ifndef PATH_MAX +#define PATH_MAX 256 +#endif + +/* Tested to work correctly (IIRC :]) */ +#define MAXINT(T) (T)( \ + ((T)-1) > 0 \ + ? (T)-1 \ + : (T)~((T)1 << (sizeof(T)*8-1)) \ + ) + +#define MININT(T) (T)( \ + ((T)-1) > 0 \ + ? (T)0 \ + : ((T)1 << (sizeof(T)*8-1)) \ + ) + +/* Large file support */ +/* Note that CONFIG_LFS forces bbox to be built with all common ops + * (stat, lseek etc) mapped to "largefile" variants by libc. + * Practically it means that open() automatically has O_LARGEFILE added + * and all filesize/file_offset parameters and struct members are "large" + * (in today's world - signed 64bit). For full support of large files, + * we need a few helper #defines (below) and careful use of off_t + * instead of int/ssize_t. No lseek64(), O_LARGEFILE etc necessary */ +#if ENABLE_LFS +/* CONFIG_LFS is on */ +# if ULONG_MAX > 0xffffffff +/* "long" is long enough on this system */ +# define XATOOFF(a) xatoul_range(a, 0, LONG_MAX) +/* usage: sz = BB_STRTOOFF(s, NULL, 10); if (errno || sz < 0) die(); */ +# define BB_STRTOOFF bb_strtoul +# define STRTOOFF strtoul +/* usage: printf("size: %"OFF_FMT"d (%"OFF_FMT"x)\n", sz, sz); */ +# define OFF_FMT "l" +# else +/* "long" is too short, need "long long" */ +# define XATOOFF(a) xatoull_range(a, 0, LLONG_MAX) +# define BB_STRTOOFF bb_strtoull +# define STRTOOFF strtoull +# define OFF_FMT "ll" +# endif +#else +/* CONFIG_LFS is off */ +# if UINT_MAX == 0xffffffff +/* While sizeof(off_t) == sizeof(int), off_t is typedef'ed to long anyway. + * gcc will throw warnings on printf("%d", off_t). Crap... */ +# define XATOOFF(a) xatoi_u(a) +# define BB_STRTOOFF bb_strtou +# define STRTOOFF strtol +# define OFF_FMT "l" +# else +# define XATOOFF(a) xatoul_range(a, 0, LONG_MAX) +# define BB_STRTOOFF bb_strtoul +# define STRTOOFF strtol +# define OFF_FMT "l" +# endif +#endif +/* scary. better ideas? (but do *test* them first!) */ +#define OFF_T_MAX ((off_t)~((off_t)1 << (sizeof(off_t)*8-1))) + +/* Some useful definitions */ +#undef FALSE +#define FALSE ((int) 0) +#undef TRUE +#define TRUE ((int) 1) +#undef SKIP +#define SKIP ((int) 2) + +/* for mtab.c */ +#define MTAB_GETMOUNTPT '1' +#define MTAB_GETDEVICE '2' + +#define BUF_SIZE 8192 +#define EXPAND_ALLOC 1024 + +/* Macros for min/max. */ +#ifndef MIN +#define MIN(a,b) (((a)<(b))?(a):(b)) +#endif + +#ifndef MAX +#define MAX(a,b) (((a)>(b))?(a):(b)) +#endif + +/* buffer allocation schemes */ +#ifdef CONFIG_FEATURE_BUFFERS_GO_ON_STACK +#define RESERVE_CONFIG_BUFFER(buffer,len) char buffer[len] +#define RESERVE_CONFIG_UBUFFER(buffer,len) unsigned char buffer[len] +#define RELEASE_CONFIG_BUFFER(buffer) ((void)0) +#else +#ifdef CONFIG_FEATURE_BUFFERS_GO_IN_BSS +#define RESERVE_CONFIG_BUFFER(buffer,len) static char buffer[len] +#define RESERVE_CONFIG_UBUFFER(buffer,len) static unsigned char buffer[len] +#define RELEASE_CONFIG_BUFFER(buffer) ((void)0) +#else +#define RESERVE_CONFIG_BUFFER(buffer,len) char *buffer=xmalloc(len) +#define RESERVE_CONFIG_UBUFFER(buffer,len) unsigned char *buffer=xmalloc(len) +#define RELEASE_CONFIG_BUFFER(buffer) free (buffer) +#endif +#endif + + +#if (__GLIBC__ < 2) +int vdprintf(int d, const char *format, va_list ap); +#endif +// This is declared here rather than #including in order to avoid +// confusing the two versions of basename. See the dirname/basename man page +// for details. +char *dirname(char *path); +/* Include our own copy of struct sysinfo to avoid binary compatibility + * problems with Linux 2.4, which changed things. Grumble, grumble. */ +struct sysinfo { + long uptime; /* Seconds since boot */ + unsigned long loads[3]; /* 1, 5, and 15 minute load averages */ + unsigned long totalram; /* Total usable main memory size */ + unsigned long freeram; /* Available memory size */ + unsigned long sharedram; /* Amount of shared memory */ + unsigned long bufferram; /* Memory used by buffers */ + unsigned long totalswap; /* Total swap space size */ + unsigned long freeswap; /* swap space still available */ + unsigned short procs; /* Number of current processes */ + unsigned short pad; /* Padding needed for m68k */ + unsigned long totalhigh; /* Total high memory size */ + unsigned long freehigh; /* Available high memory size */ + unsigned int mem_unit; /* Memory unit size in bytes */ + char _f[20-2*sizeof(long)-sizeof(int)]; /* Padding: libc5 uses this.. */ +}; +extern int sysinfo(struct sysinfo* info); + + +extern void chomp(char *s); +extern void trim(char *s); +extern char *skip_whitespace(const char *); + +extern const char *bb_mode_string(int mode); +extern int is_directory(const char *name, int followLinks, struct stat *statBuf); +extern int remove_file(const char *path, int flags); +extern int copy_file(const char *source, const char *dest, int flags); +extern int recursive_action(const char *fileName, int recurse, + int followLinks, int depthFirst, + int (*fileAction) (const char *fileName, struct stat* statbuf, void* userData, int depth), + int (*dirAction) (const char *fileName, struct stat* statbuf, void* userData, int depth), + void* userData, int depth); +extern int device_open(const char *device, int mode); +extern int get_console_fd(void); +extern char *find_block_device(char *path); +extern off_t bb_copyfd_size(int fd1, int fd2, off_t size); +extern off_t bb_copyfd_eof(int fd1, int fd2); +extern char bb_process_escape_sequence(const char **ptr); +extern char *bb_get_last_path_component(char *path); +extern int ndelay_on(int fd); + + +extern DIR *xopendir(const char *path); +extern DIR *warn_opendir(const char *path); + +char *xgetcwd(char *cwd); +char *xreadlink(const char *path); +extern void xstat(char *filename, struct stat *buf); +extern pid_t spawn(char **argv); +extern pid_t xspawn(char **argv); +extern int wait4pid(int pid); +extern void xsetgid(gid_t gid); +extern void xsetuid(uid_t uid); +extern void xdaemon(int nochdir, int noclose); +extern void xchdir(const char *path); +extern void xsetenv(const char *key, const char *value); +extern int xopen(const char *pathname, int flags); +extern int xopen3(const char *pathname, int flags, int mode); +extern off_t xlseek(int fd, off_t offset, int whence); +extern off_t fdlength(int fd); + + +extern int xsocket(int domain, int type, int protocol); +extern void xbind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen); +extern void xlisten(int s, int backlog); +extern void xconnect(int s, const struct sockaddr *s_addr, socklen_t addrlen); +extern int xconnect_tcp_v4(struct sockaddr_in *s_addr); +extern struct hostent *xgethostbyname(const char *name); +extern struct hostent *xgethostbyname2(const char *name, int af); +extern int xsocket_stream_ip4or6(sa_family_t *fp); +typedef union { + struct sockaddr sa; + struct sockaddr_in sin; +#if ENABLE_FEATURE_IPV6 + struct sockaddr_in6 sin6; +#endif +} sockaddr_inet; +extern int dotted2sockaddr(const char *dotted, struct sockaddr* sp, int socklen); +extern int create_and_bind_socket_ip4or6(const char *hostaddr, int port); +extern int setsockopt_reuseaddr(int fd); +extern int setsockopt_broadcast(int fd); + + +extern char *xstrdup(const char *s); +extern char *xstrndup(const char *s, int n); +extern char *safe_strncpy(char *dst, const char *src, size_t size); +extern char *xasprintf(const char *format, ...) __attribute__ ((format (printf, 1, 2))); + +/* dmalloc will redefine these to it's own implementation. It is safe + * to have the prototypes here unconditionally. */ +extern void *xmalloc(size_t size); +extern void *xrealloc(void *old, size_t size); +extern void *xzalloc(size_t size); + +extern ssize_t safe_read(int fd, void *buf, size_t count); +extern ssize_t full_read(int fd, void *buf, size_t count); +extern void xread(int fd, void *buf, size_t count); +extern unsigned char xread_char(int fd); +extern char *reads(int fd, char *buf, size_t count); +extern ssize_t read_close(int fd, void *buf, size_t count); +extern ssize_t open_read_close(const char *filename, void *buf, size_t count); +extern void *xmalloc_open_read_close(const char *filename, size_t *sizep); + +extern ssize_t safe_write(int fd, const void *buf, size_t count); +extern ssize_t full_write(int fd, const void *buf, size_t count); +extern void xwrite(int fd, const void *buf, size_t count); + +/* Reads and prints to stdout till eof, then closes FILE. Exits on error: */ +extern void xprint_and_close_file(FILE *file); +extern char *xmalloc_fgets(FILE *file); +/* Read up to (and including) TERMINATING_STRING: */ +extern char *xmalloc_fgets_str(FILE *file, const char *terminating_string); +/* Chops off '\n' from the end, unlike fgets: */ +extern char *xmalloc_getline(FILE *file); +extern char *bb_get_chunk_from_file(FILE *file, int *end); +extern void die_if_ferror(FILE *file, const char *msg); +extern void die_if_ferror_stdout(void); +extern void xfflush_stdout(void); +extern void fflush_stdout_and_exit(int retval) ATTRIBUTE_NORETURN; +extern int fclose_if_not_stdin(FILE *file); +extern FILE *xfopen(const char *filename, const char *mode); +/* Prints warning to stderr and returns NULL on failure: */ +extern FILE *fopen_or_warn(const char *filename, const char *mode); +/* "Opens" stdin if filename is special, else just opens file: */ +extern FILE *fopen_or_warn_stdin(const char *filename); + + +extern void smart_ulltoa5(unsigned long long ul, char buf[5]); +extern void utoa_to_buf(unsigned n, char *buf, unsigned buflen); +extern char *utoa(unsigned n); +extern void itoa_to_buf(int n, char *buf, unsigned buflen); +extern char *itoa(int n); + +struct suffix_mult { + const char *suffix; + unsigned mult; +}; +#include "xatonum.h" +/* Specialized: */ +/* Using xatoi() instead of naive atoi() is not always convenient - + * in many places people want *non-negative* values, but store them + * in signed int. Therefore we need this one: + * dies if input is not in [0, INT_MAX] range. Also will reject '-0' etc */ +int xatoi_u(const char *numstr); +/* Useful for reading port numbers */ +uint16_t xatou16(const char *numstr); + + +/* These parse entries in /etc/passwd and /etc/group. This is desirable + * for BusyBox since we want to avoid using the glibc NSS stuff, which + * increases target size and is often not needed on embedded systems. */ +extern long bb_xgetpwnam(const char *name); +extern long bb_xgetgrnam(const char *name); +/*extern char *bb_getug(char *buffer, char *idname, long id, int bufsize, char prefix);*/ +extern char *bb_getpwuid(char *name, long uid, int bufsize); +extern char *bb_getgrgid(char *group, long gid, int bufsize); +/* from chpst */ +struct bb_uidgid_t { + uid_t uid; + gid_t gid; +}; +extern unsigned uidgid_get(struct bb_uidgid_t*, const char* /*, unsigned*/); + + +enum { BB_GETOPT_ERROR = 0x80000000 }; +extern const char *opt_complementary; +#if ENABLE_GETOPT_LONG +extern const struct option *applet_long_options; +#endif +extern uint32_t option_mask32; +extern uint32_t getopt32(int argc, char **argv, const char *applet_opts, ...); + + +typedef struct llist_s { + char *data; + struct llist_s *link; +} llist_t; +extern void llist_add_to(llist_t **old_head, void *data); +extern void llist_add_to_end(llist_t **list_head, void *data); +extern void *llist_pop(llist_t **elm); +extern void llist_free(llist_t *elm, void (*freeit)(void *data)); +extern llist_t* rev_llist(llist_t *list); + +enum { + LOGMODE_NONE = 0, + LOGMODE_STDIO = 1<<0, + LOGMODE_SYSLOG = 1<<1, + LOGMODE_BOTH = LOGMODE_SYSLOG + LOGMODE_STDIO, +}; +extern const char *msg_eol; +extern int logmode; +extern int die_sleep; +extern int xfunc_error_retval; +extern void bb_show_usage(void) ATTRIBUTE_NORETURN ATTRIBUTE_EXTERNALLY_VISIBLE; +extern void bb_error_msg(const char *s, ...) __attribute__ ((format (printf, 1, 2))); +extern void bb_error_msg_and_die(const char *s, ...) __attribute__ ((noreturn, format (printf, 1, 2))); +extern void bb_perror_msg(const char *s, ...) __attribute__ ((format (printf, 1, 2))); +extern void bb_perror_msg_and_die(const char *s, ...) __attribute__ ((noreturn, format (printf, 1, 2))); +extern void bb_vherror_msg(const char *s, va_list p); +extern void bb_herror_msg(const char *s, ...) __attribute__ ((format (printf, 1, 2))); +extern void bb_herror_msg_and_die(const char *s, ...) __attribute__ ((noreturn, format (printf, 1, 2))); +extern void bb_perror_nomsg_and_die(void) ATTRIBUTE_NORETURN; +extern void bb_perror_nomsg(void); +extern void bb_info_msg(const char *s, ...) __attribute__ ((format (printf, 1, 2))); +/* These are used internally -- you shouldn't need to use them */ +extern void bb_verror_msg(const char *s, va_list p, const char *strerr); +extern void bb_vperror_msg(const char *s, va_list p); +extern void bb_vinfo_msg(const char *s, va_list p); + + +extern int bb_echo(int argc, char** argv); +extern int bb_test(int argc, char** argv); + +#ifndef BUILD_INDIVIDUAL +extern struct BB_applet *find_applet_by_name(const char *name); +extern void run_applet_by_name(const char *name, int argc, char **argv); +#endif + +extern struct mntent *find_mount_point(const char *name, const char *table); +extern void erase_mtab(const char * name); +extern unsigned int tty_baud_to_value(speed_t speed); +extern speed_t tty_value_to_baud(unsigned int value); +extern void bb_warn_ignoring_args(int n); + +extern int get_linux_version_code(void); + +extern char *query_loop(const char *device); +extern int del_loop(const char *device); +extern int set_loop(char **device, const char *file, unsigned long long offset); + + +const char *make_human_readable_str(unsigned long long size, + unsigned long block_size, unsigned long display_unit); + +char *bb_askpass(int timeout, const char * prompt); +int bb_ask_confirmation(void); +int klogctl(int type, char * b, int len); + +extern int bb_parse_mode(const char* s, mode_t* theMode); + +char *concat_path_file(const char *path, const char *filename); +char *concat_subpath_file(const char *path, const char *filename); +char *last_char_is(const char *s, int c); + +int execable_file(const char *name); +char *find_execable(const char *filename); +int exists_execable(const char *filename); + +USE_DESKTOP(long long) int uncompress(int fd_in, int fd_out); +int inflate(int in, int out); + +int create_icmp_socket(void); +int create_icmp6_socket(void); + +unsigned short bb_lookup_port(const char *port, const char *protocol, unsigned short default_port); +void bb_lookup_host(struct sockaddr_in *s_in, const char *host); + +int bb_make_directory(char *path, long mode, int flags); + +int get_signum(const char *name); +const char *get_signame(int number); + +char *bb_simplify_path(const char *path); + +#define FAIL_DELAY 3 +extern void bb_do_delay(int seconds); +extern void change_identity(const struct passwd *pw); +extern const char *change_identity_e2str(const struct passwd *pw); +extern void run_shell(const char *shell, int loginshell, const char *command, const char **additional_args); +#ifdef CONFIG_SELINUX +extern void renew_current_security_context(void); +extern void set_current_security_context(security_context_t sid); +#endif +extern int restricted_shell(const char *shell); +extern void setup_environment(const char *shell, int loginshell, int changeenv, const struct passwd *pw); +extern int correct_password(const struct passwd *pw); +extern char *pw_encrypt(const char *clear, const char *salt); +extern int obscure(const char *old, const char *newval, const struct passwd *pwdp); +extern int index_in_str_array(const char * const string_array[], const char *key); +extern int index_in_substr_array(const char * const string_array[], const char *key); +extern void print_login_issue(const char *issue_file, const char *tty); +extern void print_login_prompt(void); +#ifdef BB_NOMMU +extern void vfork_daemon(int nochdir, int noclose); +extern void vfork_daemon_rexec(int nochdir, int noclose, + int argc, char **argv, char *foreground_opt); +#endif +extern int get_terminal_width_height(int fd, int *width, int *height); +extern unsigned long get_ug_id(const char *s, long (*__bb_getxxnam)(const char *)); + +int is_in_ino_dev_hashtable(const struct stat *statbuf, char **name); +void add_to_ino_dev_hashtable(const struct stat *statbuf, const char *name); +void reset_ino_dev_hashtable(void); +#ifdef __GLIBC__ +/* At least glibc has horrendously large inline for this, so wrap it */ +extern unsigned long long bb_makedev(unsigned int major, unsigned int minor); +#undef makedev +#define makedev(a,b) bb_makedev(a,b) +#endif + + +#ifndef COMM_LEN +#ifdef TASK_COMM_LEN +enum { COMM_LEN = TASK_COMM_LEN }; +#else +/* synchronize with sizeof(task_struct.comm) in /usr/include/linux/sched.h */ +enum { COMM_LEN = 16 }; +#endif +#endif +typedef struct { + DIR *dir; +/* Fields are set to 0/NULL if failed to determine (or not requested) */ + char *cmd; + unsigned long rss; + unsigned long stime, utime; + unsigned pid; + unsigned ppid; + unsigned pgid; + unsigned sid; + unsigned uid; + unsigned gid; + /* basename of executable file in call to exec(2), size from */ + /* sizeof(task_struct.comm) in /usr/include/linux/sched.h */ + char state[4]; + char comm[COMM_LEN]; +// user/group? - use passwd/group parsing functions +} procps_status_t; +enum { + PSSCAN_PID = 1 << 0, + PSSCAN_PPID = 1 << 1, + PSSCAN_PGID = 1 << 2, + PSSCAN_SID = 1 << 3, + PSSCAN_UIDGID = 1 << 4, + PSSCAN_COMM = 1 << 5, + PSSCAN_CMD = 1 << 6, + PSSCAN_STATE = 1 << 7, + PSSCAN_RSS = 1 << 8, + PSSCAN_STIME = 1 << 9, + PSSCAN_UTIME = 1 << 10, + /* These are all retrieved from proc/NN/stat in one go: */ + PSSCAN_STAT = PSSCAN_PPID | PSSCAN_PGID | PSSCAN_SID + | PSSCAN_COMM | PSSCAN_STATE + | PSSCAN_RSS | PSSCAN_STIME | PSSCAN_UTIME, +}; +procps_status_t* alloc_procps_scan(int flags); +void free_procps_scan(procps_status_t* sp); +procps_status_t* procps_scan(procps_status_t* sp, int flags); +pid_t *find_pid_by_name(const char* procName); +pid_t *pidlist_reverse(pid_t *pidList); +void clear_username_cache(void); +const char* get_cached_username(uid_t uid); +const char* get_cached_groupname(gid_t gid); + + +extern const char bb_uuenc_tbl_base64[]; +extern const char bb_uuenc_tbl_std[]; +void bb_uuencode(const unsigned char *s, char *store, const int length, const char *tbl); + +typedef struct sha1_ctx_t { + uint32_t count[2]; + uint32_t hash[5]; + uint32_t wbuf[16]; +} sha1_ctx_t; +void sha1_begin(sha1_ctx_t *ctx); +void sha1_hash(const void *data, size_t length, sha1_ctx_t *ctx); +void *sha1_end(void *resbuf, sha1_ctx_t *ctx); + +typedef struct md5_ctx_t { + uint32_t A; + uint32_t B; + uint32_t C; + uint32_t D; + uint64_t total; + uint32_t buflen; + char buffer[128]; +} md5_ctx_t; +void md5_begin(md5_ctx_t *ctx); +void md5_hash(const void *data, size_t length, md5_ctx_t *ctx); +void *md5_end(void *resbuf, md5_ctx_t *ctx); + +uint32_t *crc32_filltable(int endian); + + +enum { /* DO NOT CHANGE THESE VALUES! cp.c depends on them. */ + FILEUTILS_PRESERVE_STATUS = 1, + FILEUTILS_DEREFERENCE = 2, + FILEUTILS_RECUR = 4, + FILEUTILS_FORCE = 8, + FILEUTILS_INTERACTIVE = 0x10, + FILEUTILS_MAKE_HARDLINK = 0x20, + FILEUTILS_MAKE_SOFTLINK = 0x40, +}; +#define FILEUTILS_CP_OPTSTR "pdRfils" + +extern const char *applet_name; +extern const char BB_BANNER[]; + +extern const char bb_msg_full_version[]; +extern const char bb_msg_memory_exhausted[]; +extern const char bb_msg_invalid_date[]; +extern const char bb_msg_read_error[]; +extern const char bb_msg_write_error[]; +extern const char bb_msg_unknown[]; +extern const char bb_msg_can_not_create_raw_socket[]; +extern const char bb_msg_perm_denied_are_you_root[]; +extern const char bb_msg_requires_arg[]; +extern const char bb_msg_invalid_arg[]; +extern const char bb_msg_standard_input[]; +extern const char bb_msg_standard_output[]; + +extern const char bb_str_default[]; + +extern const char bb_path_mtab_file[]; +extern const char bb_path_nologin_file[]; +extern const char bb_path_passwd_file[]; +extern const char bb_path_shadow_file[]; +extern const char bb_path_gshadow_file[]; +extern const char bb_path_group_file[]; +extern const char bb_path_securetty_file[]; +extern const char bb_path_motd_file[]; +extern const char bb_path_wtmp_file[]; +extern const char bb_dev_null[]; + +#ifndef BUFSIZ +#define BUFSIZ 4096 +#endif +extern char bb_common_bufsiz1[BUFSIZ+1]; + +/* You can change LIBBB_DEFAULT_LOGIN_SHELL, but don't use it, + * use bb_default_login_shell and following defines. + * If you change LIBBB_DEFAULT_LOGIN_SHELL, + * don't forget to change increment constant. */ +#define LIBBB_DEFAULT_LOGIN_SHELL "-/bin/sh" +extern const char bb_default_login_shell[]; +/* "/bin/sh" */ +#define DEFAULT_SHELL (bb_default_login_shell+1) +/* "sh" */ +#define DEFAULT_SHELL_SHORT_NAME (bb_default_login_shell+6) + + +#ifdef CONFIG_FEATURE_DEVFS +# define CURRENT_VC "/dev/vc/0" +# define VC_1 "/dev/vc/1" +# define VC_2 "/dev/vc/2" +# define VC_3 "/dev/vc/3" +# define VC_4 "/dev/vc/4" +# define VC_5 "/dev/vc/5" +#if defined(__sh__) || defined(__H8300H__) || defined(__H8300S__) +/* Yes, this sucks, but both SH (including sh64) and H8 have a SCI(F) for their + respective serial ports .. as such, we can't use the common device paths for + these. -- PFM */ +# define SC_0 "/dev/ttsc/0" +# define SC_1 "/dev/ttsc/1" +# define SC_FORMAT "/dev/ttsc/%d" +#else +# define SC_0 "/dev/tts/0" +# define SC_1 "/dev/tts/1" +# define SC_FORMAT "/dev/tts/%d" +#endif +# define VC_FORMAT "/dev/vc/%d" +# define LOOP_FORMAT "/dev/loop/%d" +# define LOOP_NAME "/dev/loop/" +# define FB_0 "/dev/fb/0" +#else +# define CURRENT_VC "/dev/tty0" +# define VC_1 "/dev/tty1" +# define VC_2 "/dev/tty2" +# define VC_3 "/dev/tty3" +# define VC_4 "/dev/tty4" +# define VC_5 "/dev/tty5" +#if defined(__sh__) || defined(__H8300H__) || defined(__H8300S__) +# define SC_0 "/dev/ttySC0" +# define SC_1 "/dev/ttySC1" +# define SC_FORMAT "/dev/ttySC%d" +#else +# define SC_0 "/dev/ttyS0" +# define SC_1 "/dev/ttyS1" +# define SC_FORMAT "/dev/ttyS%d" +#endif +# define VC_FORMAT "/dev/tty%d" +# define LOOP_FORMAT "/dev/loop%d" +# define LOOP_NAME "/dev/loop" +# define FB_0 "/dev/fb0" +#endif + +/* The following devices are the same on devfs and non-devfs systems. */ +#define CURRENT_TTY "/dev/tty" +#define CONSOLE_DEV "/dev/console" + + +#ifndef RB_POWER_OFF +/* Stop system and switch power off if possible. */ +#define RB_POWER_OFF 0x4321fedc +#endif + +/* Make sure we call functions instead of macros. */ +#undef isalnum +#undef isalpha +#undef isascii +#undef isblank +#undef iscntrl +#undef isgraph +#undef islower +#undef isprint +#undef ispunct +#undef isspace +#undef isupper +#undef isxdigit + +/* This one is more efficient - we save ~400 bytes */ +#undef isdigit +#define isdigit(a) ((unsigned)((a) - '0') <= 9) + + +#ifdef DMALLOC +#include +#endif + +#endif /* __LIBBUSYBOX_H__ */ diff --git a/include/platform.h b/include/platform.h new file mode 100644 index 000000000..345e9cb7a --- /dev/null +++ b/include/platform.h @@ -0,0 +1,276 @@ +/* vi: set sw=4 ts=4: */ +/* + Copyright 2006, Bernhard Fischer + + Licensed under the GPL v2 or later, see the file LICENSE in this tarball. +*/ +#ifndef __PLATFORM_H +#define __PLATFORM_H 1 + +/* Convenience macros to test the version of gcc. */ +#undef __GNUC_PREREQ +#if defined __GNUC__ && defined __GNUC_MINOR__ +# define __GNUC_PREREQ(maj, min) \ + ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min)) +#else +# define __GNUC_PREREQ(maj, min) 0 +#endif + +/* __restrict is known in EGCS 1.2 and above. */ +#if !__GNUC_PREREQ (2,92) +# ifndef __restrict +# define __restrict /* Ignore */ +# endif +#endif + +/* Define macros for some gcc attributes. This permits us to use the + macros freely, and know that they will come into play for the + version of gcc in which they are supported. */ + +#if !__GNUC_PREREQ (2,7) +# ifndef __attribute__ +# define __attribute__(x) +# endif +#endif + +#undef inline +#if defined(__STDC_VERSION__) && __STDC_VERSION__ > 199901L +/* it's a keyword */ +#else +# if __GNUC_PREREQ (2,7) +# define inline __inline__ +# else +# define inline +# endif +#endif + +#ifndef __const +# define __const const +#endif + +# define ATTRIBUTE_UNUSED __attribute__ ((__unused__)) +# define ATTRIBUTE_NORETURN __attribute__ ((__noreturn__)) +# define ATTRIBUTE_PACKED __attribute__ ((__packed__)) +# define ATTRIBUTE_ALIGNED(m) __attribute__ ((__aligned__(m))) +# if __GNUC_PREREQ (3,0) +# define ATTRIBUTE_ALWAYS_INLINE __attribute__ ((always_inline)) inline +# else +# define ATTRIBUTE_ALWAYS_INLINE inline +# endif + +/* -fwhole-program makes all symbols local. The attribute externally_visible + forces a symbol global. */ +# if __GNUC_PREREQ (4,1) +# define ATTRIBUTE_EXTERNALLY_VISIBLE __attribute__ ((__externally_visible__)) +# else +# define ATTRIBUTE_EXTERNALLY_VISIBLE +# endif /* GNUC >= 4.1 */ + +/* We use __extension__ in some places to suppress -pedantic warnings + about GCC extensions. This feature didn't work properly before + gcc 2.8. */ +#if !__GNUC_PREREQ (2,8) +# ifndef __extension__ +# define __extension__ +# endif +#endif + +/* gcc-2.95 had no va_copy but only __va_copy. */ +#if !__GNUC_PREREQ (3,0) +# include +# if !defined va_copy && defined __va_copy +# define va_copy(d,s) __va_copy((d),(s)) +# endif +#endif + +/* ---- Endian Detection ------------------------------------ */ + +#if (defined __digital__ && defined __unix__) +# include +# define __BIG_ENDIAN__ (BYTE_ORDER == BIG_ENDIAN) +# define __BYTE_ORDER BYTE_ORDER +#elif !defined __APPLE__ +# include +# include +#endif + +#ifdef __BIG_ENDIAN__ +# define BB_BIG_ENDIAN 1 +# define BB_LITTLE_ENDIAN 0 +#elif __BYTE_ORDER == __BIG_ENDIAN +# define BB_BIG_ENDIAN 1 +# define BB_LITTLE_ENDIAN 0 +#else +# define BB_BIG_ENDIAN 0 +# define BB_LITTLE_ENDIAN 1 +#endif + +#if BB_BIG_ENDIAN +#define SWAP_BE16(x) (x) +#define SWAP_BE32(x) (x) +#define SWAP_BE64(x) (x) +#define SWAP_LE16(x) bswap_16(x) +#define SWAP_LE32(x) bswap_32(x) +#define SWAP_LE64(x) bswap_64(x) +#else +#define SWAP_BE16(x) bswap_16(x) +#define SWAP_BE32(x) bswap_32(x) +#define SWAP_BE64(x) bswap_64(x) +#define SWAP_LE16(x) (x) +#define SWAP_LE32(x) (x) +#define SWAP_LE64(x) (x) +#endif + +/* ---- Networking ------------------------------------------ */ +#ifndef __APPLE__ +# include +#else +# include +#endif + +#ifndef __socklen_t_defined +typedef int socklen_t; +#endif + +/* ---- Compiler dependent settings ------------------------- */ +#ifndef __GNUC__ +#if defined __INTEL_COMPILER +__extension__ typedef __signed__ long long __s64; +__extension__ typedef unsigned long long __u64; +#endif /* __INTEL_COMPILER */ +#endif /* ifndef __GNUC__ */ + +#if (defined __digital__ && defined __unix__) +# undef HAVE_MNTENT_H +#else +# define HAVE_MNTENT_H 1 +#endif /* ___digital__ && __unix__ */ + +/*----- Kernel versioning ------------------------------------*/ +#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c)) + +/* ---- miscellaneous --------------------------------------- */ + +#if defined(__GNU_LIBRARY__) && __GNU_LIBRARY__ < 5 && \ + !defined(__dietlibc__) && \ + !defined(_NEWLIB_VERSION) && \ + !(defined __digital__ && defined __unix__) +# error "Sorry, this libc version is not supported :(" +#endif + +// Don't perpetuate e2fsck crap into the headers. Clean up e2fsck instead. + +#if defined __GLIBC__ || defined __UCLIBC__ \ + || defined __dietlibc__ || defined _NEWLIB_VERSION +#include +#define HAVE_FEATURES_H +#include +#define HAVE_STDINT_H +#else +/* Largest integral types. */ +#if __BIG_ENDIAN__ +typedef long int intmax_t; +typedef unsigned long int uintmax_t; +#else +__extension__ +typedef long long int intmax_t; +__extension__ +typedef unsigned long long int uintmax_t; +#endif +#endif + +/* uclibc does not implement daemon() for no-mmu systems. + * For 0.9.29 and svn, __ARCH_USE_MMU__ indicates no-mmu reliably. + * For earlier versions there is no reliable way to check if we are building + * for a mmu-less system; the user should pass EXTRA_CFLAGS="-DBB_NOMMU" + * on his own. + */ +#if defined __UCLIBC__ && __UCLIBC_MAJOR__ >= 0 && __UCLIBC_MINOR__ >= 9 && \ + __UCLIBC_SUBLEVEL__ > 28 && !defined __ARCH_USE_MMU__ +#define BB_NOMMU +#endif + +/* Platforms that haven't got dprintf need to implement fdprintf() in + * libbb. This would require a platform.c. It's not going to be cleaned + * out of the tree, so stop saying it should be. */ +#define fdprintf dprintf + +/* Don't use lchown with glibc older then 2.1.x ... uC-libc lacks it */ +#if (defined __GLIBC__ && __GLIBC__ <= 2 && __GLIBC_MINOR__ < 1) || \ + defined __UC_LIBC__ +# define lchown chown +#endif + +/* THIS SHOULD BE CLEANED OUT OF THE TREE ENTIRELY */ +/* FIXME: fix tar.c! */ +#ifndef FNM_LEADING_DIR +#define FNM_LEADING_DIR 0 +#endif + +#if (defined __digital__ && defined __unix__) +#include +#define HAVE_STANDARDS_H +#include +#define HAVE_INTTYPES_H +#define PRIu32 "u" + +/* use legacy setpgrp(pidt_,pid_t) for now. move to platform.c */ +#define bb_setpgrp do{pid_t __me = getpid();setpgrp(__me,__me);}while(0) + +#if !defined ADJ_OFFSET_SINGLESHOT && defined MOD_CLKA && defined MOD_OFFSET +#define ADJ_OFFSET_SINGLESHOT (MOD_CLKA | MOD_OFFSET) +#endif +#if !defined ADJ_FREQUENCY && defined MOD_FREQUENCY +#define ADJ_FREQUENCY MOD_FREQUENCY +#endif +#if !defined ADJ_TIMECONST && defined MOD_TIMECONST +#define ADJ_TIMECONST MOD_TIMECONST +#endif +#if !defined ADJ_TICK && defined MOD_CLKB +#define ADJ_TICK MOD_CLKB +#endif + +#else +#define bb_setpgrp setpgrp() +#endif + +#if defined(__linux__) +#include +// Make sure we have all the new mount flags we actually try to use. +#ifndef MS_BIND +#define MS_BIND (1<<12) +#endif +#ifndef MS_MOVE +#define MS_MOVE (1<<13) +#endif +#ifndef MS_RECURSIVE +#define MS_RECURSIVE (1<<14) +#endif +#ifndef MS_SILENT +#define MS_SILENT (1<<15) +#endif + +// The shared subtree stuff, which went in around 2.6.15 +#ifndef MS_UNBINDABLE +#define MS_UNBINDABLE (1<<17) +#endif +#ifndef MS_PRIVATE +#define MS_PRIVATE (1<<18) +#endif +#ifndef MS_SLAVE +#define MS_SLAVE (1<<19) +#endif +#ifndef MS_SHARED +#define MS_SHARED (1<<20) +#endif + + +#if !defined(BLKSSZGET) +#define BLKSSZGET _IO(0x12, 104) +#endif +#if !defined(BLKGETSIZE64) +#define BLKGETSIZE64 _IOR(0x12,114,size_t) +#endif +#endif + +#endif /* platform.h */ diff --git a/include/pwd_.h b/include/pwd_.h new file mode 100644 index 000000000..d161c0e8f --- /dev/null +++ b/include/pwd_.h @@ -0,0 +1,107 @@ +/* vi: set sw=4 ts=4: */ +/* Copyright (C) 1991,92,95,96,97,98,99,2001 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +/* + * POSIX Standard: 9.2.2 User Database Access + */ + +#if !defined CONFIG_USE_BB_PWD_GRP +#include + +#else + +#ifndef _PWD_H +#define _PWD_H 1 + +#include +#include +#include + +/* The passwd structure. */ +struct passwd +{ + char *pw_name; /* Username. */ + char *pw_passwd; /* Password. */ + uid_t pw_uid; /* User ID. */ + gid_t pw_gid; /* Group ID. */ + char *pw_gecos; /* Real name. */ + char *pw_dir; /* Home directory. */ + char *pw_shell; /* Shell program. */ +}; + + +/* Rewind the password-file stream. */ +extern void setpwent (void); + +/* Close the password-file stream. */ +extern void endpwent (void); + +/* Read an entry from the password-file stream, opening it if necessary. */ +extern struct passwd *getpwent (void); + +/* Read an entry from STREAM. */ +extern struct passwd *fgetpwent (FILE *__stream); + +/* Write the given entry onto the given stream. */ +extern int putpwent (__const struct passwd *__restrict __p, + FILE *__restrict __f); + +/* Search for an entry with a matching user ID. */ +extern struct passwd *getpwuid (uid_t __uid); + +/* Search for an entry with a matching username. */ +extern struct passwd *getpwnam (__const char *__name); + +/* Reentrant versions of some of the functions above. + + PLEASE NOTE: the `getpwent_r' function is not (yet) standardized. + The interface may change in later versions of this library. But + the interface is designed following the principals used for the + other reentrant functions so the chances are good this is what the + POSIX people would choose. */ + +extern int getpwent_r (struct passwd *__restrict __resultbuf, + char *__restrict __buffer, size_t __buflen, + struct passwd **__restrict __result); + +extern int getpwuid_r (uid_t __uid, + struct passwd *__restrict __resultbuf, + char *__restrict __buffer, size_t __buflen, + struct passwd **__restrict __result); + +extern int getpwnam_r (__const char *__restrict __name, + struct passwd *__restrict __resultbuf, + char *__restrict __buffer, size_t __buflen, + struct passwd **__restrict __result); + + +/* Read an entry from STREAM. This function is not standardized and + probably never will. */ +extern int fgetpwent_r (FILE *__restrict __stream, + struct passwd *__restrict __resultbuf, + char *__restrict __buffer, size_t __buflen, + struct passwd **__restrict __result); + +/* Re-construct the password-file line for the given uid + in the given buffer. This knows the format that the caller + will expect, but this need not be the format of the password file. */ +extern int getpw (uid_t __uid, char *__buffer); + +#endif /* pwd.h */ +#endif diff --git a/include/shadow_.h b/include/shadow_.h new file mode 100644 index 000000000..177ee5f93 --- /dev/null +++ b/include/shadow_.h @@ -0,0 +1,99 @@ +/* vi: set sw=4 ts=4: */ +/* Copyright (C) 1996, 1997, 1998, 1999 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +/* Declaration of types and functions for shadow password suite. */ + +#if !defined CONFIG_USE_BB_SHADOW +#include +#else + +#ifndef _SHADOW_H +#define _SHADOW_H 1 + +#include + +/* Paths to the user database files. */ +#ifndef _PATH_SHADOW +#define _PATH_SHADOW "/etc/shadow" +#endif +#define SHADOW _PATH_SHADOW + + +/* Structure of the password file. */ +struct spwd +{ + char *sp_namp; /* Login name. */ + char *sp_pwdp; /* Encrypted password. */ + long int sp_lstchg; /* Date of last change. */ + long int sp_min; /* Minimum number of days between changes. */ + long int sp_max; /* Maximum number of days between changes. */ + long int sp_warn; /* Number of days to warn user to change + the password. */ + long int sp_inact; /* Number of days the account may be + inactive. */ + long int sp_expire; /* Number of days since 1970-01-01 until + account expires. */ + unsigned long int sp_flag; /* Reserved. */ +}; + + +/* Open database for reading. */ +extern void setspent (void); + +/* Close database. */ +extern void endspent (void); + +/* Get next entry from database, perhaps after opening the file. */ +extern struct spwd *getspent (void); + +/* Get shadow entry matching NAME. */ +extern struct spwd *getspnam (__const char *__name); + +/* Read shadow entry from STRING. */ +extern struct spwd *sgetspent (__const char *__string); + +/* Read next shadow entry from STREAM. */ +extern struct spwd *fgetspent (FILE *__stream); + +/* Write line containing shadow password entry to stream. */ +extern int putspent (__const struct spwd *__p, FILE *__stream); + +/* Reentrant versions of some of the functions above. */ +extern int getspent_r (struct spwd *__result_buf, char *__buffer, + size_t __buflen, struct spwd **__result); + +extern int getspnam_r (__const char *__name, struct spwd *__result_buf, + char *__buffer, size_t __buflen, + struct spwd **__result); + +extern int sgetspent_r (__const char *__string, struct spwd *__result_buf, + char *__buffer, size_t __buflen, + struct spwd **__result); + +extern int fgetspent_r (FILE *__stream, struct spwd *__result_buf, + char *__buffer, size_t __buflen, + struct spwd **__result); +/* Protect password file against multi writers. */ +extern int lckpwdf (void); + +/* Unlock password file. */ +extern int ulckpwdf (void); + +#endif /* shadow.h */ +#endif diff --git a/include/unarchive.h b/include/unarchive.h new file mode 100644 index 000000000..7de6a63fe --- /dev/null +++ b/include/unarchive.h @@ -0,0 +1,114 @@ +/* vi: set sw=4 ts=4: */ +#ifndef __UNARCHIVE_H__ +#define __UNARCHIVE_H__ + +#define ARCHIVE_PRESERVE_DATE 1 +#define ARCHIVE_CREATE_LEADING_DIRS 2 +#define ARCHIVE_EXTRACT_UNCONDITIONAL 4 +#define ARCHIVE_EXTRACT_QUIET 8 +#define ARCHIVE_EXTRACT_NEWER 16 +#define ARCHIVE_NOPRESERVE_OWN 32 +#define ARCHIVE_NOPRESERVE_PERM 64 + +#include "libbb.h" + +typedef struct file_headers_s { + char *name; + char *link_name; + off_t size; + uid_t uid; + gid_t gid; + mode_t mode; + time_t mtime; + dev_t device; +} file_header_t; + +typedef struct archive_handle_s { + /* define if the header and data component should processed */ + char (*filter)(struct archive_handle_s *); + llist_t *accept; + /* List of files that have been rejected */ + llist_t *reject; + /* List of files that have successfully been worked on */ + llist_t *passed; + + /* Contains the processed header entry */ + file_header_t *file_header; + + /* process the header component, e.g. tar -t */ + void (*action_header)(const file_header_t *); + + /* process the data component, e.g. extract to filesystem */ + void (*action_data)(struct archive_handle_s *); + + /* How to process any sub archive, e.g. get_header_tar_gz */ + char (*action_data_subarchive)(struct archive_handle_s *); + + /* Contains the handle to a sub archive */ + struct archive_handle_s *sub_archive; + + /* The raw stream as read from disk or stdin */ + int src_fd; + + /* Count the number of bytes processed */ + off_t offset; + + /* Function that skips data: read_by_char or read_by_skip */ + void (*seek)(const struct archive_handle_s *archive_handle, const unsigned int amount); + + /* Temporary storage */ + char *buffer; + + /* Flags and misc. stuff */ + unsigned char flags; + +} archive_handle_t; + +extern archive_handle_t *init_handle(void); + +extern char filter_accept_all(archive_handle_t *archive_handle); +extern char filter_accept_list(archive_handle_t *archive_handle); +extern char filter_accept_list_reassign(archive_handle_t *archive_handle); +extern char filter_accept_reject_list(archive_handle_t *archive_handle); + +extern void unpack_ar_archive(archive_handle_t *ar_archive); + +extern void data_skip(archive_handle_t *archive_handle); +extern void data_extract_all(archive_handle_t *archive_handle); +extern void data_extract_to_stdout(archive_handle_t *archive_handle); +extern void data_extract_to_buffer(archive_handle_t *archive_handle); + +extern void header_skip(const file_header_t *file_header); +extern void header_list(const file_header_t *file_header); +extern void header_verbose_list(const file_header_t *file_header); + +extern void check_header_gzip(int src_fd); + +extern char get_header_ar(archive_handle_t *archive_handle); +extern char get_header_cpio(archive_handle_t *archive_handle); +extern char get_header_tar(archive_handle_t *archive_handle); +extern char get_header_tar_bz2(archive_handle_t *archive_handle); +extern char get_header_tar_lzma(archive_handle_t *archive_handle); +extern char get_header_tar_gz(archive_handle_t *archive_handle); + +extern void seek_by_jump(const archive_handle_t *archive_handle, const unsigned int amount); +extern void seek_by_read(const archive_handle_t *archive_handle, const unsigned int amount); + +extern ssize_t archive_xread_all_eof(archive_handle_t *archive_handle, unsigned char *buf, size_t count); + +extern void data_align(archive_handle_t *archive_handle, const unsigned short boundary); +extern const llist_t *find_list_entry(const llist_t *list, const char *filename); +extern const llist_t *find_list_entry2(const llist_t *list, const char *filename); + +extern USE_DESKTOP(long long) int uncompressStream(int src_fd, int dst_fd); +extern void inflate_init(unsigned int bufsize); +extern void inflate_cleanup(void); +extern USE_DESKTOP(long long) int inflate_unzip(int in, int out); +extern USE_DESKTOP(long long) int inflate_gunzip(int in, int out); +extern USE_DESKTOP(long long) int unlzma(int src_fd, int dst_fd); + +extern int open_transformer(int src_fd, + USE_DESKTOP(long long) int (*transformer)(int src_fd, int dst_fd)); + + +#endif diff --git a/include/usage.h b/include/usage.h new file mode 100644 index 000000000..0fba9b848 --- /dev/null +++ b/include/usage.h @@ -0,0 +1,3495 @@ +/* vi: set sw=8 ts=8: */ +/* + * This file suffers from chronically incorrect tabification + * of messages. Before editing this file: + * 1. Switch you editor to 8-space tab mode. + * 2. Do not use \t in messages, use real tab character. + * 3. Start each source line with message as follows: + * |<7 spaces>"text with tabs".... + */ + +#ifndef __BB_USAGE_H__ +#define __BB_USAGE_H__ + +#define addgroup_trivial_usage \ + "[-g GID] group_name [user_name]" +#define addgroup_full_usage \ + "Adds a group to the system\n\n" \ + "Options:\n" \ + " -g GID specify gid" + +#define adduser_trivial_usage \ + "[OPTIONS] user_name" +#define adduser_full_usage \ + "Adds a user to the system\n\n" \ + "Options:\n" \ + " -h DIR Assign home directory DIR\n" \ + " -g GECOS Assign gecos field GECOS\n" \ + " -s SHELL Assign login shell SHELL\n" \ + " -G Add the user to existing group GROUP\n" \ + " -S create a system user (ignored)\n" \ + " -D Do not assign a password (logins still possible via ssh)\n" \ + " -H Do not create the home directory" + +#define adjtimex_trivial_usage \ + "[-q] [-o offset] [-f frequency] [-p timeconstant] [-t tick]" +#define adjtimex_full_usage \ + "Reads and optionally sets system timebase parameters.\n" \ + "See adjtimex(2).\n\n" \ + "Options:\n" \ + " -q quiet mode - do not print\n" \ + " -o offset time offset, microseconds\n" \ + " -f frequency frequency adjust, integer kernel units (65536 is 1ppm)\n" \ + " (positive values make the system clock run fast)\n" \ + " -t tick microseconds per tick, usually 10000\n" \ + " -p timeconstant" + +#define ar_trivial_usage \ + "[-o] [-v] [-p] [-t] [-x] ARCHIVE FILES" +#define ar_full_usage \ + "Extract or list FILES from an ar archive.\n\n" \ + "Options:\n" \ + " -o preserve original dates\n" \ + " -p extract to stdout\n" \ + " -t list\n" \ + " -x extract\n" \ + " -v verbosely list files processed" + +#define arping_trivial_usage \ + "[-fqbDUA] [-c count] [-w timeout] [-i device] [-s sender] target" +#define arping_full_usage \ + "Ping hosts by ARP requests/replies.\n\n" \ + "Options:\n" \ + " -f Quit on first ARP reply\n" \ + " -q Be quiet\n" \ + " -b Keep broadcasting, don't go unicast\n" \ + " -D Duplicated address detection mode\n" \ + " -U Unsolicited ARP mode, update your neighbours\n" \ + " -A ARP answer mode, update your neighbours\n" \ + " -c count Stop after sending count ARP request packets\n" \ + " -w timeout Time to wait for ARP reply, in seconds\n" \ + " -i device Outgoing interface name, default is eth0\n" \ + " -s sender Set specific sender IP address\n" \ + " target Target IP address of ARP request" + +#define ash_trivial_usage \ + "[FILE]...\n" \ + "or: ash -c command [args]..." +#define ash_full_usage \ + "The ash shell (command interpreter)" + +#define awk_trivial_usage \ + "[OPTION]... [program-text] [FILE ...]" +#define awk_full_usage \ + "Options:\n" \ + " -v var=val assign value 'val' to variable 'var'\n" \ + " -F sep use 'sep' as field separator\n" \ + " -f progname read program source from file 'progname'" + +#define basename_trivial_usage \ + "FILE [SUFFIX]" +#define basename_full_usage \ + "Strips directory path and suffixes from FILE.\n" \ + "If specified, also removes any trailing SUFFIX." +#define basename_example_usage \ + "$ basename /usr/local/bin/foo\n" \ + "foo\n" \ + "$ basename /usr/local/bin/\n" \ + "bin\n" \ + "$ basename /foo/bar.txt .txt\n" \ + "bar" + +#define bunzip2_trivial_usage \ + "[OPTION]... [FILE]" +#define bunzip2_full_usage \ + "Uncompress FILE (or standard input if FILE is '-' or omitted).\n\n" \ + "Options:\n" \ + " -c Write output to standard output\n" \ + " -f Force" + +#define busybox_notes_usage \ + "Hello world!\n" + +#define bzcat_trivial_usage \ + "FILE" +#define bzcat_full_usage \ + "Uncompress to stdout." + +#define unlzma_trivial_usage \ + "[OPTION]... [FILE]" +#define unlzma_full_usage \ + "Uncompress FILE (or standard input if FILE is '-' or omitted).\n\n" \ + "Options:\n" \ + " -c Write output to standard output\n" \ + " -f Force" + +#define lzmacat_trivial_usage \ + "FILE" +#define lzmacat_full_usage \ + "Uncompress to stdout." + +#define cal_trivial_usage \ + "[-jy] [[month] year]" +#define cal_full_usage \ + "Display a calendar.\n" \ + "\nOptions:\n" \ + " -j Use julian dates\n" \ + " -y Display the entire year" + +#define cat_trivial_usage \ + "[-u] [FILE]..." +#define cat_full_usage \ + "Concatenates FILE(s) and prints them to stdout.\n\n" \ + "Options:\n" \ + " -u ignored since unbuffered i/o is always used" +#define cat_example_usage \ + "$ cat /proc/uptime\n" \ + "110716.72 17.67" + +#define catv_trivial_usage \ + "[-etv] [FILE]..." +#define catv_full_usage \ + "Display nonprinting characters as ^x or M-x.\n\n"\ + " -e End each line with $\n" \ + " -t Show tabs as ^I\n" \ + " -v Don't use ^x or M-x escapes." +#define chattr_trivial_usage \ + "[-R] [-+=AacDdijsStTu] [-v version] files..." +#define chattr_full_usage \ + "change file attributes on an ext2 fs\n\n" \ + "Modifiers:\n" \ + " - remove attributes\n" \ + " + add attributes\n" \ + " = set attributes\n" \ + "Attributes:\n" \ + " A don't track atime\n" \ + " a append mode only\n" \ + " c enable compress\n" \ + " D write dir contents synchronously\n" \ + " d do not backup with dump\n" \ + " i cannot be modified (immutable)\n" \ + " j write all data to journal first\n" \ + " s zero disk storage when deleted\n" \ + " S write file contents synchronously\n" \ + " t disable tail-merging of partial blocks with other files\n" \ + " u allow file to be undeleted\n" \ + "Options:\n" \ + " -R recursively list subdirectories\n" \ + " -v set the file's version/generation number" + +#define chgrp_trivial_usage \ + "[-Rh"USE_DESKTOP("cvf")"]... GROUP FILE..." +#define chgrp_full_usage \ + "Change the group membership of each FILE to GROUP.\n" \ + "\nOptions:\n" \ + " -R Changes files and directories recursively\n" \ + " -h Do not dereference symbolic links" \ + USE_DESKTOP( \ + "\n -c List changed files" \ + "\n -v List all files" \ + "\n -f Hide errors" \ + ) +#define chgrp_example_usage \ + "$ ls -l /tmp/foo\n" \ + "-r--r--r-- 1 andersen andersen 0 Apr 12 18:25 /tmp/foo\n" \ + "$ chgrp root /tmp/foo\n" \ + "$ ls -l /tmp/foo\n" \ + "-r--r--r-- 1 andersen root 0 Apr 12 18:25 /tmp/foo\n" + +#define chmod_trivial_usage \ + "[-R"USE_DESKTOP("cvf")"] MODE[,MODE]... FILE..." +#define chmod_full_usage \ + "Each MODE is one or more of the letters ugoa, one of the\n" \ + "symbols +-= and one or more of the letters rwxst.\n\n" \ + "Options:\n" \ + " -R Changes files and directories recursively" \ + USE_DESKTOP( \ + "\n -c List changed files" \ + "\n -v List all files" \ + "\n -f Hide errors" \ + ) +#define chmod_example_usage \ + "$ ls -l /tmp/foo\n" \ + "-rw-rw-r-- 1 root root 0 Apr 12 18:25 /tmp/foo\n" \ + "$ chmod u+x /tmp/foo\n" \ + "$ ls -l /tmp/foo\n" \ + "-rwxrw-r-- 1 root root 0 Apr 12 18:25 /tmp/foo*\n" \ + "$ chmod 444 /tmp/foo\n" \ + "$ ls -l /tmp/foo\n" \ + "-r--r--r-- 1 root root 0 Apr 12 18:25 /tmp/foo\n" + +#define chown_trivial_usage \ + "[-Rh"USE_DESKTOP("cvf")"]... OWNER[<.|:>[GROUP]] FILE..." +#define chown_full_usage \ + "Change the owner and/or group of each FILE to OWNER and/or GROUP.\n" \ + "\nOptions:\n" \ + " -R Changes files and directories recursively\n" \ + " -h Do not dereference symbolic links" \ + USE_DESKTOP( \ + "\n -c List changed files" \ + "\n -v List all files" \ + "\n -f Hide errors" \ + ) +#define chown_example_usage \ + "$ ls -l /tmp/foo\n" \ + "-r--r--r-- 1 andersen andersen 0 Apr 12 18:25 /tmp/foo\n" \ + "$ chown root /tmp/foo\n" \ + "$ ls -l /tmp/foo\n" \ + "-r--r--r-- 1 root andersen 0 Apr 12 18:25 /tmp/foo\n" \ + "$ chown root.root /tmp/foo\n" \ + "ls -l /tmp/foo\n" \ + "-r--r--r-- 1 root root 0 Apr 12 18:25 /tmp/foo\n" + +#define chpst_trivial_usage \ + "[-vP012] [-u user[:group]] [-U user[:group]] [-e dir] " \ + "[-/ dir] [-n nice] [-m bytes] [-d bytes] [-o files] " \ + "[-p processes] [-f bytes] [-c bytes] prog args" +#define chpst_full_usage \ + "Change the process state and run specified program.\n\n" \ + "-u user[:grp] set uid and gid\n" \ + "-U user[:grp] set environment variables UID and GID\n" \ + "-e dir set environment variables as specified by files\n" \ + " in the directory: file=1st_line_of_file\n" \ + "-/ dir chroot to dir\n" \ + "-n inc add inc to nice value\n" \ + "-m bytes limit data segment, stack segment, locked physical pages,\n" \ + " and total of all segment per process to bytes bytes each\n" \ + "-d bytes limit data segment\n" \ + "-o n limit the number of open file descriptors per process to n\n" \ + "-p n limit number of processes per uid to n\n" \ + "-f bytes limit output file size to bytes bytes\n" \ + "-c bytes limit core file size to bytes bytes\n" \ + "-v verbose\n" \ + "-P run prog in a new process group\n" \ + "-0 close standard input\n" \ + "-1 close standard output\n" \ + "-2 close standard error" +#define setuidgid_trivial_usage \ + "account prog args" +#define setuidgid_full_usage \ + "Sets uid and gid to account's uid and gid, removing all supplementary\n" \ + "groups, then runs prog" +#define envuidgid_trivial_usage \ + "account prog args" +#define envuidgid_full_usage \ + "Sets $UID to account's uid and $GID to account's gid, then runs prog" +#define envdir_trivial_usage \ + "dir prog args" +#define envdir_full_usage \ + "Sets various environment variables as specified by files\n" \ + "in the directory dir, then runs prog" +#define softlimit_trivial_usage \ + "[-a allbytes] [-c corebytes] [-d databytes] [-f filebytes] " \ + "[-l lockbytes] [-m membytes] [-o openfiles] [-p processes] " \ + "[-r residentbytes] [-s stackbytes] [-t cpusecs] prog args" +#define softlimit_full_usage \ + "Sets soft resource limits as specified by options, then runs prog\n" \ + "\n" \ + "-m n Same as -d n -s n -l n -a n\n" \ + "-d n Limit the data segment per process to n bytes\n" \ + "-s n Limit the stack segment per process to n bytes\n" \ + "-l n Limit the locked physical pages per process to n bytes\n" \ + "-a n Limit the total of all segments per process to n bytes\n" \ + "-o n Limit the number of open file descriptors per process to n\n" \ + "-p n Limit the number of processes per uid to n\n" \ + "Options controlling file sizes:\n" \ + "-f n Limit output file sizes to n bytes\n" \ + "-c n Limit core file sizes to n bytes\n" \ + "Efficiency opts:\n" \ + "-r n Limit the resident set size to n bytes. This limit is not\n" \ + " enforced unless physical memory is full\n" \ + "-t n Limit the CPU time to n seconds. This limit is not enforced\n" \ + " except that the process receives a SIGXCPU signal after n seconds\n" \ + "\n" \ + "Some options may have no effect on some operating systems\n" \ + "n may be =, indicating that soft limit should be set equal to hard limit" + +#define chroot_trivial_usage \ + "NEWROOT [COMMAND...]" +#define chroot_full_usage \ + "Run COMMAND with root directory set to NEWROOT." +#define chroot_example_usage \ + "$ ls -l /bin/ls\n" \ + "lrwxrwxrwx 1 root root 12 Apr 13 00:46 /bin/ls -> /BusyBox\n" \ + "# mount /dev/hdc1 /mnt -t minix\n" \ + "# chroot /mnt\n" \ + "# ls -l /bin/ls\n" \ + "-rwxr-xr-x 1 root root 40816 Feb 5 07:45 /bin/ls*\n" + +#define chvt_trivial_usage \ + "N" +#define chvt_full_usage \ + "Changes the foreground virtual terminal to /dev/ttyN" + +#define cksum_trivial_usage \ + "FILES..." +#define cksum_full_usage \ + "Calculates the CRC32 checksums of FILES." + +#define clear_trivial_usage \ + "" +#define clear_full_usage \ + "Clear screen." + +#define cmp_trivial_usage \ + "[-l] [-s] FILE1 [FILE2]" +#define cmp_full_usage \ + "Compares FILE1 vs stdin if FILE2 is not specified.\n\n" \ + "Options:\n" \ + " -l Write the byte numbers (decimal) and values (octal)\n" \ + " for all differing bytes\n" \ + " -s quiet mode - do not print" + +#define comm_trivial_usage \ + "[-123] FILE1 FILE2" +#define comm_full_usage \ + "Compares FILE1 to FILE2, or to stdin if = is specified.\n\n" \ + "Options:\n" \ + " -1 Suppress lines unique to FILE1\n" \ + " -2 Suppress lines unique to FILE2\n" \ + " -3 Suppress lines common to both files" + +#define bbconfig_trivial_usage \ + "" +#define bbconfig_full_usage \ + "Print the config file which built busybox" + +#define bbsh_trivial_usage \ + "[FILE]...\n" \ + "or: bbsh -c command [args]..." +#define bbsh_full_usage \ + "The bbsh shell (command interpreter)" + +#define cp_trivial_usage \ + "[OPTION]... SOURCE DEST" +#define cp_full_usage \ + "Copies SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY.\n" \ + "\n" \ + " -a Same as -dpR\n" \ + " -d,-P Preserve links\n" \ + " -H,-L Dereference all symlinks (implied by default)\n" \ + " -p Preserve file attributes if possible\n" \ + " -f force, overwrite\n" \ + " -i interactive, prompt before overwrite\n" \ + " -R,-r Copy directories recursively\n" \ + " -l,-s Create (sym)links" + +#define cpio_trivial_usage \ + "-[dimtuv][F cpiofile]" +#define cpio_full_usage \ + "Extract or list files from a cpio archive\n" \ + "Main operation mode:\n" \ + " d make leading directories\n" \ + " i extract\n" \ + " m preserve mtime\n" \ + " t list\n" \ + " v verbose\n" \ + " u unconditional overwrite\n" \ + " F input from file" + +#define crond_trivial_usage \ + "-d[#] -c -f -b" +#define crond_full_usage \ + " -d [#] -l [#] -S -L logfile -f -b -c dir\n" \ + " -d num debug level\n" \ + " -l num log level (8 - default)\n" \ + " -S log to syslogd (default)\n" \ + " -L file log to file\n" \ + " -f run in foreground\n" \ + " -b run in background (default)\n" \ + " -c dir working dir" + +#define crontab_trivial_usage \ + "[-c dir] {file|-}|[-u|-l|-e|-d user]" +#define crontab_full_usage \ + " file replace crontab from file\n" \ + " - replace crontab from stdin\n" \ + " -u user specify user\n" \ + " -l [user] list crontab for user\n" \ + " -e [user] edit crontab for user\n" \ + " -d [user] delete crontab for user\n" \ + " -c dir specify crontab directory" + + +#define cut_trivial_usage \ + "[OPTION]... [FILE]..." +#define cut_full_usage \ + "Prints selected fields from each input FILE to standard output.\n\n" \ + "Options:\n" \ + " -b LIST Output only bytes from LIST\n" \ + " -c LIST Output only characters from LIST\n" \ + " -d CHAR Use CHAR instead of tab as the field delimiter\n" \ + " -s Output only the lines containing delimiter\n" \ + " -f N Print only these fields\n" \ + " -n Ignored" +#define cut_example_usage \ + "$ echo \"Hello world\" | cut -f 1 -d ' '\n" \ + "Hello\n" \ + "$ echo \"Hello world\" | cut -f 2 -d ' '\n" \ + "world\n" + +#define date_trivial_usage \ + "[OPTION]... [MMDDhhmm[[CC]YY][.ss]] [+FORMAT]" +#define date_full_usage \ + "Displays the current time in the given FORMAT, or sets the system date.\n" \ + "\nOptions:\n" \ + " -R Outputs RFC-822 compliant date string\n" \ + " -d STRING Displays time described by STRING, not 'now'\n" \ +USE_FEATURE_DATE_ISOFMT( \ + " -I[TIMESPEC] Outputs an ISO-8601 compliant date/time string\n" \ + " TIMESPEC='date' (or missing) for date only,\n" \ + " 'hours', 'minutes', or 'seconds' for date and,\n" \ + " time to the indicated precision\n" \ + " -D hint Use 'hint' as date format, via strptime()\n" \ +) \ + " -s Sets time described by STRING\n" \ + " -r FILE Displays the last modification time of FILE\n" \ + " -u Prints or sets Coordinated Universal Time" +#define date_example_usage \ + "$ date\n" \ + "Wed Apr 12 18:52:41 MDT 2000\n" + +#define dc_trivial_usage \ + "expression ..." +#define dc_full_usage \ + "This is a Tiny RPN calculator that understands the\n" \ + "following operations: +, add, -, sub, *, mul, /, div, %, mod, " \ + "**, exp, and, or, not, eor.\n" \ + "For example: 'dc 2 2 add' -> 4, and 'dc 8 8 \\* 2 2 + /' -> 16.\n" \ + "\nOptions:\n" \ + "p - Prints the value on the top of the stack, without altering the stack\n" \ + "f - Prints the entire contents of the stack without altering anything\n" \ + "o - Pops the value off the top of the stack and uses it to set the output radix\n" \ + " Only 10 and 16 are supported" +#define dc_example_usage \ + "$ dc 2 2 + p\n" \ + "4\n" \ + "$ dc 8 8 \\* 2 2 + / p\n" \ + "16\n" \ + "$ dc 0 1 and p\n" \ + "0\n" \ + "$ dc 0 1 or p\n" \ + "1\n" \ + "$ echo 72 9 div 8 mul p | dc\n" \ + "64\n" + +#define dd_trivial_usage \ + "[if=FILE] [of=FILE] " USE_FEATURE_DD_IBS_OBS("[ibs=N] [obs=N] ") "[bs=N] [count=N] [skip=N]\n" \ + " [seek=N]" USE_FEATURE_DD_IBS_OBS(" [conv=notrunc|noerror|sync]") +#define dd_full_usage \ + "Copy a file, converting and formatting according to options\n\n" \ + " if=FILE read from FILE instead of stdin\n" \ + " of=FILE write to FILE instead of stdout\n" \ + " bs=N read and write N bytes at a time\n" \ + USE_FEATURE_DD_IBS_OBS( \ + " ibs=N read N bytes at a time\n") \ + USE_FEATURE_DD_IBS_OBS( \ + " obs=N write N bytes at a time\n") \ + " count=N copy only N input blocks\n" \ + " skip=N skip N input blocks\n" \ + " seek=N skip N output blocks\n" \ + USE_FEATURE_DD_IBS_OBS( \ + " conv=notrunc don't truncate output file\n" \ + " conv=noerror continue after read errors\n" \ + " conv=sync pad blocks with zeros\n") \ + "\n" \ + "Numbers may be suffixed by c (x1), w (x2), b (x512), kD (x1000), k (x1024),\n" \ + "MD (x1000000), M (x1048576), GD (x1000000000) or G (x1073741824)" +#define dd_example_usage \ + "$ dd if=/dev/zero of=/dev/ram1 bs=1M count=4\n" \ + "4+0 records in\n" \ + "4+0 records out\n" + +#define deallocvt_trivial_usage \ + "[N]" +#define deallocvt_full_usage \ + "Deallocate unused virtual terminal /dev/ttyN" + +#define delgroup_trivial_usage \ + "GROUP" +#define delgroup_full_usage \ + "Deletes group GROUP from the system" + +#define deluser_trivial_usage \ + "USER" +#define deluser_full_usage \ + "Deletes user USER from the system" + +#define devfsd_trivial_usage \ + "mntpnt [-v]" \ + USE_DEVFSD_FG_NP("[-fg][-np]" ) +#define devfsd_full_usage \ + "Optional daemon for managing devfs permissions and old device name symlinks.\n" \ + "\nOptions:\n" \ + " mntpnt The mount point where devfs is mounted.\n\n" \ + " -v Print the protocol version numbers for devfsd\n" \ + " and the kernel-side protocol version and exits." \ + USE_DEVFSD_FG_NP( "\n\n -fg Run the daemon in the foreground.\n\n" \ + " -np Exit after parsing the configuration file\n" \ + " and processing synthetic REGISTER events.\n" \ + " Do not poll for events.") + +#define df_trivial_usage \ + "[-" USE_FEATURE_HUMAN_READABLE("hm") "k] [FILESYSTEM ...]" +#define df_full_usage \ + "Print the filesystem space used and space available.\n\n" \ + "Options:\n" \ + USE_FEATURE_HUMAN_READABLE( \ + "\n -h print sizes in human readable format (e.g., 1K 243M 2G )\n" \ + " -m print sizes in megabytes\n" \ + " -k print sizes in kilobytes(default)") \ + SKIP_FEATURE_HUMAN_READABLE("\n -k ignored") +#define df_example_usage \ + "$ df\n" \ + "Filesystem 1k-blocks Used Available Use% Mounted on\n" \ + "/dev/sda3 8690864 8553540 137324 98% /\n" \ + "/dev/sda1 64216 36364 27852 57% /boot\n" \ + "$ df /dev/sda3\n" \ + "Filesystem 1k-blocks Used Available Use% Mounted on\n" \ + "/dev/sda3 8690864 8553540 137324 98% /\n" + +#define dhcprelay_trivial_usage \ + "[client_device_list] [server_device]" +#define dhcprelay_full_usage \ + "Relays dhcp requests from client devices to server device" + +#define dhcprelay_trivial_usage \ + "[client_device_list] [server_device]" +#define dhcprelay_full_usage \ + "Relays dhcp requests from client devices to server device" + +#define diff_trivial_usage \ + "[-abdiNqrTstw] [-L LABEL] [-S FILE] [-U LINES] FILE1 FILE2" +#define diff_full_usage \ + "Compare files line by line and output the differences between them.\n" \ + "This diff implementation only supports unified diffs.\n\n" \ + "Options:\n" \ + " -a Treat all files as text\n" \ + " -b Ignore changes in the amount of whitespace\n" \ + " -d Try hard to find a smaller set of changes\n" \ + " -i Ignore case differences\n" \ + " -L Use LABEL instead of the filename in the unified header\n" \ + " -N Treat absent files as empty\n" \ + " -q Output only whether files differ\n" \ + " -r Recursively compare any subdirectories\n" \ + " -S Start with FILE when comparing directories\n" \ + " -T Make tabs line up by prefixing a tab when necessary\n" \ + " -s Report when two files are the same\n" \ + " -t Expand tabs to spaces in output\n" \ + " -U Output LINES lines of context\n" \ + " -w Ignore all whitespace\n" + +#define dirname_trivial_usage \ + "FILENAME" +#define dirname_full_usage \ + "Strips non-directory suffix from FILENAME" +#define dirname_example_usage \ + "$ dirname /tmp/foo\n" \ + "/tmp\n" \ + "$ dirname /tmp/foo/\n" \ + "/tmp\n" + +#define dmesg_trivial_usage \ + "[-c] [-n LEVEL] [-s SIZE]" +#define dmesg_full_usage \ + "Prints or controls the kernel ring buffer\n\n" \ + "Options:\n" \ + " -c Clears the ring buffer's contents after printing\n" \ + " -n LEVEL Sets console logging level\n" \ + " -s SIZE Use a buffer of size SIZE" + +#define dnsd_trivial_usage \ + "[-c config] [-t seconds] [-p port] [-i iface-ip] [-d]" +#define dnsd_full_usage \ + "Small and static DNS server daemon\n\n" \ + "Options:\n" \ + " -c config filename\n" \ + " -t TTL in seconds\n" \ + " -p listening port\n" \ + " -i listening iface ip (default all)\n" \ + " -d daemonize" + +#define dos2unix_trivial_usage \ + "[option] [FILE]" +#define dos2unix_full_usage \ + "Converts FILE from dos format to unix format. When no option\n" \ + "is given, the input is converted to the opposite output format.\n" \ + "When no file is given, uses stdin for input and stdout for output.\n\n" \ + "Options:\n" \ + " -u output will be in UNIX format\n" \ + " -d output will be in DOS format" + +#define dpkg_trivial_usage \ + "[-ilCPru] [-F option] package_name" +#define dpkg_full_usage \ + "dpkg is a utility to install, remove and manage Debian packages.\n\n" \ + "Options:\n" \ + " -i Install the package\n" \ + " -l List of installed packages\n" \ + " -C Configure an unpackaged package\n" \ + " -F depends Ignore dependency problems\n" \ + " -P Purge all files of a package\n" \ + " -r Remove all but the configuration files for a package\n" \ + " -u Unpack a package, but don't configure it" + +#define dpkg_deb_trivial_usage \ + "[-cefxX] FILE [argument]" +#define dpkg_deb_full_usage \ + "Perform actions on Debian packages (.debs)\n\n" \ + "Options:\n" \ + " -c List contents of filesystem tree\n" \ + " -e Extract control files to [argument] directory\n" \ + " -f Display control field name starting with [argument]\n" \ + " -x Extract packages filesystem tree to directory\n" \ + " -X Verbose extract" +#define dpkg_deb_example_usage \ + "$ dpkg-deb -X ./busybox_0.48-1_i386.deb /tmp\n" + +#define du_trivial_usage \ + "[-aHLdclsx" USE_FEATURE_HUMAN_READABLE("hm") "k] [FILE]..." +#define du_full_usage \ + "Summarizes disk space used for each FILE and/or directory.\n" \ + "Disk space is printed in units of " \ + USE_FEATURE_DU_DEFAULT_BLOCKSIZE_1K("1024") \ + SKIP_FEATURE_DU_DEFAULT_BLOCKSIZE_1K("512") \ + " bytes.\n\n" \ + "Options:\n" \ + " -a show sizes of files in addition to directories\n" \ + " -H follow symbolic links that are FILE command line args\n" \ + " -L follow all symbolic links encountered\n" \ + " -d N limit output to directories (and files with -a) of depth < N\n" \ + " -c output a grand total\n" \ + " -l count sizes many times if hard linked\n" \ + " -s display only a total for each argument\n" \ + " -x skip directories on different filesystems\n" \ + USE_FEATURE_HUMAN_READABLE( \ + " -h print sizes in human readable format (e.g., 1K 243M 2G )\n" \ + " -m print sizes in megabytes\n" \ + ) \ + " -k print sizes in kilobytes" \ + USE_FEATURE_DU_DEFAULT_BLOCKSIZE_1K("(default)") +#define du_example_usage \ + "$ du\n" \ + "16 ./CVS\n" \ + "12 ./kernel-patches/CVS\n" \ + "80 ./kernel-patches\n" \ + "12 ./tests/CVS\n" \ + "36 ./tests\n" \ + "12 ./scripts/CVS\n" \ + "16 ./scripts\n" \ + "12 ./docs/CVS\n" \ + "104 ./docs\n" \ + "2417 .\n" + +#define dumpkmap_trivial_usage \ + "> keymap" +#define dumpkmap_full_usage \ + "Prints out a binary keyboard translation table to standard output." +#define dumpkmap_example_usage \ + "$ dumpkmap > keymap\n" + +#define dumpleases_trivial_usage \ + "[-r|-a] [-f LEASEFILE]" +#define dumpleases_full_usage \ + "Displays the DHCP leases granted by udhcpd.\n\n" \ + "Options:\n" \ + " -f, --file=FILENAME Leases file to load\n" \ + " -r, --remaining Interpret lease times as time remaining\n" \ + " -a, --absolute Interpret lease times as expire time" + +#define e2fsck_trivial_usage \ + "[-panyrcdfvstDFSV] [-b superblock] [-B blocksize] " \ + "[-I inode_buffer_blocks] [-P process_inode_size] " \ + "[-l|-L bad_blocks_file] [-C fd] [-j external_journal] " \ + "[-E extended-options] device" +#define e2fsck_full_usage \ + "Check a Linux ext2/ext3 file system.\n\n" \ + "Options:\n" \ + " -p Automatic repair (no questions)\n" \ + " -n Make no changes to the filesystem\n" \ + " -y Assume 'yes' to all questions\n" \ + " -c Check for bad blocks and add them to the badblock list\n" \ + " -f Force checking even if filesystem is marked clean\n" \ + " -v Be verbose\n" \ + " -b superblock Use alternative superblock\n" \ + " -B blocksize Force blocksize when looking for superblock\n" \ + " -j journal Set location of the external journal\n" \ + " -l file Add to badblocks list\n" \ + " -L file Set badblocks list" + +#define echo_trivial_usage \ + USE_FEATURE_FANCY_ECHO("[-neE] ") "[ARG ...]" +#define echo_full_usage \ + "Prints the specified ARGs to stdout\n\n" \ + USE_FEATURE_FANCY_ECHO( \ + "Options:\n" \ + " -n suppress trailing newline\n" \ + " -e interpret backslash-escaped characters (i.e., \\t=tab)\n" \ + " -E disable interpretation of backslash-escaped characters" \ + ) +#define echo_example_usage \ + "$ echo \"Erik is cool\"\n" \ + "Erik is cool\n" \ + USE_FEATURE_FANCY_ECHO("$ echo -e \"Erik\\nis\\ncool\"\n" \ + "Erik\n" \ + "is\n" \ + "cool\n" \ + "$ echo \"Erik\\nis\\ncool\"\n" \ + "Erik\\nis\\ncool\n") + +#define eject_trivial_usage \ + "[-t] [-T] [DEVICE]" +#define eject_full_usage \ + "Eject specified DEVICE (or default /dev/cdrom).\n\n" \ + "Options:\n" \ + " -t close tray\n" \ + " -T open/close tray (toggle)" + +#define ed_trivial_usage "" +#define ed_full_usage "" + +#define env_trivial_usage \ + "[-iu] [-] [name=value]... [command]" +#define env_full_usage \ + "Prints the current environment or runs a program after setting\n" \ + "up the specified environment.\n\n" \ + "Options:\n" \ + " -, -i start with an empty environment\n" \ + " -u remove variable from the environment" + +#define ether_wake_trivial_usage \ + "[-b] [-i iface] [-p aa:bb:cc:dd[:ee:ff]] MAC" +#define ether_wake_full_usage \ + "Send a magic packet to wake up sleeping machines.\n" \ + "MAC must be a station address (00:11:22:33:44:55) or\n" \ + " a hostname with a known 'ethers' entry.\n\n" \ + "Options:\n" \ + " -b Send wake-up packet to the broadcast address\n" \ + " -i iface Use interface ifname instead of the default \"eth0\"\n" \ + " -p pass Append the four or six byte password PW to the packet" + +#define expr_trivial_usage \ + "EXPRESSION" +#define expr_full_usage \ + "Prints the value of EXPRESSION to standard output.\n\n" \ + "EXPRESSION may be:\n" \ + " ARG1 | ARG2 ARG1 if it is neither null nor 0, otherwise ARG2\n" \ + " ARG1 & ARG2 ARG1 if neither argument is null or 0, otherwise 0\n" \ + " ARG1 < ARG2 ARG1 is less than ARG2\n" \ + " ARG1 <= ARG2 ARG1 is less than or equal to ARG2\n" \ + " ARG1 = ARG2 ARG1 is equal to ARG2\n" \ + " ARG1 != ARG2 ARG1 is unequal to ARG2\n" \ + " ARG1 >= ARG2 ARG1 is greater than or equal to ARG2\n" \ + " ARG1 > ARG2 ARG1 is greater than ARG2\n" \ + " ARG1 + ARG2 arithmetic sum of ARG1 and ARG2\n" \ + " ARG1 - ARG2 arithmetic difference of ARG1 and ARG2\n" \ + " ARG1 * ARG2 arithmetic product of ARG1 and ARG2\n" \ + " ARG1 / ARG2 arithmetic quotient of ARG1 divided by ARG2\n" \ + " ARG1 % ARG2 arithmetic remainder of ARG1 divided by ARG2\n" \ + " STRING : REGEXP anchored pattern match of REGEXP in STRING\n" \ + " match STRING REGEXP same as STRING : REGEXP\n" \ + " substr STRING POS LENGTH substring of STRING, POS counted from 1\n" \ + " index STRING CHARS index in STRING where any CHARS is found,\n" \ + " or 0\n" \ + " length STRING length of STRING\n" \ + " quote TOKEN interpret TOKEN as a string, even if\n" \ + " it is a keyword like 'match' or an\n" \ + " operator like '/'\n" \ + " ( EXPRESSION ) value of EXPRESSION\n\n" \ + "Beware that many operators need to be escaped or quoted for shells.\n" \ + "Comparisons are arithmetic if both ARGs are numbers, else\n" \ + "lexicographical. Pattern matches return the string matched between\n" \ + "\\( and \\) or null; if \\( and \\) are not used, they return the number\n" \ + "of characters matched or 0." + +#define fakeidentd_trivial_usage \ + "[-b ip] [STRING]" +#define fakeidentd_full_usage \ + "Returns a set string to auth requests\n\n" \ + " -b Bind to ip address\n" \ + " STRING The ident answer string (default is nobody)" + +#define false_trivial_usage \ + "" +#define false_full_usage \ + "Return an exit code of FALSE (1)." +#define false_example_usage \ + "$ false\n" \ + "$ echo $?\n" \ + "1\n" + +#define fbset_trivial_usage \ + "[options] [mode]" +#define fbset_full_usage \ + "Show and modify frame buffer settings" +#define fbset_example_usage \ + "$ fbset\n" \ + "mode \"1024x768-76\"\n" \ + " # D: 78.653 MHz, H: 59.949 kHz, V: 75.694 Hz\n" \ + " geometry 1024 768 1024 768 16\n" \ + " timings 12714 128 32 16 4 128 4\n" \ + " accel false\n" \ + " rgba 5/11,6/5,5/0,0/0\n" \ + "endmode\n" + +#define fdflush_trivial_usage \ + "DEVICE" +#define fdflush_full_usage \ + "Forces floppy disk drive to detect disk change" + +#define fdformat_trivial_usage \ + "[-n] DEVICE" +#define fdformat_full_usage \ + "Low-level formats a floppy disk\n\n" \ + "Options:\n" \ + " -n Don't verify after format" + +#define fdisk_trivial_usage \ + "[-luv] [-C CYLINDERS] [-H HEADS] [-S SECTORS] [-b SSZ] DISK" +#define fdisk_full_usage \ + "Change partition table\n" \ + "Options:\n" \ + " -l List partition table(s)\n" \ + " -u Give Start and End in sector (instead of cylinder) units\n" \ + " -s PARTITION Give partition size(s) in blocks\n" \ + " -b 2048: (for certain MO disks) use 2048-byte sectors\n" \ + " -C CYLINDERS Set the number of cylinders\n" \ + " -H HEADS Set the number of heads\n" \ + " -S SECTORS Set the number of sectors\n" \ + " -v Give fdisk version" + +#define find_trivial_usage \ + "[PATH...] [EXPRESSION]" +#define find_full_usage \ + "Search for files in a directory hierarchy. The default PATH is\n" \ + "the current directory; default EXPRESSION is '-print'\n" \ + "\nEXPRESSION may consist of:\n" \ + " -follow Dereference symbolic links\n" \ + " -name PATTERN File name (leading directories removed) matches PATTERN\n" \ + " -print Print (default and assumed)" \ + USE_FEATURE_FIND_PRINT0( \ + "\n -print0 Delimit output with null characters rather than" \ + "\n newlines" \ + ) USE_FEATURE_FIND_TYPE( \ + "\n -type X Filetype matches X (where X is one of: f,d,l,b,c,...)" \ + ) USE_FEATURE_FIND_PERM( \ + "\n -perm PERMS Permissions match any of (+NNN); all of (-NNN);" \ + "\n or exactly (NNN)" \ + ) USE_FEATURE_FIND_MTIME( \ + "\n -mtime DAYS Modified time is greater than (+N); less than (-N);" \ + "\n or exactly (N) days" \ + ) USE_FEATURE_FIND_MMIN( \ + "\n -mmin MINS Modified time is greater than (+N); less than (-N);" \ + "\n or exactly (N) minutes" \ + ) USE_FEATURE_FIND_NEWER( \ + "\n -newer FILE Modified time is more recent than FILE's" \ + ) USE_FEATURE_FIND_INUM( \ + "\n -inum N File has inode number N" \ + ) USE_FEATURE_FIND_EXEC( \ + "\n -exec CMD Execute CMD with all instances of {} replaced by the" \ + "\n files matching EXPRESSION" \ + ) USE_DESKTOP( \ + "\n -size N File size is N" \ + "\n -prune Stop traversing current subtree" \ + "\n (expr) Group" \ + ) + +#define find_example_usage \ + "$ find / -name passwd\n" \ + "/etc/passwd\n" + +#define fold_trivial_usage \ + "[-bs] [-w WIDTH] [FILE]" +#define fold_full_usage \ + "Wrap input lines in each FILE (standard input by default), writing to\n" \ + "standard output.\n\n" \ + "Options:\n" \ + " -b count bytes rather than columns\n" \ + " -s break at spaces\n" \ + " -w use WIDTH columns instead of 80" + +#define free_trivial_usage \ + "" +#define free_full_usage \ + "Displays the amount of free and used system memory" +#define free_example_usage \ + "$ free\n" \ + " total used free shared buffers\n" \ + " Mem: 257628 248724 8904 59644 93124\n" \ + " Swap: 128516 8404 120112\n" \ + "Total: 386144 257128 129016\n" \ + +#define freeramdisk_trivial_usage \ + "DEVICE" +#define freeramdisk_full_usage \ + "Frees all memory used by the specified ramdisk." +#define freeramdisk_example_usage \ + "$ freeramdisk /dev/ram2\n" + +#define fsck_trivial_usage \ + "[-ANPRTV] [ -C [ fd ] ] [-t fstype] [fs-options] [filesys ...]" +#define fsck_full_usage \ + "Check and repair filesystems.\n\n" \ + "Options:\n" \ + " -A Walk /etc/fstab and check all filesystems\n" \ + " -N Don't execute, just show what would be done\n" \ + " -P When using -A, check filesystems in parallel\n" \ + " -R When using -A, skip the root filesystem\n" \ + " -T Don't show title on startup\n" \ + " -V Verbose mode\n" \ + " -C Write status information to specified filedescriptor\n" \ + " -t List of filesystem types to check" + +#define fsck_minix_trivial_usage \ + "[-larvsmf] /dev/name" +#define fsck_minix_full_usage \ + "Performs a consistency check for MINIX filesystems.\n\n" \ + "Options:\n" \ + " -l Lists all filenames\n" \ + " -r Perform interactive repairs\n" \ + " -a Perform automatic repairs\n" \ + " -v verbose\n" \ + " -s Outputs super-block information\n" \ + " -m Activates MINIX-like \"mode not cleared\" warnings\n" \ + " -f Force file system check" + +#define ftpget_trivial_usage \ + "[options] remote-host local-file remote-file" +#define ftpget_full_usage \ + "Retrieve a remote file via FTP.\n\n" \ + "Options:\n" \ + " -c, --continue Continue a previous transfer\n" \ + " -v, --verbose Verbose\n" \ + " -u, --username Username to be used\n" \ + " -p, --password Password to be used\n" \ + " -P, --port Port number to be used" + +#define ftpput_trivial_usage \ + "[options] remote-host remote-file local-file" +#define ftpput_full_usage \ + "Store a local file on a remote machine via FTP.\n\n" \ + "Options:\n" \ + " -v, --verbose Verbose\n" \ + " -u, --username Username to be used\n" \ + " -p, --password Password to be used\n" \ + " -P, --port Port number to be used" + +#define fuser_trivial_usage \ + "[options] file OR port/proto" +#define fuser_full_usage \ + "Options:\n" \ + " -m Show all processes on the same mounted fs\n" \ + " -k Kill all processes that match.\n" \ + " -s Don't print or kill anything.\n" \ + " -4 When using port/proto only search IPv4 space\n" \ + " -6 When using port/proto only search IPv6 space\n" \ + " -SIGNAL When used with -k, this signal will be used to kill" + +#define getopt_trivial_usage \ + "[OPTIONS]..." +#define getopt_full_usage \ + "Parse command options\n" \ + " -a, --alternative Allow long options starting with single -\n" \ + " -l, --longoptions=longopts Long options to be recognized\n" \ + " -n, --name=progname The name under which errors are reported\n" \ + " -o, --options=optstring Short options to be recognized\n" \ + " -q, --quiet Disable error reporting by getopt(3)\n" \ + " -Q, --quiet-output No normal output\n" \ + " -s, --shell=shell Set shell quoting conventions\n" \ + " -T, --test Test for getopt(1) version\n" \ + " -u, --unquoted Do not quote the output" +#define getopt_example_usage \ + "$ cat getopt.test\n" \ + "#!/bin/sh\n" \ + "GETOPT=`getopt -o ab:c:: --long a-long,b-long:,c-long:: \\\n" \ + " -n 'example.busybox' -- \"$@\"`\n" \ + "if [ $? != 0 ] ; then exit 1 ; fi\n" \ + "eval set -- \"$GETOPT\"\n" \ + "while true ; do\n" \ + " case $1 in\n" \ + " -a|--a-long) echo \"Option a\" ; shift ;;\n" \ + " -b|--b-long) echo \"Option b, argument '$2'\" ; shift 2 ;;\n" \ + " -c|--c-long)\n" \ + " case \"$2\" in\n" \ + " \"\") echo \"Option c, no argument\"; shift 2 ;;\n" \ + " *) echo \"Option c, argument '$2'\" ; shift 2 ;;\n" \ + " esac ;;\n" \ + " --) shift ; break ;;\n" \ + " *) echo \"Internal error!\" ; exit 1 ;;\n" \ + " esac\n" \ + "done\n" + +#define getty_trivial_usage \ + "[OPTIONS]... baud_rate,... line [termtype]" +#define getty_full_usage \ + "Opens a tty, prompts for a login name, then invokes /bin/login\n\n" \ + "Options:\n" \ + " -h Enable hardware (RTS/CTS) flow control\n" \ + " -i Do not display /etc/issue before running login\n" \ + " -L Local line, so do not do carrier detect\n" \ + " -m Get baud rate from modem's CONNECT status message\n" \ + " -w Wait for a CR or LF before sending /etc/issue\n" \ + " -n Do not prompt the user for a login name\n" \ + " -f issue_file Display issue_file instead of /etc/issue\n" \ + " -l login_app Invoke login_app instead of /bin/login\n" \ + " -t timeout Terminate after timeout if no username is read\n" \ + " -I initstring Sets the init string to send before anything else\n" \ + " -H login_host Log login_host into the utmp file as the hostname" + +#define grep_trivial_usage \ + "[-rihHnqvso" \ + USE_FEATURE_GREP_EGREP_ALIAS("E") \ + USE_FEATURE_GREP_CONTEXT("ABC") \ + "] PATTERN [FILEs...]" +#define grep_full_usage \ + "Search for PATTERN in each FILE or standard input.\n\n" \ + "Options:\n" \ + " -H prefix output lines with filename where match was found\n" \ + " -h suppress the prefixing filename on output\n" \ + " -r recurse subdirectories\n" \ + " -i ignore case distinctions\n" \ + " -l list names of files that match\n" \ + " -L list names of files that do not match\n" \ + " -n print line number with output lines\n" \ + " -q be quiet. Returns 0 if PATTERN was found, 1 otherwise\n" \ + " -v select non-matching lines\n" \ + " -s suppress file open/read error messages\n" \ + " -c only print count of matching lines\n" \ + " -f read PATTERN from file\n" \ + " -o show only the part of a line that matches PATTERN\n" \ + " -e PATTERN is a regular expression\n" \ + " -F PATTERN is a set of newline-separated strings" \ + USE_FEATURE_GREP_EGREP_ALIAS("\n -E PATTERN is an extended regular expression") \ + USE_FEATURE_GREP_CONTEXT("\n -A print NUM lines of trailing context") \ + USE_FEATURE_GREP_CONTEXT("\n -B print NUM lines of leading context") \ + USE_FEATURE_GREP_CONTEXT("\n -C print NUM lines of output context") + +#define grep_example_usage \ + "$ grep root /etc/passwd\n" \ + "root:x:0:0:root:/root:/bin/bash\n" \ + "$ grep ^[rR]oo. /etc/passwd\n" \ + "root:x:0:0:root:/root:/bin/bash\n" + +#define gunzip_trivial_usage \ + "[OPTION]... FILE" +#define gunzip_full_usage \ + "Uncompress FILE (or standard input if FILE is '-').\n\n" \ + "Options:\n" \ + " -c Write output to standard output\n" \ + " -f Force read when source is a terminal\n" \ + " -t Test compressed file integrity" +#define gunzip_example_usage \ + "$ ls -la /tmp/BusyBox*\n" \ + "-rw-rw-r-- 1 andersen andersen 557009 Apr 11 10:55 /tmp/BusyBox-0.43.tar.gz\n" \ + "$ gunzip /tmp/BusyBox-0.43.tar.gz\n" \ + "$ ls -la /tmp/BusyBox*\n" \ + "-rw-rw-r-- 1 andersen andersen 1761280 Apr 14 17:47 /tmp/BusyBox-0.43.tar\n" + +#define gzip_trivial_usage \ + "[OPTION]... [FILE]..." +#define gzip_full_usage \ + "Compress FILE(s) with maximum compression.\n" \ + "When FILE is '-' or unspecified, reads standard input. Implies -c.\n\n" \ + "Options:\n" \ + " -c Write output to standard output instead of FILE.gz\n" \ + " -d Decompress\n" \ + " -f Force write when destination is a terminal" +#define gzip_example_usage \ + "$ ls -la /tmp/busybox*\n" \ + "-rw-rw-r-- 1 andersen andersen 1761280 Apr 14 17:47 /tmp/busybox.tar\n" \ + "$ gzip /tmp/busybox.tar\n" \ + "$ ls -la /tmp/busybox*\n" \ + "-rw-rw-r-- 1 andersen andersen 554058 Apr 14 17:49 /tmp/busybox.tar.gz\n" + +#define halt_trivial_usage \ + "[-d] [-n] [-f]" +#define halt_full_usage \ + "Halt the system.\n" \ + "Options:\n" \ + " -d delay interval for halting\n" \ + " -n no call to sync()\n" \ + " -f force halt (don't go through init)" + +#define hdparm_trivial_usage \ + "[options] [device] .." +#define hdparm_full_usage \ + USE_FEATURE_HDPARM_GET_IDENTITY(" If no device name is specified try to read from stdin.\n") \ + "\nOptions:\n" \ + " -a get/set fs readahead\n" \ + " -A set drive read-lookahead flag (0/1)\n" \ + " -b get/set bus state (0 == off, 1 == on, 2 == tristate)\n" \ + " -B set Advanced Power Management setting (1-255)\n" \ + " -c get/set IDE 32-bit IO setting\n" \ + " -C check IDE power mode status\n" \ + USE_FEATURE_HDPARM_HDIO_GETSET_DMA(" -d get/set using_dma flag\n") \ + " -D enable/disable drive defect-mgmt\n" \ + " -f flush buffer cache for device on exit\n" \ + " -g display drive geometry\n" \ + " -h display terse usage information\n" \ + USE_FEATURE_HDPARM_GET_IDENTITY(" -i display drive identification\n") \ + USE_FEATURE_HDPARM_GET_IDENTITY(" -I detailed/current information directly from drive\n") \ + " -k get/set keep_settings_over_reset flag (0/1)\n" \ + " -K set drive keep_features_over_reset flag (0/1)\n" \ + " -L set drive doorlock (0/1) (removable harddisks only)\n" \ + " -m get/set multiple sector count\n" \ + " -n get/set ignore-write-errors flag (0/1)\n" \ + " -p set PIO mode on IDE interface chipset (0,1,2,3,4,...)\n" \ + " -P set drive prefetch count\n" \ + " -q change next setting quietly\n" \ + " -Q get/set DMA tagged-queuing depth (if supported)\n" \ + " -r get/set readonly flag (DANGEROUS to set)\n" \ + USE_FEATURE_HDPARM_HDIO_SCAN_HWIF(" -R register an IDE interface (DANGEROUS)\n") \ + " -S set standby (spindown) timeout\n" \ + " -t perform device read timings\n" \ + " -T perform cache read timings\n" \ + " -u get/set unmaskirq flag (0/1)\n" \ + USE_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF(" -U un-register an IDE interface (DANGEROUS)\n") \ + " -v defaults; same as -mcudkrag for IDE drives\n" \ + " -V display program version and exit immediately\n" \ + USE_FEATURE_HDPARM_HDIO_DRIVE_RESET(" -w perform device reset (DANGEROUS)\n") \ + " -W set drive write-caching flag (0/1) (DANGEROUS)\n" \ + USE_FEATURE_HDPARM_HDIO_TRISTATE_HWIF(" -x tristate device for hotswap (0/1) (DANGEROUS)\n") \ + " -X set IDE xfer mode (DANGEROUS)\n" \ + " -y put IDE drive in standby mode\n" \ + " -Y put IDE drive to sleep\n" \ + " -Z disable Seagate auto-powersaving mode\n" \ + " -z re-read partition table" + +#define head_trivial_usage \ + "[OPTION]... [FILE]..." +#define head_full_usage \ + "Print first 10 lines of each FILE to standard output.\n" \ + "With more than one FILE, precede each with a header giving the\n" \ + "file name. With no FILE, or when FILE is -, read standard input.\n\n" \ + "Options:\n" \ + " -n NUM Print first NUM lines instead of first 10" \ + USE_FEATURE_FANCY_HEAD( \ + "\n -c NUM output the first NUM bytes\n" \ + " -q never output headers giving file names\n" \ + " -v always output headers giving file names" ) +#define head_example_usage \ + "$ head -n 2 /etc/passwd\n" \ + "root:x:0:0:root:/root:/bin/bash\n" \ + "daemon:x:1:1:daemon:/usr/sbin:/bin/sh\n" + +#define hexdump_trivial_usage \ + "[-[bcCdefnosvx]] [OPTION] FILE" +#define hexdump_full_usage \ + "The hexdump utility is a filter which displays the specified files,\n" \ + "or the standard input, if no files are specified, in a user specified\n" \ + "format\n" \ + " -b One-byte octal display\n" \ + " -c One-byte character display\n" \ + " -C Canonical hex+ASCII, 16 bytes per line\n" \ + " -d Two-byte decimal display\n" \ + " -e FORMAT STRING\n" \ + " -f FORMAT FILE\n" \ + " -n LENGTH Interpret only length bytes of input\n" \ + " -o Two-byte octal display\n" \ + " -s OFFSET Skip offset byte\n" \ + " -v display all input data\n" \ + " -x Two-byte hexadecimal display" + +#define hostid_trivial_usage \ + "" +#define hostid_full_usage \ + "Print out a unique 32-bit identifier for the machine." + +#define hostname_trivial_usage \ + "[OPTION] {hostname | -F FILE}" +#define hostname_full_usage \ + "Get or set the hostname or DNS domain name. If a hostname is given\n" \ + "(or FILE with the -F parameter), the host name will be set.\n\n" \ + "Options:\n" \ + " -s Short\n" \ + " -i Addresses for the hostname\n" \ + " -d DNS domain name\n" \ + " -f Fully qualified domain name\n" \ + " -F FILE Use the contents of FILE to specify the hostname" +#define hostname_example_usage \ + "$ hostname\n" \ + "sage\n" + +#define httpd_trivial_usage \ + "[-c ]" \ + " [-p ]" \ + " [-i] [-f]" \ + USE_FEATURE_HTTPD_SETUID(" [-u user[:grp]]") \ + USE_FEATURE_HTTPD_BASIC_AUTH(" [-r ]") \ + USE_FEATURE_HTTPD_AUTH_MD5(" [-m pass]") \ + " [-h home]" \ + " [-d/-e ]" +#define httpd_full_usage \ + "Listens for incoming http server requests.\n\n" \ + "Options:\n" \ + " -c FILE Specifies configuration file. (default httpd.conf)\n" \ + " -p PORT Server port (default 80)\n" \ + " -i Assume that we are started from inetd\n" \ + " -f Do not daemonize\n" \ + USE_FEATURE_HTTPD_SETUID(" -u USER[:GRP] Set uid/gid after binding to port\n") \ + USE_FEATURE_HTTPD_BASIC_AUTH(" -r REALM Authentication Realm for Basic Authentication\n") \ + USE_FEATURE_HTTPD_AUTH_MD5(" -m PASS Crypt PASS with md5 algorithm\n") \ + " -h HOME Specifies http HOME directory (default ./)\n" \ + " -e STRING HTML encode STRING\n" \ + " -d STRING URL decode STRING" + +#define hwclock_trivial_usage \ + "[-r|--show] [-s|--hctosys] [-w|--systohc] [-l|--localtime] [-u|--utc]" +#define hwclock_full_usage \ + "Query and set the hardware clock (RTC)\n\n" \ + "Options:\n" \ + " -r read hardware clock and print result\n" \ + " -s set the system time from the hardware clock\n" \ + " -w set the hardware clock to the current system time\n" \ + " -u the hardware clock is kept in coordinated universal time\n" \ + " -l the hardware clock is kept in local time" + +#define id_trivial_usage \ + "[OPTIONS]... [USERNAME]" +#define id_full_usage \ + "Print information for USERNAME or the current user\n\n" \ + "Options:\n" \ + USE_SELINUX(" -c prints only the security context\n") \ + " -g prints only the group ID\n" \ + " -u prints only the user ID\n" \ + " -n print a name instead of a number\n" \ + " -r prints the real user ID instead of the effective ID" +#define id_example_usage \ + "$ id\n" \ + "uid=1000(andersen) gid=1000(andersen)\n" + +#define ifconfig_trivial_usage \ + USE_FEATURE_IFCONFIG_STATUS("[-a]") " [
    ]" +#define ifconfig_full_usage \ + "configure a network interface\n\n" \ + "Options:\n" \ + USE_FEATURE_IPV6(" [add
    [/]]\n") \ + USE_FEATURE_IPV6(" [del
    [/]]\n") \ + " [[-]broadcast [
    ]] [[-]pointopoint [
    ]]\n" \ + " [netmask
    ] [dstaddr
    ]\n" \ + USE_FEATURE_IFCONFIG_SLIP(" [outfill ] [keepalive ]\n") \ + " " USE_FEATURE_IFCONFIG_HW("[hw ether
    ] ") \ + "[metric ] [mtu ]\n" \ + " [[-]trailers] [[-]arp] [[-]allmulti]\n" \ + " [multicast] [[-]promisc] [txqueuelen ] [[-]dynamic]\n" \ + USE_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ(" [mem_start ] [io_addr ] [irq ]\n") \ + " [up|down] ..." + +#define ifup_trivial_usage \ + "<-ahinv> " +#define ifup_full_usage \ + "ifup \n\n" \ + "Options:\n" \ + " -h this help\n" \ + " -a de/configure all interfaces automatically\n" \ + " -i FILE use FILE for interface definitions\n" \ + " -n print out what would happen, but don't do it\n" \ + " (note that this option doesn't disable mappings)\n" \ + " -v print out what would happen before doing it\n" \ + " -m don't run any mappings\n" \ + " -f force de/configuration" + +#define ifdown_trivial_usage \ + "<-ahinv> " +#define ifdown_full_usage \ + "ifdown \n\n" \ + "Options:\n" \ + " -h this help\n" \ + " -a de/configure all interfaces automatically\n" \ + " -i FILE use FILE for interface definitions\n" \ + " -n print out what would happen, but don't do it\n" \ + " (note that this option doesn't disable mappings)\n" \ + " -v print out what would happen before doing it\n" \ + " -m don't run any mappings\n" \ + " -f force de/configuration" + +#define inetd_trivial_usage \ + "[-f] [-q len] [conf]" +#define inetd_full_usage \ + "Listens for network connections and launches programs\n\n" \ + "Option:\n" \ + " -f Run as a foreground progress\n" \ + " -q Sets the size of the socket listen queue to\n" \ + " the specified value. Default is 128" + +#define init_trivial_usage \ + "" +#define init_full_usage \ + "Init is the parent of all processes." +#define init_notes_usage \ +"This version of init is designed to be run only by the kernel.\n" \ +"\n" \ +"BusyBox init doesn't support multiple runlevels. The runlevels field of\n" \ +"the /etc/inittab file is completely ignored by BusyBox init. If you want\n" \ +"runlevels, use sysvinit.\n" \ +"\n" \ +"BusyBox init works just fine without an inittab. If no inittab is found,\n" \ +"it has the following default behavior:\n" \ +"\n" \ +" ::sysinit:/etc/init.d/rcS\n" \ +" ::askfirst:/bin/sh\n" \ +" ::ctrlaltdel:/sbin/reboot\n" \ +" ::shutdown:/sbin/swapoff -a\n" \ +" ::shutdown:/bin/umount -a -r\n" \ +" ::restart:/sbin/init\n" \ +"\n" \ +"if it detects that /dev/console is _not_ a serial console, it will also run:\n" \ +"\n" \ +" tty2::askfirst:/bin/sh\n" \ +" tty3::askfirst:/bin/sh\n" \ +" tty4::askfirst:/bin/sh\n" \ +"\n" \ +"If you choose to use an /etc/inittab file, the inittab entry format is as follows:\n" \ +"\n" \ +" :::\n" \ +"\n" \ +" :\n" \ +"\n" \ +" WARNING: This field has a non-traditional meaning for BusyBox init!\n" \ +" The id field is used by BusyBox init to specify the controlling tty for\n" \ +" the specified process to run on. The contents of this field are\n" \ +" appended to \"/dev/\" and used as-is. There is no need for this field to\n" \ +" be unique, although if it isn't you may have strange results. If this\n" \ +" field is left blank, the controlling tty is set to the console. Also\n" \ +" note that if BusyBox detects that a serial console is in use, then only\n" \ +" entries whose controlling tty is either the serial console or /dev/null\n" \ +" will be run. BusyBox init does nothing with utmp. We don't need no\n" \ +" stinkin' utmp.\n" \ +"\n" \ +" :\n" \ +"\n" \ +" The runlevels field is completely ignored.\n" \ +"\n" \ +" :\n" \ +"\n" \ +" Valid actions include: sysinit, respawn, askfirst, wait,\n" \ +" once, restart, ctrlaltdel, and shutdown.\n" \ +"\n" \ +" The available actions can be classified into two groups: actions\n" \ +" that are run only once, and actions that are re-run when the specified\n" \ +" process exits.\n" \ +"\n" \ +" Run only-once actions:\n" \ +"\n" \ +" 'sysinit' is the first item run on boot. init waits until all\n" \ +" sysinit actions are completed before continuing. Following the\n" \ +" completion of all sysinit actions, all 'wait' actions are run.\n" \ +" 'wait' actions, like 'sysinit' actions, cause init to wait until\n" \ +" the specified task completes. 'once' actions are asynchronous,\n" \ +" therefore, init does not wait for them to complete. 'restart' is\n" \ +" the action taken to restart the init process. By default this should\n" \ +" simply run /sbin/init, but can be a script which runs pivot_root or it\n" \ +" can do all sorts of other interesting things. The 'ctrlaltdel' init\n" \ +" actions are run when the system detects that someone on the system\n" \ +" console has pressed the CTRL-ALT-DEL key combination. Typically one\n" \ +" wants to run 'reboot' at this point to cause the system to reboot.\n" \ +" Finally the 'shutdown' action specifies the actions to taken when\n" \ +" init is told to reboot. Unmounting filesystems and disabling swap\n" \ +" is a very good here\n" \ +"\n" \ +" Run repeatedly actions:\n" \ +"\n" \ +" 'respawn' actions are run after the 'once' actions. When a process\n" \ +" started with a 'respawn' action exits, init automatically restarts\n" \ +" it. Unlike sysvinit, BusyBox init does not stop processes from\n" \ +" respawning out of control. The 'askfirst' actions acts just like\n" \ +" respawn, except that before running the specified process it\n" \ +" displays the line \"Please press Enter to activate this console.\"\n" \ +" and then waits for the user to press enter before starting the\n" \ +" specified process.\n" \ +"\n" \ +" Unrecognized actions (like initdefault) will cause init to emit an\n" \ +" error message, and then go along with its business. All actions are\n" \ +" run in the order they appear in /etc/inittab.\n" \ +"\n" \ +" :\n" \ +"\n" \ +" Specifies the process to be executed and its command line.\n" \ +"\n" \ +"Example /etc/inittab file:\n" \ +"\n" \ +" # This is run first except when booting in single-user mode.\n" \ +" #\n" \ +" ::sysinit:/etc/init.d/rcS\n" \ +" \n" \ +" # /bin/sh invocations on selected ttys\n" \ +" #\n" \ +" # Start an \"askfirst\" shell on the console (whatever that may be)\n" \ +" ::askfirst:-/bin/sh\n" \ +" # Start an \"askfirst\" shell on /dev/tty2-4\n" \ +" tty2::askfirst:-/bin/sh\n" \ +" tty3::askfirst:-/bin/sh\n" \ +" tty4::askfirst:-/bin/sh\n" \ +" \n" \ +" # /sbin/getty invocations for selected ttys\n" \ +" #\n" \ +" tty4::respawn:/sbin/getty 38400 tty4\n" \ +" tty5::respawn:/sbin/getty 38400 tty5\n" \ +" \n" \ +" \n" \ +" # Example of how to put a getty on a serial line (for a terminal)\n" \ +" #\n" \ +" #::respawn:/sbin/getty -L ttyS0 9600 vt100\n" \ +" #::respawn:/sbin/getty -L ttyS1 9600 vt100\n" \ +" #\n" \ +" # Example how to put a getty on a modem line.\n" \ +" #::respawn:/sbin/getty 57600 ttyS2\n" \ +" \n" \ +" # Stuff to do when restarting the init process\n" \ +" ::restart:/sbin/init\n" \ +" \n" \ +" # Stuff to do before rebooting\n" \ +" ::ctrlaltdel:/sbin/reboot\n" \ +" ::shutdown:/bin/umount -a -r\n" \ +" ::shutdown:/sbin/swapoff -a\n" + +#define insmod_trivial_usage \ + "[OPTION]... MODULE [symbol=value]..." +#define insmod_full_usage \ + "Loads the specified kernel modules into the kernel.\n\n" \ + "Options:\n" \ + " -f Force module to load into the wrong kernel version\n" \ + " -k Make module autoclean-able\n" \ + " -v verbose output\n" \ + " -q quiet output\n" \ + " -L Lock to prevent simultaneous loads of a module\n" \ + USE_FEATURE_INSMOD_LOAD_MAP(" -m Output load map to stdout\n") \ + " -o NAME Set internal module name to NAME\n" \ + " -x do not export externs" + +#define install_trivial_usage \ + "[-cgmops] [sources] " +#define install_full_usage \ + "Copies files and set attributes\n\n" \ + "Options:\n" \ + " -c copy the file, default\n" \ + " -d create directories\n" \ + " -g set group ownership\n" \ + " -m set permission modes\n" \ + " -o set ownership\n" \ + " -p preserve date\n" \ + " -s strip symbol tables" + +#define ip_trivial_usage \ + "[OPTIONS] {address | link | route | tunnel | rule} {COMMAND}" +#define ip_full_usage \ + "ip [OPTIONS] OBJECT {COMMAND}\n" \ + "where OBJECT := {link | addr | route | tunnel |rule}\n" \ + "OPTIONS := { -f[amily] { inet | inet6 | link } | -o[neline] }" + +#define ipaddr_trivial_usage \ + "{ {add|del} IFADDR dev STRING | {show|flush}\n" \ + " [ dev STRING ] [ to PREFIX ] }" +#define ipaddr_full_usage \ + "ipaddr {add|delete} IFADDR dev STRING\n" \ + "ipaddr {show|flush} [ dev STRING ] [ scope SCOPE-ID ]\n" \ + " [ to PREFIX ] [ label PATTERN ]\n" \ + " IFADDR := PREFIX | ADDR peer PREFIX\n" \ + " [ broadcast ADDR ] [ anycast ADDR ]\n" \ + " [ label STRING ] [ scope SCOPE-ID ]\n" \ + " SCOPE-ID := [ host | link | global | NUMBER ]" + +#define ipcalc_trivial_usage \ + "[OPTION]...
    [[/]] [NETMASK]" +#define ipcalc_full_usage \ + "Calculate IP network settings from a IP address\n\n" \ + "Options:\n" \ + " -b --broadcast Display calculated broadcast address\n" \ + " -n --network Display calculated network address\n" \ + " -m --netmask Display default netmask for IP" \ + USE_FEATURE_IPCALC_FANCY( \ + "\n -p --prefix Display the prefix for IP/NETMASK\n" \ + " -h --hostname Display first resolved host name\n" \ + " -s --silent Don't ever display error messages") + +#define ipcrm_trivial_usage \ + "[-[MQS] key] [-[mqs] id]" +#define ipcrm_full_usage \ + "The upper-case options MQS are used to remove a shared memory segment by a\n" \ + "segment by a shmkey value. The lower-case options mqs are used\n" \ + "to remove a segment by shmid value.\n" \ + " -[mM] Remove the memory segment after the last detach\n" \ + " -[qQ] Remove the message queue\n" \ + " -[sS] Remove the semaphore" + +#define ipcs_trivial_usage \ + "[[-smq] -i shmid] | [[-asmq] [-tcplu]]" +#define ipcs_full_usage \ + " -i specify a specific resource id\n" \ + "Resource specification:\n" \ + " -m shared memory segments\n" \ + " -q message queues\n" \ + " -s semaphore arrays\n" \ + " -a all (default)\n" \ + "Output format:\n" \ + " -t time\n" \ + " -c creator\n" \ + " -p pid\n" \ + " -l limits\n" \ + " -u summary" + +#define iplink_trivial_usage \ + "{ set DEVICE { up | down | arp { on | off } | show [ DEVICE ] }" +#define iplink_full_usage \ + "iplink set DEVICE { up | down | arp { on | off } |\n" \ + " dynamic { on | off } |\n" \ + " mtu MTU }\n" \ + " iplink show [ DEVICE ]" + +#define iproute_trivial_usage \ + "{ list | flush | { add | del | change | append |\n" \ + " replace | monitor } ROUTE }" +#define iproute_full_usage \ + "iproute { list | flush } SELECTOR\n" \ + "iproute get ADDRESS [ from ADDRESS iif STRING ]\n" \ + " [ oif STRING ] [ tos TOS ]\n" \ + " iproute { add | del | change | append | replace | monitor } ROUTE\n" \ + " SELECTOR := [ root PREFIX ] [ match PREFIX ] [ proto RTPROTO ]\n" \ + " ROUTE := [ TYPE ] PREFIX [ tos TOS ] [ proto RTPROTO ]" + +#define iprule_trivial_usage \ + "{[ list | add | del ] RULE}" +#define iprule_full_usage \ + "iprule [ list | add | del ] SELECTOR ACTION\n" \ + " SELECTOR := [ from PREFIX ] [ to PREFIX ] [ tos TOS ] [ fwmark FWMARK ]\n" \ + " [ dev STRING ] [ pref NUMBER ]\n" \ + " ACTION := [ table TABLE_ID ] [ nat ADDRESS ]\n" \ + " [ prohibit | reject | unreachable ]\n" \ + " [ realms [SRCREALM/]DSTREALM ]\n" \ + " TABLE_ID := [ local | main | default | NUMBER ]" + +#define iptunnel_trivial_usage \ + "{ add | change | del | show } [ NAME ]\n" \ + " [ mode { ipip | gre | sit } ]\n" \ + " [ remote ADDR ] [ local ADDR ] [ ttl TTL ]" +#define iptunnel_full_usage \ + "iptunnel { add | change | del | show } [ NAME ]\n" \ + " [ mode { ipip | gre | sit } ] [ remote ADDR ] [ local ADDR ]\n" \ + " [ [i|o]seq ] [ [i|o]key KEY ] [ [i|o]csum ]\n" \ + " [ ttl TTL ] [ tos TOS ] [ [no]pmtudisc ] [ dev PHYS_DEV ]" + +#define kill_trivial_usage \ + "[-l] [-signal] process-id [process-id ...]" +#define kill_full_usage \ + "Send a signal (default is TERM) to the specified process(es).\n\n" \ + "Options:\n" \ + " -l List all signal names and numbers" +#define kill_example_usage \ + "$ ps | grep apache\n" \ + "252 root root S [apache]\n" \ + "263 www-data www-data S [apache]\n" \ + "264 www-data www-data S [apache]\n" \ + "265 www-data www-data S [apache]\n" \ + "266 www-data www-data S [apache]\n" \ + "267 www-data www-data S [apache]\n" \ + "$ kill 252\n" + +#define killall_trivial_usage \ + "[-l] [-q] [-signal] process-name [process-name ...]" +#define killall_full_usage \ + "Send a signal (default is TERM) to the specified process(es).\n\n" \ + "Options:\n" \ + " -l List all signal names and numbers\n" \ + " -q Do not complain if no processes were killed" +#define killall_example_usage \ + "$ killall apache\n" + +#define killall5_trivial_usage \ + "[-l] [-signal]" +#define killall5_full_usage \ + "Send a signal (default is TERM) to all processes outside current session.\n\n" \ + "Options:\n" \ + " -l List all signal names and numbers\n" \ + +#define klogd_trivial_usage \ + "[-c n] [-n]" +#define klogd_full_usage \ + "Kernel logger.\n" \ + "Options:\n" \ + " -c n Sets the default log level of console messages to n\n" \ + " -n Run as a foreground process" + +#define length_trivial_usage \ + "STRING" +#define length_full_usage \ + "Prints out the length of the specified STRING." +#define length_example_usage \ + "$ length Hello\n" \ + "5\n" + +#define less_trivial_usage \ + "[-EMNmh~?] FILE1 FILE2..." +#define less_full_usage \ + "View a file or list of files. The position within files can be\n" \ + "changed, and files can be manipulated in various ways with the\n" \ + "following options:\n\n" \ + " -E Quit once the end of a file is reached\n" \ + " -M Display a status line containing the current line numbers\n" \ + " and the percentage through the file\n" \ + " -N Prefix line numbers to each line\n" \ + " -m Display a status line containing the percentage through the\n" \ + " file\n" \ + " -~ Suppress ~s displayed when input past the end of the file is\n" \ + " reached.\n" \ + " -h, -? Display this help message" + +#define setarch_trivial_usage \ + " [args ...]" +#define setarch_full_usage \ + "Personality may be:\n" \ + " linux32 Set 32bit uname emulation\n" \ + " linux64 Set 64bit uname emulation" + +#define ln_trivial_usage \ + "[OPTION] TARGET... LINK_NAME|DIRECTORY" +#define ln_full_usage \ + "Create a link named LINK_NAME or DIRECTORY to the specified TARGET\n" \ + "\nYou may use '--' to indicate that all following arguments are non-options.\n\n" \ + "Options:\n" \ + " -s make symbolic links instead of hard links\n" \ + " -f remove existing destination files\n" \ + " -n no dereference symlinks - treat like normal file\n" \ + " -b make a backup of the target (if exists) before link operation\n" \ + " -S suffix use suffix instead of ~ when making backup files" +#define ln_example_usage \ + "$ ln -s BusyBox /tmp/ls\n" \ + "$ ls -l /tmp/ls\n" \ + "lrwxrwxrwx 1 root root 7 Apr 12 18:39 ls -> BusyBox*\n" + +#define loadfont_trivial_usage \ + "< font" +#define loadfont_full_usage \ + "Loads a console font from standard input." +#define loadfont_example_usage \ + "$ loadfont < /etc/i18n/fontname\n" + +#define loadkmap_trivial_usage \ + "< keymap" +#define loadkmap_full_usage \ + "Loads a binary keyboard translation table from standard input." +#define loadkmap_example_usage \ + "$ loadkmap < /etc/i18n/lang-keymap\n" + +#define logger_trivial_usage \ + "[OPTION]... [MESSAGE]" +#define logger_full_usage \ + "Write MESSAGE to the system log. If MESSAGE is omitted, log stdin.\n\n" \ + "Options:\n" \ + " -s Log to stderr as well as the system log\n" \ + " -t TAG Log using the specified tag (defaults to user name)\n" \ + " -p PRIORITY Enter the message with the specified priority\n" \ + " This may be numerical or a 'facility.level' pair" +#define logger_example_usage \ + "$ logger \"hello\"\n" + +#define login_trivial_usage \ + "[OPTION]... [username] [ENV=VAR ...]" +#define login_full_usage \ + "Begin a new session on the system\n\n" \ + "Options:\n" \ + " -f Do not authenticate (user already authenticated)\n" \ + " -h Name of the remote host for this login\n" \ + " -p Preserve environment" + +#define logname_trivial_usage \ + "" +#define logname_full_usage \ + "Print the name of the current user." +#define logname_example_usage \ + "$ logname\n" \ + "root\n" + +#define logread_trivial_usage \ + "[OPTION]..." +#define logread_full_usage \ + "Shows the messages from syslogd (using circular buffer).\n\n" \ + "Options:\n" \ + " -f output data as the log grows" + +#define losetup_trivial_usage \ + "[-o OFFSET] [-d] LOOPDEVICE [FILE]]" +#define losetup_full_usage \ + "(Dis)associate LOOPDEVICE with FILE, or display current associations.\n\n" \ + "Options:\n" \ + " -d Disassociate LOOPDEVICE\n" \ + " -o OFFSET Start OFFSET bytes into FILE" +#define losetup_notes_usage \ + "No arguments will display all current associations.\n" \ + "One argument (losetup /dev/loop1) will display the current association\n" \ + "(if any), or disassociate it (with -d). The display shows the offset\n" \ + "and filename of the file the loop device is currently bound to.\n\n" \ + "Two arguments (losetup /dev/loop1 file.img) create a new association,\n" \ + "with an optional offset (-o 12345). Encryption is not yet supported.\n\n" + +#define ls_trivial_usage \ + "[-1Aa" USE_FEATURE_LS_TIMESTAMPS("c") "Cd" \ + USE_FEATURE_LS_TIMESTAMPS("e") USE_FEATURE_LS_FILETYPES("F") "iln" \ + USE_FEATURE_LS_FILETYPES("p") USE_FEATURE_LS_FOLLOWLINKS("L") \ + USE_FEATURE_LS_RECURSIVE("R") USE_FEATURE_LS_SORTFILES("rS") "s" \ + USE_FEATURE_AUTOWIDTH("T") USE_FEATURE_LS_TIMESTAMPS("tu") \ + USE_FEATURE_LS_SORTFILES("v") USE_FEATURE_AUTOWIDTH("w") "x" \ + USE_FEATURE_LS_SORTFILES("X") USE_FEATURE_HUMAN_READABLE("h") "k" \ + USE_SELINUX("K") "] [filenames...]" +#define ls_full_usage \ + "List directory contents\n\n" \ + "Options:\n" \ + " -1 list files in a single column\n" \ + " -A do not list implied . and ..\n" \ + " -a do not hide entries starting with .\n" \ + " -C list entries by columns\n" \ + USE_FEATURE_LS_TIMESTAMPS(" -c with -l: show ctime\n") \ + USE_FEATURE_LS_COLOR(" --color[={always,never,auto}] to control coloring\n") \ + " -d list directory entries instead of contents\n" \ + USE_FEATURE_LS_TIMESTAMPS(" -e list both full date and full time\n") \ + USE_FEATURE_LS_FILETYPES(" -F append indicator (one of */=@|) to entries\n") \ + " -i list the i-node for each file\n" \ + " -l use a long listing format\n" \ + " -n list numeric UIDs and GIDs instead of names\n" \ + USE_FEATURE_LS_FILETYPES(" -p append indicator (one of /=@|) to entries\n") \ + USE_FEATURE_LS_FOLLOWLINKS(" -L list entries pointed to by symbolic links\n") \ + USE_FEATURE_LS_RECURSIVE(" -R list subdirectories recursively\n") \ + USE_FEATURE_LS_SORTFILES(" -r sort the listing in reverse order\n") \ + USE_FEATURE_LS_SORTFILES(" -S sort the listing by file size\n") \ + " -s list the size of each file, in blocks\n" \ + USE_FEATURE_AUTOWIDTH(" -T NUM assume Tabstop every NUM columns\n") \ + USE_FEATURE_LS_TIMESTAMPS(" -t with -l: show modification time\n") \ + USE_FEATURE_LS_TIMESTAMPS(" -u with -l: show access time\n") \ + USE_FEATURE_LS_SORTFILES(" -v sort the listing by version\n") \ + USE_FEATURE_AUTOWIDTH(" -w NUM assume the terminal is NUM columns wide\n") \ + " -x list entries by lines instead of by columns\n" \ + USE_FEATURE_LS_SORTFILES(" -X sort the listing by extension\n") \ + USE_FEATURE_HUMAN_READABLE( \ + " -h print sizes in human readable format (e.g., 1K 243M 2G )\n") \ + USE_SELINUX(" -k print security context\n") \ + USE_SELINUX(" -K print security context in long format\n") + +#define lsattr_trivial_usage \ + "[-Radlv] [files...]" +#define lsattr_full_usage \ + "list file attributes on an ext2 fs\n\n" \ + "Options:\n" \ + " -R recursively list subdirectories\n" \ + " -a do not hide entries starting with .\n" \ + " -d list directory entries instead of contents\n" \ + " -l print long flag names\n" \ + " -v list the file's version/generation number" + +#define lsmod_trivial_usage \ + "" +#define lsmod_full_usage \ + "List the currently loaded kernel modules." + +#ifdef CONFIG_FEATURE_MAKEDEVS_LEAF +#define makedevs_trivial_usage \ + "NAME TYPE MAJOR MINOR FIRST LAST [s]" +#define makedevs_full_usage \ + "Creates a range of block or character special files\n\n" \ + "TYPEs include:\n" \ + " b: Make a block (buffered) device.\n" \ + " c or u: Make a character (un-buffered) device.\n" \ + " p: Make a named pipe. MAJOR and MINOR are ignored for named pipes.\n\n" \ + "FIRST specifies the number appended to NAME to create the first device.\n" \ + "LAST specifies the number of the last item that should be created\n" \ + "If 's' is the last argument, the base device is created as well.\n\n" \ + "For example:\n" \ + " makedevs /dev/ttyS c 4 66 2 63 -> ttyS2-ttyS63\n" \ + " makedevs /dev/hda b 3 0 0 8 s -> hda,hda1-hda8" +#define makedevs_example_usage \ + "# makedevs /dev/ttyS c 4 66 2 63\n" \ + "[creates ttyS2-ttyS63]\n" \ + "# makedevs /dev/hda b 3 0 0 8 s\n" \ + "[creates hda,hda1-hda8]\n" +#endif + +#ifdef CONFIG_FEATURE_MAKEDEVS_TABLE +#define makedevs_trivial_usage \ + "[-d device_table] rootdir" +#define makedevs_full_usage \ + "Creates a range of special files as specified in a device table.\n" \ + "Device table entries take the form of:\n" \ + " \n" \ + "Where name is the file name, type can be one of:\n" \ + " f A regular file\n" \ + " d Directory\n" \ + " c Character special device file\n" \ + " b Block special device file\n" \ + " p Fifo (named pipe)\n" \ + "uid is the user id for the target file, gid is the group id for the\n" \ + "target file. The rest of the entries (major, minor, etc) apply to\n" \ + "to device special files. A '-' may be used for blank entries." +#define makedevs_example_usage \ + "For example:\n" \ + " \n" \ + "/dev d 755 0 0 - - - - -\n" \ + "/dev/console c 666 0 0 5 1 - - -\n" \ + "/dev/null c 666 0 0 1 3 0 0 -\n" \ + "/dev/zero c 666 0 0 1 5 0 0 -\n" \ + "/dev/hda b 640 0 0 3 0 0 0 -\n" \ + "/dev/hda b 640 0 0 3 1 1 1 15\n\n" \ + "Will Produce:\n" \ + "/dev\n" \ + "/dev/console\n" \ + "/dev/null\n" \ + "/dev/zero\n" \ + "/dev/hda\n" \ + "/dev/hda[0-15]\n" +#endif + +#define md5sum_trivial_usage \ + "[OPTION] [FILEs...]" \ + USE_FEATURE_MD5_SHA1_SUM_CHECK("\n or: md5sum [OPTION] -c [FILE]") +#define md5sum_full_usage \ + "Print" USE_FEATURE_MD5_SHA1_SUM_CHECK(" or check") " MD5 checksums.\n\n" \ + "Options:\n" \ + "With no FILE, or when FILE is -, read standard input." \ + USE_FEATURE_MD5_SHA1_SUM_CHECK("\n\n" \ + " -c check MD5 sums against given list\n" \ + "\nThe following two options are useful only when verifying checksums:\n" \ + " -s don't output anything, status code shows success\n" \ + " -w warn about improperly formatted MD5 checksum lines") +#define md5sum_example_usage \ + "$ md5sum < busybox\n" \ + "6fd11e98b98a58f64ff3398d7b324003\n" \ + "$ md5sum busybox\n" \ + "6fd11e98b98a58f64ff3398d7b324003 busybox\n" \ + "$ md5sum -c -\n" \ + "6fd11e98b98a58f64ff3398d7b324003 busybox\n" \ + "busybox: OK\n" \ + "^D\n" + +#define mdev_trivial_usage \ + "[-s]" +#define mdev_full_usage \ + " -s Scan /sys and populate /dev during system boot\n\n" \ + "Called with no options (via hotplug) it uses environment variables\n" \ + "to determine which device to add/remove." +#define mdev_notes_usage "" \ +USE_FEATURE_MDEV_CONFIG( \ + "The mdev config file contains lines that look like:\n" \ + " hd[a-z][0-9]* 0:3 660\n\n" \ + "That's device name (with regex match), uid:gid, and permissions.\n\n" \ + USE_FEATURE_MDEV_EXEC( \ + "Optionally, that can be followed (on the same line) by a special character\n" \ + "and a command line to run after creating/before deleting the corresponding\n" \ + "device(s). The environment variable $MDEV indicates the active device node\n" \ + "(which is useful if it's a regex match). For example:\n\n" \ + " hdc root:cdrom 660 *ln -s $MDEV cdrom\n\n" \ + "The special characters are @ (run after creating), $ (run before deleting),\n" \ + "and * (run both after creating and before deleting). The commands run in\n" \ + "the /dev directory, and use system() which calls /bin/sh.\n\n" \ + ) \ + "Config file parsing stops on the first matching line. If no config\n"\ + "entry is matched, devices are created with default 0:0 660. (Make\n"\ + "the last line match .* to override this.)\n\n" \ +) + +#define mesg_trivial_usage \ + "[y|n]" +#define mesg_full_usage \ + "mesg controls write access to your terminal\n" \ + " y Allow write access to your terminal\n" \ + " n Disallow write access to your terminal" + +#define mkdir_trivial_usage \ + "[OPTION] DIRECTORY..." +#define mkdir_full_usage \ + "Create the DIRECTORY(ies) if they do not already exist\n\n" \ + "Options:\n" \ + " -m set permission mode (as in chmod), not rwxrwxrwx - umask\n" \ + " -p no error if existing, make parent directories as needed" +#define mkdir_example_usage \ + "$ mkdir /tmp/foo\n" \ + "$ mkdir /tmp/foo\n" \ + "/tmp/foo: File exists\n" \ + "$ mkdir /tmp/foo/bar/baz\n" \ + "/tmp/foo/bar/baz: No such file or directory\n" \ + "$ mkdir -p /tmp/foo/bar/baz\n" + +#define mke2fs_trivial_usage \ + "[-c|-l filename] [-b block-size] [-f fragment-size] [-g blocks-per-group] " \ + "[-i bytes-per-inode] [-j] [-J journal-options] [-N number-of-inodes] [-n] " \ + "[-m reserved-blocks-percentage] [-o creator-os] [-O feature[,...]] [-q] " \ + "[r fs-revision-level] [-E extended-options] [-v] [-F] [-L volume-label] " \ + "[-M last-mounted-directory] [-S] [-T filesystem-type] " \ + "device [blocks-count]" +#define mke2fs_full_usage \ + " -b size block size in bytes\n" \ + " -c check for bad blocks before creating\n" \ + " -E opts set extended options\n" \ + " -f size fragment size in bytes\n" \ + " -F force (ignore sanity checks)\n" \ + " -g num number of blocks in a block group\n" \ + " -i ratio the bytes/inode ratio\n" \ + " -j create a journal (ext3)\n" \ + " -J opts set journal options (size/device)\n" \ + " -l file read bad blocks list from file\n" \ + " -L lbl set the volume label\n" \ + " -m percent percent of fs blocks to reserve for admin\n" \ + " -M dir set last mounted directory\n" \ + " -n do not actually create anything\n" \ + " -N num number of inodes to create\n" \ + " -o os set the 'creator os' field\n" \ + " -O features dir_index/filetype/has_journal/journal_dev/sparse_super\n" \ + " -q quiet execution\n" \ + " -r rev set filesystem revision\n" \ + " -S write superblock and group descriptors only\n" \ + " -T fs-type set usage type (news/largefile/largefile4)\n" \ + " -v verbose execution" + +#define mkfifo_trivial_usage \ + "[OPTIONS] name" +#define mkfifo_full_usage \ + "Creates a named pipe (identical to 'mknod name p')\n\n" \ + "Options:\n" \ + " -m create the pipe using the specified mode (default a=rw)" + +#define mkfs_minix_trivial_usage \ + "[-c | -l filename] [-nXX] [-iXX] /dev/name [blocks]" +#define mkfs_minix_full_usage \ + "Make a MINIX filesystem.\n\n" \ + "Options:\n" \ + " -c Check the device for bad blocks\n" \ + " -n [14|30] Specify the maximum length of filenames\n" \ + " -i INODES Specify the number of inodes for the filesystem\n" \ + " -l FILENAME Read the bad blocks list from FILENAME\n" \ + " -v Make a Minix version 2 filesystem" + +#define mknod_trivial_usage \ + "[OPTIONS] NAME TYPE MAJOR MINOR" +#define mknod_full_usage \ + "Create a special file (block, character, or pipe).\n\n" \ + "Options:\n" \ + " -m create the special file using the specified mode (default a=rw)\n\n" \ + "TYPEs include:\n" \ + " b: Make a block (buffered) device\n" \ + " c or u: Make a character (un-buffered) device\n" \ + " p: Make a named pipe. MAJOR and MINOR are ignored for named pipes" +#define mknod_example_usage \ + "$ mknod /dev/fd0 b 2 0\n" \ + "$ mknod -m 644 /tmp/pipe p\n" + +#define mkswap_trivial_usage \ + "[-c] [-v0|-v1] device [block-count]" +#define mkswap_full_usage \ + "Prepare a disk partition to be used as a swap partition.\n\n" \ + "Options:\n" \ + " -c Check for read-ability\n" \ + " -v0 Make version 0 swap [max 128 Megs]\n" \ + " -v1 Make version 1 swap [big!] (default for kernels >\n" \ + " 2.1.117)\n" \ + " block-count Number of block to use (default is entire partition)" + +#define mktemp_trivial_usage \ + "[-dq] TEMPLATE" +#define mktemp_full_usage \ + "Creates a temporary file with its name based on TEMPLATE.\n" \ + "TEMPLATE is any name with six 'Xs' (i.e., /tmp/temp.XXXXXX).\n\n" \ + "Options:\n" \ + " -d Make a directory instead of a file\n" \ + " -q Fail silently if an error occurs" +#define mktemp_example_usage \ + "$ mktemp /tmp/temp.XXXXXX\n" \ + "/tmp/temp.mWiLjM\n" \ + "$ ls -la /tmp/temp.mWiLjM\n" \ + "-rw------- 1 andersen andersen 0 Apr 25 17:10 /tmp/temp.mWiLjM\n" + +#define modprobe_trivial_usage \ + "[-knqrsv] MODULE [symbol=value ...]" +#define modprobe_full_usage \ + "Options:\n" \ + " -k Make module autoclean-able\n" \ + " -n Just show what would be done\n" \ + " -q Quiet output\n" \ + " -r Remove module (stacks) or do autoclean\n" \ + " -s Report via syslog instead of stderr\n" \ + " -v Verbose output" +#define modprobe_notes_usage \ +"modprobe can (un)load a stack of modules, passing each module options (when\n" \ +"loading). modprobe uses a configuration file to determine what option(s) to\n" \ +"pass each module it loads.\n" \ +"\n" \ +"The configuration file is searched (in order) amongst:\n" \ +"\n" \ +" /etc/modprobe.conf (2.6 only)\n" \ +" /etc/modules.conf\n" \ +" /etc/conf.modules (deprecated)\n" \ +"\n" \ +"They all have the same syntax (see below). If none is present, it is\n" \ +"_not_ an error; each loaded module is then expected to load without\n" \ +"options. Once a file is found, the others are tested for.\n" \ +"\n" \ +"/etc/modules.conf entry format:\n" \ +"\n" \ +" alias \n" \ +" Makes it possible to modprobe alias_name, when there is no such module.\n" \ +" It makes sense if your mod_name is long, or you want a more representative\n" \ +" name for that module (eg. 'scsi' in place of 'aha7xxx').\n" \ +" This makes it also possible to use a different set of options (below) for\n" \ +" the module and the alias.\n" \ +" A module can be aliased more than once.\n" \ +"\n" \ +" options \n" \ +" When loading module mod_name (or the module aliased by alias_name), pass\n" \ +" the \"symbol=value\" pairs as option to that module.\n" \ +"\n" \ +"Sample /etc/modules.conf file:\n" \ +"\n" \ +" options tulip irq=3\n" \ +" alias tulip tulip2\n" \ +" options tulip2 irq=4 io=0x308\n" \ +"\n" \ +"Other functionality offered by 'classic' modprobe is not available in\n" \ +"this implementation.\n" \ +"\n" \ +"If module options are present both in the config file, and on the command line,\n" \ +"then the options from the command line will be passed to the module _after_\n" \ +"the options from the config file. That way, you can have defaults in the config\n" \ +"file, and override them for a specific usage from the command line.\n" +#define modprobe_example_usage \ + "(with the above /etc/modules.conf):\n\n" \ + "$ modprobe tulip\n" \ + " will load the module 'tulip' with default option 'irq=3'\n\n" \ + "$ modprobe tulip irq=5\n" \ + " will load the module 'tulip' with option 'irq=5', thus overriding the default\n\n" \ + "$ modprobe tulip2\n" \ + " will load the module 'tulip' with default options 'irq=4 io=0x308',\n" \ + " which are the default for alias 'tulip2'\n\n" \ + "$ modprobe tulip2 irq=8\n" \ + " will load the module 'tulip' with default options 'irq=4 io=0x308 irq=8',\n" \ + " which are the default for alias 'tulip2' overridden by the option 'irq=8'\n\n" \ + " from the command line\n\n" \ + "$ modprobe tulip2 irq=2 io=0x210\n" \ + " will load the module 'tulip' with default options 'irq=4 io=0x308 irq=4 io=0x210',\n" \ + " which are the default for alias 'tulip2' overridden by the options 'irq=2 io=0x210'\n\n" \ + " from the command line\n" + +#define more_trivial_usage \ + "[FILE ...]" +#define more_full_usage \ + "More is a filter for viewing FILE one screenful at a time." +#define more_example_usage \ + "$ dmesg | more\n" + +#define mount_trivial_usage \ + "[flags] DEVICE NODE [-o options,more-options]" +#define mount_full_usage \ + "Mount a filesystem. Filesystem autodetection requires /proc be mounted.\n\n" \ + "Flags:\n" \ + " -a: Mount all filesystems in fstab\n" \ + USE_FEATURE_MTAB_SUPPORT( \ + " -f: \"Fake\" Add entry to mount table but don't mount it\n" \ + " -n: Don't write a mount table entry\n" \ + ) \ + " -o option: One of many filesystem options, listed below\n" \ + " -r: Mount the filesystem read-only\n" \ + " -t fs-type: Specify the filesystem type\n" \ + " -w: Mount for reading and writing (default)\n" \ + "\n" \ + "Options for use with the \"-o\" flag:\n" \ + USE_FEATURE_MOUNT_LOOP( \ + " loop: Ignored (loop devices are autodetected)\n" \ + ) \ + USE_FEATURE_MOUNT_FLAGS( \ + " [a]sync: Writes are asynchronous / synchronous\n" \ + " [no]atime: Disable / enable updates to inode access times\n" \ + " [no]diratime: Disable / enable atime updates to directories\n" \ + " [no]dev: Allow use of special device files / disallow them\n" \ + " [no]exec: Allow use of executable files / disallow them\n" \ + " [no]suid: Allow set-user-id-root programs / disallow them\n" \ + " [r]shared: Convert [recursively] to a shared subtree.\n" \ + " [r]slave: Convert [recursively] to a slave subtree.\n" \ + " [r]private: Convert [recursively] to a private subtree\n" \ + " [un]bindable: Make mount point [un]able to be bind mounted.\n" \ + " bind: Bind a directory to an additional location\n" \ + " move: Relocate an existing mount point.\n" \ + ) \ + " remount: Re-mount a mounted filesystem, changing its flags\n" \ + " ro/rw: Mount for read-only / read-write\n" \ + "\nThere are EVEN MORE flags that are specific to each filesystem\n" \ + "You'll have to see the written documentation for those filesystems" +#define mount_example_usage \ + "$ mount\n" \ + "/dev/hda3 on / type minix (rw)\n" \ + "proc on /proc type proc (rw)\n" \ + "devpts on /dev/pts type devpts (rw)\n" \ + "$ mount /dev/fd0 /mnt -t msdos -o ro\n" \ + "$ mount /tmp/diskimage /opt -t ext2 -o loop\n" \ + "$ mount cd_image.iso mydir\n" +#define mount_notes_usage \ + "Returns 0 for success, number of failed mounts for -a, or errno for one mount." + +#define mountpoint_trivial_usage \ + "[-q] <[-d] DIR | -x DEVICE>" +#define mountpoint_full_usage \ + "mountpoint checks if the directory is a mountpoint\n\n" \ + "Options:\n" \ + " -q Be more quiet\n" \ + " -d Print major/minor device number of the filesystem\n" \ + " -x Print major/minor device number of the blockdevice" +#define mountpoint_example_usage \ + "$ mountpoint /proc\n" \ + "/proc is not a mountpoint\n" \ + "$ mountpoint /sys\n" \ + "/sys is a mountpoint\n" + +#define mt_trivial_usage \ + "[-f device] opcode value" +#define mt_full_usage \ + "Control magnetic tape drive operation\n" \ + "\nAvailable Opcodes:\n\n" \ + "bsf bsfm bsr bss datacompression drvbuffer eof eom erase\n" \ + "fsf fsfm fsr fss load lock mkpart nop offline ras1 ras2\n" \ + "ras3 reset retension rewind rewoffline seek setblk setdensity\n" \ + "setpart tell unload unlock weof wset" + +#define mv_trivial_usage \ + "[OPTION]... SOURCE DEST\n" \ + "or: mv [OPTION]... SOURCE... DIRECTORY" +#define mv_full_usage \ + "Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.\n\n" \ + "Options:\n" \ + " -f don't prompt before overwriting\n" \ + " -i interactive, prompt before overwrite" +#define mv_example_usage \ + "$ mv /tmp/foo /bin/bar\n" + +#define nameif_trivial_usage \ + "[-s] [-c FILE] [{IFNAME MACADDR}]" +#define nameif_full_usage \ + "Nameif renames network interface while it in the down state.\n\n" \ + "Options:\n" \ + " -c FILE Use configuration file (default is /etc/mactab)\n" \ + " -s Use syslog (LOCAL0 facility)\n" \ + " IFNAME MACADDR new_interface_name interface_mac_address" +#define nameif_example_usage \ + "$ nameif -s dmz0 00:A0:C9:8C:F6:3F\n" \ + " or\n" \ + "$ nameif -c /etc/my_mactab_file\n" \ + +#if ENABLE_NC_SERVER || ENABLE_NC_EXTRA +#define NC_OPTIONS_STR "\n\nOptions:" +#else +#define NC_OPTIONS_STR +#endif + +#define nc_trivial_usage \ + USE_NC_EXTRA("[-iN] [-wN] ")USE_NC_SERVER("[-l] [-p PORT] ") \ + "["USE_NC_EXTRA("-f FILENAME|")"IPADDR PORTNUM]"USE_NC_EXTRA(" [-e COMMAND]") +#define nc_full_usage \ + "Netcat opens a pipe to IP:port" USE_NC_EXTRA(" or file") \ + NC_OPTIONS_STR \ + USE_NC_EXTRA( \ + "\n -e exec rest of command line after connect\n" \ + " -i SECS delay interval for lines sent\n" \ + " -w SECS timeout for connect\n" \ + " -f file use file (ala /dev/ttyS0) instead of network" \ + ) \ + USE_NC_SERVER( \ + "\n -l listen mode, for inbound connects\n" \ + USE_NC_EXTRA(" (use -l twice with -e for persistent server)\n") \ + " -p PORT local port number" \ + ) + +#define nc_notes_usage "" \ + USE_NC_EXTRA( \ + "To use netcat as a terminal emulator on a serial port:\n\n" \ + "$ stty 115200 -F /dev/ttyS0\n" \ + "$ stty raw -echo -ctlecho && nc -f /dev/ttyS0\n" \ + ) "" + +#define nc_example_usage \ + "$ nc foobar.somedomain.com 25\n" \ + "220 foobar ESMTP Exim 3.12 #1 Sat, 15 Apr 2000 00:03:02 -0600\n" \ + "help\n" \ + "214-Commands supported:\n" \ + "214- HELO EHLO MAIL RCPT DATA AUTH\n" \ + "214 NOOP QUIT RSET HELP\n" \ + "quit\n" \ + "221 foobar closing connection\n" + +#define netstat_trivial_usage \ + "[-laenrtuwx]" +#define netstat_full_usage \ + "Netstat displays Linux networking information.\n\n" \ + "Options:\n" \ + " -l display listening server sockets\n" \ + " -a display all sockets (default: connected)\n" \ + " -e display other/more information\n" \ + " -n don't resolve names\n" \ + " -r display routing table\n" \ + " -t tcp sockets\n" \ + " -u udp sockets\n" \ + " -w raw sockets\n" \ + " -x unix sockets" + +#define nice_trivial_usage \ + "[-n ADJUST] [COMMAND [ARG] ...]" +#define nice_full_usage \ + "Nice runs a program with modified scheduling priority.\n\n" \ + "Options:\n" \ + " -n ADJUST Adjust the scheduling priority by ADJUST" + +#define nmeter_trivial_usage \ + "format_string" +#define nmeter_full_usage \ + "Nmeter monitors your system in real time.\n\n" \ + "Format specifiers:\n" \ + "%Nc or %[cN] monitor CPU. N - bar size, default 10\n" \ + " (displays: S:system U:user N:niced D:iowait I:irq i:softirq)\n" \ + "%[niface] monitor network interface 'iface'\n" \ + "%m monitor allocated memory\n" \ + "%[mf] monitor free memory\n" \ + "%[mt] monitor total memory\n" \ + "%s monitor allocated swap\n" \ + "%f monitor number of used file descriptors\n" \ + "%Ni monitor total/specific IRQ rate\n" \ + "%x monitor context switch rate\n" \ + "%p monitor forks\n" \ + "%[pn] monitor # of processes\n" \ + "%b monitor block io\n" \ + "%Nt show time (with N decimal points)\n" \ + "%Nd milliseconds between updates (default=1000)\n" \ + "%r print instead of at EOL" +#define nmeter_example_usage \ + "nmeter '%250d%t %20c int %i bio %b mem %m forks%p'" + +#define nohup_trivial_usage \ + "COMMAND [ARGS]" +#define nohup_full_usage \ + "run a command immune to hangups, with output to a non-tty" +#define nohup_example_usage \ + "$ nohup make &" + +#define nslookup_trivial_usage \ + "[HOST] [SERVER]" +#define nslookup_full_usage \ + "Queries the nameserver for the IP address of the given HOST\n" \ + "optionally using a specified DNS server" +#define nslookup_example_usage \ + "$ nslookup localhost\n" \ + "Server: default\n" \ + "Address: default\n" \ + "\n" \ + "Name: debian\n" \ + "Address: 127.0.0.1\n" + +#define od_trivial_usage \ + "[-aBbcDdeFfHhIiLlOovXx] [FILE]" +#define od_full_usage \ + "Write an unambiguous representation, octal bytes by default, of FILE\n" \ + "to standard output. With no FILE, or when FILE is -, read standard input." + +#define openvt_trivial_usage \ + " [ARGS...]" +#define openvt_full_usage \ + "Start a command on a new virtual terminal" +#define openvt_example_usage \ + "openvt 2 /bin/ash\n" + +#define passwd_trivial_usage \ + "[OPTION] [name]" +#define passwd_full_usage \ + "Change a user password. If no name is specified,\n" \ + "changes the password for the current user.\n" \ + "Options:\n" \ + " -a Define which algorithm shall be used for the password\n" \ + " (Choices: des, md5, sha1)\n" \ + " -d Delete the password for the specified user account\n" \ + " -l Locks (disables) the specified user account\n" \ + " -u Unlocks (re-enables) the specified user account" + +#define patch_trivial_usage \ + "[-p] [-i ]" +#define patch_full_usage \ + " -p Strip leading components from file names\n" \ + " -i Read instead of stdin" +#define patch_example_usage \ + "$ patch -p1 < example.diff\n" \ + "$ patch -p0 -i example.diff" + +#if (ENABLE_FEATURE_PIDOF_SINGLE || ENABLE_FEATURE_PIDOF_OMIT) +#define USAGE_PIDOF "Options:" +#else +#define USAGE_PIDOF "\n This version of pidof accepts no options." +#endif + +#define pidof_trivial_usage \ + "process-name [OPTION] [process-name ...]" + +#define pidof_full_usage \ + "Lists the PIDs of all processes with names that match the\n" \ + "names on the command line.\n" \ + USAGE_PIDOF \ + USE_FEATURE_PIDOF_SINGLE("\n -s display only a single PID") \ + USE_FEATURE_PIDOF_OMIT("\n -o omit given pid.") \ + USE_FEATURE_PIDOF_OMIT("\n Use %PPID to omit the parent pid of pidof itself") +#define pidof_example_usage \ + "$ pidof init\n" \ + "1\n" \ + USE_FEATURE_PIDOF_OMIT("$ pidof /bin/sh\n20351 5973 5950\n") \ + USE_FEATURE_PIDOF_OMIT("$ pidof /bin/sh -o %PPID\n20351 5950") + +#ifndef CONFIG_FEATURE_FANCY_PING +#define ping_trivial_usage "host" +#define ping_full_usage "Send ICMP ECHO_REQUEST packets to network hosts" +#else +#define ping_trivial_usage \ + "[OPTION]... host" +#define ping_full_usage \ + "Send ICMP ECHO_REQUEST packets to network hosts.\n\n" \ + "Options:\n" \ + " -c COUNT Send only COUNT pings\n" \ + " -s SIZE Send SIZE data bytes in packets (default=56)\n" \ + " -I IPADDR Use IPADDR as source address\n" \ + " -q Quiet mode, only displays output at start\n" \ + " and when finished" +#endif +#define ping_example_usage \ + "$ ping localhost\n" \ + "PING slag (127.0.0.1): 56 data bytes\n" \ + "64 bytes from 127.0.0.1: icmp_seq=0 ttl=255 time=20.1 ms\n" \ + "\n" \ + "--- debian ping statistics ---\n" \ + "1 packets transmitted, 1 packets received, 0% packet loss\n" \ + "round-trip min/avg/max = 20.1/20.1/20.1 ms\n" + +#ifndef CONFIG_FEATURE_FANCY_PING6 +#define ping6_trivial_usage "host" +#define ping6_full_usage "Send ICMP ECHO_REQUEST packets to network hosts" +#else +#define ping6_trivial_usage \ + "[OPTION]... host" +#define ping6_full_usage \ + "Send ICMP ECHO_REQUEST packets to network hosts.\n\n" \ + "Options:\n" \ + " -c COUNT Send only COUNT pings\n" \ + " -s SIZE Send SIZE data bytes in packets (default=56)\n" \ + " -q Quiet mode, only displays output at start\n" \ + " and when finished" +#endif +#define ping6_example_usage \ + "$ ping6 ip6-localhost\n" \ + "PING ip6-localhost (::1): 56 data bytes\n" \ + "64 bytes from ::1: icmp6_seq=0 ttl=64 time=20.1 ms\n" \ + "\n" \ + "--- ip6-localhost ping statistics ---\n" \ + "1 packets transmitted, 1 packets received, 0% packet loss\n" \ + "round-trip min/avg/max = 20.1/20.1/20.1 ms\n" + +#define pivot_root_trivial_usage \ + "NEW_ROOT PUT_OLD" +#define pivot_root_full_usage \ + "Move the current root file system to PUT_OLD and make NEW_ROOT\n" \ + "the new root file system." + +#define poweroff_trivial_usage \ + "[-d] [-n] [-f]" +#define poweroff_full_usage \ + "Halt and shut off power.\n" \ + "Options:\n" \ + " -d delay interval for halting\n" \ + " -n no call to sync()\n" \ + " -f force power off (don't go through init)" + +#define printenv_trivial_usage \ + "[VARIABLES...]" +#define printenv_full_usage \ + "print all or part of environment\n\n" \ + "If no environment VARIABLE specified, print them all." + +#define printf_trivial_usage \ + "FORMAT [ARGUMENT...]" +#define printf_full_usage \ + "Formats and prints ARGUMENT(s) according to FORMAT,\n" \ + "Where FORMAT controls the output exactly as in C printf." +#define printf_example_usage \ + "$ printf \"Val=%d\\n\" 5\n" \ + "Val=5\n" + + +#if ENABLE_DESKTOP + +#define ps_trivial_usage \ + "" +#define ps_full_usage \ + "Report process status\n" \ + "\nOptions:" \ + "\n -o col1,col2=header Select columns for display" \ + +#else /* !ENABLE_DESKTOP */ + +#if !defined CONFIG_SELINUX && !ENABLE_FEATURE_PS_WIDE +#define USAGE_PS "\n This version of ps accepts no options." +#else +#define USAGE_PS "\nOptions:" +#endif + +#define ps_trivial_usage \ + "" +#define ps_full_usage \ + "Report process status\n" \ + USAGE_PS \ + USE_SELINUX("\n -c show SE Linux context") \ + USE_FEATURE_PS_WIDE("\n w wide output") + +#endif /* ENABLE_DESKTOP */ + +#define ps_example_usage \ + "$ ps\n" \ + " PID Uid Gid State Command\n" \ + " 1 root root S init\n" \ + " 2 root root S [kflushd]\n" \ + " 3 root root S [kupdate]\n" \ + " 4 root root S [kpiod]\n" \ + " 5 root root S [kswapd]\n" \ + " 742 andersen andersen S [bash]\n" \ + " 743 andersen andersen S -bash\n" \ + " 745 root root S [getty]\n" \ + " 2990 andersen andersen R ps\n" + + +#define pwd_trivial_usage \ + "" +#define pwd_full_usage \ + "Print the full filename of the current working directory." +#define pwd_example_usage \ + "$ pwd\n" \ + "/root\n" + +#define raidautorun_trivial_usage \ + "DEVICE" +#define raidautorun_full_usage \ + "Tells the kernel to automatically search and start RAID arrays" +#define raidautorun_example_usage \ + "$ raidautorun /dev/md0" + +#define rdate_trivial_usage \ + "[-sp] HOST" +#define rdate_full_usage \ + "Get and possibly set the system date and time from a remote HOST.\n\n" \ + "Options:\n" \ + " -s Set the system date and time (default)\n" \ + " -p Print the date and time" + +#define readahead_trivial_usage \ + "[FILE]..." +#define readahead_full_usage \ + "Preloads FILE(s) in RAM cache so that subsequent reads for those" \ + "files do not block on disk I/O." + +#define readlink_trivial_usage \ + USE_FEATURE_READLINK_FOLLOW("[-f] ") "FILE" +#define readlink_full_usage \ + "Displays the value of a symbolic link." \ + USE_FEATURE_READLINK_FOLLOW("\n\nOptions:\n" \ + " -f canonicalize by following all symlinks") + +#define readprofile_trivial_usage \ + "[OPTIONS]..." +#define readprofile_full_usage \ + "Options:\n" \ + " -m (default: /boot/System.map)\n" \ + " -p (default: /proc/profile)\n" \ + " -M set the profiling multiplier to \n" \ + " -i print only info about the sampling step\n" \ + " -v print verbose data\n" \ + " -a print all symbols, even if count is 0\n" \ + " -b print individual histogram-bin counts\n" \ + " -s print individual counters within functions\n" \ + " -r reset all the counters (root only)\n" \ + " -n disable byte order auto-detection" + +#define realpath_trivial_usage \ + "pathname ..." +#define realpath_full_usage \ + "Returns the absolute pathnames of given argument." + +#define reboot_trivial_usage \ + "[-d] [-n] [-f]" +#define reboot_full_usage \ + "Reboot the system.\n" \ + "Options:\n" \ + " -d delay interval for rebooting\n" \ + " -n no call to sync()\n" \ + " -f force reboot (don't go through init)" + +#define renice_trivial_usage \ + "{{-n INCREMENT} | PRIORITY} [[ -p | -g | -u ] ID ...]" +#define renice_full_usage \ + "Changes priority of running processes.\n\n" \ + "Options:\n" \ + " -n adjusts current nice value (smaller is faster)\n" \ + " -p process id(s) (default)\n" \ + " -g process group id(s)\n" \ + " -u process user name(s) and/or id(s)" + +#define reset_trivial_usage \ + "" +#define reset_full_usage \ + "Resets the screen." + +#define resize_trivial_usage \ + "" +#define resize_full_usage \ + "Resizes the screen." + +#define rm_trivial_usage \ + "[OPTION]... FILE..." +#define rm_full_usage \ + "Remove (unlink) the FILE(s). You may use '--' to\n" \ + "indicate that all following arguments are non-options.\n\n" \ + "Options:\n" \ + " -i always prompt before removing each destination\n" \ + " -f remove existing destinations, never prompt\n" \ + " -r or -R remove the contents of directories recursively" +#define rm_example_usage \ + "$ rm -rf /tmp/foo\n" + +#define rmdir_trivial_usage \ + "[OPTION]... DIRECTORY..." +#define rmdir_full_usage \ + "Remove the DIRECTORY(ies), if they are empty." +#define rmdir_example_usage \ + "# rmdir /tmp/foo\n" + +#define rmmod_trivial_usage \ + "[OPTION]... [MODULE]..." +#define rmmod_full_usage \ + "Unloads the specified kernel modules from the kernel.\n\n" \ + "Options:\n" \ + " -a Remove all unused modules (recursively)" +#define rmmod_example_usage \ + "$ rmmod tulip\n" + +#define route_trivial_usage \ + "[{add|del|delete}]" +#define route_full_usage \ + "Edit the kernel's routing tables.\n\n" \ + "Options:\n" \ + " -n Dont resolve names\n" \ + " -e Display other/more information\n" \ + " -A inet" USE_FEATURE_IPV6("{6}") " Select address family" + +#define rpm_trivial_usage \ + "-i -q[ildc]p package.rpm" +#define rpm_full_usage \ + "Manipulates RPM packages" \ + "\n\nOptions:" \ + "\n -i Install package" \ + "\n -q Query package" \ + "\n -p Query uninstalled package" \ + "\n -i Show information" \ + "\n -l List contents" \ + "\n -d List documents" \ + "\n -c List config files" + +#define rpm2cpio_trivial_usage \ + "package.rpm" +#define rpm2cpio_full_usage \ + "Outputs a cpio archive of the rpm file." + +#define run_parts_trivial_usage \ + "[-t] [-a ARG] [-u MASK] DIRECTORY" +#define run_parts_full_usage \ + "Run a bunch of scripts in a directory.\n\n" \ + "Options:\n" \ + " -t Prints what would be run, but does not actually run anything\n" \ + " -a ARG Pass ARG as an argument for every program invoked\n" \ + " -u MASK Set the umask to MASK before executing every program" + +#define runlevel_trivial_usage \ + "[utmp]" +#define runlevel_full_usage \ + "Find the current and previous system runlevel.\n\n" \ + "If no utmp file exists or if no runlevel record can be found,\n" \ + "runlevel prints \"unknown\"" +#define runlevel_example_usage \ + "$ runlevel /var/run/utmp\n" \ + "N 2" + +#define runsv_trivial_usage \ + "dir" +#define runsv_full_usage \ + "Start and monitor a service and optionally an appendant log service." + +#define runsvdir_trivial_usage \ + "[-P] dir" +#define runsvdir_full_usage \ + "Start a runsv process for each subdirectory." + +#define rx_trivial_usage \ + "FILE" +#define rx_full_usage \ + "Receive a file using the xmodem protocol." +#define rx_example_usage \ + "$ rx /tmp/foo\n" + +#define sed_trivial_usage \ + "[-efinr] pattern [files...]" +#define sed_full_usage \ + "Options:\n" \ + " -e script add the script to the commands to be executed\n" \ + " -f scriptfile add script-file contents to the\n" \ + " commands to be executed\n" \ + " -i edit files in-place\n" \ + " -n suppress automatic printing of pattern space\n" \ + " -r use extended regular expression syntax\n" \ + "\n" \ + "If no -e or -f is given, the first non-option argument is taken as the sed\n" \ + "script to interpret. All remaining arguments are names of input files; if no\n" \ + "input files are specified, then the standard input is read. Source files\n" \ + "will not be modified unless -i option is given." + +#define sed_example_usage \ + "$ echo \"foo\" | sed -e 's/f[a-zA-Z]o/bar/g'\n" \ + "bar\n" + +#define seq_trivial_usage \ + "[first [increment]] last" +#define seq_full_usage \ + "Print numbers from FIRST to LAST, in steps of INCREMENT.\n" \ + "FIRST, INCREMENT default to 1\n" \ + "Arguments:\n" \ + " LAST\n" \ + " FIRST LAST\n" \ + " FIRST INCREMENT LAST" + +#define setconsole_trivial_usage \ + "[-r|--reset] [DEVICE]" +#define setconsole_full_usage \ + "Redirects system console output to DEVICE (default: /dev/tty).\n\n" \ + "Options:\n" \ + " -r Reset output to /dev/console." + +#define setkeycodes_trivial_usage \ + "SCANCODE KEYCODE ..." +#define setkeycodes_full_usage \ + "Set entries into the kernel's scancode-to-keycode map,\n" \ + "allowing unusual keyboards to generate usable keycodes.\n\n" \ + "SCANCODE may be either xx or e0xx (hexadecimal),\n" \ + "and KEYCODE is given in decimal" +#define setkeycodes_example_usage \ + "$ setkeycodes e030 127\n" + +#define setlogcons_trivial_usage \ + "N" +#define setlogcons_full_usage \ + "Redirects the kernel output to console N (0 for current)." + +#define setsid_trivial_usage \ + "program [arg ...]" +#define setsid_full_usage \ + "Runs any program in a new session by calling setsid() before\n" \ + "exec'ing the rest of its arguments. See setsid(2) for details." + +#define lash_trivial_usage \ + "[FILE]...\n" \ + "or: sh -c command [args]..." +#define lash_full_usage \ + "The BusyBox LAme SHell (command interpreter)" +#define lash_notes_usage \ + "This command does not yet have proper documentation.\n\n" \ + "Use lash just as you would use any other shell. It properly handles pipes,\n" \ + "redirects, job control, can be used as the shell for scripts, and has a\n" \ + "sufficient set of builtins to do what is needed. It does not (yet) support\n" \ + "Bourne Shell syntax. If you need things like \"if-then-else\", \"while\", and such\n" \ + "use ash or bash. If you just need a very simple and extremely small shell,\n" \ + "this will do the job." + +#define last_trivial_usage \ + "" +#define last_full_usage \ + "Shows listing of the last users that logged into the system" + +#define sha1sum_trivial_usage \ + "[OPTION] [FILEs...]" \ + USE_FEATURE_MD5_SHA1_SUM_CHECK("\n or: sha1sum [OPTION] -c [FILE]") +#define sha1sum_full_usage \ + "Print" USE_FEATURE_MD5_SHA1_SUM_CHECK(" or check") " SHA1 checksums.\n\n" \ + "Options:\n" \ + "With no FILE, or when FILE is -, read standard input." \ + USE_FEATURE_MD5_SHA1_SUM_CHECK("\n\n" \ + " -c check SHA1 sums against given list\n" \ + "\nThe following two options are useful only when verifying checksums:\n" \ + " -s don't output anything, status code shows success\n" \ + " -w warn about improperly formatted SHA1 checksum lines") + +#define sleep_trivial_usage \ + USE_FEATURE_FANCY_SLEEP("[") "N" USE_FEATURE_FANCY_SLEEP("]...") +#define sleep_full_usage \ + SKIP_FEATURE_FANCY_SLEEP("Pause for N seconds.") \ + USE_FEATURE_FANCY_SLEEP( \ + "Pause for a time equal to the total of the args given, where each arg can\n" \ + " have an optional suffix of (s)econds, (m)inutes, (h)ours, or (d)ays.") +#define sleep_example_usage \ + "$ sleep 2\n" \ + "[2 second delay results]\n" \ + USE_FEATURE_FANCY_SLEEP("$ sleep 1d 3h 22m 8s\n" \ + "[98528 second delay results]\n") + +#define sort_trivial_usage \ + "[-nru" USE_FEATURE_SORT_BIG("gMcszbdfimSTokt] [-o outfile] [-k start[.offset][opts][,end[.offset][opts]] [-t char") "] [FILE]..." +#define sort_full_usage \ + "Sorts lines of text in the specified files\n\n" \ + "Options:\n" \ + USE_FEATURE_SORT_BIG( \ + " -b ignore leading blanks\n" \ + " -c check whether input is sorted\n" \ + " -d dictionary order (blank or alphanumeric only)\n" \ + " -f ignore case\n" \ + " -g general numerical sort\n" \ + " -i ignore unprintable characters\n" \ + " -k specify sort key\n" \ + " -M sort month\n" \ + ) \ + " -n sort numbers\n" \ + USE_FEATURE_SORT_BIG( \ + " -o output to file\n" \ + " -k sort by key\n" \ + " -t use key separator other than whitespace\n" \ + ) \ + " -r reverse sort order\n" \ + USE_FEATURE_SORT_BIG(" -s stable (don't sort ties alphabetically)\n") \ + " -u suppress duplicate lines" \ + USE_FEATURE_SORT_BIG("\n -z input terminated by nulls, not newlines\n") \ + USE_FEATURE_SORT_BIG(" -mST ignored for GNU compatibility") \ + "" +#define sort_example_usage \ + "$ echo -e \"e\\nf\\nb\\nd\\nc\\na\" | sort\n" \ + "a\n" \ + "b\n" \ + "c\n" \ + "d\n" \ + "e\n" \ + "f\n" \ + USE_FEATURE_SORT_BIG( \ + "$ echo -e \"c 3\\nb 2\\nd 2\" | $SORT -k 2,2n -k 1,1r\n" \ + "d 2\n" \ + "b 2\n" \ + "c 3\n" \ + ) \ + "" + +#define start_stop_daemon_trivial_usage \ + "[OPTIONS] [--start|--stop] ... [-- arguments...]" +#define start_stop_daemon_full_usage \ + "Program to start and stop services." \ + "\n\nOptions:" \ + "\n -S|--start start" \ + "\n -K|--stop stop" \ + "\n -a|--startas starts process specified by pathname" \ + "\n -b|--background force process into background" \ + "\n -u|--user | stop this user's processes" \ + "\n -x|--exec program to either start or check" \ + "\n -m|--make-pidfile create the -p file and enter pid in it" \ + "\n -n|--name stop processes with this name" \ + "\n -p|--pidfile save or load pid using a pid-file" \ + "\n -q|--quiet be quiet" \ +USE_FEATURE_START_STOP_DAEMON_FANCY( \ + "\n -o|--oknodo exit status 0 if nothing done" \ + "\n -v|--verbose be verbose" \ + "\n -N|--nicelevel add N to process's nice level" \ +) \ + "\n -s|--signal signal to send (default TERM)" \ + "\n -U|--chuid | start process with this name" + +#define stat_trivial_usage \ + "[OPTION] FILE..." +#define stat_full_usage \ + "display file (default) or filesystem status.\n\n" \ + "Options:\n" \ + USE_FEATURE_STAT_FORMAT(" -c fmt use the specified format\n") \ + " -f display filesystem status\n" \ + " -L,-l dereference links\n" \ + " -t display info in terse form\n" \ + USE_FEATURE_STAT_FORMAT( \ + "\nValid format sequences for files:\n" \ + " %a Access rights in octal\n" \ + " %A Access rights in human readable form\n" \ + " %b Number of blocks allocated (see %B)\n" \ + " %B The size in bytes of each block reported by %b\n" \ + " %d Device number in decimal\n" \ + " %D Device number in hex\n" \ + " %f Raw mode in hex\n" \ + " %F File type\n" \ + " %g Group ID of owner\n" \ + " %G Group name of owner\n" \ + " %h Number of hard links\n" \ + " %i Inode number\n" \ + " %n File name\n" \ + " %N Quoted file name with dereference if symbolic link\n" \ + " %o I/O block size\n" \ + " %s Total size, in bytes\n" \ + " %t Major device type in hex\n" \ + " %T Minor device type in hex\n" \ + " %u User ID of owner\n" \ + " %U User name of owner\n" \ + " %x Time of last access\n" \ + " %X Time of last access as seconds since Epoch\n" \ + " %y Time of last modification\n" \ + " %Y Time of last modification as seconds since Epoch\n" \ + " %z Time of last change\n" \ + " %Z Time of last change as seconds since Epoch\n" \ + "\nValid format sequences for file systems:\n" \ + " %a Free blocks available to non-superuser\n" \ + " %b Total data blocks in file system\n" \ + " %c Total file nodes in file system\n" \ + " %d Free file nodes in file system\n" \ + " %f Free blocks in file system\n" \ + " %i File System ID in hex\n" \ + " %l Maximum length of filenames\n" \ + " %n File name\n" \ + " %s Block size (for faster transfers)\n" \ + " %S Fundamental block size (for block counts)\n" \ + " %t Type in hex\n" \ + " %T Type in human readable form\n" \ + ) + +#define strings_trivial_usage \ + "[-afo] [-n length] [file ... ]" +#define strings_full_usage \ + "Display printable strings in a binary file." \ + "\n\nOptions:" \ + "\n -a Scan the whole files (this is the default)." \ + "\n -f Precede each string with the name of the file where it was found." \ + "\n -n N Specifies that at least N characters forms a sequence (default 4)" \ + "\n -o Each string is preceded by its decimal offset in the file" + +#define stty_trivial_usage \ + "[-a|g] [-F DEVICE] [SETTING]..." +#define stty_full_usage \ + "Without arguments, prints baud rate, line discipline," \ + "\nand deviations from stty sane." \ + "\n\nOptions:" \ + "\n -F DEVICE open device instead of stdin" \ + "\n -a print all current settings in human-readable form" \ + "\n -g print in stty-readable form" \ + "\n [SETTING] see manpage" + +#define su_trivial_usage \ + "[OPTION]... [-] [username]" +#define su_full_usage \ + "Change user id or become root.\n" \ + "Options:\n" \ + " -p, -m Preserve environment" \ + "\n -c Command to pass to 'sh -c'" \ + "\n -s Shell to use instead of default shell" + +#define sulogin_trivial_usage \ + "[OPTION]... [tty-device]" +#define sulogin_full_usage \ + "Single user login\n" \ + "Options:\n" \ + " -t Timeout" + +#define sum_trivial_usage \ + "[rs] [files...]" +#define sum_full_usage \ + "checksum and count the blocks in a file\n\n" \ + "Options:\n" \ + " -r use BSD sum algorithm (1K blocks)\n" \ + " -s use System V sum algorithm (512byte blocks)" + +#define sv_trivial_usage \ + "[-v] [-w sec] command service..." +#define sv_full_usage \ + "Report the current status and control the state of services " \ + "monitored by the runsv supervisor." + +#define svlogd_trivial_usage \ + "[-ttv] [-r c] [-R abc] [-l len] [-b buflen] dir..." +#define svlogd_full_usage \ + "Continuously read log data from standard input, optionally " \ + "filter log messages, and write the data to one or more automatically " \ + "rotated logs." + +#define swapoff_trivial_usage \ + "[-a] [DEVICE]" +#define swapoff_full_usage \ + "Stop swapping virtual memory pages on DEVICE.\n\n" \ + "Options:\n" \ + " -a Stop swapping on all swap devices" + +#define swapon_trivial_usage \ + "[-a] [DEVICE]" +#define swapon_full_usage \ + "Start swapping virtual memory pages on DEVICE.\n\n" \ + "Options:\n" \ + " -a Start swapping on all swap devices" + +#define switch_root_trivial_usage \ + "[-c /dev/console] NEW_ROOT NEW_INIT [ARGUMENTS_TO_INIT]" +#define switch_root_full_usage \ + "Use from PID 1 under initramfs to free initramfs, chroot to NEW_ROOT,\n" \ + "and exec NEW_INIT.\n\n" \ + "Options:\n" \ + " -c Redirect console to device on new root" + +#define sync_trivial_usage \ + "" +#define sync_full_usage \ + "Write all buffered filesystem blocks to disk." + +#define sysctl_trivial_usage \ + "[OPTIONS]... [VALUE]..." +#define sysctl_full_usage \ + "configure kernel parameters at runtime\n\n" \ + "Options:\n" \ + " -n Use this option to disable printing of the key name when printing values\n" \ + " -w Use this option when you want to change a sysctl setting\n" \ + " -p Load in sysctl settings from the file specified or /etc/sysctl.conf if none given\n" \ + " -a Display all values currently available\n" \ + " -A Display all values currently available in table form" +#define sysctl_example_usage \ + "sysctl [-n] variable ...\n" \ + "sysctl [-n] -w variable=value ...\n" \ + "sysctl [-n] -a\n" \ + "sysctl [-n] -p (default /etc/sysctl.conf)\n" \ + "sysctl [-n] -A\n" + +#define syslogd_trivial_usage \ + "[OPTION]..." +#define syslogd_full_usage \ + "Linux system and kernel logging utility.\n" \ + "Note that this version of syslogd ignores /etc/syslog.conf.\n\n" \ + "Options:\n" \ + " -m MIN Minutes between MARK lines (default=20, 0=off)\n" \ + " -n Run as a foreground process\n" \ + " -O FILE Use an alternate log file (default=/var/log/messages)\n" \ + " -l n Sets the local log level of messages to n\n" \ + " -S Make logging output smaller" \ + USE_FEATURE_ROTATE_LOGFILE( \ + "\n -s SIZE Max size (KB) before rotate (default=200KB, 0=off)\n" \ + " -b NUM Number of rotated logs to keep (default=1, max=99, 0=purge)") \ + USE_FEATURE_REMOTE_LOG( \ + "\n -R HOST[:PORT] Log to IP or hostname on PORT (default PORT=514/UDP)\n" \ + " -L Log locally and via network logging (default is network only)") \ + USE_FEATURE_IPC_SYSLOG( \ + "\n -C [size(KiB)] Log to a circular buffer (read the buffer using logread)") +#define syslogd_example_usage \ + "$ syslogd -R masterlog:514\n" \ + "$ syslogd -R 192.168.1.1:601\n" + +#define tail_trivial_usage \ + "[OPTION]... [FILE]..." +#define tail_full_usage \ + "Print last 10 lines of each FILE to standard output.\n" \ + "With more than one FILE, precede each with a header giving the\n" \ + "file name. With no FILE, or when FILE is -, read standard input.\n\n" \ + "Options:\n" \ + USE_FEATURE_FANCY_TAIL(" -c N[kbm] output the last N bytes\n") \ + " -n N[kbm] print last N lines instead of last 10\n" \ + " -f output data as the file grows" \ + USE_FEATURE_FANCY_TAIL( "\n -q never output headers giving file names\n" \ + " -s SEC wait SEC seconds between reads with -f\n" \ + " -v always output headers giving file names\n\n" \ + "If the first character of N (bytes or lines) is a '+', output begins with\n" \ + "the Nth item from the start of each file, otherwise, print the last N items\n" \ + "in the file. N bytes may be suffixed by k (x1024), b (x512), or m (1024^2)." ) +#define tail_example_usage \ + "$ tail -n 1 /etc/resolv.conf\n" \ + "nameserver 10.0.0.1\n" + +#define tar_trivial_usage \ + "-[" USE_FEATURE_TAR_CREATE("c") USE_FEATURE_TAR_GZIP("z") \ + USE_FEATURE_TAR_BZIP2("j") USE_FEATURE_TAR_LZMA("a") \ + USE_FEATURE_TAR_COMPRESS("Z") "xtvO] " \ + USE_FEATURE_TAR_FROM("[-X FILE] ") \ + "[-f TARFILE] [-C DIR] [FILE(s)] ..." +#define tar_full_usage \ + "Create, extract, or list files from a tar file.\n\n" \ + "Options:\n" \ + USE_FEATURE_TAR_CREATE(" c create\n") \ + " x extract\n" \ + " t list\n" \ + "\nArchive format selection:\n" \ + USE_FEATURE_TAR_GZIP(" z Filter the archive through gzip\n") \ + USE_FEATURE_TAR_BZIP2(" j Filter the archive through bzip2\n") \ + USE_FEATURE_TAR_LZMA(" a Filter the archive through lzma\n") \ + USE_FEATURE_TAR_COMPRESS(" Z Filter the archive through compress\n") \ + "\nFile selection:\n" \ + " f name of TARFILE or \"-\" for stdin\n" \ + " O extract to stdout\n" \ + USE_FEATURE_TAR_FROM( \ + " exclude file to exclude\n" \ + " X file with names to exclude\n" \ + ) \ + " C change to directory DIR before operation\n" \ + " v verbosely list files processed" +#define tar_example_usage \ + "$ zcat /tmp/tarball.tar.gz | tar -xf -\n" \ + "$ tar -cf /tmp/tarball.tar /usr/local\n" + +#define taskset_trivial_usage \ + "[OPTIONS] [mask] [pid | command [arg]...]" +#define taskset_full_usage \ + "Set or get CPU affinity.\n\n" \ + "Options:\n" \ + " -p operate on an existing PID" +#define taskset_example_usage \ + "$ taskset 0x7 ./dgemm_test&\n" \ + "$ taskset -p 0x1 $!\n" \ + "pid 4790's current affinity mask: 7\n" \ + "pid 4790's new affinity mask: 1\n" \ + "$ taskset 0x7 /bin/sh -c './taskset -p 0x1 $$'\n" \ + "pid 6671's current affinity mask: 1\n" \ + "pid 6671's new affinity mask: 1\n" \ + "$ taskset -p 1\n" + "pid 1's current affinity mask: 3\n" + +#define tee_trivial_usage \ + "[OPTION]... [FILE]..." +#define tee_full_usage \ + "Copy standard input to each FILE, and also to standard output.\n\n" \ + "Options:\n" \ + " -a append to the given FILEs, do not overwrite\n" \ + " -i ignore interrupt signals (SIGINT)" +#define tee_example_usage \ + "$ echo \"Hello\" | tee /tmp/foo\n" \ + "$ cat /tmp/foo\n" \ + "Hello\n" + +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN +#define telnet_trivial_usage \ + "[-a] [-l USER] HOST [PORT]" +#define telnet_full_usage \ + "Telnet is used to establish interactive communication with another\n" \ + "computer over a network using the TELNET protocol.\n\n" \ + "Options:\n" \ + " -a Attempt an automatic login with the USER variable\n" \ + " -l USER Attempt an automatic login with the USER argument\n" \ + " HOST The official name, alias or the IP address of the\n" \ + " remote host.\n" \ + " PORT The remote port number to connect to. If it is not\n" \ + " specified, the default telnet (23) port is used." +#else +#define telnet_trivial_usage \ + "HOST [PORT]" +#define telnet_full_usage \ + "Telnet is used to establish interactive communication with another\n" \ + "computer over a network using the TELNET protocol." +#endif + +#ifdef CONFIG_FEATURE_TELNETD_STANDALONE +#define telnetd_trivial_usage \ + "[OPTION]" +#define telnetd_full_usage \ + "Telnetd listens for incoming TELNET connections on PORT.\n" \ + "Options:\n" \ + " -p PORT listen for connections on PORT (default 23)\n" \ + " -l LOGIN exec LOGIN on connect\n" \ + " -f issue_file Display issue_file instead of /etc/issue\n" \ + " -F Foreground mode\n" \ + " -i Inetd mode" +#else +#define telnetd_trivial_usage \ + "[OPTION]" +#define telnetd_full_usage \ + "Telnetd uses incoming TELNET connections via inetd.\n" \ + "Options:\n" \ + " -l LOGIN exec LOGIN on connect\n" \ + " -f issue_file Display issue_file instead of /etc/issue" +#endif + +#define test_trivial_usage \ + "EXPRESSION\n or [ EXPRESSION ]" +#define test_full_usage \ + "Checks file types and compares values returning an exit\n" \ + "code determined by the value of EXPRESSION." +#define test_example_usage \ + "$ test 1 -eq 2\n" \ + "$ echo $?\n" \ + "1\n" \ + "$ test 1 -eq 1\n" \ + "$ echo $?\n" \ + "0\n" \ + "$ [ -d /etc ]\n" \ + "$ echo $?\n" \ + "0\n" \ + "$ [ -d /junk ]\n" \ + "$ echo $?\n" \ + "1\n" + +#define tftp_trivial_usage \ + "[OPTION]... HOST [PORT]" +#define tftp_full_usage \ + "Transfers a file from/to a tftp server using \"octet\" mode.\n\n" \ + "Options:\n" \ + " -l FILE Local FILE\n" \ + " -r FILE Remote FILE" \ + USE_FEATURE_TFTP_GET( \ + "\n -g Get file" \ + ) \ + USE_FEATURE_TFTP_PUT( \ + "\n -p Put file" \ + ) \ + USE_FEATURE_TFTP_BLOCKSIZE( \ + "\n -b SIZE Transfer blocks of SIZE octets" \ + ) +#define time_trivial_usage \ + "[OPTION]... COMMAND [ARGS...]" +#define time_full_usage \ + "Runs the program COMMAND with arguments ARGS. When COMMAND finishes,\n" \ + "COMMAND's resource usage information is displayed\n\n" \ + "Options:\n" \ + " -v Displays verbose resource usage information" + +#define top_trivial_usage \ + "[-b] [-n count] [-d seconds]" +#define top_full_usage \ + "top provides a view of process activity in real time.\n" \ + "It reads the status of all processes from /proc each \n" \ + "and shows the status for however many processes will fit on the screen." + +#define touch_trivial_usage \ + "[-c] FILE [FILE ...]" +#define touch_full_usage \ + "Update the last-modified date on the given FILE[s].\n\n" \ + "Options:\n" \ + " -c Do not create any files" +#define touch_example_usage \ + "$ ls -l /tmp/foo\n" \ + "/bin/ls: /tmp/foo: No such file or directory\n" \ + "$ touch /tmp/foo\n" \ + "$ ls -l /tmp/foo\n" \ + "-rw-rw-r-- 1 andersen andersen 0 Apr 15 01:11 /tmp/foo\n" + +#define tr_trivial_usage \ + "[-cds] STRING1 [STRING2]" +#define tr_full_usage \ + "Translate, squeeze, and/or delete characters from\n" \ + "standard input, writing to standard output.\n\n" \ + "Options:\n" \ + " -c take complement of STRING1\n" \ + " -d delete input characters coded STRING1\n" \ + " -s squeeze multiple output characters of STRING2 into one character" +#define tr_example_usage \ + "$ echo \"gdkkn vnqkc\" | tr [a-y] [b-z]\n" \ + "hello world\n" + +#define traceroute_trivial_usage \ + "[-FIldnrv] [-f 1st_ttl] [-m max_ttl] [-p port#] [-q nqueries]\n" \ + " [-s src_addr] [-t tos] [-w wait] [-g gateway] [-i iface]\n" \ + " [-z pausemsecs] host [data size]" +#define traceroute_full_usage \ + "trace the route ip packets follow going to \"host\"\n" \ + "Options:\n" \ + " -F Set the don't fragment bit\n" \ + " -I Use ICMP ECHO instead of UDP datagrams\n" \ + " -l Display the ttl value of the returned packet\n" \ + " -d Set SO_DEBUG options to socket\n" \ + " -n Print hop addresses numerically rather than symbolically\n" \ + " -r Bypass the normal routing tables and send directly to a host\n" \ + " -v Verbose output\n" \ + " -m max_ttl Set the max time-to-live (max number of hops)\n" \ + " -p port# Set the base UDP port number used in probes\n" \ + " (default is 33434)\n" \ + " -q nqueries Set the number of probes per 'ttl' to nqueries\n" \ + " (default is 3)\n" \ + " -s src_addr Use the following IP address as the source address\n" \ + " -t tos Set the type-of-service in probe packets to the following value\n" \ + " (default 0)\n" \ + " -w wait Set the time (in seconds) to wait for a response to a probe\n" \ + " (default 3 sec)\n" \ + " -g Specify a loose source route gateway (8 maximum)" + + +#define true_trivial_usage \ + "" +#define true_full_usage \ + "Return an exit code of TRUE (0)." +#define true_example_usage \ + "$ true\n" \ + "$ echo $?\n" \ + "0\n" + +#define tty_trivial_usage \ + "" +#define tty_full_usage \ + "Print the file name of the terminal connected to standard input.\n\n" \ + "Options:\n" \ + " -s print nothing, only return an exit status" +#define tty_example_usage \ + "$ tty\n" \ + "/dev/tty2\n" + +#define tune2fs_trivial_usage \ + "[-c max-mounts-count] [-e errors-behavior] [-g group] " \ + "[-i interval[d|m|w]] [-j] [-J journal-options] [-l] [-s sparse-flag] " \ + "[-m reserved-blocks-percent] [-o [^]mount-options[,...]] " \ + "[-r reserved-blocks-count] [-u user] [-C mount-count] " \ + "[-L volume-label] [-M last-mounted-dir] [-O [^]feature[,...]] " \ + "[-T last-check-time] [-U UUID] device" +#define tune2fs_full_usage \ + "Adjust filesystem options on ext[23] filesystems." + +#define udhcpc_trivial_usage \ + "[-Cfbnqtv] [-c CID] [-V VCLS] [-H HOSTNAME] [-i INTERFACE]\n[-p pidfile] [-r IP] [-s script]" +#define udhcpc_full_usage \ + " -V,--vendorclass=CLASSID Set vendor class identifier\n" \ + " -i,--interface=INTERFACE Interface to use (default: eth0)\n" \ + " -H,-h,--hostname=HOSTNAME Client hostname\n" \ + " -c,--clientid=CLIENTID Set client identifier\n" \ + " -C,--clientid-none Suppress default client identifier\n" \ + " -p,--pidfile=file Store process ID of daemon in file\n" \ + " -r,--request=IP IP address to request (default: none)\n" \ + " -s,--script=file Run file at dhcp events (default: /usr/share/udhcpc/default.script)\n" \ + " -t,--retries=NUM Send up to NUM request packets\n"\ + " -f,--foreground Do not fork after getting lease\n" \ + " -b,--background Fork to background if lease cannot be immediately negotiated\n" \ + " -n,--now Exit with failure if lease cannot be immediately negotiated\n" \ + " -q,--quit Quit after obtaining lease\n" \ + " -R,--release Release IP on quit\n" \ + " -v,--version Display version" \ + +#define udhcpd_trivial_usage \ + "[configfile]\n" \ + +#define udhcpd_full_usage \ + "" + +#define umount_trivial_usage \ + "[flags] FILESYSTEM|DIRECTORY" +#define umount_full_usage \ + "Unmount file systems\n" \ + "\nFlags:\n" " -a Unmount all file systems" \ + USE_FEATURE_MTAB_SUPPORT(" in /etc/mtab\n -n Don't erase /etc/mtab entries") \ + "\n -r Try to remount devices as read-only if mount is busy" \ + "\n -l Lazy umount (detach filesystem)" \ + "\n -f Force umount (i.e., unreachable NFS server)" \ + USE_FEATURE_MOUNT_LOOP("\n -D Do not free loop device (if a loop device has been used)") +#define umount_example_usage \ + "$ umount /dev/hdc1\n" + +#define uname_trivial_usage \ + "[OPTION]..." +#define uname_full_usage \ + "Print certain system information. With no OPTION, same as -s.\n\n" \ + "Options:\n" \ + " -a print all information\n" \ + " -m the machine (hardware) type\n" \ + " -n print the machine's network node hostname\n" \ + " -r print the operating system release\n" \ + " -s print the operating system name\n" \ + " -p print the host processor type\n" \ + " -v print the operating system version" +#define uname_example_usage \ + "$ uname -a\n" \ + "Linux debian 2.4.23 #2 Tue Dec 23 17:09:10 MST 2003 i686 GNU/Linux\n" + +#define uncompress_trivial_usage \ + "[-c] [-f] [ name ... ]" +#define uncompress_full_usage \ + "Uncompress .Z file[s]\n" \ + "Options:\n" \ + " -c extract to stdout\n" \ + " -f force overwrite an existing file" + +#define uniq_trivial_usage \ + "[-fscdu]... [INPUT [OUTPUT]]" +#define uniq_full_usage \ + "Discard all but one of successive identical lines from INPUT\n" \ + "(or standard input), writing to OUTPUT (or standard output).\n\n" \ + "Options:\n" \ + " -c prefix lines by the number of occurrences\n" \ + " -d only print duplicate lines\n" \ + " -u only print unique lines\n" \ + " -f N skip the first N fields\n" \ + " -s N skip the first N chars (after any skipped fields)" +#define uniq_example_usage \ + "$ echo -e \"a\\na\\nb\\nc\\nc\\na\" | sort | uniq\n" \ + "a\n" \ + "b\n" \ + "c\n" + +#define unix2dos_trivial_usage \ + "[option] [FILE]" +#define unix2dos_full_usage \ + "Converts FILE from unix format to dos format. When no option\n" \ + "is given, the input is converted to the opposite output format.\n" \ + "When no file is given, uses stdin for input and stdout for output.\n" \ + "Options:\n" \ + " -u output will be in UNIX format\n" \ + " -d output will be in DOS format" + +#define unzip_trivial_usage \ + "[-opts[modifiers]] file[.zip] [list] [-x xlist] [-d exdir]" +#define unzip_full_usage \ + "Extracts files from ZIP archives.\n\n" \ + "Options:\n" \ + " -l list archive contents (short form)\n" \ + " -n never overwrite existing files (default)\n" \ + " -o overwrite files without prompting\n" \ + " -p send output to stdout\n" \ + " -q be quiet\n" \ + " -x exclude these files\n" \ + " -d extract files into this directory" + +#define uptime_trivial_usage \ + "" +#define uptime_full_usage \ + "Display the time since the last boot." +#define uptime_example_usage \ + "$ uptime\n" \ + " 1:55pm up 2:30, load average: 0.09, 0.04, 0.00\n" + +#define usleep_trivial_usage \ + "N" +#define usleep_full_usage \ + "Pause for N microseconds." +#define usleep_example_usage \ + "$ usleep 1000000\n" \ + "[pauses for 1 second]\n" + +#define uudecode_trivial_usage \ + "[FILE]..." +#define uudecode_full_usage \ + "Uudecode a file that is uuencoded.\n\n" \ + "Options:\n" \ + " -o FILE direct output to FILE" +#define uudecode_example_usage \ + "$ uudecode -o busybox busybox.uu\n" \ + "$ ls -l busybox\n" \ + "-rwxr-xr-x 1 ams ams 245264 Jun 7 21:35 busybox\n" + +#define uuencode_trivial_usage \ + "[OPTION] [INFILE] REMOTEFILE" +#define uuencode_full_usage \ + "Uuencode a file.\n\n" \ + "Options:\n" \ + " -m use base64 encoding per RFC1521" +#define uuencode_example_usage \ + "$ uuencode busybox busybox\n" \ + "begin 755 busybox\n" \ + "\n" \ + "$ uudecode busybox busybox > busybox.uu\n" \ + "$\n" + +#define vconfig_trivial_usage \ + "COMMAND [OPTIONS] ..." +#define vconfig_full_usage \ + "vconfig lets you create and remove virtual ethernet devices.\n\n" \ + "Options:\n" \ + " add [interface-name] [vlan_id]\n" \ + " rem [vlan-name]\n" \ + " set_flag [interface-name] [flag-num] [0 | 1]\n" \ + " set_egress_map [vlan-name] [skb_priority] [vlan_qos]\n" \ + " set_ingress_map [vlan-name] [skb_priority] [vlan_qos]\n" \ + " set_name_type [name-type]" + +#define vi_trivial_usage \ + "[OPTION] [FILE]..." +#define vi_full_usage \ + "edit FILE.\n\n" \ + "Options:\n" \ + " -R Read-only- do not write to the file" + +#define vlock_trivial_usage \ + "[OPTIONS]" +#define vlock_full_usage \ + "Lock a virtual terminal. A password is required to unlock\n" \ + "Options:\n" \ + " -a Lock all VTs" + +#define watch_trivial_usage \ + "[-n ] [-t] COMMAND..." +#define watch_full_usage \ + "Executes a program periodically\n\n" \ + "Options:\n" \ + " -n Loop period in seconds - default is 2\n" + " -t Don't print header" +#define watch_example_usage \ + "$ watch date\n" \ + "Mon Dec 17 10:31:40 GMT 2000\n" \ + "Mon Dec 17 10:31:42 GMT 2000\n" \ + "Mon Dec 17 10:31:44 GMT 2000" + +#define watchdog_trivial_usage \ + "[-t ] [-F] DEV" +#define watchdog_full_usage \ + "Periodically write to watchdog device DEV.\n" \ + "Options:\n" \ + " -t Timer period in seconds - default is 30\n" \ + " -F Stay in the foreground and don't fork" + +#define wc_trivial_usage \ + "[OPTION]... [FILE]..." +#define wc_full_usage \ + "Print line, word, and byte counts for each FILE, and a total line if\n" \ + "more than one FILE is specified. With no FILE, read standard input.\n\n" \ + "Options:\n" \ + " -c print the byte counts\n" \ + " -l print the newline counts\n" \ + " -L print the length of the longest line\n" \ + " -w print the word counts" +#define wc_example_usage \ + "$ wc /etc/passwd\n" \ + " 31 46 1365 /etc/passwd\n" + +#define wget_trivial_usage \ + "[-c|--continue] [-q|--quiet] [-O|--output-document file]\n" \ + " [--header 'header: value'] [-Y|--proxy on/off] [-P DIR]\n" \ + " [-U|--user-agent agent] url" +#define wget_full_usage \ + "wget retrieves files via HTTP or FTP\n\n" \ + "Options:\n" \ + " -c continue retrieval of aborted transfers\n" \ + " -q quiet mode - do not print\n" \ + " -P Set directory prefix to DIR\n" \ + " -O save to filename ('-' for stdout)\n" \ + " -U adjust 'User-Agent' field\n" \ + " -Y use proxy ('on' or 'off')" + +#define which_trivial_usage \ + "[COMMAND ...]" +#define which_full_usage \ + "Locates a COMMAND." +#define which_example_usage \ + "$ which login\n" \ + "/bin/login\n" + +#define who_trivial_usage \ + " " +#define who_full_usage \ + "Prints the current user names and related information" + +#define whoami_trivial_usage \ + "" +#define whoami_full_usage \ + "Prints the user name associated with the current effective user id." + +#define xargs_trivial_usage \ + "[OPTIONS] [COMMAND] [ARGS...]" +#define xargs_full_usage \ + "Executes COMMAND on every item given by standard input.\n\n" \ + "Options:\n" \ + USE_FEATURE_XARGS_SUPPORT_CONFIRMATION(" -p Prompt the user about whether to run each command\n") \ + " -r Do not run command for empty read lines\n" \ + USE_FEATURE_XARGS_SUPPORT_TERMOPT(" -x Exit if the size is exceeded\n") \ + USE_FEATURE_XARGS_SUPPORT_ZERO_TERM(" -0 Input filenames are terminated by a null character\n") \ + " -t Print the command line on stderr before executing it" +#define xargs_example_usage \ + "$ ls | xargs gzip\n" \ + "$ find . -name '*.c' -print | xargs rm\n" + +#define yes_trivial_usage \ + "[OPTION]... [STRING]..." +#define yes_full_usage \ + "Repeatedly outputs a line with all specified STRING(s), or 'y'." + +#define zcat_trivial_usage \ + "FILE" +#define zcat_full_usage \ + "Uncompress to stdout." + +#define zcip_trivial_usage \ + "[OPTIONS] ifname script" +#define zcip_full_usage \ + "zcip manages a ZeroConf IPv4 link-local address.\n" \ + "Options:\n" \ + " -f foreground mode\n" \ + " -q quit after address (no daemon)\n" \ + " -r 169.254.x.x request this address first\n" \ + " -v verbose" + +#endif /* __BB_USAGE_H__ */ diff --git a/include/xatonum.h b/include/xatonum.h new file mode 100644 index 000000000..585d84623 --- /dev/null +++ b/include/xatonum.h @@ -0,0 +1,157 @@ +/* vi: set sw=4 ts=4: */ +/* + * ascii-to-numbers implementations for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + */ + +/* Provides extern declarations of functions */ +#define DECLARE_STR_CONV(type, T, UT) \ +\ +unsigned type xstrto##UT##_range_sfx(const char *str, int b, unsigned type l, unsigned type u, const struct suffix_mult *sfx); \ +unsigned type xstrto##UT##_range(const char *str, int b, unsigned type l, unsigned type u); \ +unsigned type xstrto##UT##_sfx(const char *str, int b, const struct suffix_mult *sfx); \ +unsigned type xstrto##UT(const char *str, int b); \ +unsigned type xato##UT##_range_sfx(const char *str, unsigned type l, unsigned type u, const struct suffix_mult *sfx); \ +unsigned type xato##UT##_range(const char *str, unsigned type l, unsigned type u); \ +unsigned type xato##UT##_sfx(const char *str, const struct suffix_mult *sfx); \ +unsigned type xato##UT(const char *str); \ +type xstrto##T##_range_sfx(const char *str, int b, type l, type u, const struct suffix_mult *sfx) ;\ +type xstrto##T##_range(const char *str, int b, type l, type u); \ +type xato##T##_range_sfx(const char *str, type l, type u, const struct suffix_mult *sfx); \ +type xato##T##_range(const char *str, type l, type u); \ +type xato##T##_sfx(const char *str, const struct suffix_mult *sfx); \ +type xato##T(const char *str); \ + +/* Unsigned long long functions always exist */ +DECLARE_STR_CONV(long long, ll, ull) + + +/* Provides extern inline definitions of functions */ +/* (useful for mapping them to the type of the same width) */ +#define DEFINE_EQUIV_STR_CONV(narrow, N, W, UN, UW) \ +\ +extern inline \ +unsigned narrow xstrto##UN##_range_sfx(const char *str, int b, unsigned narrow l, unsigned narrow u, const struct suffix_mult *sfx) \ +{ return xstrto##UW##_range_sfx(str, b, l, u, sfx); } \ +extern inline \ +unsigned narrow xstrto##UN##_range(const char *str, int b, unsigned narrow l, unsigned narrow u) \ +{ return xstrto##UW##_range(str, b, l, u); } \ +extern inline \ +unsigned narrow xstrto##UN##_sfx(const char *str, int b, const struct suffix_mult *sfx) \ +{ return xstrto##UW##_sfx(str, b, sfx); } \ +extern inline \ +unsigned narrow xstrto##UN(const char *str, int b) \ +{ return xstrto##UW(str, b); } \ +extern inline \ +unsigned narrow xato##UN##_range_sfx(const char *str, unsigned narrow l, unsigned narrow u, const struct suffix_mult *sfx) \ +{ return xato##UW##_range_sfx(str, l, u, sfx); } \ +extern inline \ +unsigned narrow xato##UN##_range(const char *str, unsigned narrow l, unsigned narrow u) \ +{ return xato##UW##_range(str, l, u); } \ +extern inline \ +unsigned narrow xato##UN##_sfx(const char *str, const struct suffix_mult *sfx) \ +{ return xato##UW##_sfx(str, sfx); } \ +extern inline \ +unsigned narrow xato##UN(const char *str) \ +{ return xato##UW(str); } \ +extern inline \ +narrow xstrto##N##_range_sfx(const char *str, int b, narrow l, narrow u, const struct suffix_mult *sfx) \ +{ return xstrto##W##_range_sfx(str, b, l, u, sfx); } \ +extern inline \ +narrow xstrto##N##_range(const char *str, int b, narrow l, narrow u) \ +{ return xstrto##W##_range(str, b, l, u); } \ +extern inline \ +narrow xato##N##_range_sfx(const char *str, narrow l, narrow u, const struct suffix_mult *sfx) \ +{ return xato##W##_range_sfx(str, l, u, sfx); } \ +extern inline \ +narrow xato##N##_range(const char *str, narrow l, narrow u) \ +{ return xato##W##_range(str, l, u); } \ +extern inline \ +narrow xato##N##_sfx(const char *str, const struct suffix_mult *sfx) \ +{ return xato##W##_sfx(str, sfx); } \ +extern inline \ +narrow xato##N(const char *str) \ +{ return xato##W(str); } \ + +/* If long == long long, then just map them one-to-one */ +#if ULONG_MAX == ULLONG_MAX +DEFINE_EQUIV_STR_CONV(long, l, ll, ul, ull) +#else +/* Else provide extern defs */ +DECLARE_STR_CONV(long, l, ul) +#endif + +/* Same for int -> [long] long */ +#if UINT_MAX == ULLONG_MAX +DEFINE_EQUIV_STR_CONV(int, i, ll, u, ull) +#elif UINT_MAX == ULONG_MAX +DEFINE_EQUIV_STR_CONV(int, i, l, u, ul) +#else +DECLARE_STR_CONV(int, i, u) +#endif + +/* Specialized */ + +int BUG_xatou32_unimplemented(void); +extern inline uint32_t xatou32(const char *numstr) +{ + if (UINT_MAX == 0xffffffff) + return xatou(numstr); + if (ULONG_MAX == 0xffffffff) + return xatoul(numstr); + return BUG_xatou32_unimplemented(); +} + +/* Non-aborting kind of convertors */ + +unsigned long long bb_strtoull(const char *arg, char **endp, int base); +long long bb_strtoll(const char *arg, char **endp, int base); + +#if ULONG_MAX == ULLONG_MAX +extern inline +unsigned long bb_strtoul(const char *arg, char **endp, int base) +{ return bb_strtoull(arg, endp, base); } +extern inline +unsigned long bb_strtol(const char *arg, char **endp, int base) +{ return bb_strtoll(arg, endp, base); } +#else +unsigned long bb_strtoul(const char *arg, char **endp, int base); +long bb_strtol(const char *arg, char **endp, int base); +#endif + +#if UINT_MAX == ULLONG_MAX +extern inline +unsigned long bb_strtou(const char *arg, char **endp, int base) +{ return bb_strtoull(arg, endp, base); } +extern inline +unsigned long bb_strtoi(const char *arg, char **endp, int base) +{ return bb_strtoll(arg, endp, base); } +#elif UINT_MAX == ULONG_MAX +extern inline +unsigned long bb_strtou(const char *arg, char **endp, int base) +{ return bb_strtoul(arg, endp, base); } +extern inline +unsigned long bb_strtoi(const char *arg, char **endp, int base) +{ return bb_strtol(arg, endp, base); } +#else +unsigned long bb_strtou(const char *arg, char **endp, int base); +long bb_strtoi(const char *arg, char **endp, int base); +#endif + +int BUG_bb_strtou32_unimplemented(void); +extern inline +uint32_t bb_strtou32(const char *arg, char **endp, int base) +{ + if (sizeof(uint32_t) == sizeof(unsigned)) + return bb_strtou(arg, endp, base); + if (sizeof(uint32_t) == sizeof(unsigned long)) + return bb_strtoul(arg, endp, base); + return BUG_bb_strtou32_unimplemented(); +} + +/* Floating point */ + +/* double bb_strtod(const char *arg, char **endp); */ diff --git a/include/xregex.h b/include/xregex.h new file mode 100644 index 000000000..4185818a8 --- /dev/null +++ b/include/xregex.h @@ -0,0 +1,17 @@ +/* vi: set sw=4 ts=4: */ +/* + * Busybox xregcomp utility routine. This isn't in libbb.h because the + * C library we're linking against may not support regex.h. + * + * Based in part on code from sash, Copyright (c) 1999 by David I. Bell + * Permission has been granted to redistribute this code under the GPL. + * + * Licensed under GPLv2 or later, see file License in this tarball for details. + */ +#ifndef __BB_REGEX__ +#define __BB_REGEX__ + +#include +extern void xregcomp(regex_t *preg, const char *regex, int cflags); + +#endif diff --git a/init/Config.in b/init/Config.in new file mode 100644 index 000000000..c0ad5263d --- /dev/null +++ b/init/Config.in @@ -0,0 +1,84 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Init Utilities" + +config INIT + bool "init" + default n + select FEATURE_SYSLOG + help + init is the first program run when the system boots. + +config DEBUG_INIT + bool "debugging aid" + default n + depends on INIT + help + Turn this on to disable all the dangerous + rebooting stuff when debugging. + +config FEATURE_USE_INITTAB + bool "Support reading an inittab file" + default y + depends on INIT + help + Allow init to read an inittab file when the system boot. + +config FEATURE_INIT_SCTTY + bool "Support running commands with a controlling-tty" + default n + depends on INIT + help + If this option is enabled a command starting with hyphen (-) + is run in its own session (setsid(2)) and possibly with a + controlling tty (TIOCSCTTY). This is not the traditional init + behavour, but is often what you want in an embedded system where + the console is only accessed during development or for maintenance. + +config FEATURE_EXTRA_QUIET + bool "Be _extra_ quiet on boot" + default y + depends on INIT + help + Prevent init from logging some messages to the console during boot. + +config FEATURE_INIT_COREDUMPS + bool "Support dumping core for child processes (debugging only)" + default n + depends on INIT + help + If this option is enabled and the file /.init_enable_core + exists, then init will call setrlimit() to allow unlimited + core file sizes. If this option is disabled, processes + will not generate any core files. + + + +config FEATURE_INITRD + bool "Support running init from within an initrd (not initramfs)" + default y + depends on INIT + help + Legacy support for running init under the old-style initrd. Allows + the name linuxrc to act as init, and it doesn't assume init is PID 1. + + This does not apply to initramfs, which runs /init as PID 1 and + requires no special support. + +config HALT + bool "poweroff, halt, and reboot" + default y + help + Stop all processes and either halt, reboot, or power off the system. + +config MESG + bool "mesg" + default y + help + Mesg controls access to your terminal by others. It is typically + used to allow or disallow other users to write to your terminal + +endmenu diff --git a/init/Kbuild b/init/Kbuild new file mode 100644 index 000000000..e99360241 --- /dev/null +++ b/init/Kbuild @@ -0,0 +1,12 @@ +# 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_HALT) += halt.o +lib-$(CONFIG_INIT) += init.o +lib-$(CONFIG_MESG) += mesg.o +lib-$(CONFIG_INIT) += init_shared.o +lib-$(CONFIG_HALT) += init_shared.o diff --git a/init/halt.c b/init/halt.c new file mode 100644 index 000000000..a6cf48bbe --- /dev/null +++ b/init/halt.c @@ -0,0 +1,58 @@ +/* vi: set sw=4 ts=4: */ +/* + * Poweroff reboot and halt, oh my. + * + * Copyright 2006 by Rob Landley + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include + +int halt_main(int argc, char *argv[]) +{ + static const int magic[] = { +#ifdef RB_HALT_SYSTEM +RB_HALT_SYSTEM, +#elif defined RB_HALT +RB_HALT, +#endif +#ifdef RB_POWER_OFF +RB_POWER_OFF, +#elif defined RB_POWERDOWN +RB_POWERDOWN, +#endif +RB_AUTOBOOT + }; + static const int signals[] = { SIGUSR1, SIGUSR2, SIGTERM }; + + char *delay; + int which, flags, rc = 1; + + /* Figure out which applet we're running */ + for (which = 0; "hpr"[which] != *applet_name; which++); + + /* Parse and handle arguments */ + flags = getopt32(argc, argv, "d:nf", &delay); + if (flags & 1) sleep(xatou(delay)); + if (!(flags & 2)) sync(); + + /* Perform action. */ + if (ENABLE_INIT && !(flags & 4)) { + if (ENABLE_FEATURE_INITRD) { + pid_t *pidlist = find_pid_by_name("linuxrc"); + if (pidlist[0] > 0) + rc = kill(pidlist[0], signals[which]); + if (ENABLE_FEATURE_CLEAN_UP) + free(pidlist); + } + if (rc) + rc = kill(1, signals[which]); + } else + rc = reboot(magic[which]); + + if (rc) + bb_error_msg("no"); + return rc; +} diff --git a/init/init.c b/init/init.c new file mode 100644 index 000000000..213a5c149 --- /dev/null +++ b/init/init.c @@ -0,0 +1,1131 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini init implementation for busybox + * + * Copyright (C) 1995, 1996 by Bruce Perens . + * Copyright (C) 1999-2004 by Erik Andersen + * Adjusted by so many folks, it's impossible to keep track. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include +#include +#include +#include +#include +#include + +#include "init_shared.h" + +#ifdef CONFIG_SYSLOGD +# include +#endif + +#define INIT_BUFFS_SIZE 256 + +/* From */ +struct vt_stat { + unsigned short v_active; /* active vt */ + unsigned short v_signal; /* signal to send */ + unsigned short v_state; /* vt bitmask */ +}; +enum { VT_GETSTATE = 0x5603 }; /* get global vt state info */ + +/* From */ +struct serial_struct { + int type; + int line; + unsigned int port; + int irq; + int flags; + int xmit_fifo_size; + int custom_divisor; + int baud_base; + unsigned short close_delay; + char io_type; + char reserved_char[1]; + int hub6; + unsigned short closing_wait; /* time to wait before closing */ + unsigned short closing_wait2; /* no longer used... */ + unsigned char *iomem_base; + unsigned short iomem_reg_shift; + unsigned int port_high; + unsigned long iomap_base; /* cookie passed into ioremap */ + int reserved[1]; +}; + +#ifndef _PATH_STDPATH +#define _PATH_STDPATH "/usr/bin:/bin:/usr/sbin:/sbin" +#endif + +#if defined CONFIG_FEATURE_INIT_COREDUMPS +/* + * When a file named CORE_ENABLE_FLAG_FILE exists, setrlimit is called + * before processes are spawned to set core file size as unlimited. + * This is for debugging only. Don't use this is production, unless + * you want core dumps lying about.... + */ +#define CORE_ENABLE_FLAG_FILE "/.init_enable_core" +#include +#endif + +#define INITTAB "/etc/inittab" /* inittab file location */ +#ifndef INIT_SCRIPT +#define INIT_SCRIPT "/etc/init.d/rcS" /* Default sysinit script. */ +#endif + +#define MAXENV 16 /* Number of env. vars */ + +#define CONSOLE_BUFF_SIZE 32 + +/* Allowed init action types */ +#define SYSINIT 0x001 +#define RESPAWN 0x002 +#define ASKFIRST 0x004 +#define WAIT 0x008 +#define ONCE 0x010 +#define CTRLALTDEL 0x020 +#define SHUTDOWN 0x040 +#define RESTART 0x080 + +/* A mapping between "inittab" action name strings and action type codes. */ +struct init_action_type { + const char *name; + int action; +}; + +static const struct init_action_type actions[] = { + {"sysinit", SYSINIT}, + {"respawn", RESPAWN}, + {"askfirst", ASKFIRST}, + {"wait", WAIT}, + {"once", ONCE}, + {"ctrlaltdel", CTRLALTDEL}, + {"shutdown", SHUTDOWN}, + {"restart", RESTART}, + {0, 0} +}; + +/* Set up a linked list of init_actions, to be read from inittab */ +struct init_action { + pid_t pid; + char command[INIT_BUFFS_SIZE]; + char terminal[CONSOLE_BUFF_SIZE]; + struct init_action *next; + int action; +}; + +/* Static variables */ +static struct init_action *init_action_list = NULL; +static char console[CONSOLE_BUFF_SIZE] = CONSOLE_DEV; + +#ifndef CONFIG_SYSLOGD +static char *log_console = VC_5; +#endif +#if !ENABLE_DEBUG_INIT +static sig_atomic_t got_cont = 0; +#endif + +enum { + LOG = 0x1, + CONSOLE = 0x2, + +#if defined CONFIG_FEATURE_EXTRA_QUIET + MAYBE_CONSOLE = 0x0, +#else + MAYBE_CONSOLE = CONSOLE, +#endif + +#ifndef RB_HALT_SYSTEM + RB_HALT_SYSTEM = 0xcdef0123, /* FIXME: this overflows enum */ + RB_ENABLE_CAD = 0x89abcdef, + RB_DISABLE_CAD = 0, + RB_POWER_OFF = 0x4321fedc, + RB_AUTOBOOT = 0x01234567, +#endif +}; + +static const char * const environment[] = { + "HOME=/", + "PATH=" _PATH_STDPATH, + "SHELL=/bin/sh", + "USER=root", + NULL +}; + +/* Function prototypes */ +static void delete_init_action(struct init_action *a); +static int waitfor(const struct init_action *a, pid_t pid); +#if !ENABLE_DEBUG_INIT +static void shutdown_signal(int sig); +#endif + +static void loop_forever(void) +{ + while (1) + sleep(1); +} + +/* Print a message to the specified device. + * Device may be bitwise-or'd from LOG | CONSOLE */ +#if ENABLE_DEBUG_INIT +#define messageD message +#else +#define messageD(...) do {;} while(0); +#endif +static void message(int device, const char *fmt, ...) + __attribute__ ((format(printf, 2, 3))); +static void message(int device, const char *fmt, ...) +{ + va_list arguments; + int l; + RESERVE_CONFIG_BUFFER(msg, 1024); +#ifndef CONFIG_SYSLOGD + static int log_fd = -1; +#endif + + msg[0] = '\r'; + va_start(arguments, fmt); + l = vsnprintf(msg + 1, 1024 - 2, fmt, arguments) + 1; + va_end(arguments); + +#ifdef CONFIG_SYSLOGD + /* Log the message to syslogd */ + if (device & LOG) { + /* don`t out "\r\n" */ + openlog(applet_name, 0, LOG_DAEMON); + syslog(LOG_INFO, "%s", msg + 1); + closelog(); + } + + msg[l++] = '\n'; + msg[l] = 0; +#else + + msg[l++] = '\n'; + msg[l] = 0; + /* Take full control of the log tty, and never close it. + * It's mine, all mine! Muhahahaha! */ + if (log_fd < 0) { + if ((log_fd = device_open(log_console, O_RDWR | O_NONBLOCK | O_NOCTTY)) < 0) { + log_fd = -2; + bb_error_msg("bummer, can't write to log on %s!", log_console); + device = CONSOLE; + } else { + fcntl(log_fd, F_SETFD, FD_CLOEXEC); + } + } + if ((device & LOG) && (log_fd >= 0)) { + full_write(log_fd, msg, l); + } +#endif + + if (device & CONSOLE) { + int fd = device_open(CONSOLE_DEV, + O_WRONLY | O_NOCTTY | O_NONBLOCK); + /* Always send console messages to /dev/console so people will see them. */ + if (fd >= 0) { + full_write(fd, msg, l); + close(fd); +#if ENABLE_DEBUG_INIT + /* all descriptors may be closed */ + } else { + bb_error_msg("bummer, can't print: "); + va_start(arguments, fmt); + vfprintf(stderr, fmt, arguments); + va_end(arguments); +#endif + } + } + RELEASE_CONFIG_BUFFER(msg); +} + +/* Set terminal settings to reasonable defaults */ +static void set_term(void) +{ + struct termios tty; + + tcgetattr(STDIN_FILENO, &tty); + + /* set control chars */ + tty.c_cc[VINTR] = 3; /* C-c */ + tty.c_cc[VQUIT] = 28; /* C-\ */ + tty.c_cc[VERASE] = 127; /* C-? */ + tty.c_cc[VKILL] = 21; /* C-u */ + tty.c_cc[VEOF] = 4; /* C-d */ + tty.c_cc[VSTART] = 17; /* C-q */ + tty.c_cc[VSTOP] = 19; /* C-s */ + tty.c_cc[VSUSP] = 26; /* C-z */ + + /* use line dicipline 0 */ + tty.c_line = 0; + + /* Make it be sane */ + tty.c_cflag &= CBAUD | CBAUDEX | CSIZE | CSTOPB | PARENB | PARODD; + tty.c_cflag |= CREAD | HUPCL | CLOCAL; + + + /* input modes */ + tty.c_iflag = ICRNL | IXON | IXOFF; + + /* output modes */ + tty.c_oflag = OPOST | ONLCR; + + /* local modes */ + tty.c_lflag = + ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN; + + tcsetattr(STDIN_FILENO, TCSANOW, &tty); +} + +static void console_init(void) +{ + int fd; + int tried = 0; + struct vt_stat vt; + struct serial_struct sr; + char *s; + + if ((s = getenv("CONSOLE")) != NULL || (s = getenv("console")) != NULL) { + safe_strncpy(console, s, sizeof(console)); + } else { + /* 2.2 kernels: identify the real console backend and try to use it */ + if (ioctl(0, TIOCGSERIAL, &sr) == 0) { + /* this is a serial console */ + snprintf(console, sizeof(console) - 1, SC_FORMAT, sr.line); + } else if (ioctl(0, VT_GETSTATE, &vt) == 0) { + /* this is linux virtual tty */ + snprintf(console, sizeof(console) - 1, VC_FORMAT, vt.v_active); + } else { + safe_strncpy(console, CONSOLE_DEV, sizeof(console)); + tried++; + } + } + + while ((fd = open(console, O_RDONLY | O_NONBLOCK)) < 0 && tried < 2) { + /* Can't open selected console -- try + logical system console and VT_MASTER */ + safe_strncpy(console, (tried == 0 ? CONSOLE_DEV : CURRENT_VC), + sizeof(console)); + tried++; + } + if (fd < 0) { + /* Perhaps we should panic here? */ +#ifndef CONFIG_SYSLOGD + log_console = +#endif + safe_strncpy(console, bb_dev_null, sizeof(console)); + } else { + s = getenv("TERM"); + /* check for serial console */ + if (ioctl(fd, TIOCGSERIAL, &sr) == 0) { + /* Force the TERM setting to vt102 for serial console -- + * if TERM is set to linux (the default) */ + if (s == NULL || strcmp(s, "linux") == 0) + putenv("TERM=vt102"); +#ifndef CONFIG_SYSLOGD + log_console = console; +#endif + } else { + if (s == NULL) + putenv("TERM=linux"); + } + close(fd); + } + messageD(LOG, "console=%s", console); +} + +static void fixup_argv(int argc, char **argv, char *new_argv0) +{ + int len; + + /* Fix up argv[0] to be certain we claim to be init */ + len = strlen(argv[0]); + memset(argv[0], 0, len); + safe_strncpy(argv[0], new_argv0, len + 1); + + /* Wipe argv[1]-argv[N] so they don't clutter the ps listing */ + len = 1; + while (argc > len) { + memset(argv[len], 0, strlen(argv[len])); + len++; + } +} + +/* Open the new terminal device */ +static void open_new_terminal(const char * const device, const int fail) { + struct stat sb; + + if ((device_open(device, O_RDWR)) < 0) { + if (stat(device, &sb) != 0) { + message(LOG | CONSOLE, "device '%s' does not exist.", device); + } else { + message(LOG | CONSOLE, "Bummer, can't open %s", device); + } + if (fail) + _exit(1); + /* else */ +#if !ENABLE_DEBUG_INIT + shutdown_signal(SIGUSR1); +#else + _exit(2); +#endif + } +} + +static pid_t run(const struct init_action *a) +{ + int i; + pid_t pid; + char *s, *tmpCmd, *cmd[INIT_BUFFS_SIZE], *cmdpath; + char buf[INIT_BUFFS_SIZE + 6]; /* INIT_BUFFS_SIZE+strlen("exec ")+1 */ + sigset_t nmask, omask; + static const char press_enter[] = +#ifdef CUSTOMIZED_BANNER +#include CUSTOMIZED_BANNER +#endif + "\nPlease press Enter to activate this console. "; + + /* Block sigchild while forking. */ + sigemptyset(&nmask); + sigaddset(&nmask, SIGCHLD); + sigprocmask(SIG_BLOCK, &nmask, &omask); + + if ((pid = fork()) == 0) { + + /* Clean up */ + close(0); + close(1); + close(2); + sigprocmask(SIG_SETMASK, &omask, NULL); + + /* Reset signal handlers that were set by the parent process */ + signal(SIGUSR1, SIG_DFL); + signal(SIGUSR2, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGHUP, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGCONT, SIG_DFL); + signal(SIGSTOP, SIG_DFL); + signal(SIGTSTP, SIG_DFL); + + /* Create a new session and make ourself the process + * group leader */ + setsid(); + + /* Open the new terminal device */ + open_new_terminal(a->terminal, 1); + + /* Make sure the terminal will act fairly normal for us */ + set_term(); + /* Setup stdout, stderr for the new process so + * they point to the supplied terminal */ + dup(0); + dup(0); + + /* If the init Action requires us to wait, then force the + * supplied terminal to be the controlling tty. */ + if (a->action & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN | RESTART)) { + + /* Now fork off another process to just hang around */ + if ((pid = fork()) < 0) { + message(LOG | CONSOLE, "Can't fork!"); + _exit(1); + } + + if (pid > 0) { + + /* We are the parent -- wait till the child is done */ + signal(SIGINT, SIG_IGN); + signal(SIGTSTP, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + signal(SIGCHLD, SIG_DFL); + + waitfor(NULL, pid); + /* See if stealing the controlling tty back is necessary */ + if (tcgetpgrp(0) != getpid()) + _exit(0); + + /* Use a temporary process to steal the controlling tty. */ + if ((pid = fork()) < 0) { + message(LOG | CONSOLE, "Can't fork!"); + _exit(1); + } + if (pid == 0) { + setsid(); + ioctl(0, TIOCSCTTY, 1); + _exit(0); + } + waitfor(NULL, pid); + _exit(0); + } + + /* Now fall though to actually execute things */ + } + + /* See if any special /bin/sh requiring characters are present */ + if (strpbrk(a->command, "~`!$^&*()=|\\{}[];\"'<>?") != NULL) { + cmd[0] = (char *)DEFAULT_SHELL; + cmd[1] = "-c"; + cmd[2] = strcat(strcpy(buf, "exec "), a->command); + cmd[3] = NULL; + } else { + /* Convert command (char*) into cmd (char**, one word per string) */ + strcpy(buf, a->command); + s = buf; + for (tmpCmd = buf, i = 0; (tmpCmd = strsep(&s, " \t")) != NULL;) { + if (*tmpCmd != '\0') { + cmd[i] = tmpCmd; + i++; + } + } + cmd[i] = NULL; + } + + cmdpath = cmd[0]; + + /* + Interactive shells want to see a dash in argv[0]. This + typically is handled by login, argv will be setup this + way if a dash appears at the front of the command path + (like "-/bin/sh"). + */ + + if (*cmdpath == '-') { + + /* skip over the dash */ + ++cmdpath; + + /* find the last component in the command pathname */ + s = bb_get_last_path_component(cmdpath); + + /* make a new argv[0] */ + if ((cmd[0] = malloc(strlen(s) + 2)) == NULL) { + message(LOG | CONSOLE, bb_msg_memory_exhausted); + cmd[0] = cmdpath; + } else { + cmd[0][0] = '-'; + strcpy(cmd[0] + 1, s); + } +#ifdef CONFIG_FEATURE_INIT_SCTTY + /* Establish this process as session leader and + * (attempt) to make the tty (if any) a controlling tty. + */ + (void) setsid(); + (void) ioctl(0, TIOCSCTTY, 0/*don't steal it*/); +#endif + } + +#if !defined(__UCLIBC__) || defined(__ARCH_HAS_MMU__) + if (a->action & ASKFIRST) { + char c; + /* + * Save memory by not exec-ing anything large (like a shell) + * before the user wants it. This is critical if swap is not + * enabled and the system has low memory. Generally this will + * be run on the second virtual console, and the first will + * be allowed to start a shell or whatever an init script + * specifies. + */ + messageD(LOG, "Waiting for enter to start '%s'" + "(pid %d, terminal %s)\n", + cmdpath, getpid(), a->terminal); + full_write(1, press_enter, sizeof(press_enter) - 1); + while(read(0, &c, 1) == 1 && c != '\n') + ; + } +#endif + + /* Log the process name and args */ + message(LOG, "Starting pid %d, console %s: '%s'", + getpid(), a->terminal, cmdpath); + +#if defined CONFIG_FEATURE_INIT_COREDUMPS + { + struct stat sb; + if (stat(CORE_ENABLE_FLAG_FILE, &sb) == 0) { + struct rlimit limit; + + limit.rlim_cur = RLIM_INFINITY; + limit.rlim_max = RLIM_INFINITY; + setrlimit(RLIMIT_CORE, &limit); + } + } +#endif + + /* Now run it. The new program will take over this PID, + * so nothing further in init.c should be run. */ + execv(cmdpath, cmd); + + /* We're still here? Some error happened. */ + message(LOG | CONSOLE, "Bummer, cannot run '%s': %m", cmdpath); + _exit(-1); + } + sigprocmask(SIG_SETMASK, &omask, NULL); + return pid; +} + +static int waitfor(const struct init_action *a, pid_t pid) +{ + int runpid; + int status, wpid; + + runpid = (NULL == a)? pid : run(a); + while (1) { + wpid = waitpid(runpid,&status,0); + if (wpid == runpid) + break; + if (wpid == -1 && errno == ECHILD) { + /* we missed its termination */ + break; + } + /* FIXME other errors should maybe trigger an error, but allow + * the program to continue */ + } + return wpid; +} + +/* Run all commands of a particular type */ +static void run_actions(int action) +{ + struct init_action *a, *tmp; + + for (a = init_action_list; a; a = tmp) { + tmp = a->next; + if (a->action == action) { + if (access(a->terminal, R_OK | W_OK)) { + delete_init_action(a); + } else if (a->action & (SYSINIT | WAIT | CTRLALTDEL | SHUTDOWN | RESTART)) { + waitfor(a, 0); + delete_init_action(a); + } else if (a->action & ONCE) { + run(a); + delete_init_action(a); + } else if (a->action & (RESPAWN | ASKFIRST)) { + /* Only run stuff with pid==0. If they have + * a pid, that means it is still running */ + if (a->pid == 0) { + a->pid = run(a); + } + } + } + } +} + +#if !ENABLE_DEBUG_INIT +static void init_reboot(unsigned long magic) +{ + pid_t pid; + /* We have to fork here, since the kernel calls do_exit(0) in + * linux/kernel/sys.c, which can cause the machine to panic when + * the init process is killed.... */ + if ((pid = fork()) == 0) { + reboot(magic); + _exit(0); + } + waitpid (pid, NULL, 0); +} + +static void shutdown_system(void) +{ + sigset_t block_signals; + + /* run everything to be run at "shutdown". This is done _prior_ + * to killing everything, in case people wish to use scripts to + * shut things down gracefully... */ + run_actions(SHUTDOWN); + + /* first disable all our signals */ + sigemptyset(&block_signals); + sigaddset(&block_signals, SIGHUP); + sigaddset(&block_signals, SIGQUIT); + sigaddset(&block_signals, SIGCHLD); + sigaddset(&block_signals, SIGUSR1); + sigaddset(&block_signals, SIGUSR2); + sigaddset(&block_signals, SIGINT); + sigaddset(&block_signals, SIGTERM); + sigaddset(&block_signals, SIGCONT); + sigaddset(&block_signals, SIGSTOP); + sigaddset(&block_signals, SIGTSTP); + sigprocmask(SIG_BLOCK, &block_signals, NULL); + + /* Allow Ctrl-Alt-Del to reboot system. */ + init_reboot(RB_ENABLE_CAD); + + message(CONSOLE | LOG, "The system is going down NOW !!"); + sync(); + + /* Send signals to every process _except_ pid 1 */ + message(CONSOLE | LOG, init_sending_format, "TERM"); + kill(-1, SIGTERM); + sleep(1); + sync(); + + message(CONSOLE | LOG, init_sending_format, "KILL"); + kill(-1, SIGKILL); + sleep(1); + + sync(); +} + +static void exec_signal(int sig ATTRIBUTE_UNUSED) +{ + struct init_action *a, *tmp; + sigset_t unblock_signals; + + for (a = init_action_list; a; a = tmp) { + tmp = a->next; + if (a->action & RESTART) { + shutdown_system(); + + /* unblock all signals, blocked in shutdown_system() */ + sigemptyset(&unblock_signals); + sigaddset(&unblock_signals, SIGHUP); + sigaddset(&unblock_signals, SIGQUIT); + sigaddset(&unblock_signals, SIGCHLD); + sigaddset(&unblock_signals, SIGUSR1); + sigaddset(&unblock_signals, SIGUSR2); + sigaddset(&unblock_signals, SIGINT); + sigaddset(&unblock_signals, SIGTERM); + sigaddset(&unblock_signals, SIGCONT); + sigaddset(&unblock_signals, SIGSTOP); + sigaddset(&unblock_signals, SIGTSTP); + sigprocmask(SIG_UNBLOCK, &unblock_signals, NULL); + + /* Close whatever files are open. */ + close(0); + close(1); + close(2); + + /* Open the new terminal device */ + open_new_terminal(a->terminal, 0); + + /* Make sure the terminal will act fairly normal for us */ + set_term(); + /* Setup stdout, stderr on the supplied terminal */ + dup(0); + dup(0); + + messageD(CONSOLE | LOG, "Trying to re-exec %s", a->command); + execl(a->command, a->command, NULL); + + message(CONSOLE | LOG, "exec of '%s' failed: %m", + a->command); + sync(); + sleep(2); + init_reboot(RB_HALT_SYSTEM); + loop_forever(); + } + } +} + +static void shutdown_signal(int sig) +{ + char *m; + int rb; + + shutdown_system(); + + if (sig == SIGTERM) { + m = "reboot"; + rb = RB_AUTOBOOT; + } else if (sig == SIGUSR2) { + m = "poweroff"; + rb = RB_POWER_OFF; + } else { + m = "halt"; + rb = RB_HALT_SYSTEM; + } + message(CONSOLE | LOG, "Requesting system %s.", m); + sync(); + + /* allow time for last message to reach serial console */ + sleep(2); + + init_reboot(rb); + + loop_forever(); +} + +static void ctrlaltdel_signal(int sig ATTRIBUTE_UNUSED) +{ + run_actions(CTRLALTDEL); +} + +/* The SIGSTOP & SIGTSTP handler */ +static void stop_handler(int sig ATTRIBUTE_UNUSED) +{ + int saved_errno = errno; + + got_cont = 0; + while (!got_cont) + pause(); + got_cont = 0; + errno = saved_errno; +} + +/* The SIGCONT handler */ +static void cont_handler(int sig ATTRIBUTE_UNUSED) +{ + got_cont = 1; +} + +#endif /* ! ENABLE_DEBUG_INIT */ + +static void new_init_action(int action, const char *command, const char *cons) +{ + struct init_action *new_action, *a, *last; + + if (*cons == '\0') + cons = console; + + if (strcmp(cons, bb_dev_null) == 0 && (action & ASKFIRST)) + return; + + new_action = calloc((size_t) (1), sizeof(struct init_action)); + if (!new_action) { + message(LOG | CONSOLE, "Memory allocation failure"); + loop_forever(); + } + + /* Append to the end of the list */ + for (a = last = init_action_list; a; a = a->next) { + /* don't enter action if it's already in the list, + * but do overwrite existing actions */ + if ((strcmp(a->command, command) == 0) && + (strcmp(a->terminal, cons) ==0)) { + a->action = action; + free(new_action); + return; + } + last = a; + } + if (last) { + last->next = new_action; + } else { + init_action_list = new_action; + } + strcpy(new_action->command, command); + new_action->action = action; + strcpy(new_action->terminal, cons); + messageD(LOG|CONSOLE, "command='%s' action='%d' terminal='%s'\n", + new_action->command, new_action->action, new_action->terminal); +} + +static void delete_init_action(struct init_action *action) +{ + struct init_action *a, *b = NULL; + + for (a = init_action_list; a; b = a, a = a->next) { + if (a == action) { + if (b == NULL) { + init_action_list = a->next; + } else { + b->next = a->next; + } + free(a); + break; + } + } +} + +/* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined, + * then parse_inittab() simply adds in some default + * actions(i.e., runs INIT_SCRIPT and then starts a pair + * of "askfirst" shells). If CONFIG_FEATURE_USE_INITTAB + * _is_ defined, but /etc/inittab is missing, this + * results in the same set of default behaviors. + */ +static void parse_inittab(void) +{ +#ifdef CONFIG_FEATURE_USE_INITTAB + FILE *file; + char buf[INIT_BUFFS_SIZE], lineAsRead[INIT_BUFFS_SIZE]; + char tmpConsole[CONSOLE_BUFF_SIZE]; + char *id, *runlev, *action, *command, *eol; + const struct init_action_type *a = actions; + + + file = fopen(INITTAB, "r"); + if (file == NULL) { + /* No inittab file -- set up some default behavior */ +#endif + /* Reboot on Ctrl-Alt-Del */ + new_init_action(CTRLALTDEL, "/sbin/reboot", ""); + /* Umount all filesystems on halt/reboot */ + new_init_action(SHUTDOWN, "/bin/umount -a -r", ""); + /* Swapoff on halt/reboot */ + if(ENABLE_SWAPONOFF) new_init_action(SHUTDOWN, "/sbin/swapoff -a", ""); + /* Prepare to restart init when a HUP is received */ + new_init_action(RESTART, "/sbin/init", ""); + /* Askfirst shell on tty1-4 */ + new_init_action(ASKFIRST, bb_default_login_shell, ""); + new_init_action(ASKFIRST, bb_default_login_shell, VC_2); + new_init_action(ASKFIRST, bb_default_login_shell, VC_3); + new_init_action(ASKFIRST, bb_default_login_shell, VC_4); + /* sysinit */ + new_init_action(SYSINIT, INIT_SCRIPT, ""); + + return; +#ifdef CONFIG_FEATURE_USE_INITTAB + } + + while (fgets(buf, INIT_BUFFS_SIZE, file) != NULL) { + /* Skip leading spaces */ + for (id = buf; *id == ' ' || *id == '\t'; id++); + + /* Skip the line if it's a comment */ + if (*id == '#' || *id == '\n') + continue; + + /* Trim the trailing \n */ + eol = strrchr(id, '\n'); + if (eol != NULL) + *eol = '\0'; + + /* Keep a copy around for posterity's sake (and error msgs) */ + strcpy(lineAsRead, buf); + + /* Separate the ID field from the runlevels */ + runlev = strchr(id, ':'); + if (runlev == NULL || *(runlev + 1) == '\0') { + message(LOG | CONSOLE, "Bad inittab entry: %s", lineAsRead); + continue; + } else { + *runlev = '\0'; + ++runlev; + } + + /* Separate the runlevels from the action */ + action = strchr(runlev, ':'); + if (action == NULL || *(action + 1) == '\0') { + message(LOG | CONSOLE, "Bad inittab entry: %s", lineAsRead); + continue; + } else { + *action = '\0'; + ++action; + } + + /* Separate the action from the command */ + command = strchr(action, ':'); + if (command == NULL || *(command + 1) == '\0') { + message(LOG | CONSOLE, "Bad inittab entry: %s", lineAsRead); + continue; + } else { + *command = '\0'; + ++command; + } + + /* Ok, now process it */ + for (a = actions; a->name != 0; a++) { + if (strcmp(a->name, action) == 0) { + if (*id != '\0') { + if(strncmp(id, "/dev/", 5) == 0) + id += 5; + strcpy(tmpConsole, "/dev/"); + safe_strncpy(tmpConsole + 5, id, + CONSOLE_BUFF_SIZE - 5); + id = tmpConsole; + } + new_init_action(a->action, command, id); + break; + } + } + if (a->name == 0) { + /* Choke on an unknown action */ + message(LOG | CONSOLE, "Bad inittab entry: %s", lineAsRead); + } + } + fclose(file); + return; +#endif /* CONFIG_FEATURE_USE_INITTAB */ +} + +#ifdef CONFIG_FEATURE_USE_INITTAB +static void reload_signal(int sig ATTRIBUTE_UNUSED) +{ + struct init_action *a, *tmp; + + message(LOG, "Reloading /etc/inittab"); + + /* disable old entrys */ + for (a = init_action_list; a; a = a->next ) { + a->action = ONCE; + } + + parse_inittab(); + + /* remove unused entrys */ + for (a = init_action_list; a; a = tmp) { + tmp = a->next; + if (a->action & (ONCE | SYSINIT | WAIT ) && + a->pid == 0 ) { + delete_init_action(a); + } + } + run_actions(RESPAWN); + return; +} +#endif /* CONFIG_FEATURE_USE_INITTAB */ + +int init_main(int argc, char **argv) +{ + struct init_action *a; + pid_t wpid; + + if (argc > 1 && !strcmp(argv[1], "-q")) { + return kill(1,SIGHUP); + } +#if !ENABLE_DEBUG_INIT + /* Expect to be invoked as init with PID=1 or be invoked as linuxrc */ + if (getpid() != 1 && + (!ENABLE_FEATURE_INITRD || !strstr(applet_name, "linuxrc"))) + { + bb_show_usage(); + } + /* Set up sig handlers -- be sure to + * clear all of these in run() */ + signal(SIGHUP, exec_signal); + signal(SIGQUIT, exec_signal); + signal(SIGUSR1, shutdown_signal); + signal(SIGUSR2, shutdown_signal); + signal(SIGINT, ctrlaltdel_signal); + signal(SIGTERM, shutdown_signal); + signal(SIGCONT, cont_handler); + signal(SIGSTOP, stop_handler); + signal(SIGTSTP, stop_handler); + + /* Turn off rebooting via CTL-ALT-DEL -- we get a + * SIGINT on CAD so we can shut things down gracefully... */ + init_reboot(RB_DISABLE_CAD); +#endif + + /* Figure out where the default console should be */ + console_init(); + + /* Close whatever files are open, and reset the console. */ + close(0); + close(1); + close(2); + + if (device_open(console, O_RDWR | O_NOCTTY) == 0) { + set_term(); + close(0); + } + + chdir("/"); + setsid(); + { + const char * const *e; + /* Make sure environs is set to something sane */ + for(e = environment; *e; e++) + putenv((char *) *e); + } + + if (argc > 1) setenv("RUNLEVEL", argv[1], 1); + + /* Hello world */ + message(MAYBE_CONSOLE | LOG, "init started: %s", bb_msg_full_version); + + /* Make sure there is enough memory to do something useful. */ + if (ENABLE_SWAPONOFF) { + struct sysinfo info; + + if (!sysinfo(&info) && + (info.mem_unit ? : 1) * (long long)info.totalram < 1024*1024) + { + message(CONSOLE,"Low memory: forcing swapon."); + /* swapon -a requires /proc typically */ + new_init_action(SYSINIT, "/bin/mount -t proc proc /proc", ""); + /* Try to turn on swap */ + new_init_action(SYSINIT, "/sbin/swapon -a", ""); + run_actions(SYSINIT); /* wait and removing */ + } + } + + /* Check if we are supposed to be in single user mode */ + if (argc > 1 && (!strcmp(argv[1], "single") || + !strcmp(argv[1], "-s") || !strcmp(argv[1], "1"))) { + /* Start a shell on console */ + new_init_action(RESPAWN, bb_default_login_shell, ""); + } else { + /* Not in single user mode -- see what inittab says */ + + /* NOTE that if CONFIG_FEATURE_USE_INITTAB is NOT defined, + * then parse_inittab() simply adds in some default + * actions(i.e., runs INIT_SCRIPT and then starts a pair + * of "askfirst" shells */ + parse_inittab(); + } + +#ifdef CONFIG_SELINUX + if (getenv("SELINUX_INIT") == NULL) { + int enforce = 0; + + putenv("SELINUX_INIT=YES"); + if (selinux_init_load_policy(&enforce) == 0) { + execv(argv[0], argv); + } else if (enforce > 0) { + /* SELinux in enforcing mode but load_policy failed */ + /* At this point, we probably can't open /dev/console, so log() won't work */ + message(CONSOLE,"Unable to load SELinux Policy. Machine is in enforcing mode. Halting now."); + exit(1); + } + } +#endif /* CONFIG_SELINUX */ + + /* Make the command line just say "init" -- thats all, nothing else */ + fixup_argv(argc, argv, "init"); + + /* Now run everything that needs to be run */ + + /* First run the sysinit command */ + run_actions(SYSINIT); + + /* Next run anything that wants to block */ + run_actions(WAIT); + + /* Next run anything to be run only once */ + run_actions(ONCE); + +#ifdef CONFIG_FEATURE_USE_INITTAB + /* Redefine SIGHUP to reread /etc/inittab */ + signal(SIGHUP, reload_signal); +#else + signal(SIGHUP, SIG_IGN); +#endif /* CONFIG_FEATURE_USE_INITTAB */ + + + /* Now run the looping stuff for the rest of forever */ + while (1) { + /* run the respawn stuff */ + run_actions(RESPAWN); + + /* run the askfirst stuff */ + run_actions(ASKFIRST); + + /* Don't consume all CPU time -- sleep a bit */ + sleep(1); + + /* Wait for a child process to exit */ + wpid = wait(NULL); + while (wpid > 0) { + /* Find out who died and clean up their corpse */ + for (a = init_action_list; a; a = a->next) { + if (a->pid == wpid) { + /* Set the pid to 0 so that the process gets + * restarted by run_actions() */ + a->pid = 0; + message(LOG, "Process '%s' (pid %d) exited. " + "Scheduling it for restart.", + a->command, wpid); + } + } + /* see if anyone else is waiting to be reaped */ + wpid = waitpid(-1, NULL, WNOHANG); + } + } +} diff --git a/init/init_shared.c b/init/init_shared.c new file mode 100644 index 000000000..47480fc21 --- /dev/null +++ b/init/init_shared.c @@ -0,0 +1,63 @@ +/* vi: set sw=4 ts=4: */ +/* + * Stuff shared between init, reboot, halt, and poweroff + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include +#include +#include "init_shared.h" + +const char * const init_sending_format = "Sending SIG%s to all processes."; +#ifndef CONFIG_INIT +const char * const bb_shutdown_format = "\r%s\n"; +int bb_shutdown_system(unsigned long magic) +{ + int pri = LOG_KERN|LOG_NOTICE|LOG_FACMASK; + const char *message; + + /* Don't kill ourself */ + signal(SIGTERM,SIG_IGN); + signal(SIGHUP,SIG_IGN); + bb_setpgrp; + + /* Allow Ctrl-Alt-Del to reboot system. */ +#ifndef RB_ENABLE_CAD +#define RB_ENABLE_CAD 0x89abcdef +#endif + reboot(RB_ENABLE_CAD); + + openlog(applet_name, 0, pri); + + message = "\nThe system is going down NOW !!"; + syslog(pri, "%s", message); + printf(bb_shutdown_format, message); + + sync(); + + /* Send signals to every process _except_ pid 1 */ + message = "TERM"; + syslog(pri, init_sending_format, message); + printf(bb_shutdown_format, message); + + kill(-1, SIGTERM); + sleep(1); + sync(); + + message = "KILL"; + syslog(pri, init_sending_format, message); + printf(bb_shutdown_format, message); + + kill(-1, SIGKILL); + sleep(1); + + sync(); + + reboot(magic); + return 0; /* Shrug */ +} +#endif diff --git a/init/init_shared.h b/init/init_shared.h new file mode 100644 index 000000000..6df8de484 --- /dev/null +++ b/init/init_shared.h @@ -0,0 +1,10 @@ +/* vi: set sw=4 ts=4: */ +/* + * Helper functions shared by init et al. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ +extern int kill_init(int sig); +extern int bb_shutdown_system(unsigned long magic); +extern const char * const init_sending_format; + diff --git a/init/mesg.c b/init/mesg.c new file mode 100644 index 000000000..7e47644c3 --- /dev/null +++ b/init/mesg.c @@ -0,0 +1,47 @@ +/* vi: set sw=4 ts=4: */ +/* + * mesg implementation for busybox + * + * Copyright (c) 2002 Manuel Novoa III + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include +#include + +#ifdef USE_TTY_GROUP +#define S_IWGRP_OR_S_IWOTH S_IWGRP +#else +#define S_IWGRP_OR_S_IWOTH (S_IWGRP | S_IWOTH) +#endif + +int mesg_main(int argc, char *argv[]) +{ + struct stat sb; + char *tty; + char c = 0; + + if ((--argc == 0) + || ((argc == 1) && (((c = **++argv) == 'y') || (c == 'n')))) { + if ((tty = ttyname(STDERR_FILENO)) == NULL) { + tty = "ttyname"; + } else if (stat(tty, &sb) == 0) { + if (argc == 0) { + puts(((sb.st_mode & (S_IWGRP | S_IWOTH)) == + 0) ? "is n" : "is y"); + return EXIT_SUCCESS; + } + if (chmod + (tty, + (c == + 'y') ? sb.st_mode | (S_IWGRP_OR_S_IWOTH) : sb. + st_mode & ~(S_IWGRP | S_IWOTH)) == 0) { + return EXIT_SUCCESS; + } + } + bb_perror_msg_and_die("%s", tty); + } + bb_show_usage(); +} diff --git a/libbb/Config.in b/libbb/Config.in new file mode 100644 index 000000000..92ee55cbc --- /dev/null +++ b/libbb/Config.in @@ -0,0 +1,29 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Busybox Library Tuning" + +config PASSWORD_MINLEN + int "Minimum password length" + default 6 + range 5 32 + help + Minimum allowable password length. + +config MD5_SIZE_VS_SPEED + int " MD5: Trade Bytes for Speed" + default 2 + range 0 3 + help + Trade binary size versus speed for the md5sum algorithm. + Approximate values running uClibc and hashing + linux-2.4.4.tar.bz2 were: + user times (sec) text size (386) + 0 (fastest) 1.1 6144 + 1 1.4 5392 + 2 3.0 5088 + 3 (smallest) 5.1 4912 + +endmenu diff --git a/libbb/Kbuild b/libbb/Kbuild new file mode 100644 index 000000000..c15615302 --- /dev/null +++ b/libbb/Kbuild @@ -0,0 +1,117 @@ +# 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-y += ask_confirmation.o +lib-y += bb_askpass.o +lib-y += bb_do_delay.o +lib-y += bb_pwd.o +lib-y += bb_strtonum.o +lib-y += change_identity.o +lib-y += chomp.o +lib-y += compare_string_array.o +lib-y += concat_path_file.o +lib-y += concat_subpath_file.o +lib-y += copy_file.o +lib-y += copyfd.o +lib-y += crc32.o +lib-y += create_icmp6_socket.o +lib-y += create_icmp_socket.o +lib-y += default_error_retval.o +lib-y += device_open.o +lib-y += dump.o +lib-y += error_msg.o +lib-y += error_msg_and_die.o +lib-y += execable.o +lib-y += fclose_nonstdin.o +lib-y += fflush_stdout_and_exit.o +lib-y += fgets_str.o +lib-y += find_pid_by_name.o +lib-y += find_root_device.o +lib-y += full_write.o +lib-y += get_console.o +lib-y += get_last_path_component.o +lib-y += get_line_from_file.o +lib-y += getopt32.o +lib-y += herror_msg.o +lib-y += herror_msg_and_die.o +lib-y += human_readable.o +lib-y += inet_common.o +lib-y += info_msg.o +lib-y += inode_hash.o +lib-y += isdirectory.o +lib-y += kernel_version.o +lib-y += last_char_is.o +lib-y += llist.o +lib-y += login.o +lib-y += make_directory.o +lib-y += makedev.o +lib-y += md5.o +lib-y += messages.o +lib-y += mode_string.o +lib-y += mtab_file.o +lib-y += obscure.o +lib-y += parse_mode.o +lib-y += perror_msg.o +lib-y += perror_msg_and_die.o +lib-y += perror_nomsg.o +lib-y += perror_nomsg_and_die.o +lib-y += process_escape_sequence.o +lib-y += procps.o +lib-y += read.o +lib-y += recursive_action.o +lib-y += remove_file.o +lib-y += restricted_shell.o +lib-y += run_shell.o +lib-y += safe_strncpy.o +lib-y += safe_write.o +lib-y += setup_environment.o +lib-y += sha1.o +lib-y += simplify_path.o +lib-y += skip_whitespace.o +lib-y += speed_table.o +lib-y += trim.o +lib-y += u_signal_names.o +lib-y += uuencode.o +lib-y += vdprintf.o +lib-y += verror_msg.o +lib-y += vfork_daemon_rexec.o +lib-y += vherror_msg.o +lib-y += vinfo_msg.o +lib-y += vperror_msg.o +lib-y += warn_ignoring_args.o +lib-y += wfopen.o +lib-y += wfopen_input.o +lib-y += xatonum.o +lib-y += xconnect.o +lib-y += xfuncs.o +lib-y += xgetcwd.o +lib-y += xgethostbyname.o +lib-y += xgethostbyname2.o +lib-y += xreadlink.o + +# conditionally compiled objects: +lib-$(CONFIG_FEATURE_MOUNT_LOOP) += loop.o +lib-$(CONFIG_LOSETUP) += loop.o +lib-$(CONFIG_FEATURE_MTAB_SUPPORT) += mtab.o +lib-$(CONFIG_PASSWD) += pw_encrypt.o +lib-$(CONFIG_SULOGIN) += pw_encrypt.o +lib-$(CONFIG_FEATURE_HTTPD_AUTH_MD5) += pw_encrypt.o +lib-$(CONFIG_VLOCK) += correct_password.o +lib-$(CONFIG_SU) += correct_password.o +lib-$(CONFIG_LOGIN) += correct_password.o +lib-$(CONFIG_DF) += find_mount_point.o +lib-$(CONFIG_EJECT) += find_mount_point.o + +# We shouldn't build xregcomp.c if we don't need it - this ensures we don't +# require regex.h to be in the include dir even if we don't need it thereby +# allowing us to build busybox even if uclibc regex support is disabled. + +lib-$(CONFIG_AWK) += xregcomp.o +lib-$(CONFIG_SED) += xregcomp.o +lib-$(CONFIG_LESS) += xregcomp.o +lib-$(CONFIG_DEVFSD) += xregcomp.o diff --git a/libbb/README b/libbb/README new file mode 100644 index 000000000..4f28f7e34 --- /dev/null +++ b/libbb/README @@ -0,0 +1,11 @@ +Please see the LICENSE file for copyright information (GPLv2) + +libbb is BusyBox's utility library. All of this stuff used to be stuffed into +a single file named utility.c. When I split utility.c to create libbb, some of +the very oldest stuff ended up without their original copyright and licensing +information (which is now lost in the mists of time). If you see something +that you wrote that is mis-attributed, do let me know so we can fix that up. + + Erik Andersen + + diff --git a/libbb/ask_confirmation.c b/libbb/ask_confirmation.c new file mode 100644 index 000000000..4642fa036 --- /dev/null +++ b/libbb/ask_confirmation.c @@ -0,0 +1,36 @@ +/* vi: set sw=4 ts=4: */ +/* + * bb_ask_confirmation implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* Read a line from stdin. If the first non-whitespace char is 'y' or 'Y', + * return 1. Otherwise return 0. + */ + +#include +#include +#include "libbb.h" + +int bb_ask_confirmation(void) +{ + int retval = 0; + int first = 1; + int c; + + while (((c = getchar()) != EOF) && (c != '\n')) { + /* Make sure we get the actual function call for isspace, + * as speed is not critical here. */ + if (first && !(isspace)(c)) { + --first; + if ((c == 'y') || (c == 'Y')) { + ++retval; + } + } + } + + return retval; +} diff --git a/libbb/bb_askpass.c b/libbb/bb_askpass.c new file mode 100644 index 000000000..097a0a290 --- /dev/null +++ b/libbb/bb_askpass.c @@ -0,0 +1,75 @@ +/* vi: set sw=4 ts=4: */ +/* + * Ask for a password + * I use a static buffer in this function. Plan accordingly. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "libbb.h" + +/* do nothing signal handler */ +static void askpass_timeout(int ATTRIBUTE_UNUSED ignore) +{ +} + +char *bb_askpass(int timeout, const char * prompt) +{ + static char passwd[64]; + + char *ret; + int i; + struct sigaction sa; + struct termios old, new; + + tcgetattr(STDIN_FILENO, &old); + tcflush(STDIN_FILENO, TCIFLUSH); + + memset(passwd, 0, sizeof(passwd)); + + fputs(prompt, stdout); + fflush(stdout); + + tcgetattr(STDIN_FILENO, &new); + new.c_iflag &= ~(IUCLC|IXON|IXOFF|IXANY); + new.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|TOSTOP); + tcsetattr(STDIN_FILENO, TCSANOW, &new); + + if (timeout) { + sa.sa_flags = 0; + sa.sa_handler = askpass_timeout; + sigaction(SIGALRM, &sa, NULL); + alarm(timeout); + } + + ret = NULL; + if (read(STDIN_FILENO, passwd, sizeof(passwd)-1) > 0) { + ret = passwd; + i = 0; + /* Last byte is guaranteed to be 0 + (read did not overwrite it) */ + do { + if (passwd[i] == '\r' || passwd[i] == '\n') + passwd[i] = '\0'; + } while (passwd[i++]); + } + + if (timeout) { + alarm(0); + } + + tcsetattr(STDIN_FILENO, TCSANOW, &old); + puts(""); + fflush(stdout); + return ret; +} diff --git a/libbb/bb_do_delay.c b/libbb/bb_do_delay.c new file mode 100644 index 000000000..e14b67a19 --- /dev/null +++ b/libbb/bb_do_delay.c @@ -0,0 +1,24 @@ +/* vi: set sw=4 ts=4: */ +/* + * Busybox utility routines. + * + * Copyright (C) 2005 by Tito Ragusa + * + * Licensed under the GPL v2, see the file LICENSE in this tarball. + */ + +#include +#include +#include "libbb.h" + +void bb_do_delay(int seconds) +{ + time_t start, now; + + time(&start); + now = start; + while (difftime(now, start) < seconds) { + sleep(seconds); + time(&now); + } +} diff --git a/libbb/bb_pwd.c b/libbb/bb_pwd.c new file mode 100644 index 000000000..b5125b0f4 --- /dev/null +++ b/libbb/bb_pwd.c @@ -0,0 +1,130 @@ +/* vi: set sw=4 ts=4: */ +/* + * password utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include +#include +#include +#include "libbb.h" + + /* + * if bufsize is > 0 char *buffer cannot be set to NULL. + * If idname is not NULL it is written on the static + * allocated buffer (and a pointer to it is returned). + * if idname is NULL, id as string is written to the static + * allocated buffer and NULL is returned. + * if bufsize is = 0 char *buffer can be set to NULL. + * If idname exists a pointer to it is returned, + * else NULL is returned. + * if bufsize is < 0 char *buffer can be set to NULL. + * If idname exists a pointer to it is returned, + * else an error message is printed and the program exits. + */ + +/* internal function for bb_getpwuid and bb_getgrgid */ +static char * bb_getug(char *buffer, char *idname, long id, int bufsize, char prefix) +{ + if (bufsize > 0 ) { + assert(buffer!=NULL); + if(idname) { + return safe_strncpy(buffer, idname, bufsize); + } + snprintf(buffer, bufsize, "%ld", id); + } else if (bufsize < 0 && !idname) { + bb_error_msg_and_die("unknown %cid %ld", prefix, id); + } + return idname; +} + + /* Hacked by Tito Ragusa (c) 2004 to make it more + * flexible : + * + * if bufsize is > 0 char *group cannot be set to NULL. + * On success groupname is written on static allocated buffer + * group (and a pointer to it is returned). + * On failure gid as string is written to static allocated + * buffer group and NULL is returned. + * if bufsize is = 0 char *group can be set to NULL. + * On success groupname is returned. + * On failure NULL is returned. + * if bufsize is < 0 char *group can be set to NULL. + * On success groupname is returned. + * On failure an error message is printed and + * the program exits. + */ + +/* gets a groupname given a gid */ +char * bb_getgrgid(char *group, long gid, int bufsize) +{ + struct group *mygroup = getgrgid(gid); + + return bb_getug(group, (mygroup) ? + mygroup->gr_name : (char *)mygroup, gid, bufsize, 'g'); +} + +/* returns a gid given a group name */ +long bb_xgetgrnam(const char *name) +{ + struct group *mygroup; + + mygroup = getgrnam(name); + if (mygroup==NULL) + bb_error_msg_and_die("unknown group name: %s", name); + + return mygroup->gr_gid; +} + +/* returns a uid given a username */ +long bb_xgetpwnam(const char *name) +{ + struct passwd *myuser; + + myuser = getpwnam(name); + if (myuser==NULL) + bb_error_msg_and_die("unknown user name: %s", name); + + return myuser->pw_uid; +} + + /* Hacked by Tito Ragusa (c) 2004 to make it more + * flexible : + * + * if bufsize is > 0 char *name cannot be set to NULL. + * On success username is written on the static allocated + * buffer name (and a pointer to it is returned). + * On failure uid as string is written to the static + * allocated buffer name and NULL is returned. + * if bufsize is = 0 char *name can be set to NULL. + * On success username is returned. + * On failure NULL is returned. + * if bufsize is < 0 char *name can be set to NULL + * On success username is returned. + * On failure an error message is printed and + * the program exits. + */ + +/* gets a username given a uid */ +char * bb_getpwuid(char *name, long uid, int bufsize) +{ + struct passwd *myuser = getpwuid(uid); + + return bb_getug(name, myuser ? myuser->pw_name : (char *)myuser, + uid, bufsize, 'u'); +} + +unsigned long get_ug_id(const char *s, + long (*__bb_getxxnam)(const char *)) +{ + unsigned long r; + + r = bb_strtoul(s, NULL, 10); + if (errno) + r = __bb_getxxnam(s); + + return r; +} diff --git a/libbb/bb_strtonum.c b/libbb/bb_strtonum.c new file mode 100644 index 000000000..6fbd1f87d --- /dev/null +++ b/libbb/bb_strtonum.c @@ -0,0 +1,155 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +/* On exit: errno = 0 only if there was non-empty, '\0' terminated value + * errno = EINVAL if value was not '\0' terminated, but othervise ok + * Return value is still valid, caller should just check whether end[0] + * is a valid terminating char for particular case. OTOH, if caller + * requires '\0' terminated input, [s]he can just check errno == 0. + * errno = ERANGE if value had alphanumeric terminating char ("1234abcg"). + * errno = ERANGE if value is out of range, missing, etc. + * errno = ERANGE if value had minus sign for strtouXX (even "-0" is not ok ) + */ + +static unsigned long long ret_ERANGE(void) +{ + errno = ERANGE; /* this ain't as small as it looks (on glibc) */ + return ULLONG_MAX; +} + +static unsigned long long handle_errors(unsigned long long v, char **endp, char *endptr) +{ + if (endp) *endp = endptr; + + /* Check for the weird "feature": + * a "-" string is apparently a valid "number" for strto[u]l[l]! + * It returns zero and errno is 0! :( */ + if (endptr[-1] == '-') + return ret_ERANGE(); + + /* errno is already set to ERANGE by strtoXXX if value overflowed */ + if (endptr[0]) { + /* "1234abcg" or out-of-range? */ + if (isalnum(endptr[0]) || errno) + return ret_ERANGE(); + /* good number, just suspicious terminator */ + errno = EINVAL; + } + return v; +} + + +unsigned long long bb_strtoull(const char *arg, char **endp, int base) +{ + unsigned long long v; + char *endptr; + + /* strtoul(" -4200000000") returns 94967296, errno 0 (!) */ + /* I don't think that this is right. Preventing this... */ + if (!isalnum(arg[0])) return ret_ERANGE(); + + /* not 100% correct for lib func, but convenient for the caller */ + errno = 0; + v = strtoull(arg, &endptr, base); + return handle_errors(v, endp, endptr); +} + +long long bb_strtoll(const char *arg, char **endp, int base) +{ + unsigned long long v; + char *endptr; + + if (arg[0] != '-' && !isalnum(arg[0])) return ret_ERANGE(); + errno = 0; + v = strtoll(arg, &endptr, base); + return handle_errors(v, endp, endptr); +} + +#if ULONG_MAX != ULLONG_MAX +unsigned long bb_strtoul(const char *arg, char **endp, int base) +{ + unsigned long v; + char *endptr; + + if (!isalnum(arg[0])) return ret_ERANGE(); + errno = 0; + v = strtoul(arg, &endptr, base); + return handle_errors(v, endp, endptr); +} + +long bb_strtol(const char *arg, char **endp, int base) +{ + long v; + char *endptr; + + if (arg[0] != '-' && !isalnum(arg[0])) return ret_ERANGE(); + errno = 0; + v = strtol(arg, &endptr, base); + return handle_errors(v, endp, endptr); +} +#endif + +#if UINT_MAX != ULONG_MAX +unsigned bb_strtou(const char *arg, char **endp, int base) +{ + unsigned long v; + char *endptr; + + if (!isalnum(arg[0])) return ret_ERANGE(); + errno = 0; + v = strtoul(arg, &endptr, base); + if (v > UINT_MAX) return ret_ERANGE(); + return handle_errors(v, endp, endptr); +} + +int bb_strtoi(const char *arg, char **endp, int base) +{ + long v; + char *endptr; + + if (arg[0] != '-' && !isalnum(arg[0])) return ret_ERANGE(); + errno = 0; + v = strtol(arg, &endptr, base); + if (v > INT_MAX) return ret_ERANGE(); + if (v < INT_MIN) return ret_ERANGE(); + return handle_errors(v, endp, endptr); +} +#endif + +/* Floating point */ + +#if 0 + +#include /* just for HUGE_VAL */ +#define NOT_DIGIT(a) (((unsigned char)(a-'0')) > 9) +double bb_strtod(const char *arg, char **endp) +{ + double v; + char *endptr; + + if (arg[0] != '-' && NOT_DIGIT(arg[0])) goto err; + errno = 0; + v = strtod(arg, &endptr); + if (endp) *endp = endptr; + if (endptr[0]) { + /* "1234abcg" or out-of-range? */ + if (isalnum(endptr[0]) || errno) { + err: + errno = ERANGE; + return HUGE_VAL; + } + /* good number, just suspicious terminator */ + errno = EINVAL; + } + return v; +} + +#endif diff --git a/libbb/change_identity.c b/libbb/change_identity.c new file mode 100644 index 000000000..3f888f523 --- /dev/null +++ b/libbb/change_identity.c @@ -0,0 +1,51 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright 1989 - 1991, Julianne Frances Haugh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Julianne F. Haugh nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "libbb.h" + + +/* Become the user and group(s) specified by PW. */ +const char *change_identity_e2str(const struct passwd *pw) +{ + if (initgroups(pw->pw_name, pw->pw_gid) == -1) + return "cannot set groups"; + endgrent(); /* ?? */ + xsetgid(pw->pw_gid); + xsetuid(pw->pw_uid); + return NULL; +} + +void change_identity(const struct passwd *pw) +{ + const char *err_msg = change_identity_e2str(pw); + + if (err_msg) + bb_perror_msg_and_die("%s", err_msg); +} diff --git a/libbb/chomp.c b/libbb/chomp.c new file mode 100644 index 000000000..eab770760 --- /dev/null +++ b/libbb/chomp.c @@ -0,0 +1,19 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) many different people. + * If you wrote this, please acknowledge your work. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +void chomp(char *s) +{ + char *lc = last_char_is(s, '\n'); + + if (lc) + *lc = 0; +} diff --git a/libbb/compare_string_array.c b/libbb/compare_string_array.c new file mode 100644 index 000000000..d15578ca3 --- /dev/null +++ b/libbb/compare_string_array.c @@ -0,0 +1,37 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +/* returns the array index of the string */ +/* (index of first match is returned, or -1) */ +int index_in_str_array(const char * const string_array[], const char *key) +{ + int i; + + for (i = 0; string_array[i] != 0; i++) { + if (strcmp(string_array[i], key) == 0) { + return i; + } + } + return -1; +} + +/* returns the array index of the string, even if it matches only a beginning */ +/* (index of first match is returned, or -1) */ +int index_in_substr_array(const char * const string_array[], const char *key) +{ + int i; + int len = strlen(key); + if (!len) + return -1; + + for (i = 0; string_array[i] != 0; i++) { + if (strncmp(string_array[i], key, len) == 0) { + return i; + } + } + return -1; +} diff --git a/libbb/concat_path_file.c b/libbb/concat_path_file.c new file mode 100644 index 000000000..9aae601a4 --- /dev/null +++ b/libbb/concat_path_file.c @@ -0,0 +1,27 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) many different people. + * If you wrote this, please acknowledge your work. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* concatenate path and file name to new allocation buffer, + * not adding '/' if path name already has '/' +*/ + +#include "libbb.h" + +char *concat_path_file(const char *path, const char *filename) +{ + char *lc; + + if (!path) + path = ""; + lc = last_char_is(path, '/'); + while (*filename == '/') + filename++; + return xasprintf("%s%s%s", path, (lc==NULL ? "/" : ""), filename); +} diff --git a/libbb/concat_subpath_file.c b/libbb/concat_subpath_file.c new file mode 100644 index 000000000..6ebc01bf5 --- /dev/null +++ b/libbb/concat_subpath_file.c @@ -0,0 +1,23 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) (C) 2003 Vladimir Oleynik + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* + This function make special for recursive actions with usage + concat_path_file(path, filename) + and skiping "." and ".." directory entries +*/ + +#include "libbb.h" + +char *concat_subpath_file(const char *path, const char *f) +{ + if(f && *f == '.' && (!f[1] || (f[1] == '.' && !f[2]))) + return NULL; + return concat_path_file(path, f); +} diff --git a/libbb/copy_file.c b/libbb/copy_file.c new file mode 100644 index 000000000..0135831fe --- /dev/null +++ b/libbb/copy_file.c @@ -0,0 +1,282 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini copy_file implementation for busybox + * + * Copyright (C) 2001 by Matt Kraai + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + */ + +#include "libbb.h" + +static int retry_overwrite(const char *dest, int flags) +{ + if (!(flags & (FILEUTILS_FORCE|FILEUTILS_INTERACTIVE))) { + fprintf(stderr, "'%s' exists\n", dest); + return -1; + } + if (flags & FILEUTILS_INTERACTIVE) { + fprintf(stderr, "%s: overwrite '%s'? ", applet_name, dest); + if (!bb_ask_confirmation()) + return 0; // not allowed to overwrite + } + if (unlink(dest) < 0) { + bb_perror_msg("cannot remove '%s'", dest); + return -1; // error + } + return 1; // ok (to try again) +} + +int copy_file(const char *source, const char *dest, int flags) +{ + struct stat source_stat; + struct stat dest_stat; + int status = 0; + signed char dest_exists = 0; + signed char ovr; + +#define FLAGS_DEREF (flags & FILEUTILS_DEREFERENCE) + + if ((FLAGS_DEREF ? stat : lstat)(source, &source_stat) < 0) { + // This may be a dangling symlink. + // Making [sym]links to dangling symlinks works, so... + if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) + goto make_links; + bb_perror_msg("cannot stat '%s'", source); + return -1; + } + + if (lstat(dest, &dest_stat) < 0) { + if (errno != ENOENT) { + bb_perror_msg("cannot stat '%s'", dest); + return -1; + } + } else { + if (source_stat.st_dev == dest_stat.st_dev + && source_stat.st_ino == dest_stat.st_ino + ) { + bb_error_msg("'%s' and '%s' are the same file", source, dest); + return -1; + } + dest_exists = 1; + } + + if (S_ISDIR(source_stat.st_mode)) { + DIR *dp; + struct dirent *d; + mode_t saved_umask = 0; + + if (!(flags & FILEUTILS_RECUR)) { + bb_error_msg("omitting directory '%s'", source); + return -1; + } + + /* Create DEST. */ + if (dest_exists) { + if (!S_ISDIR(dest_stat.st_mode)) { + bb_error_msg("target '%s' is not a directory", dest); + return -1; + } + } else { + mode_t mode; + saved_umask = umask(0); + + mode = source_stat.st_mode; + if (!(flags & FILEUTILS_PRESERVE_STATUS)) + mode = source_stat.st_mode & ~saved_umask; + mode |= S_IRWXU; + + if (mkdir(dest, mode) < 0) { + umask(saved_umask); + bb_perror_msg("cannot create directory '%s'", dest); + return -1; + } + + umask(saved_umask); + } + + /* Recursively copy files in SOURCE. */ + dp = opendir(source); + if (dp == NULL) { + status = -1; + goto preserve_status; + } + + while ((d = readdir(dp)) != NULL) { + char *new_source, *new_dest; + + new_source = concat_subpath_file(source, d->d_name); + if (new_source == NULL) + continue; + new_dest = concat_path_file(dest, d->d_name); + if (copy_file(new_source, new_dest, flags) < 0) + status = -1; + free(new_source); + free(new_dest); + } + closedir(dp); + + if (!dest_exists + && chmod(dest, source_stat.st_mode & ~saved_umask) < 0 + ) { + bb_perror_msg("cannot change permissions of '%s'", dest); + status = -1; + } + + } else if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) { + int (*lf)(const char *oldpath, const char *newpath); + make_links: + // Hmm... maybe + // if (DEREF && MAKE_SOFTLINK) source = realpath(source) ? + // (but realpath returns NULL on dangling symlinks...) + lf = (flags & FILEUTILS_MAKE_SOFTLINK) ? symlink : link; + if (lf(source, dest) < 0) { + ovr = retry_overwrite(dest, flags); + if (ovr <= 0) + return ovr; + if (lf(source, dest) < 0) { + bb_perror_msg("cannot create link '%s'", dest); + return -1; + } + } + return 0; + + } else if (S_ISREG(source_stat.st_mode) + // Huh? DEREF uses stat, which never returns links IIRC... + || (FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) + ) { + int src_fd; + int dst_fd; + if (ENABLE_FEATURE_PRESERVE_HARDLINKS) { + char *link_name; + + if (!FLAGS_DEREF + && is_in_ino_dev_hashtable(&source_stat, &link_name) + ) { + if (link(link_name, dest) < 0) { + ovr = retry_overwrite(dest, flags); + if (ovr <= 0) + return ovr; + if (link(link_name, dest) < 0) { + bb_perror_msg("cannot create link '%s'", dest); + return -1; + } + } + return 0; + } + // TODO: probably is_in_.. and add_to_... + // can be combined: find_or_add_... + add_to_ino_dev_hashtable(&source_stat, dest); + } + + src_fd = open(source, O_RDONLY); + if (src_fd == -1) { + bb_perror_msg("cannot open '%s'", source); + return -1; + } + + // POSIX: if exists and -i, ask (w/o -i assume yes). + // Then open w/o EXCL. + // If open still fails and -f, try unlink, then try open again. + // Result: a mess: + // If dest is a softlink, we overwrite softlink's destination! + // (or fail, if it points to dir/nonexistent location/etc). + // This is strange, but POSIX-correct. + // coreutils cp has --remove-destination to override this... + dst_fd = open(dest, (flags & FILEUTILS_INTERACTIVE) + ? O_WRONLY|O_CREAT|O_TRUNC|O_EXCL + : O_WRONLY|O_CREAT|O_TRUNC, source_stat.st_mode); + if (dst_fd == -1) { + // We would not do POSIX insanity. -i asks, + // then _unlinks_ the offender. Presto. + // Or else we will end up having 3 open()s! + ovr = retry_overwrite(dest, flags); + if (ovr <= 0) { + close(src_fd); + return ovr; + } + dst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, source_stat.st_mode); + if (dst_fd == -1) { + bb_perror_msg("cannot open '%s'", dest); + close(src_fd); + return -1; + } + } + + if (bb_copyfd_eof(src_fd, dst_fd) == -1) + status = -1; + if (close(dst_fd) < 0) { + bb_perror_msg("cannot close '%s'", dest); + status = -1; + } + if (close(src_fd) < 0) { + bb_perror_msg("cannot close '%s'", source); + status = -1; + } + + } else if (S_ISBLK(source_stat.st_mode) || S_ISCHR(source_stat.st_mode) + || S_ISSOCK(source_stat.st_mode) || S_ISFIFO(source_stat.st_mode) + || S_ISLNK(source_stat.st_mode) + ) { + // We are lazy here, a bit lax with races... + if (dest_exists) { + ovr = retry_overwrite(dest, flags); + if (ovr <= 0) + return ovr; + } + if (S_ISFIFO(source_stat.st_mode)) { + if (mkfifo(dest, source_stat.st_mode) < 0) { + bb_perror_msg("cannot create fifo '%s'", dest); + return -1; + } + } else if (S_ISLNK(source_stat.st_mode)) { + char *lpath; + + lpath = xreadlink(source); + if (symlink(lpath, dest) < 0) { + bb_perror_msg("cannot create symlink '%s'", dest); + free(lpath); + return -1; + } + free(lpath); + + if (flags & FILEUTILS_PRESERVE_STATUS) + if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0) + bb_perror_msg("cannot preserve %s of '%s'", "ownership", dest); + + return 0; + + } else { + if (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0) { + bb_perror_msg("cannot create '%s'", dest); + return -1; + } + } + } else { + bb_error_msg("internal error: unrecognized file type"); + return -1; + } + + preserve_status: + + if (flags & FILEUTILS_PRESERVE_STATUS + /* Cannot happen: */ + /* && !(flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) */ + ) { + struct utimbuf times; + + times.actime = source_stat.st_atime; + times.modtime = source_stat.st_mtime; + if (utime(dest, ×) < 0) + bb_perror_msg("cannot preserve %s of '%s'", "times", dest); + if (chown(dest, source_stat.st_uid, source_stat.st_gid) < 0) { + source_stat.st_mode &= ~(S_ISUID | S_ISGID); + bb_perror_msg("cannot preserve %s of '%s'", "ownership", dest); + } + if (chmod(dest, source_stat.st_mode) < 0) + bb_perror_msg("cannot preserve %s of '%s'", "permissions", dest); + } + + return status; +} diff --git a/libbb/copyfd.c b/libbb/copyfd.c new file mode 100644 index 000000000..c6b886647 --- /dev/null +++ b/libbb/copyfd.c @@ -0,0 +1,86 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2005 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include + +#include "libbb.h" + + +#if BUFSIZ < 4096 +#undef BUFSIZ +#define BUFSIZ 4096 +#endif + + +static off_t bb_full_fd_action(int src_fd, int dst_fd, off_t size) +{ + int status = -1; + off_t total = 0; + RESERVE_CONFIG_BUFFER(buffer, BUFSIZ); + + if (src_fd < 0) goto out; + + if (!size) { + size = BUFSIZ; + status = 1; /* copy until eof */ + } + + while (1) { + ssize_t rd; + + rd = safe_read(src_fd, buffer, size > BUFSIZ ? BUFSIZ : size); + + if (!rd) { /* eof - all done. */ + status = 0; + break; + } + if (rd < 0) { + bb_perror_msg(bb_msg_read_error); + break; + } + /* dst_fd == -1 is a fake, else... */ + if (dst_fd >= 0) { + ssize_t wr = full_write(dst_fd, buffer, rd); + if (wr < rd) { + bb_perror_msg(bb_msg_write_error); + break; + } + } + total += rd; + if (status < 0) { + size -= rd; + if (!size) { + status = 0; + break; + } + } + } + +out: + RELEASE_CONFIG_BUFFER(buffer); + + return status ? -1 : total; +} + + +off_t bb_copyfd_size(int fd1, int fd2, off_t size) +{ + if (size) { + return bb_full_fd_action(fd1, fd2, size); + } + return 0; +} + +off_t bb_copyfd_eof(int fd1, int fd2) +{ + return bb_full_fd_action(fd1, fd2, 0); +} diff --git a/libbb/correct_password.c b/libbb/correct_password.c new file mode 100644 index 000000000..fd7e0b56c --- /dev/null +++ b/libbb/correct_password.c @@ -0,0 +1,65 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright 1989 - 1991, Julianne Frances Haugh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Julianne F. Haugh nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "libbb.h" + +/* Ask the user for a password. + Return 1 if the user gives the correct password for entry PW, + 0 if not. Return 1 without asking for a password if run by UID 0 + or if PW has an empty password. */ + +int correct_password(const struct passwd *pw) +{ + char *unencrypted, *encrypted, *correct; + +#ifdef CONFIG_FEATURE_SHADOWPASSWDS + if (!strcmp(pw->pw_passwd, "x") || !strcmp(pw->pw_passwd, "*")) { + struct spwd *sp = getspnam(pw->pw_name); + + if (!sp) + bb_error_msg_and_die("no valid shadow password"); + + correct = sp->sp_pwdp; + } + else +#endif + correct = pw->pw_passwd; + + if (!correct || correct[0] == '\0') + return 1; + + unencrypted = bb_askpass(0, "Password: "); + if (!unencrypted) { + return 0; + } + encrypted = crypt(unencrypted, correct); + memset(unencrypted, 0, strlen(unencrypted)); + return (!strcmp(encrypted, correct)) ? 1 : 0; +} diff --git a/libbb/crc32.c b/libbb/crc32.c new file mode 100644 index 000000000..1e4a57e8a --- /dev/null +++ b/libbb/crc32.c @@ -0,0 +1,39 @@ +/* vi: set sw=4 ts=4: */ +/* + * CRC32 table fill function + * Copyright (C) 2006 by Rob Sullivan + * (I can't really claim much credit however, as the algorithm is + * very well-known) + * + * The following function creates a CRC32 table depending on whether + * a big-endian (0x04c11db7) or little-endian (0xedb88320) CRC32 is + * required. Admittedly, there are other CRC32 polynomials floating + * around, but Busybox doesn't use them. + * + * endian = 1: big-endian + * endian = 0: little-endian + */ + +#include "libbb.h" + +uint32_t *crc32_filltable(int endian) +{ + + uint32_t *crc_table = xmalloc(256 * sizeof(uint32_t)); + uint32_t polynomial = endian ? 0x04c11db7 : 0xedb88320; + uint32_t c; + int i, j; + + for (i = 0; i < 256; i++) { + c = endian ? (i << 24) : i; + for (j = 8; j; j--) { + if (endian) + c = (c&0x80000000) ? ((c << 1) ^ polynomial) : (c << 1); + else + c = (c&1) ? ((c >> 1) ^ polynomial) : (c >> 1); + } + *crc_table++ = c; + } + + return crc_table - 256; +} diff --git a/libbb/create_icmp6_socket.c b/libbb/create_icmp6_socket.c new file mode 100644 index 000000000..c3d1b5578 --- /dev/null +++ b/libbb/create_icmp6_socket.c @@ -0,0 +1,39 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * create raw socket for icmp (IPv6 version) protocol test permission + * and drop root privileges if running setuid + * + */ + +#include +#include +#include +#include +#include +#include "libbb.h" + +#ifdef CONFIG_FEATURE_IPV6 +int create_icmp6_socket(void) +{ + struct protoent *proto; + int sock; + + proto = getprotobyname("ipv6-icmp"); + /* if getprotobyname failed, just silently force + * proto->p_proto to have the correct value for "ipv6-icmp" */ + if ((sock = socket(AF_INET6, SOCK_RAW, + (proto ? proto->p_proto : IPPROTO_ICMPV6))) < 0) { + if (errno == EPERM) + bb_error_msg_and_die(bb_msg_perm_denied_are_you_root); + else + bb_perror_msg_and_die(bb_msg_can_not_create_raw_socket); + } + + /* drop root privs if running setuid */ + xsetuid(getuid()); + + return sock; +} +#endif diff --git a/libbb/create_icmp_socket.c b/libbb/create_icmp_socket.c new file mode 100644 index 000000000..431c4d8a7 --- /dev/null +++ b/libbb/create_icmp_socket.c @@ -0,0 +1,37 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * create raw socket for icmp protocol test permission + * and drop root privileges if running setuid + * + */ + +#include +#include +#include +#include +#include +#include "libbb.h" + +int create_icmp_socket(void) +{ + struct protoent *proto; + int sock; + + proto = getprotobyname("icmp"); + /* if getprotobyname failed, just silently force + * proto->p_proto to have the correct value for "icmp" */ + if ((sock = socket(AF_INET, SOCK_RAW, + (proto ? proto->p_proto : 1))) < 0) { /* 1 == ICMP */ + if (errno == EPERM) + bb_error_msg_and_die(bb_msg_perm_denied_are_you_root); + else + bb_perror_msg_and_die(bb_msg_can_not_create_raw_socket); + } + + /* drop root privs if running setuid */ + xsetuid(getuid()); + + return sock; +} diff --git a/libbb/default_error_retval.c b/libbb/default_error_retval.c new file mode 100644 index 000000000..f4e46a4b5 --- /dev/null +++ b/libbb/default_error_retval.c @@ -0,0 +1,19 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* Seems silly to copyright a global variable. ;-) Oh well. + * + * At least one applet (cmp) returns a value different from the typical + * EXIT_FAILURE values (1) when an error occurs. So, make it configurable + * by the applet. I suppose we could use a wrapper function to set it, but + * that too seems silly. + */ + +#include +#include "libbb.h" + +int xfunc_error_retval = EXIT_FAILURE; diff --git a/libbb/device_open.c b/libbb/device_open.c new file mode 100644 index 000000000..c788b6982 --- /dev/null +++ b/libbb/device_open.c @@ -0,0 +1,33 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include "libbb.h" + + +/* try to open up the specified device */ +int device_open(const char *device, int mode) +{ + int m, f, fd = -1; + + m = mode | O_NONBLOCK; + + /* Retry up to 5 times */ + /* TODO: explain why it can't be considered insane */ + for (f = 0; f < 5; f++) + if ((fd = open(device, m, 0600)) >= 0) + break; + if (fd < 0) + return fd; + /* Reset original flags. */ + if (m != mode) + fcntl(fd, F_SETFL, mode); + return fd; +} diff --git a/libbb/dump.c b/libbb/dump.c new file mode 100644 index 000000000..10710894e --- /dev/null +++ b/libbb/dump.c @@ -0,0 +1,803 @@ +/* vi: set sw=4 ts=4: */ +/* + * Support code for the hexdump and od applets, + * based on code from util-linux v 2.11l + * + * Copyright (c) 1989 + * The Regents of the University of California. All rights reserved. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Original copyright notice is retained at the end of this file. + */ + +#include "libbb.h" +#include "dump.h" + +enum _vflag bb_dump_vflag = FIRST; +FS *bb_dump_fshead; /* head of format strings */ +static FU *endfu; +static char **_argv; +static off_t savaddress; /* saved address/offset in stream */ +static off_t eaddress; /* end address */ +static off_t address; /* address/offset in stream */ +off_t bb_dump_skip; /* bytes to skip */ +static int exitval; /* final exit value */ +int bb_dump_blocksize; /* data block size */ +int bb_dump_length = -1; /* max bytes to read */ + +static const char index_str[] = ".#-+ 0123456789"; + +static const char size_conv_str[] = +"\x1\x4\x4\x4\x4\x4\x4\x8\x8\x8\x8\010cdiouxXeEfgG"; + +static const char lcc[] = "diouxX"; + +int bb_dump_size(FS * fs) +{ + FU *fu; + int bcnt, cur_size; + char *fmt; + const char *p; + int prec; + + /* figure out the data block bb_dump_size needed for each format unit */ + for (cur_size = 0, fu = fs->nextfu; fu; fu = fu->nextfu) { + if (fu->bcnt) { + cur_size += fu->bcnt * fu->reps; + continue; + } + for (bcnt = prec = 0, fmt = fu->fmt; *fmt; ++fmt) { + if (*fmt != '%') + continue; + /* + * bb_dump_skip any special chars -- save precision in + * case it's a %s format. + */ + while (strchr(index_str + 1, *++fmt)); + if (*fmt == '.' && isdigit(*++fmt)) { + prec = atoi(fmt); + while (isdigit(*++fmt)); + } + if (!(p = strchr(size_conv_str + 12, *fmt))) { + if (*fmt == 's') { + bcnt += prec; + } else if (*fmt == '_') { + ++fmt; + if ((*fmt == 'c') || (*fmt == 'p') || (*fmt == 'u')) { + bcnt += 1; + } + } + } else { + bcnt += size_conv_str[p - (size_conv_str + 12)]; + } + } + cur_size += bcnt * fu->reps; + } + return cur_size; +} + +static void rewrite(FS * fs) +{ + enum { NOTOKAY, USEBCNT, USEPREC } sokay; + PR *pr, **nextpr = NULL; + FU *fu; + char *p1, *p2, *p3; + char savech, *fmtp; + const char *byte_count_str; + int nconv, prec = 0; + + for (fu = fs->nextfu; fu; fu = fu->nextfu) { + /* + * break each format unit into print units; each + * conversion character gets its own. + */ + for (nconv = 0, fmtp = fu->fmt; *fmtp; nextpr = &pr->nextpr) { + /* NOSTRICT */ + /* DBU:[dvae@cray.com] calloc so that forward ptrs start out NULL*/ + pr = xzalloc(sizeof(PR)); + if (!fu->nextpr) + fu->nextpr = pr; + /* ignore nextpr -- its unused inside the loop and is + * uninitialized 1st time thru. + */ + + /* bb_dump_skip preceding text and up to the next % sign */ + for (p1 = fmtp; *p1 && *p1 != '%'; ++p1); + + /* only text in the string */ + if (!*p1) { + pr->fmt = fmtp; + pr->flags = F_TEXT; + break; + } + + /* + * get precision for %s -- if have a byte count, don't + * need it. + */ + if (fu->bcnt) { + sokay = USEBCNT; + /* bb_dump_skip to conversion character */ + for (++p1; strchr(index_str, *p1); ++p1); + } else { + /* bb_dump_skip any special chars, field width */ + while (strchr(index_str + 1, *++p1)); + if (*p1 == '.' && isdigit(*++p1)) { + sokay = USEPREC; + prec = atoi(p1); + while (isdigit(*++p1)); + } else + sokay = NOTOKAY; + } + + p2 = p1 + 1; /* set end pointer */ + + /* + * figure out the byte count for each conversion; + * rewrite the format as necessary, set up blank- + * pbb_dump_adding for end of data. + */ + + if (*p1 == 'c') { + pr->flags = F_CHAR; + DO_BYTE_COUNT_1: + byte_count_str = "\001"; + DO_BYTE_COUNT: + if (fu->bcnt) { + do { + if (fu->bcnt == *byte_count_str) { + break; + } + } while (*++byte_count_str); + } + /* Unlike the original, output the remainder of the format string. */ + if (!*byte_count_str) { + bb_error_msg_and_die("bad byte count for conversion character %s", p1); + } + pr->bcnt = *byte_count_str; + } else if (*p1 == 'l') { + ++p2; + ++p1; + DO_INT_CONV: + { + const char *e; + if (!(e = strchr(lcc, *p1))) { + goto DO_BAD_CONV_CHAR; + } + pr->flags = F_INT; + if (e > lcc + 1) { + pr->flags = F_UINT; + } + byte_count_str = "\004\002\001"; + goto DO_BYTE_COUNT; + } + /* NOTREACHED */ + } else if (strchr(lcc, *p1)) { + goto DO_INT_CONV; + } else if (strchr("eEfgG", *p1)) { + pr->flags = F_DBL; + byte_count_str = "\010\004"; + goto DO_BYTE_COUNT; + } else if (*p1 == 's') { + pr->flags = F_STR; + if (sokay == USEBCNT) { + pr->bcnt = fu->bcnt; + } else if (sokay == USEPREC) { + pr->bcnt = prec; + } else { /* NOTOKAY */ + bb_error_msg_and_die("%%s requires a precision or a byte count"); + } + } else if (*p1 == '_') { + ++p2; + switch (p1[1]) { + case 'A': + endfu = fu; + fu->flags |= F_IGNORE; + /* FALLTHROUGH */ + case 'a': + pr->flags = F_ADDRESS; + ++p2; + if ((p1[2] != 'd') && (p1[2] != 'o') && (p1[2] != 'x')) { + goto DO_BAD_CONV_CHAR; + } + *p1 = p1[2]; + break; + case 'c': + pr->flags = F_C; + /* *p1 = 'c'; set in conv_c */ + goto DO_BYTE_COUNT_1; + case 'p': + pr->flags = F_P; + *p1 = 'c'; + goto DO_BYTE_COUNT_1; + case 'u': + pr->flags = F_U; + /* *p1 = 'c'; set in conv_u */ + goto DO_BYTE_COUNT_1; + default: + goto DO_BAD_CONV_CHAR; + } + } else { + DO_BAD_CONV_CHAR: + bb_error_msg_and_die("bad conversion character %%%s", p1); + } + + /* + * copy to PR format string, set conversion character + * pointer, update original. + */ + savech = *p2; + p1[1] = '\0'; + pr->fmt = xstrdup(fmtp); + *p2 = savech; + pr->cchar = pr->fmt + (p1 - fmtp); + + /* DBU:[dave@cray.com] w/o this, trailing fmt text, space is lost. + * Skip subsequent text and up to the next % sign and tack the + * additional text onto fmt: eg. if fmt is "%x is a HEX number", + * we lose the " is a HEX number" part of fmt. + */ + for (p3 = p2; *p3 && *p3 != '%'; p3++); + if (p3 > p2) + { + savech = *p3; + *p3 = '\0'; + pr->fmt = xrealloc(pr->fmt, strlen(pr->fmt)+(p3-p2)+1); + strcat(pr->fmt, p2); + *p3 = savech; + p2 = p3; + } + + fmtp = p2; + + /* only one conversion character if byte count */ + if (!(pr->flags & F_ADDRESS) && fu->bcnt && nconv++) { + bb_error_msg_and_die("byte count with multiple conversion characters"); + } + } + /* + * if format unit byte count not specified, figure it out + * so can adjust rep count later. + */ + if (!fu->bcnt) + for (pr = fu->nextpr; pr; pr = pr->nextpr) + fu->bcnt += pr->bcnt; + } + /* + * if the format string interprets any data at all, and it's + * not the same as the bb_dump_blocksize, and its last format unit + * interprets any data at all, and has no iteration count, + * repeat it as necessary. + * + * if, rep count is greater than 1, no trailing whitespace + * gets output from the last iteration of the format unit. + */ + for (fu = fs->nextfu;; fu = fu->nextfu) { + if (!fu->nextfu && fs->bcnt < bb_dump_blocksize && + !(fu->flags & F_SETREP) && fu->bcnt) + fu->reps += (bb_dump_blocksize - fs->bcnt) / fu->bcnt; + if (fu->reps > 1) { + for (pr = fu->nextpr;; pr = pr->nextpr) + if (!pr->nextpr) + break; + for (p1 = pr->fmt, p2 = NULL; *p1; ++p1) + p2 = isspace(*p1) ? p1 : NULL; + if (p2) + pr->nospace = p2; + } + if (!fu->nextfu) + break; + } +} + +static void do_skip(char *fname, int statok) +{ + struct stat sbuf; + + if (statok) { + if (fstat(STDIN_FILENO, &sbuf)) { + bb_perror_msg_and_die("%s", fname); + } + if ((!(S_ISCHR(sbuf.st_mode) || + S_ISBLK(sbuf.st_mode) || + S_ISFIFO(sbuf.st_mode))) && bb_dump_skip >= sbuf.st_size) { + /* If bb_dump_size valid and bb_dump_skip >= size */ + bb_dump_skip -= sbuf.st_size; + address += sbuf.st_size; + return; + } + } + if (fseek(stdin, bb_dump_skip, SEEK_SET)) { + bb_perror_msg_and_die("%s", fname); + } + savaddress = address += bb_dump_skip; + bb_dump_skip = 0; +} + +static int next(char **argv) +{ + static int done; + int statok; + + if (argv) { + _argv = argv; + return 1; + } + for (;;) { + if (*_argv) { + if (!(freopen(*_argv, "r", stdin))) { + bb_perror_msg("%s", *_argv); + exitval = 1; + ++_argv; + continue; + } + statok = done = 1; + } else { + if (done++) + return 0; + statok = 0; + } + if (bb_dump_skip) + do_skip(statok ? *_argv : "stdin", statok); + if (*_argv) + ++_argv; + if (!bb_dump_skip) + return 1; + } + /* NOTREACHED */ +} + +static unsigned char *get(void) +{ + static int ateof = 1; + static unsigned char *curp=NULL, *savp; /*DBU:[dave@cray.com]initialize curp */ + int n; + int need, nread; + unsigned char *tmpp; + + if (!curp) { + address = (off_t)0; /*DBU:[dave@cray.com] initialize,initialize..*/ + curp = (unsigned char *) xmalloc(bb_dump_blocksize); + savp = (unsigned char *) xmalloc(bb_dump_blocksize); + } else { + tmpp = curp; + curp = savp; + savp = tmpp; + address = savaddress += bb_dump_blocksize; + } + for (need = bb_dump_blocksize, nread = 0;;) { + /* + * if read the right number of bytes, or at EOF for one file, + * and no other files are available, zero-pad the rest of the + * block and set the end flag. + */ + if (!bb_dump_length || (ateof && !next((char **) NULL))) { + if (need == bb_dump_blocksize) { + return NULL; + } + if (bb_dump_vflag != ALL && !memcmp(curp, savp, nread)) { + if (bb_dump_vflag != DUP) { + puts("*"); + } + return NULL; + } + memset((char *) curp + nread, 0, need); + eaddress = address + nread; + return curp; + } + n = fread((char *) curp + nread, sizeof(unsigned char), + bb_dump_length == -1 ? need : MIN(bb_dump_length, need), stdin); + if (!n) { + if (ferror(stdin)) { + bb_perror_msg("%s", _argv[-1]); + } + ateof = 1; + continue; + } + ateof = 0; + if (bb_dump_length != -1) { + bb_dump_length -= n; + } + if (!(need -= n)) { + if (bb_dump_vflag == ALL || bb_dump_vflag == FIRST + || memcmp(curp, savp, bb_dump_blocksize)) { + if (bb_dump_vflag == DUP || bb_dump_vflag == FIRST) { + bb_dump_vflag = WAIT; + } + return curp; + } + if (bb_dump_vflag == WAIT) { + puts("*"); + } + bb_dump_vflag = DUP; + address = savaddress += bb_dump_blocksize; + need = bb_dump_blocksize; + nread = 0; + } else { + nread += n; + } + } +} + +static void bpad(PR * pr) +{ + char *p1, *p2; + + /* + * remove all conversion flags; '-' is the only one valid + * with %s, and it's not useful here. + */ + pr->flags = F_BPAD; + *pr->cchar = 's'; + for (p1 = pr->fmt; *p1 != '%'; ++p1); + for (p2 = ++p1; *p1 && strchr(" -0+#", *p1); ++p1) + if (pr->nospace) pr->nospace--; + while ((*p2++ = *p1++) != 0); +} + +static const char conv_str[] = + "\0\\0\0" + "\007\\a\0" /* \a */ + "\b\\b\0" + "\f\\b\0" + "\n\\n\0" + "\r\\r\0" + "\t\\t\0" + "\v\\v\0" + "\0"; + + +static void conv_c(PR * pr, unsigned char * p) +{ + const char *str = conv_str; + char buf[10]; + + do { + if (*p == *str) { + ++str; + goto strpr; + } + str += 4; + } while (*str); + + if (isprint(*p)) { + *pr->cchar = 'c'; + (void) printf(pr->fmt, *p); + } else { + sprintf(buf, "%03o", (int) *p); + str = buf; + strpr: + *pr->cchar = 's'; + printf(pr->fmt, str); + } +} + +static void conv_u(PR * pr, unsigned char * p) +{ + static const char list[] = + "nul\0soh\0stx\0etx\0eot\0enq\0ack\0bel\0" + "bs\0_ht\0_lf\0_vt\0_ff\0_cr\0_so\0_si\0_" + "dle\0dcl\0dc2\0dc3\0dc4\0nak\0syn\0etb\0" + "can\0em\0_sub\0esc\0fs\0_gs\0_rs\0_us"; + + /* od used nl, not lf */ + if (*p <= 0x1f) { + *pr->cchar = 's'; + printf(pr->fmt, list + (4 * (int)*p)); + } else if (*p == 0x7f) { + *pr->cchar = 's'; + printf(pr->fmt, "del"); + } else if (isprint(*p)) { + *pr->cchar = 'c'; + printf(pr->fmt, *p); + } else { + *pr->cchar = 'x'; + printf(pr->fmt, (int) *p); + } +} + +static void display(void) +{ +/* extern FU *endfu; */ + FS *fs; + FU *fu; + PR *pr; + int cnt; + unsigned char *bp; + + off_t saveaddress; + unsigned char savech = 0, *savebp; + + while ((bp = get()) != NULL) { + for (fs = bb_dump_fshead, savebp = bp, saveaddress = address; fs; + fs = fs->nextfs, bp = savebp, address = saveaddress) { + for (fu = fs->nextfu; fu; fu = fu->nextfu) { + if (fu->flags & F_IGNORE) { + break; + } + for (cnt = fu->reps; cnt; --cnt) { + for (pr = fu->nextpr; pr; address += pr->bcnt, + bp += pr->bcnt, pr = pr->nextpr) { + if (eaddress && address >= eaddress && + !(pr->flags & (F_TEXT | F_BPAD))) { + bpad(pr); + } + if (cnt == 1 && pr->nospace) { + savech = *pr->nospace; + *pr->nospace = '\0'; + } +/* PRINT; */ + switch (pr->flags) { + case F_ADDRESS: + printf(pr->fmt, (unsigned int) address); + break; + case F_BPAD: + printf(pr->fmt, ""); + break; + case F_C: + conv_c(pr, bp); + break; + case F_CHAR: + printf(pr->fmt, *bp); + break; + case F_DBL:{ + double dval; + float fval; + + switch (pr->bcnt) { + case 4: + memmove((char *) &fval, (char *) bp, + sizeof(fval)); + printf(pr->fmt, fval); + break; + case 8: + memmove((char *) &dval, (char *) bp, + sizeof(dval)); + printf(pr->fmt, dval); + break; + } + break; + } + case F_INT:{ + int ival; + short sval; + + switch (pr->bcnt) { + case 1: + printf(pr->fmt, (int) *bp); + break; + case 2: + memmove((char *) &sval, (char *) bp, + sizeof(sval)); + printf(pr->fmt, (int) sval); + break; + case 4: + memmove((char *) &ival, (char *) bp, + sizeof(ival)); + printf(pr->fmt, ival); + break; + } + break; + } + case F_P: + printf(pr->fmt, isprint(*bp) ? *bp : '.'); + break; + case F_STR: + printf(pr->fmt, (char *) bp); + break; + case F_TEXT: + printf(pr->fmt); + break; + case F_U: + conv_u(pr, bp); + break; + case F_UINT:{ + unsigned int ival; + unsigned short sval; + + switch (pr->bcnt) { + case 1: + printf(pr->fmt, (unsigned int) * bp); + break; + case 2: + memmove((char *) &sval, (char *) bp, + sizeof(sval)); + printf(pr->fmt, (unsigned int) sval); + break; + case 4: + memmove((char *) &ival, (char *) bp, + sizeof(ival)); + printf(pr->fmt, ival); + break; + } + break; + } + } + if (cnt == 1 && pr->nospace) { + *pr->nospace = savech; + } + } + } + } + } + } + if (endfu) { + /* + * if eaddress not set, error or file bb_dump_size was multiple of + * bb_dump_blocksize, and no partial block ever found. + */ + if (!eaddress) { + if (!address) { + return; + } + eaddress = address; + } + for (pr = endfu->nextpr; pr; pr = pr->nextpr) { + switch (pr->flags) { + case F_ADDRESS: + (void) printf(pr->fmt, (unsigned int) eaddress); + break; + case F_TEXT: + (void) printf(pr->fmt); + break; + } + } + } +} + +int bb_dump_dump(char **argv) +{ + FS *tfs; + + /* figure out the data block bb_dump_size */ + for (bb_dump_blocksize = 0, tfs = bb_dump_fshead; tfs; tfs = tfs->nextfs) { + tfs->bcnt = bb_dump_size(tfs); + if (bb_dump_blocksize < tfs->bcnt) { + bb_dump_blocksize = tfs->bcnt; + } + } + /* rewrite the rules, do syntax checking */ + for (tfs = bb_dump_fshead; tfs; tfs = tfs->nextfs) { + rewrite(tfs); + } + + next(argv); + display(); + + return exitval; +} + +void bb_dump_add(const char *fmt) +{ + const char *p; + char *p1; + char *p2; + static FS **nextfs; + FS *tfs; + FU *tfu, **nextfu; + const char *savep; + + /* start new linked list of format units */ + tfs = xzalloc(sizeof(FS)); /*DBU:[dave@cray.com] start out NULL */ + if (!bb_dump_fshead) { + bb_dump_fshead = tfs; + } else { + *nextfs = tfs; + } + nextfs = &tfs->nextfs; + nextfu = &tfs->nextfu; + + /* take the format string and break it up into format units */ + for (p = fmt;;) { + /* bb_dump_skip leading white space */ + p = skip_whitespace(p); + if (!*p) { + break; + } + + /* allocate a new format unit and link it in */ + /* NOSTRICT */ + /* DBU:[dave@cray.com] calloc so that forward pointers start out NULL */ + tfu = xzalloc(sizeof(FU)); + *nextfu = tfu; + nextfu = &tfu->nextfu; + tfu->reps = 1; + + /* if leading digit, repetition count */ + if (isdigit(*p)) { + for (savep = p; isdigit(*p); ++p); + if (!isspace(*p) && *p != '/') { + bb_error_msg_and_die("bad format {%s}", fmt); + } + /* may overwrite either white space or slash */ + tfu->reps = atoi(savep); + tfu->flags = F_SETREP; + /* bb_dump_skip trailing white space */ + p = skip_whitespace(++p); + } + + /* bb_dump_skip slash and trailing white space */ + if (*p == '/') { + p = skip_whitespace(++p); + } + + /* byte count */ + if (isdigit(*p)) { + for (savep = p; isdigit(*p); ++p); + if (!isspace(*p)) { + bb_error_msg_and_die("bad format {%s}", fmt); + } + tfu->bcnt = atoi(savep); + /* bb_dump_skip trailing white space */ + p = skip_whitespace(++p); + } + + /* format */ + if (*p != '"') { + bb_error_msg_and_die("bad format {%s}", fmt); + } + for (savep = ++p; *p != '"';) { + if (*p++ == 0) { + bb_error_msg_and_die("bad format {%s}", fmt); + } + } + tfu->fmt = xmalloc(p - savep + 1); + strncpy(tfu->fmt, savep, p - savep); + tfu->fmt[p - savep] = '\0'; +/* escape(tfu->fmt); */ + + p1 = tfu->fmt; + + /* alphabetic escape sequences have to be done in place */ + for (p2 = p1;; ++p1, ++p2) { + if (!*p1) { + *p2 = *p1; + break; + } + if (*p1 == '\\') { + const char *cs = conv_str + 4; + ++p1; + *p2 = *p1; + do { + if (*p1 == cs[2]) { + *p2 = cs[0]; + break; + } + cs += 4; + } while (*cs); + } + } + + p++; + } +} + +/* + * Copyright (c) 1989 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ diff --git a/libbb/error_msg.c b/libbb/error_msg.c new file mode 100644 index 000000000..b2141f3a2 --- /dev/null +++ b/libbb/error_msg.c @@ -0,0 +1,23 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include "libbb.h" + +void bb_error_msg(const char *s, ...) +{ + va_list p; + + va_start(p, s); + bb_verror_msg(s, p, NULL); + va_end(p); +} diff --git a/libbb/error_msg_and_die.c b/libbb/error_msg_and_die.c new file mode 100644 index 000000000..10d953513 --- /dev/null +++ b/libbb/error_msg_and_die.c @@ -0,0 +1,28 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include "libbb.h" + +int die_sleep; + +void bb_error_msg_and_die(const char *s, ...) +{ + va_list p; + + va_start(p, s); + bb_verror_msg(s, p, NULL); + va_end(p); + if (die_sleep) + sleep(die_sleep); + exit(xfunc_error_retval); +} diff --git a/libbb/execable.c b/libbb/execable.c new file mode 100644 index 000000000..817c06736 --- /dev/null +++ b/libbb/execable.c @@ -0,0 +1,61 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 2006 Gabriel Somlo + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +/* check if path points to an executable file; + * return 1 if found; + * return 0 otherwise; + */ +int execable_file(const char *name) +{ + struct stat s; + return (!access(name, X_OK) && !stat(name, &s) && S_ISREG(s.st_mode)); +} + +/* search $PATH for an executable file; + * return allocated string containing full path if found; + * return NULL otherwise; + */ +char *find_execable(const char *filename) +{ + char *path, *p, *n; + + p = path = xstrdup(getenv("PATH")); + while (p) { + n = strchr(p, ':'); + if (n) + *n++ = '\0'; + if (*p != '\0') { /* it's not a PATH="foo::bar" situation */ + p = concat_path_file(p, filename); + if (execable_file(p)) { + free(path); + return p; + } + free(p); + } + p = n; + } + free(path); + return NULL; +} + +/* search $PATH for an executable file; + * return 1 if found; + * return 0 otherwise; + */ +int exists_execable(const char *filename) +{ + char *ret = find_execable(filename); + if (ret) { + free(ret); + return 1; + } + return 0; +} diff --git a/libbb/fclose_nonstdin.c b/libbb/fclose_nonstdin.c new file mode 100644 index 000000000..88e8474f2 --- /dev/null +++ b/libbb/fclose_nonstdin.c @@ -0,0 +1,23 @@ +/* vi: set sw=4 ts=4: */ +/* + * fclose_nonstdin implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* A number of standard utilities can accept multiple command line args + * of '-' for stdin, according to SUSv3. So we encapsulate the check + * here to save a little space. + */ + +#include "libbb.h" + +int fclose_if_not_stdin(FILE *f) +{ + if (f != stdin) { + return fclose(f); + } + return 0; +} diff --git a/libbb/fflush_stdout_and_exit.c b/libbb/fflush_stdout_and_exit.c new file mode 100644 index 000000000..456ce9513 --- /dev/null +++ b/libbb/fflush_stdout_and_exit.c @@ -0,0 +1,24 @@ +/* vi: set sw=4 ts=4: */ +/* + * fflush_stdout_and_exit implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* Attempt to fflush(stdout), and exit with an error code if stdout is + * in an error state. + */ + +#include "libbb.h" + +void fflush_stdout_and_exit(int retval) +{ + if (fflush(stdout)) { + retval = xfunc_error_retval; + } + if (die_sleep) + sleep(die_sleep); + exit(retval); +} diff --git a/libbb/fgets_str.c b/libbb/fgets_str.c new file mode 100644 index 000000000..1bc6c3b1c --- /dev/null +++ b/libbb/fgets_str.c @@ -0,0 +1,53 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) many different people. + * If you wrote this, please acknowledge your work. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +/* Read up to (and including) TERMINATING_STRING from FILE and return it. + * Return NULL on EOF. */ + +char *xmalloc_fgets_str(FILE *file, const char *terminating_string) +{ + char *linebuf = NULL; + const int term_length = strlen(terminating_string); + int end_string_offset; + int linebufsz = 0; + int idx = 0; + int ch; + + while (1) { + ch = fgetc(file); + if (ch == EOF) { + free(linebuf); + return NULL; + } + + /* grow the line buffer as necessary */ + while (idx > linebufsz - 2) { + linebufsz += 200; + linebuf = xrealloc(linebuf, linebufsz); + } + + linebuf[idx] = ch; + idx++; + + /* Check for terminating string */ + end_string_offset = idx - term_length; + if (end_string_offset > 0 + && memcmp(&linebuf[end_string_offset], terminating_string, term_length) == 0 + ) { + idx -= term_length; + break; + } + } + linebuf = xrealloc(linebuf, idx + 1); + linebuf[idx] = '\0'; + return linebuf; +} diff --git a/libbb/find_mount_point.c b/libbb/find_mount_point.c new file mode 100644 index 000000000..cb00b9806 --- /dev/null +++ b/libbb/find_mount_point.c @@ -0,0 +1,53 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include + +/* + * Given a block device, find the mount table entry if that block device + * is mounted. + * + * Given any other file (or directory), find the mount table entry for its + * filesystem. + */ +struct mntent *find_mount_point(const char *name, const char *table) +{ + struct stat s; + dev_t mountDevice; + FILE *mountTable; + struct mntent *mountEntry; + + if (stat(name, &s) != 0) + return 0; + + if ((s.st_mode & S_IFMT) == S_IFBLK) + mountDevice = s.st_rdev; + else + mountDevice = s.st_dev; + + + mountTable = setmntent(table ? table : bb_path_mtab_file, "r"); + if (!mountTable) + return 0; + + while ((mountEntry = getmntent(mountTable)) != 0) { + if (strcmp(name, mountEntry->mnt_dir) == 0 + || strcmp(name, mountEntry->mnt_fsname) == 0 + ) { /* String match. */ + break; + } + if (stat(mountEntry->mnt_fsname, &s) == 0 && s.st_rdev == mountDevice) /* Match the device. */ + break; + if (stat(mountEntry->mnt_dir, &s) == 0 && s.st_dev == mountDevice) /* Match the directory's mount point. */ + break; + } + endmntent(mountTable); + return mountEntry; +} diff --git a/libbb/find_pid_by_name.c b/libbb/find_pid_by_name.c new file mode 100644 index 000000000..e98616940 --- /dev/null +++ b/libbb/find_pid_by_name.c @@ -0,0 +1,55 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "libbb.h" + +/* find_pid_by_name() + * + * Modified by Vladimir Oleynik for use with libbb/procps.c + * This finds the pid of the specified process. + * Currently, it's implemented by rummaging through + * the proc filesystem. + * + * Returns a list of all matching PIDs + * It is the caller's duty to free the returned pidlist. + */ +pid_t* find_pid_by_name(const char* procName) +{ + pid_t* pidList; + int i = 0; + procps_status_t* p = NULL; + + pidList = xmalloc(sizeof(*pidList)); + while ((p = procps_scan(p, PSSCAN_PID|PSSCAN_COMM))) { + if (strncmp(p->comm, procName, sizeof(p->comm)-1) == 0) { + pidList = xrealloc(pidList, sizeof(*pidList) * (i+2)); + pidList[i++] = p->pid; + } + } + + pidList[i] = 0; + return pidList; +} + +pid_t *pidlist_reverse(pid_t *pidList) +{ + int i = 0; + while (pidList[i]) + i++; + if (--i >= 0) { + pid_t k; + int j; + for (j = 0; i > j; i--, j++) { + k = pidList[i]; + pidList[i] = pidList[j]; + pidList[j] = k; + } + } + return pidList; +} diff --git a/libbb/find_root_device.c b/libbb/find_root_device.c new file mode 100644 index 000000000..71b79b8d0 --- /dev/null +++ b/libbb/find_root_device.c @@ -0,0 +1,33 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +char *find_block_device(char *path) +{ + DIR *dir; + struct dirent *entry; + struct stat st; + dev_t dev; + char *retpath=NULL; + + if(stat(path, &st) || !(dir = opendir("/dev"))) return NULL; + dev = (st.st_mode & S_IFMT) == S_IFBLK ? st.st_rdev : st.st_dev; + while((entry = readdir(dir)) != NULL) { + char devpath[PATH_MAX]; + sprintf(devpath,"/dev/%s", entry->d_name); + if(!stat(devpath, &st) && S_ISBLK(st.st_mode) && st.st_rdev == dev) { + retpath = xstrdup(devpath); + break; + } + } + closedir(dir); + + return retpath; +} diff --git a/libbb/full_write.c b/libbb/full_write.c new file mode 100644 index 000000000..bceac6de5 --- /dev/null +++ b/libbb/full_write.c @@ -0,0 +1,38 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include "libbb.h" + +/* + * Write all of the supplied buffer out to a file. + * This does multiple writes as necessary. + * Returns the amount written, or -1 on an error. + */ +ssize_t full_write(int fd, const void *buf, size_t len) +{ + ssize_t cc; + ssize_t total; + + total = 0; + + while (len) { + cc = safe_write(fd, buf, len); + + if (cc < 0) + return cc; /* write() returns -1 on failure. */ + + total += cc; + buf = ((const char *)buf) + cc; + len -= cc; + } + + return total; +} diff --git a/libbb/get_console.c b/libbb/get_console.c new file mode 100644 index 000000000..52ae3d215 --- /dev/null +++ b/libbb/get_console.c @@ -0,0 +1,77 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) many different people. If you wrote this, please + * acknowledge your work. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include +#include "libbb.h" + + + +/* From */ +enum { KDGKBTYPE = 0x4B33 }; /* get keyboard type */ + + +static int open_a_console(const char *fnam) +{ + int fd; + + /* try read-write */ + fd = open(fnam, O_RDWR); + + /* if failed, try read-only */ + if (fd < 0 && errno == EACCES) + fd = open(fnam, O_RDONLY); + + /* if failed, try write-only */ + if (fd < 0 && errno == EACCES) + fd = open(fnam, O_WRONLY); + + return fd; +} + +/* + * Get an fd for use with kbd/console ioctls. + * We try several things because opening /dev/console will fail + * if someone else used X (which does a chown on /dev/console). + */ + +int get_console_fd(void) +{ + int fd; + + static const char * const choise_console_names[] = { + CONSOLE_DEV, CURRENT_VC, CURRENT_TTY + }; + + for (fd = 2; fd >= 0; fd--) { + int fd4name; + int choise_fd; + char arg; + + fd4name = open_a_console(choise_console_names[fd]); + chk_std: + choise_fd = fd4name >= 0 ? fd4name : fd; + + arg = 0; + if (ioctl(choise_fd, KDGKBTYPE, &arg) == 0) + return choise_fd; + if(fd4name >= 0) { + close(fd4name); + fd4name = -1; + goto chk_std; + } + } + + bb_error_msg("cannot get file descriptor referring to console"); + return fd; /* total failure */ +} diff --git a/libbb/get_last_path_component.c b/libbb/get_last_path_component.c new file mode 100644 index 000000000..311909726 --- /dev/null +++ b/libbb/get_last_path_component.c @@ -0,0 +1,32 @@ +/* vi: set sw=4 ts=4: */ +/* + * bb_get_last_path_component implementation for busybox + * + * Copyright (C) 2001 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +char *bb_get_last_path_component(char *path) +{ + char *first = path; + char *last; + + last = path - 1; + + while (*path) { + if ((*path != '/') && (path > ++last)) { + last = first = path; + } + ++path; + } + + if (*first == '/') { + last = first; + } + last[1] = 0; + + return first; +} diff --git a/libbb/get_line_from_file.c b/libbb/get_line_from_file.c new file mode 100644 index 000000000..3f2c6096e --- /dev/null +++ b/libbb/get_line_from_file.c @@ -0,0 +1,68 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 2005, 2006 Rob Landley + * Copyright (C) 2004 Erik Andersen + * Copyright (C) 2001 Matt Krai + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +/* This function reads an entire line from a text file, up to a newline + * or NUL byte, inclusive. It returns a malloc'ed char * which must be + * stored and free'ed by the caller. If end is null '\n' isn't considered + * end of line. If end isn't null, length of the chunk read is stored in it. */ + +char *bb_get_chunk_from_file(FILE * file, int *end) +{ + int ch; + int idx = 0; + char *linebuf = NULL; + int linebufsz = 0; + + while ((ch = getc(file)) != EOF) { + /* grow the line buffer as necessary */ + if (idx >= linebufsz) { + linebuf = xrealloc(linebuf, linebufsz += 80); + } + linebuf[idx++] = (char) ch; + if (!ch || (end && ch == '\n')) + break; + } + if (end) + *end = idx; + if (linebuf) { + // huh, does fgets discard prior data on error like this? + // I don't think so.... + //if (ferror(file)) { + // free(linebuf); + // return NULL; + //} + linebuf = xrealloc(linebuf, idx+1); + linebuf[idx] = '\0'; + } + return linebuf; +} + +/* Get line, including trailing \n if any */ +char *xmalloc_fgets(FILE * file) +{ + int i; + + return bb_get_chunk_from_file(file, &i); +} + +/* Get line. Remove trailing \n */ +char *xmalloc_getline(FILE * file) +{ + int i; + char *c = bb_get_chunk_from_file(file, &i); + + if (i && c[--i] == '\n') + c[i] = 0; + + return c; +} diff --git a/libbb/getopt32.c b/libbb/getopt32.c new file mode 100644 index 000000000..dddf8121a --- /dev/null +++ b/libbb/getopt32.c @@ -0,0 +1,529 @@ +/* vi: set sw=4 ts=4: */ +/* + * universal getopt32 implementation for busybox + * + * Copyright (C) 2003-2005 Vladimir Oleynik + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include + +/* Documentation + +uint32_t +getopt32(int argc, char **argv, const char *applet_opts, ...) + + The command line options must be declared in const char + *applet_opts as a string of chars, for example: + + flags = getopt32(argc, argv, "rnug"); + + If one of the given options is found, a flag value is added to + the return value (an unsigned long). + + The flag value is determined by the position of the char in + applet_opts string. For example, in the above case: + + flags = getopt32(argc, argv, "rnug"); + + "r" will add 1 (bit 0) + "n" will add 2 (bit 1) + "u will add 4 (bit 2) + "g" will add 8 (bit 3) + + and so on. You can also look at the return value as a bit + field and each option sets one bit. + + On exit, global variable optind is set so that if you + will do argc -= optind; argv += optind; then + argc will be equal to number of remaining non-option + arguments, first one would be in argv[0], next in argv[1] and so on + (options and their parameters will be moved into argv[] + positions prior to argv[optind]). + + ":" If one of the options requires an argument, then add a ":" + after the char in applet_opts and provide a pointer to store + the argument. For example: + + char *pointer_to_arg_for_a; + char *pointer_to_arg_for_b; + char *pointer_to_arg_for_c; + char *pointer_to_arg_for_d; + + flags = getopt32(argc, argv, "a:b:c:d:", + &pointer_to_arg_for_a, &pointer_to_arg_for_b, + &pointer_to_arg_for_c, &pointer_to_arg_for_d); + + The type of the pointer (char* or llist_t*) may be controlled + by the "::" special separator that is set in the external string + opt_complementary (see below for more info). + + "::" If option can have an *optional* argument, then add a "::" + after its char in applet_opts and provide a pointer to store + the argument. Note that optional arguments _must_ + immediately follow the option: -oparam, not -o param. + + "+" If the first character in the applet_opts string is a plus, + then option processing will stop as soon as a non-option is + encountered in the argv array. Useful for applets like env + which should not process arguments to subprograms: + env -i ls -d / + Here we want env to process just the '-i', not the '-d'. + +const struct option *applet_long_options + + This struct allows you to define long options. The syntax for + declaring the array is just like that of getopt's longopts. + (see getopt(3)) + + static const struct option applet_long_options[] = { + //name,has_arg,flag,val + { "verbose", 0, 0, 'v' }, + { 0, 0, 0, 0 } + }; + applet_long_options = applet_long_options; + + The last member of struct option (val) typically is set to + matching short option from applet_opts. If there is no matching + char in applet_opts, then: + - return bit have next position after short options + - if has_arg is not "no_argument", use ptr for arg also + - opt_complementary affects it too + + Note: a good applet will make long options configurable via the + config process and not a required feature. The current standard + is to name the config option CONFIG_FEATURE__LONG_OPTIONS. + +const char *opt_complementary + + ":" The colon (":") is used to separate groups of two or more chars + and/or groups of chars and special characters (stating some + conditions to be checked). + + "abc" If groups of two or more chars are specified, the first char + is the main option and the other chars are secondary options. + Their flags will be turned on if the main option is found even + if they are not specifed on the command line. For example: + + opt_complementary = "abc"; + flags = getopt32(argc, argv, "abcd") + + If getopt() finds "-a" on the command line, then + getopt32's return value will be as if "-a -b -c" were + found. + + "ww" Adjacent double options have a counter associated which indicates + the number of occurences of the option. + For example the ps applet needs: + if w is given once, GNU ps sets the width to 132, + if w is given more than once, it is "unlimited" + + int w_counter = 0; + opt_complementary = "ww"; + getopt32(argc, argv, "w", &w_counter); + if (w_counter) + width = (w_counter == 1) ? 132 : INT_MAX; + else + get_terminal_width(...&width...); + + w_counter is a pointer to an integer. It has to be passed to + getopt32() after all other option argument sinks. + + For example: accept multiple -v to indicate the level of verbosity + and for each -b optarg, add optarg to my_b. Finally, if b is given, + turn off c and vice versa: + + llist_t *my_b = NULL; + int verbose_level = 0; + opt_complementary = "vv:b::b-c:c-b"; + f = getopt32(argc, argv, "vb:c", &my_b, &verbose_level); + if (f & 2) // -c after -b unsets -b flag + while (my_b) { dosomething_with(my_b->data); my_b = my_b->link; } + if (my_b) // but llist is stored if -b is specified + free_llist(my_b); + if (verbose_level) printf("verbose level is %d\n", verbose_level); + +Special characters: + + "-" A dash as the first char in a opt_complementary group forces + all arguments to be treated as options, even if they have + no leading dashes. Next char in this case can't be a digit (0-9), + use ':' or end of line. For example: + + opt_complementary = "-:w-x:x-w"; + getopt32(argc, argv, "wx"); + + Allows any arguments to be given without a dash (./program w x) + as well as with a dash (./program -x). + + "--" A double dash at the beginning of opt_complementary means the + argv[1] string should always be treated as options, even if it isn't + prefixed with a "-". This is useful for special syntax in applets + such as "ar" and "tar": + tar xvf foo.tar + + "-N" A dash as the first char in a opt_complementary group followed + by a single digit (0-9) means that at least N non-option + arguments must be present on the command line + + "=N" An equal sign as the first char in a opt_complementary group followed + by a single digit (0-9) means that exactly N non-option + arguments must be present on the command line + + "?N" A "?" as the first char in a opt_complementary group followed + by a single digit (0-9) means that at most N arguments must be present + on the command line. + + "V-" An option with dash before colon or end-of-line results in + bb_show_usage being called if this option is encountered. + This is typically used to implement "print verbose usage message + and exit" option. + + "-" A dash between two options causes the second of the two + to be unset (and ignored) if it is given on the command line. + + [FIXME: what if they are the same? like "x-x"? Is it ever useful?] + + For example: + The du applet has the options "-s" and "-d depth". If + getopt32 finds -s, then -d is unset or if it finds -d + then -s is unset. (Note: busybox implements the GNU + "--max-depth" option as "-d".) To obtain this behavior, you + set opt_complementary = "s-d:d-s". Only one flag value is + added to getopt32's return value depending on the + position of the options on the command line. If one of the + two options requires an argument pointer (":" in applet_opts + as in "d:") optarg is set accordingly. + + char *smax_print_depth; + + opt_complementary = "s-d:d-s:x-x"; + opt = getopt32(argc, argv, "sd:x", &smax_print_depth); + + if (opt & 2) + max_print_depth = atoi(smax_print_depth); + if (opt & 4) + printf("Detected odd -x usage\n"); + + "--" A double dash between two options, or between an option and a group + of options, means that they are mutually exclusive. Unlike + the "-" case above, an error will be forced if the options + are used together. + + For example: + The cut applet must have only one type of list specified, so + -b, -c and -f are mutally exclusive and should raise an error + if specified together. In this case you must set + opt_complementary = "b--cf:c--bf:f--bc". If two of the + mutually exclusive options are found, getopt32's + return value will have the error flag set (BB_GETOPT_ERROR) so + that we can check for it: + + if (flags & BB_GETOPT_ERROR) + bb_show_usage(); + + "x--x" Variation of the above, it means that -x option should occur + at most once. + + "?" A "?" as the first char in a opt_complementary group means: + if BB_GETOPT_ERROR is detected, don't return, call bb_show_usage + and exit instead. Next char after '?' can't be a digit. + + "::" A double colon after a char in opt_complementary means that the + option can occur multiple times. Each occurrence will be saved as + a llist_t element instead of char*. + + For example: + The grep applet can have one or more "-e pattern" arguments. + In this case you should use getopt32() as follows: + + llist_t *patterns = NULL; + + (this pointer must be initializated to NULL if the list is empty + as required by *llist_add_to(llist_t *old_head, char *new_item).) + + opt_complementary = "e::"; + + getopt32(argc, argv, "e:", &patterns); + $ grep -e user -e root /etc/passwd + root:x:0:0:root:/root:/bin/bash + user:x:500:500::/home/user:/bin/bash + + "?" An "?" between an option and a group of options means that + at least one of them is required to occur if the first option + occurs in preceding command line arguments. + + For example from "id" applet: + + // Don't allow -n -r -rn -ug -rug -nug -rnug + opt_complementary = "r?ug:n?ug:?u--g:g--u"; + flags = getopt32(argc, argv, "rnug"); + + This example allowed only: + $ id; id -u; id -g; id -ru; id -nu; id -rg; id -ng; id -rnu; id -rng + + "X" A opt_complementary group with just a single letter means + that this option is required. If more than one such group exists, + at least one option is required to occur (not all of them). + For example from "start-stop-daemon" applet: + + // Don't allow -KS -SK, but -S or -K is required + opt_complementary = "K:S:?K--S:S--K"; + flags = getopt32(argc, argv, "KS...); + + + Don't forget to use ':'. For example, "?322-22-23X-x-a" + is interpreted as "?3:22:-2:2-2:2-3Xa:2--x" - + max 3 args; count uses of '-2'; min 2 args; if there is + a '-2' option then unset '-3', '-X' and '-a'; if there is + a '-2' and after it a '-x' then error out. +*/ + +/* Code here assumes that 'unsigned' is at least 32 bits wide */ + +const char *opt_complementary; + +typedef struct { + int opt; + int list_flg; + unsigned switch_on; + unsigned switch_off; + unsigned incongruously; + unsigned requires; + void **optarg; /* char **optarg or llist_t **optarg */ + int *counter; +} t_complementary; + +/* You can set applet_long_options for parse called long options */ +#if ENABLE_GETOPT_LONG +static const struct option bb_default_long_options[] = { +/* { "help", 0, NULL, '?' }, */ + { 0, 0, 0, 0 } +}; + +const struct option *applet_long_options = bb_default_long_options; +#endif + +uint32_t option_mask32; + +uint32_t +getopt32(int argc, char **argv, const char *applet_opts, ...) +{ + unsigned flags = 0; + unsigned requires = 0; + t_complementary complementary[33]; + int c; + const unsigned char *s; + t_complementary *on_off; + va_list p; +#if ENABLE_GETOPT_LONG + const struct option *l_o; +#endif + unsigned trigger; + char **pargv = NULL; + int min_arg = 0; + int max_arg = -1; + +#define SHOW_USAGE_IF_ERROR 1 +#define ALL_ARGV_IS_OPTS 2 +#define FIRST_ARGV_IS_OPT 4 +#define FREE_FIRST_ARGV_IS_OPT 8 + int spec_flgs = 0; + + va_start(p, applet_opts); + + c = 0; + on_off = complementary; + memset(on_off, 0, sizeof(complementary)); + + /* skip GNU extension */ + s = (const unsigned char *)applet_opts; + if (*s == '+' || *s == '-') + s++; + while (*s) { + if (c >= 32) break; + on_off->opt = *s; + on_off->switch_on = (1 << c); + if (*++s == ':') { + on_off->optarg = va_arg(p, void **); + while (*++s == ':') /* skip */; + } + on_off++; + c++; + } + +#if ENABLE_GETOPT_LONG + for (l_o = applet_long_options; l_o->name; l_o++) { + if (l_o->flag) + continue; + for (on_off = complementary; on_off->opt != 0; on_off++) + if (on_off->opt == l_o->val) + goto next_long; + if (c >= 32) break; + on_off->opt = l_o->val; + on_off->switch_on = (1 << c); + if (l_o->has_arg != no_argument) + on_off->optarg = va_arg(p, void **); + c++; + next_long: ; + } +#endif /* ENABLE_GETOPT_LONG */ + for (s = (const unsigned char *)opt_complementary; s && *s; s++) { + t_complementary *pair; + unsigned *pair_switch; + + if (*s == ':') + continue; + c = s[1]; + if (*s == '?') { + if (c < '0' || c > '9') { + spec_flgs |= SHOW_USAGE_IF_ERROR; + } else { + max_arg = c - '0'; + s++; + } + continue; + } + if (*s == '-') { + if (c < '0' || c > '9') { + if (c == '-') { + spec_flgs |= FIRST_ARGV_IS_OPT; + s++; + } else + spec_flgs |= ALL_ARGV_IS_OPTS; + } else { + min_arg = c - '0'; + s++; + } + continue; + } + if (*s == '=') { + min_arg = max_arg = c - '0'; + s++; + continue; + } + for (on_off = complementary; on_off->opt; on_off++) + if (on_off->opt == *s) + break; + if (c == ':' && s[2] == ':') { + on_off->list_flg++; + continue; + } + if (c == ':' || c == '\0') { + requires |= on_off->switch_on; + continue; + } + if (c == '-' && (s[2] == ':' || s[2] == '\0')) { + flags |= on_off->switch_on; + on_off->incongruously |= on_off->switch_on; + s++; + continue; + } + if (c == *s) { + on_off->counter = va_arg(p, int *); + s++; + } + pair = on_off; + pair_switch = &(pair->switch_on); + for (s++; *s && *s != ':'; s++) { + if (*s == '?') { + pair_switch = &(pair->requires); + } else if (*s == '-') { + if (pair_switch == &(pair->switch_off)) + pair_switch = &(pair->incongruously); + else + pair_switch = &(pair->switch_off); + } else { + for (on_off = complementary; on_off->opt; on_off++) + if (on_off->opt == *s) { + *pair_switch |= on_off->switch_on; + break; + } + } + } + s--; + } + va_end (p); + +#if ENABLE_AR || ENABLE_TAR + if (spec_flgs & FIRST_ARGV_IS_OPT) { + if (argv[1] && argv[1][0] != '-' && argv[1][0] != '\0') { + argv[1] = xasprintf("-%s", argv[1]); + if (ENABLE_FEATURE_CLEAN_UP) + spec_flgs |= FREE_FIRST_ARGV_IS_OPT; + } + } +#endif + /* Note: just "getopt() <= 0" will not work good for + * "fake" short options, like this one: + * wget $'-\203' "Test: test" http://kernel.org/ + * (supposed to act as --header, but doesn't) */ +#if ENABLE_GETOPT_LONG + while ((c = getopt_long(argc, argv, applet_opts, + applet_long_options, NULL)) != -1) { +#else + while ((c = getopt(argc, argv, applet_opts)) != -1) { +#endif /* ENABLE_GETOPT_LONG */ + c &= 0xff; /* fight libc's sign extends */ +loop_arg_is_opt: + for (on_off = complementary; on_off->opt != c; on_off++) { + /* c==0 if long opt have non NULL flag */ + if (on_off->opt == 0 && c != 0) + bb_show_usage(); + } + if (flags & on_off->incongruously) { + if ((spec_flgs & SHOW_USAGE_IF_ERROR)) + bb_show_usage(); + flags |= BB_GETOPT_ERROR; + } + trigger = on_off->switch_on & on_off->switch_off; + flags &= ~(on_off->switch_off ^ trigger); + flags |= on_off->switch_on ^ trigger; + flags ^= trigger; + if (on_off->counter) + (*(on_off->counter))++; + if (on_off->list_flg) { + llist_add_to((llist_t **)(on_off->optarg), optarg); + } else if (on_off->optarg) { + *(char **)(on_off->optarg) = optarg; + } + if (pargv != NULL) + break; + } + + if (spec_flgs & ALL_ARGV_IS_OPTS) { + /* process argv is option, for example "ps" applet */ + if (pargv == NULL) + pargv = argv + optind; + while (*pargv) { + c = **pargv; + if (c == '\0') { + pargv++; + } else { + (*pargv)++; + goto loop_arg_is_opt; + } + } + } + +#if (ENABLE_AR || ENABLE_TAR) && ENABLE_FEATURE_CLEAN_UP + if (spec_flgs & FREE_FIRST_ARGV_IS_OPT) + free(argv[1]); +#endif + /* check depending requires for given options */ + for (on_off = complementary; on_off->opt; on_off++) { + if (on_off->requires && (flags & on_off->switch_on) && + (flags & on_off->requires) == 0) + bb_show_usage(); + } + if (requires && (flags & requires) == 0) + bb_show_usage(); + argc -= optind; + if (argc < min_arg || (max_arg >= 0 && argc > max_arg)) + bb_show_usage(); + + option_mask32 = flags; + return flags; +} diff --git a/libbb/herror_msg.c b/libbb/herror_msg.c new file mode 100644 index 000000000..1e6908d82 --- /dev/null +++ b/libbb/herror_msg.c @@ -0,0 +1,22 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include + +#include "libbb.h" + +void bb_herror_msg(const char *s, ...) +{ + va_list p; + + va_start(p, s); + bb_vherror_msg(s, p); + va_end(p); +} diff --git a/libbb/herror_msg_and_die.c b/libbb/herror_msg_and_die.c new file mode 100644 index 000000000..f62ddd2ea --- /dev/null +++ b/libbb/herror_msg_and_die.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include + +#include "libbb.h" + +void bb_herror_msg_and_die(const char *s, ...) +{ + va_list p; + + va_start(p, s); + bb_vherror_msg(s, p); + va_end(p); + if (die_sleep) + sleep(die_sleep); + exit(xfunc_error_retval); +} diff --git a/libbb/human_readable.c b/libbb/human_readable.c new file mode 100644 index 000000000..ff1b55141 --- /dev/null +++ b/libbb/human_readable.c @@ -0,0 +1,89 @@ +/* vi: set sw=4 ts=4: */ +/* + * June 30, 2001 Manuel Novoa III + * + * All-integer version (hey, not everyone has floating point) of + * make_human_readable_str, modified from similar code I had written + * for busybox several months ago. + * + * Notes: + * 1) I'm using an unsigned long long to hold the product size * block_size, + * as df (which calls this routine) could request a representation of a + * partition size in bytes > max of unsigned long. If long longs aren't + * available, it would be possible to do what's needed using polynomial + * representations (say, powers of 1024) and manipulating coefficients. + * The base ten "bytes" output could be handled similarly. + * + * 2) This routine always outputs a decimal point and a tenths digit when + * display_unit != 0. Hence, it isn't uncommon for the returned string + * to have a length of 5 or 6. + * + * It might be nice to add a flag to indicate no decimal digits in + * that case. This could be either an additional parameter, or a + * special value of display_unit. Such a flag would also be nice for du. + * + * Some code to omit the decimal point and tenths digit is sketched out + * and "#if 0"'d below. + */ + +#include +#include "libbb.h" + +const char *make_human_readable_str(unsigned long long size, + unsigned long block_size, unsigned long display_unit) +{ + /* The code will adjust for additional (appended) units. */ + static const char zero_and_units[] = { '0', 0, 'k', 'M', 'G', 'T' }; + static const char fmt[] = "%llu"; + static const char fmt_tenths[] = "%llu.%d%c"; + + static char str[21]; /* Sufficient for 64 bit unsigned integers. */ + + unsigned long long val; + int frac; + const char *u; + const char *f; + + u = zero_and_units; + f = fmt; + frac = 0; + + val = size * block_size; + if (val == 0) { + return u; + } + + if (display_unit) { + val += display_unit/2; /* Deal with rounding. */ + val /= display_unit; /* Don't combine with the line above!!! */ + } else { + ++u; + while ((val >= 1024) + && (u < zero_and_units + sizeof(zero_and_units) - 1) + ) { + f = fmt_tenths; + ++u; + frac = (((int)(val % 1024)) * 10 + 1024/2) / 1024; + val /= 1024; + } + if (frac >= 10) { /* We need to round up here. */ + ++val; + frac = 0; + } +#if 0 + /* Sample code to omit decimal point and tenths digit. */ + if ( /* no_tenths */ 1 ) { + if ( frac >= 5 ) { + ++val; + } + f = "%llu%*c" /* fmt_no_tenths */ ; + frac = 1; + } +#endif + } + + /* If f==fmt then 'frac' and 'u' are ignored. */ + snprintf(str, sizeof(str), f, val, frac, *u); + + return str; +} diff --git a/libbb/inet_common.c b/libbb/inet_common.c new file mode 100644 index 000000000..d8e00353e --- /dev/null +++ b/libbb/inet_common.c @@ -0,0 +1,235 @@ +/* vi: set sw=4 ts=4: */ +/* + * stolen from net-tools-1.59 and stripped down for busybox by + * Erik Andersen + * + * Heavily modified by Manuel Novoa III Mar 12, 2001 + * + * Version: $Id: inet_common.c,v 1.8 2004/03/10 07:42:38 mjn3 Exp $ + * + */ + +#include "libbb.h" +#include "inet_common.h" + +int INET_resolve(const char *name, struct sockaddr_in *s_in, int hostfirst) +{ + struct hostent *hp; + struct netent *np; + + /* Grmpf. -FvK */ + s_in->sin_family = AF_INET; + s_in->sin_port = 0; + + /* Default is special, meaning 0.0.0.0. */ + if (!strcmp(name, bb_str_default)) { + s_in->sin_addr.s_addr = INADDR_ANY; + return 1; + } + /* Look to see if it's a dotted quad. */ + if (inet_aton(name, &s_in->sin_addr)) { + return 0; + } + /* If we expect this to be a hostname, try hostname database first */ +#ifdef DEBUG + if (hostfirst) { + bb_error_msg("gethostbyname (%s)", name); + } +#endif + if (hostfirst && (hp = gethostbyname(name)) != (struct hostent *) NULL) { + memcpy((char *) &s_in->sin_addr, (char *) hp->h_addr_list[0], + sizeof(struct in_addr)); + return 0; + } + /* Try the NETWORKS database to see if this is a known network. */ +#ifdef DEBUG + bb_error_msg("getnetbyname (%s)", name); +#endif + if ((np = getnetbyname(name)) != (struct netent *) NULL) { + s_in->sin_addr.s_addr = htonl(np->n_net); + return 1; + } + if (hostfirst) { + /* Don't try again */ + return -1; + } +#ifdef DEBUG + res_init(); + _res.options |= RES_DEBUG; +#endif + +#ifdef DEBUG + bb_error_msg("gethostbyname (%s)", name); +#endif + if ((hp = gethostbyname(name)) == (struct hostent *) NULL) { + return -1; + } + memcpy((char *) &s_in->sin_addr, (char *) hp->h_addr_list[0], + sizeof(struct in_addr)); + + return 0; +} + +/* cache */ +struct addr { + struct sockaddr_in addr; + char *name; + int host; + struct addr *next; +}; + +static struct addr *INET_nn = NULL; /* addr-to-name cache */ + +/* numeric: & 0x8000: default instead of *, + * & 0x4000: host instead of net, + * & 0x0fff: don't resolve + */ +int INET_rresolve(char *name, size_t len, struct sockaddr_in *s_in, + int numeric, unsigned int netmask) +{ + struct hostent *ent; + struct netent *np; + struct addr *pn; + unsigned long ad, host_ad; + int host = 0; + + /* Grmpf. -FvK */ + if (s_in->sin_family != AF_INET) { +#ifdef DEBUG + bb_error_msg("rresolve: unsupport address family %d !", + s_in->sin_family); +#endif + errno = EAFNOSUPPORT; + return -1; + } + ad = (unsigned long) s_in->sin_addr.s_addr; +#ifdef DEBUG + bb_error_msg("rresolve: %08lx, mask %08x, num %08x", ad, netmask, numeric); +#endif + if (ad == INADDR_ANY) { + if ((numeric & 0x0FFF) == 0) { + if (numeric & 0x8000) + safe_strncpy(name, bb_str_default, len); + else + safe_strncpy(name, "*", len); + return 0; + } + } + if (numeric & 0x0FFF) { + safe_strncpy(name, inet_ntoa(s_in->sin_addr), len); + return 0; + } + + if ((ad & (~netmask)) != 0 || (numeric & 0x4000)) + host = 1; + pn = INET_nn; + while (pn != NULL) { + if (pn->addr.sin_addr.s_addr == ad && pn->host == host) { + safe_strncpy(name, pn->name, len); +#ifdef DEBUG + bb_error_msg("rresolve: found %s %08lx in cache", + (host ? "host" : "net"), ad); +#endif + return 0; + } + pn = pn->next; + } + + host_ad = ntohl(ad); + np = NULL; + ent = NULL; + if (host) { +#ifdef DEBUG + bb_error_msg("gethostbyaddr (%08lx)", ad); +#endif + ent = gethostbyaddr((char *) &ad, 4, AF_INET); + if (ent != NULL) { + safe_strncpy(name, ent->h_name, len); + } + } else { +#ifdef DEBUG + bb_error_msg("getnetbyaddr (%08lx)", host_ad); +#endif + np = getnetbyaddr(host_ad, AF_INET); + if (np != NULL) { + safe_strncpy(name, np->n_name, len); + } + } + if ((ent == NULL) && (np == NULL)) { + safe_strncpy(name, inet_ntoa(s_in->sin_addr), len); + } + pn = (struct addr *) xmalloc(sizeof(struct addr)); + pn->addr = *s_in; + pn->next = INET_nn; + pn->host = host; + pn->name = xstrdup(name); + INET_nn = pn; + + return 0; +} + +#ifdef CONFIG_FEATURE_IPV6 + +int INET6_resolve(const char *name, struct sockaddr_in6 *sin6) +{ + struct addrinfo req, *ai; + int s; + + memset(&req, '\0', sizeof req); + req.ai_family = AF_INET6; + s = getaddrinfo(name, NULL, &req, &ai); + if (s) { + bb_error_msg("getaddrinfo: %s: %d", name, s); + return -1; + } + memcpy(sin6, ai->ai_addr, sizeof(struct sockaddr_in6)); + + freeaddrinfo(ai); + + return 0; +} + +#ifndef IN6_IS_ADDR_UNSPECIFIED +# define IN6_IS_ADDR_UNSPECIFIED(a) \ + (((uint32_t *) (a))[0] == 0 && ((uint32_t *) (a))[1] == 0 && \ + ((uint32_t *) (a))[2] == 0 && ((uint32_t *) (a))[3] == 0) +#endif + + +int INET6_rresolve(char *name, size_t len, struct sockaddr_in6 *sin6, + int numeric) +{ + int s; + + /* Grmpf. -FvK */ + if (sin6->sin6_family != AF_INET6) { +#ifdef DEBUG + bb_error_msg("rresolve: unsupport address family %d!", + sin6->sin6_family); +#endif + errno = EAFNOSUPPORT; + return -1; + } + if (numeric & 0x7FFF) { + inet_ntop(AF_INET6, &sin6->sin6_addr, name, len); + return 0; + } + if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { + if (numeric & 0x8000) { + strcpy(name, bb_str_default); + } else { + name[0] = '*'; + name[1] = '\0'; + } + return 0; + } + + s = getnameinfo((struct sockaddr *) sin6, sizeof(struct sockaddr_in6), name, len, NULL, 0, 0); + if (s) { + bb_error_msg("getnameinfo failed"); + return -1; + } + return 0; +} + +#endif /* CONFIG_FEATURE_IPV6 */ diff --git a/libbb/info_msg.c b/libbb/info_msg.c new file mode 100644 index 000000000..78d5c8f32 --- /dev/null +++ b/libbb/info_msg.c @@ -0,0 +1,23 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include "libbb.h" + +void bb_info_msg(const char *s, ...) +{ + va_list p; + + va_start(p, s); + bb_vinfo_msg(s, p); + va_end(p); +} diff --git a/libbb/inode_hash.c b/libbb/inode_hash.c new file mode 100644 index 000000000..2ac1623f4 --- /dev/null +++ b/libbb/inode_hash.c @@ -0,0 +1,88 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) many different people. + * If you wrote this, please acknowledge your work. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include "libbb.h" + +#define HASH_SIZE 311 /* Should be prime */ +#define hash_inode(i) ((i) % HASH_SIZE) + +typedef struct ino_dev_hash_bucket_struct { + struct ino_dev_hash_bucket_struct *next; + ino_t ino; + dev_t dev; + char name[1]; +} ino_dev_hashtable_bucket_t; + +static ino_dev_hashtable_bucket_t *ino_dev_hashtable[HASH_SIZE]; + +/* + * Return 1 if statbuf->st_ino && statbuf->st_dev are recorded in + * `ino_dev_hashtable', else return 0 + * + * If NAME is a non-NULL pointer to a character pointer, and there is + * a match, then set *NAME to the value of the name slot in that + * bucket. + */ +int is_in_ino_dev_hashtable(const struct stat *statbuf, char **name) +{ + ino_dev_hashtable_bucket_t *bucket; + + bucket = ino_dev_hashtable[hash_inode(statbuf->st_ino)]; + while (bucket != NULL) { + if ((bucket->ino == statbuf->st_ino) && + (bucket->dev == statbuf->st_dev)) + { + if (name) *name = bucket->name; + return 1; + } + bucket = bucket->next; + } + return 0; +} + +/* Add statbuf to statbuf hash table */ +void add_to_ino_dev_hashtable(const struct stat *statbuf, const char *name) +{ + int i; + size_t s; + ino_dev_hashtable_bucket_t *bucket; + + i = hash_inode(statbuf->st_ino); + s = name ? strlen(name) : 0; + bucket = xmalloc(sizeof(ino_dev_hashtable_bucket_t) + s); + bucket->ino = statbuf->st_ino; + bucket->dev = statbuf->st_dev; + if (name) + strcpy(bucket->name, name); + else + bucket->name[0] = '\0'; + bucket->next = ino_dev_hashtable[i]; + ino_dev_hashtable[i] = bucket; +} + +#ifdef CONFIG_FEATURE_CLEAN_UP +/* Clear statbuf hash table */ +void reset_ino_dev_hashtable(void) +{ + int i; + ino_dev_hashtable_bucket_t *bucket; + + for (i = 0; i < HASH_SIZE; i++) { + while (ino_dev_hashtable[i] != NULL) { + bucket = ino_dev_hashtable[i]->next; + free(ino_dev_hashtable[i]); + ino_dev_hashtable[i] = bucket; + } + } +} +#endif diff --git a/libbb/isdirectory.c b/libbb/isdirectory.c new file mode 100644 index 000000000..b35919869 --- /dev/null +++ b/libbb/isdirectory.c @@ -0,0 +1,39 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Based in part on code from sash, Copyright (c) 1999 by David I. Bell + * Permission has been granted to redistribute this code under the GPL. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "libbb.h" + +/* + * Return TRUE if a fileName is a directory. + * Nonexistent files return FALSE. + */ +int is_directory(const char *fileName, const int followLinks, struct stat *statBuf) +{ + int status; + struct stat astatBuf; + + if (statBuf == NULL) { + /* set from auto stack buffer */ + statBuf = &astatBuf; + } + + if (followLinks) + status = stat(fileName, statBuf); + else + status = lstat(fileName, statBuf); + + if (status < 0 || !(S_ISDIR(statBuf->st_mode))) { + status = FALSE; + } + else status = TRUE; + + return status; +} diff --git a/libbb/kernel_version.c b/libbb/kernel_version.c new file mode 100644 index 000000000..50b82ae18 --- /dev/null +++ b/libbb/kernel_version.c @@ -0,0 +1,37 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include /* for uname(2) */ + +#include "libbb.h" + +/* Returns current kernel version encoded as major*65536 + minor*256 + patch, + * so, for example, to check if the kernel is greater than 2.2.11: + * + * if (get_linux_version_code() > KERNEL_VERSION(2,2,11)) { } + */ +int get_linux_version_code(void) +{ + struct utsname name; + char *s; + int i, r; + + if (uname(&name) == -1) { + bb_perror_msg("cannot get system information"); + return 0; + } + + s = name.release; + r = 0; + for (i = 0; i < 3; i++) { + r = r * 256 + atoi(strtok(s, ".")); + s = NULL; + } + return r; +} diff --git a/libbb/last_char_is.c b/libbb/last_char_is.c new file mode 100644 index 000000000..3616d5916 --- /dev/null +++ b/libbb/last_char_is.c @@ -0,0 +1,24 @@ +/* vi: set sw=4 ts=4: */ +/* + * busybox library eXtended function + * + * Copyright (C) 2001 Larry Doolittle, + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +/* Find out if the last character of a string matches the one given Don't + * underrun the buffer if the string length is 0. Also avoids a possible + * space-hogging inline of strlen() per usage. + */ +char* last_char_is(const char *s, int c) +{ + if (s) { + s = strrchr(s, c); + if (s && !s[1]) + return (char*)s; + } + return NULL; +} diff --git a/libbb/llist.c b/libbb/llist.c new file mode 100644 index 000000000..8a74832ee --- /dev/null +++ b/libbb/llist.c @@ -0,0 +1,78 @@ +/* vi: set sw=4 ts=4: */ +/* + * linked list helper functions. + * + * Copyright (C) 2003 Glenn McGrath + * Copyright (C) 2005 Vladimir Oleynik + * Copyright (C) 2005 Bernhard Fischer + * Copyright (C) 2006 Rob Landley + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include +#include "libbb.h" + +/* Add data to the start of the linked list. */ +void llist_add_to(llist_t **old_head, void *data) +{ + llist_t *new_head = xmalloc(sizeof(llist_t)); + new_head->data = data; + new_head->link = *old_head; + *old_head = new_head; +} + +/* Add data to the end of the linked list. */ +void llist_add_to_end(llist_t **list_head, void *data) +{ + llist_t *new_item = xmalloc(sizeof(llist_t)); + new_item->data = data; + new_item->link = NULL; + + if (!*list_head) *list_head = new_item; + else { + llist_t *tail = *list_head; + while (tail->link) tail = tail->link; + tail->link = new_item; + } +} + +/* Remove first element from the list and return it */ +void *llist_pop(llist_t **head) +{ + void *data; + + if(!*head) data = *head; + else { + void *next = (*head)->link; + data = (*head)->data; + free(*head); + *head = next; + } + + return data; +} + +/* Recursively free all elements in the linked list. If freeit != NULL + * call it on each datum in the list */ +void llist_free(llist_t *elm, void (*freeit)(void *data)) +{ + while (elm) { + void *data = llist_pop(&elm); + if (freeit) freeit(data); + } +} + +/* Reverse list order. Useful since getopt32 saves option params + * in reverse order */ +llist_t* rev_llist(llist_t *list) +{ + llist_t *new = NULL; + while (list) { + llist_t *next = list->link; + list->link = new; + new = list; + list = next; + } + return new; +} diff --git a/libbb/login.c b/libbb/login.c new file mode 100644 index 000000000..6ebb9a6a0 --- /dev/null +++ b/libbb/login.c @@ -0,0 +1,105 @@ +/* vi: set sw=4 ts=4: */ +/* + * issue.c: issue printing code + * + * Copyright (C) 2003 Bastian Blank + * + * Optimize and correcting OCRNL by Vladimir Oleynik + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include /* MAXHOSTNAMELEN */ +#include +#include +#include "libbb.h" + +#include +#include + +#define LOGIN " login: " + +static const char fmtstr_d[] = "%A, %d %B %Y"; +static const char fmtstr_t[] = "%H:%M:%S"; + +void print_login_issue(const char *issue_file, const char *tty) +{ + FILE *fd; + int c; + char buf[256+1]; + const char *outbuf; + time_t t; + struct utsname uts; + + time(&t); + uname(&uts); + + puts("\r"); /* start a new line */ + + fd = fopen(issue_file, "r"); + if (!fd) + return; + while ((c = fgetc(fd)) != EOF) { + outbuf = buf; + buf[0] = c; + buf[1] = '\0'; + if(c == '\n') { + buf[1] = '\r'; + buf[2] = '\0'; + } + if (c == '\\' || c == '%') { + c = fgetc(fd); + switch (c) { + case 's': + outbuf = uts.sysname; + break; + case 'n': + outbuf = uts.nodename; + break; + case 'r': + outbuf = uts.release; + break; + case 'v': + outbuf = uts.version; + break; + case 'm': + outbuf = uts.machine; + break; + case 'D': + case 'o': + c = getdomainname(buf, sizeof(buf) - 1); + buf[c >= 0 ? c : 0] = '\0'; + break; + case 'd': + strftime(buf, sizeof(buf), fmtstr_d, localtime(&t)); + break; + case 't': + strftime(buf, sizeof(buf), fmtstr_t, localtime(&t)); + break; + case 'h': + gethostname(buf, sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; + break; + case 'l': + outbuf = tty; + break; + default: + buf[0] = c; + } + } + fputs(outbuf, stdout); + } + fclose(fd); + fflush(stdout); +} + +void print_login_prompt(void) +{ + char buf[MAXHOSTNAMELEN+1]; + + if (gethostname(buf, MAXHOSTNAMELEN) == 0) + fputs(buf, stdout); + + fputs(LOGIN, stdout); + fflush(stdout); +} diff --git a/libbb/loop.c b/libbb/loop.c new file mode 100644 index 000000000..14835ec24 --- /dev/null +++ b/libbb/loop.c @@ -0,0 +1,149 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * Copyright (C) 2005 by Rob Landley + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "libbb.h" + +/* For 2.6, use the cleaned up header to get the 64 bit API. */ +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) +#include +typedef struct loop_info64 bb_loop_info; +#define BB_LOOP_SET_STATUS LOOP_SET_STATUS64 +#define BB_LOOP_GET_STATUS LOOP_GET_STATUS64 + +/* For 2.4 and earlier, use the 32 bit API (and don't trust the headers) */ +#else +/* Stuff stolen from linux/loop.h for 2.4 and earlier kernels*/ +#include +#define LO_NAME_SIZE 64 +#define LO_KEY_SIZE 32 +#define LOOP_SET_FD 0x4C00 +#define LOOP_CLR_FD 0x4C01 +#define BB_LOOP_SET_STATUS 0x4C02 +#define BB_LOOP_GET_STATUS 0x4C03 +typedef struct { + int lo_number; + __kernel_dev_t lo_device; + unsigned long lo_inode; + __kernel_dev_t lo_rdevice; + int lo_offset; + int lo_encrypt_type; + int lo_encrypt_key_size; + int lo_flags; + char lo_file_name[LO_NAME_SIZE]; + unsigned char lo_encrypt_key[LO_KEY_SIZE]; + unsigned long lo_init[2]; + char reserved[4]; +} bb_loop_info; +#endif + +char *query_loop(const char *device) +{ + int fd; + bb_loop_info loopinfo; + char *dev = 0; + + fd = open(device, O_RDONLY); + if (fd < 0) return 0; + if (!ioctl(fd, BB_LOOP_GET_STATUS, &loopinfo)) + dev = xasprintf("%ld %s", (long) loopinfo.lo_offset, + (char *)loopinfo.lo_file_name); + close(fd); + + return dev; +} + + +int del_loop(const char *device) +{ + int fd, rc; + + fd = open(device, O_RDONLY); + if (fd < 0) return 1; + rc = ioctl(fd, LOOP_CLR_FD, 0); + close(fd); + + return rc; +} + +/* Returns 0 if mounted RW, 1 if mounted read-only, <0 for error. + *device is loop device to use, or if *device==NULL finds a loop device to + mount it on and sets *device to a strdup of that loop device name. This + search will re-use an existing loop device already bound to that + file/offset if it finds one. + */ +int set_loop(char **device, const char *file, unsigned long long offset) +{ + char dev[20], *try; + bb_loop_info loopinfo; + struct stat statbuf; + int i, dfd, ffd, mode, rc=-1; + + /* Open the file. Barf if this doesn't work. */ + mode = O_RDWR; + ffd = open(file, mode); + if (ffd < 0) { + mode = O_RDONLY; + ffd = open(file, mode); + if (ffd < 0) + return -errno; + } + + /* Find a loop device. */ + try = *device ? : dev; + for (i=0;rc;i++) { + sprintf(dev, LOOP_FORMAT, i); + + /* Ran out of block devices, return failure. */ + if (stat(try, &statbuf) || !S_ISBLK(statbuf.st_mode)) { + rc=-ENOENT; + break; + } + /* Open the sucker and check its loopiness. */ + dfd = open(try, mode); + if (dfd < 0 && errno == EROFS) { + mode = O_RDONLY; + dfd = open(try, mode); + } + if (dfd < 0) goto try_again; + + rc = ioctl(dfd, BB_LOOP_GET_STATUS, &loopinfo); + + /* If device free, claim it. */ + if (rc && errno == ENXIO) { + memset(&loopinfo, 0, sizeof(loopinfo)); + safe_strncpy((char *)loopinfo.lo_file_name, file, LO_NAME_SIZE); + loopinfo.lo_offset = offset; + /* Associate free loop device with file. */ + if (!ioctl(dfd, LOOP_SET_FD, ffd)) { + if (!ioctl(dfd, BB_LOOP_SET_STATUS, &loopinfo)) rc = 0; + else ioctl(dfd, LOOP_CLR_FD, 0); + } + + /* If this block device already set up right, re-use it. + (Yes this is racy, but associating two loop devices with the same + file isn't pretty either. In general, mounting the same file twice + without using losetup manually is problematic.) + */ + } else if (strcmp(file,(char *)loopinfo.lo_file_name) + || offset != loopinfo.lo_offset) { + rc = -1; + } + close(dfd); +try_again: + if (*device) break; + } + close(ffd); + if (!rc) { + if (!*device) *device = strdup(dev); + return mode==O_RDONLY ? 1 : 0; + } + return rc; +} diff --git a/libbb/make_directory.c b/libbb/make_directory.c new file mode 100644 index 000000000..fbec4e20e --- /dev/null +++ b/libbb/make_directory.c @@ -0,0 +1,104 @@ +/* vi: set sw=4 ts=4: */ +/* + * parse_mode implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* Mar 5, 2003 Manuel Novoa III + * + * This is the main work function for the 'mkdir' applet. As such, it + * strives to be SUSv3 compliant in it's behaviour when recursively + * making missing parent dirs, and in it's mode setting of the final + * directory 'path'. + * + * To recursively build all missing intermediate directories, make + * sure that (flags & FILEUTILS_RECUR) is non-zero. Newly created + * intermediate directories will have at least u+wx perms. + * + * To set specific permissions on 'path', pass the appropriate 'mode' + * val. Otherwise, pass -1 to get default permissions. + */ + +#include +#include +#include +#include "libbb.h" + +int bb_make_directory (char *path, long mode, int flags) +{ + mode_t mask; + const char *fail_msg; + char *s = path; + char c; + struct stat st; + + mask = umask(0); + if (mode == -1) { + umask(mask); + mode = (S_IXUSR | S_IXGRP | S_IXOTH | + S_IWUSR | S_IWGRP | S_IWOTH | + S_IRUSR | S_IRGRP | S_IROTH) & ~mask; + } else { + umask(mask & ~0300); + } + + do { + c = 0; + + if (flags & FILEUTILS_RECUR) { /* Get the parent. */ + /* Bypass leading non-'/'s and then subsequent '/'s. */ + while (*s) { + if (*s == '/') { + do { + ++s; + } while (*s == '/'); + c = *s; /* Save the current char */ + *s = 0; /* and replace it with nul. */ + break; + } + ++s; + } + } + + if (mkdir(path, 0777) < 0) { + /* If we failed for any other reason than the directory + * already exists, output a diagnostic and return -1.*/ + if (errno != EEXIST + || !(flags & FILEUTILS_RECUR) + || (stat(path, &st) < 0 || !S_ISDIR(st.st_mode))) { + fail_msg = "create"; + umask(mask); + break; + } + /* Since the directory exists, don't attempt to change + * permissions if it was the full target. Note that + * this is not an error conditon. */ + if (!c) { + umask(mask); + return 0; + } + } + + if (!c) { + /* Done. If necessary, updated perms on the newly + * created directory. Failure to update here _is_ + * an error.*/ + umask(mask); + if ((mode != -1) && (chmod(path, mode) < 0)){ + fail_msg = "set permissions of"; + break; + } + return 0; + } + + /* Remove any inserted nul from the path (recursive mode). */ + *s = c; + + } while (1); + + bb_perror_msg ("cannot %s directory '%s'", fail_msg, path); + return -1; +} diff --git a/libbb/makedev.c b/libbb/makedev.c new file mode 100644 index 000000000..4903e4783 --- /dev/null +++ b/libbb/makedev.c @@ -0,0 +1,20 @@ +/* + * Utility routines. + * + * Copyright (C) 2006 Denis Vlasenko + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +/* We do not include libbb.h - #define makedev() is there! */ +#include +#include + +#ifdef __GLIBC__ +/* At least glibc has horrendously large inline for this, so wrap it */ +/* uclibc people please check - do we need "&& !__UCLIBC__" above? */ +unsigned long long bb_makedev(unsigned int major, unsigned int minor) +{ + return makedev(major, minor); +} +#endif diff --git a/libbb/md5.c b/libbb/md5.c new file mode 100644 index 000000000..e672559cf --- /dev/null +++ b/libbb/md5.c @@ -0,0 +1,450 @@ +/* vi: set sw=4 ts=4: */ +/* + * md5.c - Compute MD5 checksum of strings according to the + * definition of MD5 in RFC 1321 from April 1992. + * + * Written by Ulrich Drepper , 1995. + * + * Copyright (C) 1995-1999 Free Software Foundation, Inc. + * Copyright (C) 2001 Manuel Novoa III + * Copyright (C) 2003 Glenn L. McGrath + * Copyright (C) 2003 Erik Andersen + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "libbb.h" + +#if CONFIG_MD5_SIZE_VS_SPEED < 0 || CONFIG_MD5_SIZE_VS_SPEED > 3 +# define MD5_SIZE_VS_SPEED 2 +#else +# define MD5_SIZE_VS_SPEED CONFIG_MD5_SIZE_VS_SPEED +#endif + +/* Initialize structure containing state of computation. + * (RFC 1321, 3.3: Step 3) + */ +void md5_begin(md5_ctx_t *ctx) +{ + ctx->A = 0x67452301; + ctx->B = 0xefcdab89; + ctx->C = 0x98badcfe; + ctx->D = 0x10325476; + + ctx->total = 0; + ctx->buflen = 0; +} + +/* These are the four functions used in the four steps of the MD5 algorithm + * and defined in the RFC 1321. The first function is a little bit optimized + * (as found in Colin Plumbs public domain implementation). + * #define FF(b, c, d) ((b & c) | (~b & d)) + */ +# define FF(b, c, d) (d ^ (b & (c ^ d))) +# define FG(b, c, d) FF (d, b, c) +# define FH(b, c, d) (b ^ c ^ d) +# define FI(b, c, d) (c ^ (b | ~d)) + +/* Hash a single block, 64 bytes long and 4-byte aligned. */ +static void md5_hash_block(const void *buffer, md5_ctx_t *ctx) +{ + uint32_t correct_words[16]; + const uint32_t *words = buffer; + +# if MD5_SIZE_VS_SPEED > 0 + static const uint32_t C_array[] = { + /* round 1 */ + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + /* round 2 */ + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + /* round 3 */ + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + /* round 4 */ + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 + }; + + static const char P_array[] = { +# if MD5_SIZE_VS_SPEED > 1 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* 1 */ +# endif /* MD5_SIZE_VS_SPEED > 1 */ + 1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, /* 2 */ + 5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2, /* 3 */ + 0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9 /* 4 */ + }; + +# if MD5_SIZE_VS_SPEED > 1 + static const char S_array[] = { + 7, 12, 17, 22, + 5, 9, 14, 20, + 4, 11, 16, 23, + 6, 10, 15, 21 + }; +# endif /* MD5_SIZE_VS_SPEED > 1 */ +# endif + + uint32_t A = ctx->A; + uint32_t B = ctx->B; + uint32_t C = ctx->C; + uint32_t D = ctx->D; + + /* Process all bytes in the buffer with 64 bytes in each round of + the loop. */ + uint32_t *cwp = correct_words; + uint32_t A_save = A; + uint32_t B_save = B; + uint32_t C_save = C; + uint32_t D_save = D; + +# if MD5_SIZE_VS_SPEED > 1 +# define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s))) + + const uint32_t *pc; + const char *pp; + const char *ps; + int i; + uint32_t temp; + + for (i = 0; i < 16; i++) { + cwp[i] = SWAP_LE32(words[i]); + } + words += 16; + +# if MD5_SIZE_VS_SPEED > 2 + pc = C_array; + pp = P_array; + ps = S_array - 4; + + for (i = 0; i < 64; i++) { + if ((i & 0x0f) == 0) + ps += 4; + temp = A; + switch (i >> 4) { + case 0: + temp += FF(B, C, D); + break; + case 1: + temp += FG(B, C, D); + break; + case 2: + temp += FH(B, C, D); + break; + case 3: + temp += FI(B, C, D); + } + temp += cwp[(int) (*pp++)] + *pc++; + CYCLIC(temp, ps[i & 3]); + temp += B; + A = D; + D = C; + C = B; + B = temp; + } +# else + pc = C_array; + pp = P_array; + ps = S_array; + + for (i = 0; i < 16; i++) { + temp = A + FF(B, C, D) + cwp[(int) (*pp++)] + *pc++; + CYCLIC(temp, ps[i & 3]); + temp += B; + A = D; + D = C; + C = B; + B = temp; + } + + ps += 4; + for (i = 0; i < 16; i++) { + temp = A + FG(B, C, D) + cwp[(int) (*pp++)] + *pc++; + CYCLIC(temp, ps[i & 3]); + temp += B; + A = D; + D = C; + C = B; + B = temp; + } + ps += 4; + for (i = 0; i < 16; i++) { + temp = A + FH(B, C, D) + cwp[(int) (*pp++)] + *pc++; + CYCLIC(temp, ps[i & 3]); + temp += B; + A = D; + D = C; + C = B; + B = temp; + } + ps += 4; + for (i = 0; i < 16; i++) { + temp = A + FI(B, C, D) + cwp[(int) (*pp++)] + *pc++; + CYCLIC(temp, ps[i & 3]); + temp += B; + A = D; + D = C; + C = B; + B = temp; + } + +# endif /* MD5_SIZE_VS_SPEED > 2 */ +# else + /* First round: using the given function, the context and a constant + the next context is computed. Because the algorithms processing + unit is a 32-bit word and it is determined to work on words in + little endian byte order we perhaps have to change the byte order + before the computation. To reduce the work for the next steps + we store the swapped words in the array CORRECT_WORDS. */ + +# define OP(a, b, c, d, s, T) \ + do \ + { \ + a += FF (b, c, d) + (*cwp++ = SWAP_LE32(*words)) + T; \ + ++words; \ + CYCLIC (a, s); \ + a += b; \ + } \ + while (0) + + /* It is unfortunate that C does not provide an operator for + cyclic rotation. Hope the C compiler is smart enough. */ + /* gcc 2.95.4 seems to be --aaronl */ +# define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s))) + + /* Before we start, one word to the strange constants. + They are defined in RFC 1321 as + + T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64 + */ + +# if MD5_SIZE_VS_SPEED == 1 + const uint32_t *pc; + const char *pp; + int i; +# endif /* MD5_SIZE_VS_SPEED */ + + /* Round 1. */ +# if MD5_SIZE_VS_SPEED == 1 + pc = C_array; + for (i = 0; i < 4; i++) { + OP(A, B, C, D, 7, *pc++); + OP(D, A, B, C, 12, *pc++); + OP(C, D, A, B, 17, *pc++); + OP(B, C, D, A, 22, *pc++); + } +# else + OP(A, B, C, D, 7, 0xd76aa478); + OP(D, A, B, C, 12, 0xe8c7b756); + OP(C, D, A, B, 17, 0x242070db); + OP(B, C, D, A, 22, 0xc1bdceee); + OP(A, B, C, D, 7, 0xf57c0faf); + OP(D, A, B, C, 12, 0x4787c62a); + OP(C, D, A, B, 17, 0xa8304613); + OP(B, C, D, A, 22, 0xfd469501); + OP(A, B, C, D, 7, 0x698098d8); + OP(D, A, B, C, 12, 0x8b44f7af); + OP(C, D, A, B, 17, 0xffff5bb1); + OP(B, C, D, A, 22, 0x895cd7be); + OP(A, B, C, D, 7, 0x6b901122); + OP(D, A, B, C, 12, 0xfd987193); + OP(C, D, A, B, 17, 0xa679438e); + OP(B, C, D, A, 22, 0x49b40821); +# endif /* MD5_SIZE_VS_SPEED == 1 */ + + /* For the second to fourth round we have the possibly swapped words + in CORRECT_WORDS. Redefine the macro to take an additional first + argument specifying the function to use. */ +# undef OP +# define OP(f, a, b, c, d, k, s, T) \ + do \ + { \ + a += f (b, c, d) + correct_words[k] + T; \ + CYCLIC (a, s); \ + a += b; \ + } \ + while (0) + + /* Round 2. */ +# if MD5_SIZE_VS_SPEED == 1 + pp = P_array; + for (i = 0; i < 4; i++) { + OP(FG, A, B, C, D, (int) (*pp++), 5, *pc++); + OP(FG, D, A, B, C, (int) (*pp++), 9, *pc++); + OP(FG, C, D, A, B, (int) (*pp++), 14, *pc++); + OP(FG, B, C, D, A, (int) (*pp++), 20, *pc++); + } +# else + OP(FG, A, B, C, D, 1, 5, 0xf61e2562); + OP(FG, D, A, B, C, 6, 9, 0xc040b340); + OP(FG, C, D, A, B, 11, 14, 0x265e5a51); + OP(FG, B, C, D, A, 0, 20, 0xe9b6c7aa); + OP(FG, A, B, C, D, 5, 5, 0xd62f105d); + OP(FG, D, A, B, C, 10, 9, 0x02441453); + OP(FG, C, D, A, B, 15, 14, 0xd8a1e681); + OP(FG, B, C, D, A, 4, 20, 0xe7d3fbc8); + OP(FG, A, B, C, D, 9, 5, 0x21e1cde6); + OP(FG, D, A, B, C, 14, 9, 0xc33707d6); + OP(FG, C, D, A, B, 3, 14, 0xf4d50d87); + OP(FG, B, C, D, A, 8, 20, 0x455a14ed); + OP(FG, A, B, C, D, 13, 5, 0xa9e3e905); + OP(FG, D, A, B, C, 2, 9, 0xfcefa3f8); + OP(FG, C, D, A, B, 7, 14, 0x676f02d9); + OP(FG, B, C, D, A, 12, 20, 0x8d2a4c8a); +# endif /* MD5_SIZE_VS_SPEED == 1 */ + + /* Round 3. */ +# if MD5_SIZE_VS_SPEED == 1 + for (i = 0; i < 4; i++) { + OP(FH, A, B, C, D, (int) (*pp++), 4, *pc++); + OP(FH, D, A, B, C, (int) (*pp++), 11, *pc++); + OP(FH, C, D, A, B, (int) (*pp++), 16, *pc++); + OP(FH, B, C, D, A, (int) (*pp++), 23, *pc++); + } +# else + OP(FH, A, B, C, D, 5, 4, 0xfffa3942); + OP(FH, D, A, B, C, 8, 11, 0x8771f681); + OP(FH, C, D, A, B, 11, 16, 0x6d9d6122); + OP(FH, B, C, D, A, 14, 23, 0xfde5380c); + OP(FH, A, B, C, D, 1, 4, 0xa4beea44); + OP(FH, D, A, B, C, 4, 11, 0x4bdecfa9); + OP(FH, C, D, A, B, 7, 16, 0xf6bb4b60); + OP(FH, B, C, D, A, 10, 23, 0xbebfbc70); + OP(FH, A, B, C, D, 13, 4, 0x289b7ec6); + OP(FH, D, A, B, C, 0, 11, 0xeaa127fa); + OP(FH, C, D, A, B, 3, 16, 0xd4ef3085); + OP(FH, B, C, D, A, 6, 23, 0x04881d05); + OP(FH, A, B, C, D, 9, 4, 0xd9d4d039); + OP(FH, D, A, B, C, 12, 11, 0xe6db99e5); + OP(FH, C, D, A, B, 15, 16, 0x1fa27cf8); + OP(FH, B, C, D, A, 2, 23, 0xc4ac5665); +# endif /* MD5_SIZE_VS_SPEED == 1 */ + + /* Round 4. */ +# if MD5_SIZE_VS_SPEED == 1 + for (i = 0; i < 4; i++) { + OP(FI, A, B, C, D, (int) (*pp++), 6, *pc++); + OP(FI, D, A, B, C, (int) (*pp++), 10, *pc++); + OP(FI, C, D, A, B, (int) (*pp++), 15, *pc++); + OP(FI, B, C, D, A, (int) (*pp++), 21, *pc++); + } +# else + OP(FI, A, B, C, D, 0, 6, 0xf4292244); + OP(FI, D, A, B, C, 7, 10, 0x432aff97); + OP(FI, C, D, A, B, 14, 15, 0xab9423a7); + OP(FI, B, C, D, A, 5, 21, 0xfc93a039); + OP(FI, A, B, C, D, 12, 6, 0x655b59c3); + OP(FI, D, A, B, C, 3, 10, 0x8f0ccc92); + OP(FI, C, D, A, B, 10, 15, 0xffeff47d); + OP(FI, B, C, D, A, 1, 21, 0x85845dd1); + OP(FI, A, B, C, D, 8, 6, 0x6fa87e4f); + OP(FI, D, A, B, C, 15, 10, 0xfe2ce6e0); + OP(FI, C, D, A, B, 6, 15, 0xa3014314); + OP(FI, B, C, D, A, 13, 21, 0x4e0811a1); + OP(FI, A, B, C, D, 4, 6, 0xf7537e82); + OP(FI, D, A, B, C, 11, 10, 0xbd3af235); + OP(FI, C, D, A, B, 2, 15, 0x2ad7d2bb); + OP(FI, B, C, D, A, 9, 21, 0xeb86d391); +# endif /* MD5_SIZE_VS_SPEED == 1 */ +# endif /* MD5_SIZE_VS_SPEED > 1 */ + + /* Add the starting values of the context. */ + A += A_save; + B += B_save; + C += C_save; + D += D_save; + + /* Put checksum in context given as argument. */ + ctx->A = A; + ctx->B = B; + ctx->C = C; + ctx->D = D; +} + +/* Feed data through a temporary buffer to call md5_hash_aligned_block() + * with chunks of data that are 4-byte aligned and a multiple of 64 bytes. + * This function's internal buffer remembers previous data until it has 64 + * bytes worth to pass on. Call md5_end() to flush this buffer. */ + +void md5_hash(const void *buffer, size_t len, md5_ctx_t *ctx) +{ + char *buf=(char *)buffer; + + /* RFC 1321 specifies the possible length of the file up to 2^64 bits, + * Here we only track the number of bytes. */ + + ctx->total += len; + + // Process all input. + + while (len) { + int i = 64 - ctx->buflen; + + // Copy data into aligned buffer. + + if (i > len) i = len; + memcpy(ctx->buffer + ctx->buflen, buf, i); + len -= i; + ctx->buflen += i; + buf += i; + + // When buffer fills up, process it. + + if (ctx->buflen == 64) { + md5_hash_block(ctx->buffer, ctx); + ctx->buflen = 0; + } + } +} + +/* Process the remaining bytes in the buffer and put result from CTX + * in first 16 bytes following RESBUF. The result is always in little + * endian byte order, so that a byte-wise output yields to the wanted + * ASCII representation of the message digest. + * + * IMPORTANT: On some systems it is required that RESBUF is correctly + * aligned for a 32 bits value. + */ +void *md5_end(void *resbuf, md5_ctx_t *ctx) +{ + char *buf = ctx->buffer; + int i; + + /* Pad data to block size. */ + + buf[ctx->buflen++] = 0x80; + memset(buf + ctx->buflen, 0, 128 - ctx->buflen); + + /* Put the 64-bit file length in *bits* at the end of the buffer. */ + ctx->total <<= 3; + if (ctx->buflen > 56) buf += 64; + for (i = 0; i < 8; i++) buf[56 + i] = ctx->total >> (i*8); + + /* Process last bytes. */ + if (buf != ctx->buffer) md5_hash_block(ctx->buffer, ctx); + md5_hash_block(buf, ctx); + + /* Put result from CTX in first 16 bytes following RESBUF. The result is + * always in little endian byte order, so that a byte-wise output yields + * to the wanted ASCII representation of the message digest. + * + * IMPORTANT: On some systems it is required that RESBUF is correctly + * aligned for a 32 bits value. + */ + ((uint32_t *) resbuf)[0] = SWAP_LE32(ctx->A); + ((uint32_t *) resbuf)[1] = SWAP_LE32(ctx->B); + ((uint32_t *) resbuf)[2] = SWAP_LE32(ctx->C); + ((uint32_t *) resbuf)[3] = SWAP_LE32(ctx->D); + + return resbuf; +} + diff --git a/libbb/messages.c b/libbb/messages.c new file mode 100644 index 000000000..c640faf5b --- /dev/null +++ b/libbb/messages.c @@ -0,0 +1,53 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +#ifndef BB_EXTRA_VERSION +#define BANNER "BusyBox v" BB_VER " (" BB_BT ")" +#else +#define BANNER "BusyBox v" BB_VER " (" BB_EXTRA_VERSION ")" +#endif +const char BB_BANNER[] = BANNER; +const char bb_msg_full_version[] = BANNER " multi-call binary"; + +const char bb_msg_memory_exhausted[] = "memory exhausted"; +const char bb_msg_invalid_date[] = "invalid date '%s'"; +const char bb_msg_write_error[] = "write error"; +const char bb_msg_read_error[] = "read error"; +const char bb_msg_unknown[] = "(unknown)"; +const char bb_msg_can_not_create_raw_socket[] = "can't create raw socket"; +const char bb_msg_perm_denied_are_you_root[] = "permission denied. (are you root?)"; +const char bb_msg_requires_arg[] = "%s requires an argument"; +const char bb_msg_invalid_arg[] = "invalid argument '%s' to '%s'"; +const char bb_msg_standard_input[] = "standard input"; +const char bb_msg_standard_output[] = "standard output"; + +const char bb_str_default[] = "default"; + +const char bb_path_passwd_file[] = "/etc/passwd"; +const char bb_path_shadow_file[] = "/etc/shadow"; +const char bb_path_group_file[] = "/etc/group"; +const char bb_path_gshadow_file[] = "/etc/gshadow"; +const char bb_path_nologin_file[] = "/etc/nologin"; +const char bb_path_securetty_file[] = "/etc/securetty"; +const char bb_path_motd_file[] = "/etc/motd"; +const char bb_default_login_shell[] = LIBBB_DEFAULT_LOGIN_SHELL; +const char bb_dev_null[] = "/dev/null"; + +#include +/* This is usually something like "/var/adm/wtmp" or "/var/log/wtmp" */ +const char bb_path_wtmp_file[] = +#if defined _PATH_WTMP +_PATH_WTMP; +#elif defined WTMP_FILE +WTMP_FILE; +#else +# error unknown path to wtmp file +#endif + +char bb_common_bufsiz1[BUFSIZ+1]; diff --git a/libbb/mode_string.c b/libbb/mode_string.c new file mode 100644 index 000000000..01029bfee --- /dev/null +++ b/libbb/mode_string.c @@ -0,0 +1,128 @@ +/* vi: set sw=4 ts=4: */ +/* + * mode_string implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* Aug 13, 2003 + * Fix a bug reported by junkio@cox.net involving the mode_chars index. + */ + + +#include +#include + +#include "libbb.h" + +#if ( S_ISUID != 04000 ) || ( S_ISGID != 02000 ) || ( S_ISVTX != 01000 ) \ + || ( S_IRUSR != 00400 ) || ( S_IWUSR != 00200 ) || ( S_IXUSR != 00100 ) \ + || ( S_IRGRP != 00040 ) || ( S_IWGRP != 00020 ) || ( S_IXGRP != 00010 ) \ + || ( S_IROTH != 00004 ) || ( S_IWOTH != 00002 ) || ( S_IXOTH != 00001 ) +#error permission bitflag value assumption(s) violated! +#endif + +#if ( S_IFSOCK!= 0140000 ) || ( S_IFLNK != 0120000 ) \ + || ( S_IFREG != 0100000 ) || ( S_IFBLK != 0060000 ) \ + || ( S_IFDIR != 0040000 ) || ( S_IFCHR != 0020000 ) \ + || ( S_IFIFO != 0010000 ) +#warning mode type bitflag value assumption(s) violated! falling back to larger version + +#if (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISVTX) == 07777 +#undef mode_t +#define mode_t unsigned short +#endif + +static const mode_t mode_flags[] = { + S_IRUSR, S_IWUSR, S_IXUSR, S_ISUID, + S_IRGRP, S_IWGRP, S_IXGRP, S_ISGID, + S_IROTH, S_IWOTH, S_IXOTH, S_ISVTX +}; + +/* The static const char arrays below are duplicated for the two cases + * because moving them ahead of the mode_flags declaration cause a text + * size increase with the gcc version I'm using. */ + +/* The previous version used "0pcCd?bB-?l?s???". However, the '0', 'C', + * and 'B' types don't appear to be available on linux. So I removed them. */ +static const char type_chars[16] = "?pc?d?b?-?l?s???"; +/* 0123456789abcdef */ +static const char mode_chars[7] = "rwxSTst"; + +const char *bb_mode_string(int mode) +{ + static char buf[12]; + char *p = buf; + + int i, j, k; + + *p = type_chars[ (mode >> 12) & 0xf ]; + i = 0; + do { + j = k = 0; + do { + *++p = '-'; + if (mode & mode_flags[i+j]) { + *p = mode_chars[j]; + k = j; + } + } while (++j < 3); + if (mode & mode_flags[i+j]) { + *p = mode_chars[3 + (k & 2) + ((i&8) >> 3)]; + } + i += 4; + } while (i < 12); + + /* Note: We don't bother with nul termination because bss initialization + * should have taken care of that for us. If the user scribbled in buf + * memory, they deserve whatever happens. But we'll at least assert. */ + assert(buf[10] == 0); + + return buf; +} + +#else + +/* The previous version used "0pcCd?bB-?l?s???". However, the '0', 'C', + * and 'B' types don't appear to be available on linux. So I removed them. */ +static const char type_chars[16] = "?pc?d?b?-?l?s???"; +/* 0123456789abcdef */ +static const char mode_chars[7] = "rwxSTst"; + +const char *bb_mode_string(int mode) +{ + static char buf[12]; + char *p = buf; + + int i, j, k, m; + + *p = type_chars[ (mode >> 12) & 0xf ]; + i = 0; + m = 0400; + do { + j = k = 0; + do { + *++p = '-'; + if (mode & m) { + *p = mode_chars[j]; + k = j; + } + m >>= 1; + } while (++j < 3); + ++i; + if (mode & (010000 >> i)) { + *p = mode_chars[3 + (k & 2) + (i == 3)]; + } + } while (i < 3); + + /* Note: We don't bother with nul termination because bss initialization + * should have taken care of that for us. If the user scribbled in buf + * memory, they deserve whatever happens. But we'll at least assert. */ + assert(buf[10] == 0); + + return buf; +} + +#endif diff --git a/libbb/mtab.c b/libbb/mtab.c new file mode 100644 index 000000000..18386efb5 --- /dev/null +++ b/libbb/mtab.c @@ -0,0 +1,52 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "libbb.h" + +#if ENABLE_FEATURE_MTAB_SUPPORT +void erase_mtab(const char *name) +{ + struct mntent *entries = NULL; + int i, count = 0; + FILE *mountTable; + struct mntent *m; + + mountTable = setmntent(bb_path_mtab_file, "r"); + /* Bummer. Fall back on trying the /proc filesystem */ + if (!mountTable) mountTable = setmntent("/proc/mounts", "r"); + if (!mountTable) { + bb_perror_msg(bb_path_mtab_file); + return; + } + + while ((m = getmntent(mountTable)) != 0) { + i = count++; + entries = xrealloc(entries, count * sizeof(entries[0])); + entries[i].mnt_fsname = xstrdup(m->mnt_fsname); + entries[i].mnt_dir = xstrdup(m->mnt_dir); + entries[i].mnt_type = xstrdup(m->mnt_type); + entries[i].mnt_opts = xstrdup(m->mnt_opts); + entries[i].mnt_freq = m->mnt_freq; + entries[i].mnt_passno = m->mnt_passno; + } + endmntent(mountTable); + + mountTable = setmntent(bb_path_mtab_file, "w"); + if (mountTable) { + for (i = 0; i < count; i++) { + if (strcmp(entries[i].mnt_fsname, name) != 0 + && strcmp(entries[i].mnt_dir, name) != 0) + addmntent(mountTable, &entries[i]); + } + endmntent(mountTable); + } else if (errno != EROFS) + bb_perror_msg(bb_path_mtab_file); +} +#endif diff --git a/libbb/mtab_file.c b/libbb/mtab_file.c new file mode 100644 index 000000000..67367e3d7 --- /dev/null +++ b/libbb/mtab_file.c @@ -0,0 +1,17 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "libbb.h" + + +/* Busybox mount uses either /proc/mounts or /etc/mtab to + * get the list of currently mounted filesystems */ +const char bb_path_mtab_file[] = +USE_FEATURE_MTAB_SUPPORT("/etc/mtab")SKIP_FEATURE_MTAB_SUPPORT("/proc/mounts"); diff --git a/libbb/obscure.c b/libbb/obscure.c new file mode 100644 index 000000000..2599095df --- /dev/null +++ b/libbb/obscure.c @@ -0,0 +1,170 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini weak password checker implementation for busybox + * + * Copyright (C) 2006 Tito Ragusa + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* A good password: + 1) should contain at least six characters (man passwd); + 2) empty passwords are not permitted; + 3) should contain a mix of four different types of characters + upper case letters, + lower case letters, + numbers, + special characters such as !@#$%^&*,;". + This password types should not be permitted: + a) pure numbers: birthdates, social security number, license plate, phone numbers; + b) words and all letters only passwords (uppercase, lowercase or mixed) + as palindromes, consecutive or repetitive letters + or adjacent letters on your keyboard; + c) username, real name, company name or (e-mail?) address + in any form (as-is, reversed, capitalized, doubled, etc.). + (we can check only against username, gecos and hostname) + d) common and obvious letter-number replacements + (e.g. replace the letter O with number 0) + such as "M1cr0$0ft" or "P@ssw0rd" (CAVEAT: we cannot check for them + without the use of a dictionary). + + For each missing type of characters an increase of password length is + requested. + + If user is root we warn only. + + CAVEAT: some older versions of crypt() truncates passwords to 8 chars, + so that aaaaaaaa1Q$ is equal to aaaaaaaa making it possible to fool + some of our checks. We don't test for this special case as newer versions + of crypt do not truncate passwords. +*/ + +#include "libbb.h" + +static int string_checker_helper(const char *p1, const char *p2) __attribute__ ((__pure__)); + +static int string_checker_helper(const char *p1, const char *p2) +{ + /* as-is or capitalized */ + if (strcasecmp(p1, p2) == 0 + /* as sub-string */ + || strcasestr(p2, p1) != NULL + /* invert in case haystack is shorter than needle */ + || strcasestr(p1, p2) != NULL) + return 1; + return 0; +} + +static int string_checker(const char *p1, const char *p2) +{ + int size; + /* check string */ + int ret = string_checker_helper(p1, p2); + /* Make our own copy */ + char *p = xstrdup(p1); + /* reverse string */ + size = strlen(p); + + while (size--) { + *p = p1[size]; + p++; + } + /* restore pointer */ + p -= strlen(p1); + /* check reversed string */ + ret |= string_checker_helper(p, p2); + /* clean up */ + memset(p, 0, strlen(p1)); + free(p); + return ret; +} + +#define LOWERCASE 1 +#define UPPERCASE 2 +#define NUMBERS 4 +#define SPECIAL 8 + +static const char *obscure_msg(const char *old_p, const char *new_p, const struct passwd *pw) +{ + int i; + int c; + int length; + int mixed = 0; + /* Add 2 for each type of characters to the minlen of password */ + int size = CONFIG_PASSWORD_MINLEN + 8; + const char *p; + char hostname[255]; + + /* size */ + if (!new_p || (length = strlen(new_p)) < CONFIG_PASSWORD_MINLEN) + return "too short"; + + /* no username as-is, as sub-string, reversed, capitalized, doubled */ + if (string_checker(new_p, pw->pw_name)) { + return "similar to username"; + } + /* no gecos as-is, as sub-string, reversed, capitalized, doubled */ + if (*pw->pw_gecos && string_checker(new_p, pw->pw_gecos)) { + return "similar to gecos"; + } + /* hostname as-is, as sub-string, reversed, capitalized, doubled */ + if (gethostname(hostname, 255) == 0) { + hostname[254] = '\0'; + if (string_checker(new_p, hostname)) { + return "similar to hostname"; + } + } + + /* Should / Must contain a mix of: */ + for (i = 0; i < length; i++) { + if (islower(new_p[i])) { /* a-z */ + mixed |= LOWERCASE; + } else if (isupper(new_p[i])) { /* A-Z */ + mixed |= UPPERCASE; + } else if (isdigit(new_p[i])) { /* 0-9 */ + mixed |= NUMBERS; + } else { /* special characters */ + mixed |= SPECIAL; + } + /* More than 50% similar characters ? */ + c = 0; + p = new_p; + while (1) { + if ((p = strchr(p, new_p[i])) == NULL) { + break; + } + c++; + if (!++p) { + break; /* move past the matched char if possible */ + } + } + + if (c >= (length / 2)) { + return "too many similar characters"; + } + } + for (i=0; i<4; i++) + if (mixed & (1< + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* http://www.opengroup.org/onlinepubs/007904975/utilities/chmod.html */ + +#include +#include +#include +#include "libbb.h" + +#define FILEMODEBITS (S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO) + +int bb_parse_mode(const char *s, mode_t *current_mode) +{ + static const mode_t who_mask[] = { + S_ISUID | S_ISGID | S_ISVTX | S_IRWXU | S_IRWXG | S_IRWXO, /* a */ + S_ISUID | S_IRWXU, /* u */ + S_ISGID | S_IRWXG, /* g */ + S_IRWXO /* o */ + }; + + static const mode_t perm_mask[] = { + S_IRUSR | S_IRGRP | S_IROTH, /* r */ + S_IWUSR | S_IWGRP | S_IWOTH, /* w */ + S_IXUSR | S_IXGRP | S_IXOTH, /* x */ + S_IXUSR | S_IXGRP | S_IXOTH, /* X -- special -- see below */ + S_ISUID | S_ISGID, /* s */ + S_ISVTX /* t */ + }; + + static const char who_chars[] = "augo"; + static const char perm_chars[] = "rwxXst"; + + const char *p; + + mode_t wholist; + mode_t permlist; + mode_t mask; + mode_t new_mode; + char op; + + assert(s); + + if (((unsigned int)(*s - '0')) < 8) { + unsigned long tmp; + char *e; + + tmp = strtol(s, &e, 8); + if (*e || (tmp > 07777U)) { /* Check range and trailing chars. */ + return 0; + } + *current_mode = tmp; + return 1; + } + + mask = umask(0); + umask(mask); + + new_mode = *current_mode; + + /* Note: We allow empty clauses, and hence empty modes. + * We treat an empty mode as no change to perms. */ + + while (*s) { /* Process clauses. */ + + if (*s == ',') { /* We allow empty clauses. */ + ++s; + continue; + } + + /* Get a wholist. */ + wholist = 0; + + WHO_LIST: + p = who_chars; + do { + if (*p == *s) { + wholist |= who_mask[(int)(p-who_chars)]; + if (!*++s) { + return 0; + } + goto WHO_LIST; + } + } while (*++p); + + do { /* Process action list. */ + if ((*s != '+') && (*s != '-')) { + if (*s != '=') { + return 0; + } + /* Since op is '=', clear all bits corresponding to the + * wholist, of all file bits if wholist is empty. */ + permlist = ~FILEMODEBITS; + if (wholist) { + permlist = ~wholist; + } + new_mode &= permlist; + } + op = *s++; + + /* Check for permcopy. */ + p = who_chars + 1; /* Skip 'a' entry. */ + do { + if (*p == *s) { + int i = 0; + permlist = who_mask[(int)(p-who_chars)] + & (S_IRWXU | S_IRWXG | S_IRWXO) + & new_mode; + do { + if (permlist & perm_mask[i]) { + permlist |= perm_mask[i]; + } + } while (++i < 3); + ++s; + goto GOT_ACTION; + } + } while (*++p); + + /* It was not a permcopy, so get a permlist. */ + permlist = 0; + + PERM_LIST: + p = perm_chars; + do { + if (*p == *s) { + if ((*p != 'X') + || (new_mode & (S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH)) + ) { + permlist |= perm_mask[(int)(p-perm_chars)]; + } + if (!*++s) { + break; + } + goto PERM_LIST; + } + } while (*++p); + + GOT_ACTION: + if (permlist) { /* The permlist was nonempty. */ + mode_t tmp = ~mask; + if (wholist) { + tmp = wholist; + } + permlist &= tmp; + + if (op == '-') { + new_mode &= ~permlist; + } else { + new_mode |= permlist; + } + } + } while (*s && (*s != ',')); + } + + *current_mode = new_mode; + + return 1; +} diff --git a/libbb/perror_msg.c b/libbb/perror_msg.c new file mode 100644 index 000000000..7fb0830be --- /dev/null +++ b/libbb/perror_msg.c @@ -0,0 +1,23 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include "libbb.h" + +void bb_perror_msg(const char *s, ...) +{ + va_list p; + + va_start(p, s); + bb_vperror_msg(s, p); + va_end(p); +} diff --git a/libbb/perror_msg_and_die.c b/libbb/perror_msg_and_die.c new file mode 100644 index 000000000..2303ba211 --- /dev/null +++ b/libbb/perror_msg_and_die.c @@ -0,0 +1,26 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include "libbb.h" + +void bb_perror_msg_and_die(const char *s, ...) +{ + va_list p; + + va_start(p, s); + bb_vperror_msg(s, p); + va_end(p); + if (die_sleep) + sleep(die_sleep); + exit(xfunc_error_retval); +} diff --git a/libbb/perror_nomsg.c b/libbb/perror_nomsg.c new file mode 100644 index 000000000..3aefd5301 --- /dev/null +++ b/libbb/perror_nomsg.c @@ -0,0 +1,16 @@ +/* vi: set sw=4 ts=4: */ +/* + * bb_perror_nomsg implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +void bb_perror_nomsg(void) +{ + /* Ignore the gcc warning about a null format string. */ + bb_perror_msg(NULL); +} diff --git a/libbb/perror_nomsg_and_die.c b/libbb/perror_nomsg_and_die.c new file mode 100644 index 000000000..e5623c2a9 --- /dev/null +++ b/libbb/perror_nomsg_and_die.c @@ -0,0 +1,17 @@ +/* vi: set sw=4 ts=4: */ +/* + * bb_perror_nomsg_and_die implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "libbb.h" + +void bb_perror_nomsg_and_die(void) +{ + /* Ignore the gcc warning about a null format string. */ + bb_perror_msg_and_die(NULL); +} diff --git a/libbb/process_escape_sequence.c b/libbb/process_escape_sequence.c new file mode 100644 index 000000000..138e751f5 --- /dev/null +++ b/libbb/process_escape_sequence.c @@ -0,0 +1,89 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) Manuel Novoa III + * and Vladimir Oleynik + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include "libbb.h" + +#define WANT_HEX_ESCAPES 1 + +/* Usual "this only works for ascii compatible encodings" disclaimer. */ +#undef _tolower +#define _tolower(X) ((X)|((char) 0x20)) + +char bb_process_escape_sequence(const char **ptr) +{ + static const char charmap[] = { + 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', 0, + '\a', '\b', '\f', '\n', '\r', '\t', '\v', '\\', '\\' }; + + const char *p; + const char *q; + unsigned int num_digits; + unsigned int r; + unsigned int n; + unsigned int d; + unsigned int base; + + num_digits = n = 0; + base = 8; + q = *ptr; + +#ifdef WANT_HEX_ESCAPES + if (*q == 'x') { + ++q; + base = 16; + ++num_digits; + } +#endif + + do { + d = (unsigned int)(*q - '0'); +#ifdef WANT_HEX_ESCAPES + if (d >= 10) { + d = ((unsigned int)(_tolower(*q) - 'a')) + 10; + } +#endif + + if (d >= base) { +#ifdef WANT_HEX_ESCAPES + if ((base == 16) && (!--num_digits)) { +/* return '\\'; */ + --q; + } +#endif + break; + } + + r = n * base + d; + if (r > UCHAR_MAX) { + break; + } + + n = r; + ++q; + } while (++num_digits < 3); + + if (num_digits == 0) { /* mnemonic escape sequence? */ + p = charmap; + do { + if (*p == *q) { + q++; + break; + } + } while (*++p); + n = *(p+(sizeof(charmap)/2)); + } + + *ptr = q; + + return (char) n; +} diff --git a/libbb/procps.c b/libbb/procps.c new file mode 100644 index 000000000..017710ff4 --- /dev/null +++ b/libbb/procps.c @@ -0,0 +1,255 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright 1998 by Albert Cahalan; all rights reserved. + * Copyright (C) 2002 by Vladimir Oleynik + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + + +typedef struct unsigned_to_name_map_t { + unsigned id; + char name[12]; +} unsigned_to_name_map_t; + +typedef struct cache_t { + unsigned_to_name_map_t *cache; + int size; +} cache_t; + +static cache_t username, groupname; + +static void clear_cache(cache_t *cp) +{ + free(cp->cache); + cp->cache = NULL; + cp->size = 0; +} +void clear_username_cache(void) +{ + clear_cache(&username); + clear_cache(&groupname); +} + +#if 0 /* more generic, but we don't need that yet */ +/* Returns -N-1 if not found. */ +/* cp->cache[N] is allocated and must be filled in this case */ +static int get_cached(cache_t *cp, unsigned id) +{ + int i; + for (i = 0; i < cp->size; i++) + if (cp->cache[i].id == id) + return i; + i = cp->size++; + cp->cache = xrealloc(cp->cache, cp->size * sizeof(*cp->cache)); + cp->cache[i++].id = id; + return -i; +} +#endif + +typedef char* ug_func(char *name, long uid, int bufsize); +static char* get_cached(cache_t *cp, unsigned id, ug_func* fp) +{ + int i; + for (i = 0; i < cp->size; i++) + if (cp->cache[i].id == id) + return cp->cache[i].name; + i = cp->size++; + cp->cache = xrealloc(cp->cache, cp->size * sizeof(*cp->cache)); + cp->cache[i].id = id; + fp(cp->cache[i].name, id, sizeof(cp->cache[i].name)); + return cp->cache[i].name; +} +const char* get_cached_username(uid_t uid) +{ + return get_cached(&username, uid, bb_getpwuid); +} +const char* get_cached_groupname(gid_t gid) +{ + return get_cached(&groupname, gid, bb_getgrgid); +} + + +#define PROCPS_BUFSIZE 1024 + +static int read_to_buf(const char *filename, void *buf) +{ + ssize_t ret; + ret = open_read_close(filename, buf, PROCPS_BUFSIZE-1); + ((char *)buf)[ret > 0 ? ret : 0] = '\0'; + return ret; +} + +procps_status_t* alloc_procps_scan(int flags) +{ + procps_status_t* sp = xzalloc(sizeof(procps_status_t)); + sp->dir = xopendir("/proc"); + return sp; +} + +void free_procps_scan(procps_status_t* sp) +{ + closedir(sp->dir); + free(sp->cmd); + free(sp); +} + +void BUG_comm_size(void); +procps_status_t* procps_scan(procps_status_t* sp, int flags) +{ + struct dirent *entry; + char buf[PROCPS_BUFSIZE]; + char filename[sizeof("/proc//cmdline") + sizeof(int)*3]; + char *filename_tail; + long tasknice; + unsigned pid; + int n; + struct stat sb; + + if (!sp) + sp = alloc_procps_scan(flags); + + for (;;) { + entry = readdir(sp->dir); + if (entry == NULL) { + free_procps_scan(sp); + return NULL; + } + pid = bb_strtou(entry->d_name, NULL, 10); + if (errno) + continue; + + /* After this point we have to break, not continue + * ("continue" would mean that current /proc/NNN + * is not a valid process info) */ + + memset(&sp->rss, 0, sizeof(*sp) - offsetof(procps_status_t, rss)); + + sp->pid = pid; + if (!(flags & ~PSSCAN_PID)) break; + + filename_tail = filename + sprintf(filename, "/proc/%d", pid); + + if (flags & PSSCAN_UIDGID) { + if (stat(filename, &sb)) + break; + /* Need comment - is this effective or read UID/GID? */ + sp->uid = sb.st_uid; + sp->gid = sb.st_gid; + } + + if (flags & PSSCAN_STAT) { + char *cp; + /* see proc(5) for some details on this */ + strcpy(filename_tail, "/stat"); + n = read_to_buf(filename, buf); + if (n < 0) + break; + cp = strrchr(buf, ')'); /* split into "PID (cmd" and "" */ + if (!cp || cp[1] != ' ') + break; + cp[0] = '\0'; + if (sizeof(sp->comm) < 16) + BUG_comm_size(); + sscanf(buf, "%*s (%15c", sp->comm); + n = sscanf(cp+2, + "%c %u " /* state, ppid */ + "%u %u %*s %*s " /* pgid, sid, tty, tpgid */ + "%*s %*s %*s %*s %*s " /* flags, min_flt, cmin_flt, maj_flt, cmaj_flt */ + "%lu %lu " /* utime, stime */ + "%*s %*s %*s " /* cutime, cstime, priority */ + "%ld " /* nice */ + "%*s %*s %*s " /* timeout, it_real_value, start_time */ + "%*s " /* vsize */ + "%lu", /* rss */ + sp->state, &sp->ppid, + &sp->pgid, &sp->sid, + &sp->utime, &sp->stime, + &tasknice, + &sp->rss); + if (n != 8) + break; + + if (sp->rss == 0 && sp->state[0] != 'Z') + sp->state[1] = 'W'; + else + sp->state[1] = ' '; + if (tasknice < 0) + sp->state[2] = '<'; + else if (tasknice > 0) + sp->state[2] = 'N'; + else + sp->state[2] = ' '; + +#ifdef PAGE_SHIFT + sp->rss <<= (PAGE_SHIFT - 10); /* 2**10 = 1kb */ +#else + sp->rss *= (getpagesize() >> 10); /* 2**10 = 1kb */ +#endif + } + + if (flags & PSSCAN_CMD) { + free(sp->cmd); + sp->cmd = NULL; + strcpy(filename_tail, "/cmdline"); + n = read_to_buf(filename, buf); + if (n <= 0) + break; + if (buf[n-1] == '\n') { + if (!--n) + break; + buf[n] = '\0'; + } + do { + n--; + if ((unsigned char)(buf[n]) < ' ') + buf[n] = ' '; + } while (n); + sp->cmd = strdup(buf); + } + break; + } + return sp; +} +/* from kernel: + // pid comm S ppid pgid sid tty_nr tty_pgrp flg + sprintf(buffer,"%d (%s) %c %d %d %d %d %d %lu %lu \ +%lu %lu %lu %lu %lu %ld %ld %ld %ld %d 0 %llu %lu %ld %lu %lu %lu %lu %lu \ +%lu %lu %lu %lu %lu %lu %lu %lu %d %d %lu %lu %llu\n", + task->pid, + tcomm, + state, + ppid, + pgid, + sid, + tty_nr, + tty_pgrp, + task->flags, + min_flt, + + cmin_flt, + maj_flt, + cmaj_flt, + cputime_to_clock_t(utime), + cputime_to_clock_t(stime), + cputime_to_clock_t(cutime), + cputime_to_clock_t(cstime), + priority, + nice, + num_threads, + // 0, + start_time, + vsize, + mm ? get_mm_rss(mm) : 0, + rsslim, + mm ? mm->start_code : 0, + mm ? mm->end_code : 0, + mm ? mm->start_stack : 0, + esp, + eip, +the rest is some obsolete cruft +*/ diff --git a/libbb/pw_encrypt.c b/libbb/pw_encrypt.c new file mode 100644 index 000000000..d546bc883 --- /dev/null +++ b/libbb/pw_encrypt.c @@ -0,0 +1,29 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routine. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include + +char *pw_encrypt(const char *clear, const char *salt) +{ + static char cipher[128]; + char *cp; + +#if 0 /* was CONFIG_FEATURE_SHA1_PASSWORDS, but there is no such thing??? */ + if (strncmp(salt, "$2$", 3) == 0) { + return sha1_crypt(clear); + } +#endif + cp = (char *) crypt(clear, salt); + /* if crypt (a nonstandard crypt) returns a string too large, + truncate it so we don't overrun buffers and hope there is + enough security in what's left */ + safe_strncpy(cipher, cp, sizeof(cipher)); + return cipher; +} diff --git a/libbb/read.c b/libbb/read.c new file mode 100644 index 000000000..b3648b4d7 --- /dev/null +++ b/libbb/read.c @@ -0,0 +1,134 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +ssize_t safe_read(int fd, void *buf, size_t count) +{ + ssize_t n; + + do { + n = read(fd, buf, count); + } while (n < 0 && errno == EINTR); + + return n; +} + +/* + * Read all of the supplied buffer from a file. + * This does multiple reads as necessary. + * Returns the amount read, or -1 on an error. + * A short read is returned on an end of file. + */ +ssize_t full_read(int fd, void *buf, size_t len) +{ + ssize_t cc; + ssize_t total; + + total = 0; + + while (len) { + cc = safe_read(fd, buf, len); + + if (cc < 0) + return cc; /* read() returns -1 on failure. */ + + if (cc == 0) + break; + + buf = ((char *)buf) + cc; + total += cc; + len -= cc; + } + + return total; +} + +// Die with an error message if we can't read the entire buffer. +void xread(int fd, void *buf, size_t count) +{ + if (count) { + ssize_t size = full_read(fd, buf, count); + if (size != count) + bb_error_msg_and_die("short read"); + } +} + +// Die with an error message if we can't read one character. +unsigned char xread_char(int fd) +{ + char tmp; + + xread(fd, &tmp, 1); + + return tmp; +} + +// Read one line a-la fgets. Works only on seekable streams +char *reads(int fd, char *buffer, size_t size) +{ + char *p; + + if (size < 2) + return NULL; + size = full_read(fd, buffer, size-1); + if ((ssize_t)size <= 0) + return NULL; + + buffer[size] = '\0'; + p = strchr(buffer, '\n'); + if (p) { + off_t offset; + *p++ = '\0'; + // avoid incorrect (unsigned) widening + offset = (off_t)(p-buffer) - (off_t)size; + // set fd position the right after the \n + if (offset && lseek(fd, offset, SEEK_CUR) == (off_t)-1) + return NULL; + } + return buffer; +} + +ssize_t read_close(int fd, void *buf, size_t size) +{ + int e; + size = full_read(fd, buf, size); + e = errno; + close(fd); + errno = e; + return size; +} + +ssize_t open_read_close(const char *filename, void *buf, size_t size) +{ + int fd = open(filename, O_RDONLY); + if (fd < 0) + return fd; + return read_close(fd, buf, size); +} + +void *xmalloc_open_read_close(const char *filename, size_t *sizep) +{ + char *buf; + size_t size = sizep ? *sizep : INT_MAX; + int fd = xopen(filename, O_RDONLY); + off_t len = xlseek(fd, 0, SEEK_END); + xlseek(fd, 0, SEEK_SET); + + if (len > size) + bb_error_msg_and_die("file '%s' is too big", filename); + size = len; + buf = xmalloc(size+1); + size = read_close(fd, buf, size); + if ((ssize_t)size < 0) + bb_perror_msg_and_die("'%s'", filename); + buf[size] = '\0'; + if (sizep) *sizep = size; + return buf; +} diff --git a/libbb/recursive_action.c b/libbb/recursive_action.c new file mode 100644 index 000000000..121a3dffd --- /dev/null +++ b/libbb/recursive_action.c @@ -0,0 +1,126 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +#undef DEBUG_RECURS_ACTION + +/* + * Walk down all the directories under the specified + * location, and do something (something specified + * by the fileAction and dirAction function pointers). + * + * Unfortunately, while nftw(3) could replace this and reduce + * code size a bit, nftw() wasn't supported before GNU libc 2.1, + * and so isn't sufficiently portable to take over since glibc2.1 + * is so stinking huge. + */ + +static int true_action(const char *fileName, struct stat *statbuf, void* userData, int depth) +{ + return TRUE; +} + +/* fileAction return value of 0 on any file in directory will make + * recursive_action() return 0, but it doesn't stop directory traversal + * (fileAction/dirAction will be called on each file). + * + * if !depthFirst, dirAction return value of 0 (FALSE) or 2 (SKIP) + * prevents recursion into that directory, instead + * recursive_action() returns 0 (if FALSE) or 1 (if SKIP). + * + * followLinks=0/1 differs mainly in handling of links to dirs. + * 0: lstat(statbuf). Calls fileAction on link name even if points to dir. + * 1: stat(statbuf). Calls dirAction and optionally recurse on link to dir. + */ + +int recursive_action(const char *fileName, + int recurse, int followLinks, int depthFirst, + int (*fileAction)(const char *fileName, struct stat *statbuf, void* userData, int depth), + int (*dirAction)(const char *fileName, struct stat *statbuf, void* userData, int depth), + void* userData, + int depth) +{ + struct stat statbuf; + int status; + DIR *dir; + struct dirent *next; + + if (!fileAction) fileAction = true_action; + if (!dirAction) dirAction = true_action; + + status = (followLinks ? stat : lstat)(fileName, &statbuf); + + if (status < 0) { +#ifdef DEBUG_RECURS_ACTION + bb_error_msg("status=%d followLinks=%d TRUE=%d", + status, followLinks, TRUE); +#endif + bb_perror_msg("%s", fileName); + return FALSE; + } + + /* If S_ISLNK(m), then we know that !S_ISDIR(m). + * Then we can skip checking first part: if it is true, then + * (!dir) is also true! */ + if ( /* (!followLinks && S_ISLNK(statbuf.st_mode)) || */ + !S_ISDIR(statbuf.st_mode) + ) { + return fileAction(fileName, &statbuf, userData, depth); + } + + /* It's a directory (or a link to one, and followLinks is set) */ + + if (!recurse) { + return dirAction(fileName, &statbuf, userData, depth); + } + + if (!depthFirst) { + status = dirAction(fileName, &statbuf, userData, depth); + if (!status) { + bb_perror_msg("%s", fileName); + return FALSE; + } + if (status == SKIP) + return TRUE; + } + + dir = opendir(fileName); + if (!dir) { + /* findutils-4.1.20 reports this */ + /* (i.e. it doesn't silently return with exit code 1) */ + /* To trigger: "find -exec rm -rf {} \;" */ + bb_perror_msg("%s", fileName); + return FALSE; + } + status = TRUE; + while ((next = readdir(dir)) != NULL) { + char *nextFile; + + nextFile = concat_subpath_file(fileName, next->d_name); + if (nextFile == NULL) + continue; + if (!recursive_action(nextFile, TRUE, followLinks, depthFirst, + fileAction, dirAction, userData, depth+1)) { + status = FALSE; + } + free(nextFile); + } + closedir(dir); + if (depthFirst) { + if (!dirAction(fileName, &statbuf, userData, depth)) { + bb_perror_msg("%s", fileName); + return FALSE; + } + } + + if (!status) + return FALSE; + return TRUE; +} diff --git a/libbb/remove_file.c b/libbb/remove_file.c new file mode 100644 index 000000000..ab159a481 --- /dev/null +++ b/libbb/remove_file.c @@ -0,0 +1,111 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini remove_file implementation for busybox + * + * Copyright (C) 2001 Matt Kraai + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "libbb.h" + +int remove_file(const char *path, int flags) +{ + struct stat path_stat; + int path_exists = 1; + + if (lstat(path, &path_stat) < 0) { + if (errno != ENOENT) { + bb_perror_msg("cannot stat '%s'", path); + return -1; + } + + path_exists = 0; + } + + if (!path_exists) { + if (!(flags & FILEUTILS_FORCE)) { + bb_perror_msg("cannot remove '%s'", path); + return -1; + } + return 0; + } + + if (S_ISDIR(path_stat.st_mode)) { + DIR *dp; + struct dirent *d; + int status = 0; + + if (!(flags & FILEUTILS_RECUR)) { + bb_error_msg("%s: is a directory", path); + return -1; + } + + if ((!(flags & FILEUTILS_FORCE) && access(path, W_OK) < 0 && + isatty(0)) || + (flags & FILEUTILS_INTERACTIVE)) { + fprintf(stderr, "%s: descend into directory '%s'? ", applet_name, + path); + if (!bb_ask_confirmation()) + return 0; + } + + if ((dp = opendir(path)) == NULL) { + return -1; + } + + while ((d = readdir(dp)) != NULL) { + char *new_path; + + new_path = concat_subpath_file(path, d->d_name); + if(new_path == NULL) + continue; + if (remove_file(new_path, flags) < 0) + status = -1; + free(new_path); + } + + if (closedir(dp) < 0) { + bb_perror_msg("cannot close '%s'", path); + return -1; + } + + if (flags & FILEUTILS_INTERACTIVE) { + fprintf(stderr, "%s: remove directory '%s'? ", applet_name, path); + if (!bb_ask_confirmation()) + return status; + } + + if (rmdir(path) < 0) { + bb_perror_msg("cannot remove '%s'", path); + return -1; + } + + return status; + } else { + if ((!(flags & FILEUTILS_FORCE) && access(path, W_OK) < 0 && + !S_ISLNK(path_stat.st_mode) && + isatty(0)) || + (flags & FILEUTILS_INTERACTIVE)) { + fprintf(stderr, "%s: remove '%s'? ", applet_name, path); + if (!bb_ask_confirmation()) + return 0; + } + + if (unlink(path) < 0) { + bb_perror_msg("cannot remove '%s'", path); + return -1; + } + + return 0; + } +} diff --git a/libbb/restricted_shell.c b/libbb/restricted_shell.c new file mode 100644 index 000000000..74a64140f --- /dev/null +++ b/libbb/restricted_shell.c @@ -0,0 +1,57 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright 1989 - 1991, Julianne Frances Haugh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Julianne F. Haugh nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "libbb.h" + + + +/* Return 1 if SHELL is a restricted shell (one not returned by + getusershell), else 0, meaning it is a standard shell. */ + +int restricted_shell ( const char *shell ) +{ + char *line; + + setusershell ( ); + while (( line = getusershell ( ))) { + if (( *line != '#' ) && ( strcmp ( line, shell ) == 0 )) + break; + } + endusershell ( ); + return line ? 0 : 1; +} + diff --git a/libbb/run_shell.c b/libbb/run_shell.c new file mode 100644 index 000000000..6be09088d --- /dev/null +++ b/libbb/run_shell.c @@ -0,0 +1,101 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright 1989 - 1991, Julianne Frances Haugh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Julianne F. Haugh nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "libbb.h" +#ifdef CONFIG_SELINUX +#include /* for setexeccon */ +#endif + +#ifdef CONFIG_SELINUX +static security_context_t current_sid; + +void +renew_current_security_context(void) +{ + if (current_sid) + freecon(current_sid); /* Release old context */ + getcon(¤t_sid); /* update */ +} +void +set_current_security_context(security_context_t sid) +{ + if (current_sid) + freecon(current_sid); /* Release old context */ + current_sid = sid; +} + +#endif + +/* Run SHELL, or DEFAULT_SHELL if SHELL is empty. + If COMMAND is nonzero, pass it to the shell with the -c option. + If ADDITIONAL_ARGS is nonzero, pass it to the shell as more + arguments. */ + +void run_shell(const char *shell, int loginshell, const char *command, const char **additional_args) +{ + const char **args; + int argno = 1; + int additional_args_cnt = 0; + + for (args = additional_args; args && *args; args++) + additional_args_cnt++; + + args = xmalloc(sizeof(char*) * (4 + additional_args_cnt)); + + args[0] = bb_get_last_path_component(xstrdup(shell)); + + if (loginshell) + args[0] = xasprintf("-%s", args[0]); + + if (command) { + args[argno++] = "-c"; + args[argno++] = command; + } + if (additional_args) { + for (; *additional_args; ++additional_args) + args[argno++] = *additional_args; + } + args[argno] = NULL; +#ifdef CONFIG_SELINUX + if (current_sid && !setexeccon(current_sid)) { + freecon(current_sid); + execve(shell, (char **) args, environ); + } else +#endif + execv(shell, (char **) args); + bb_perror_msg_and_die("cannot run %s", shell); +} diff --git a/libbb/safe_strncpy.c b/libbb/safe_strncpy.c new file mode 100644 index 000000000..42bc16ea0 --- /dev/null +++ b/libbb/safe_strncpy.c @@ -0,0 +1,21 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "libbb.h" + + + +/* Like strncpy but make sure the resulting string is always 0 terminated. */ +char * safe_strncpy(char *dst, const char *src, size_t size) +{ + if (!size) return dst; + dst[--size] = '\0'; + return strncpy(dst, src, size); +} diff --git a/libbb/safe_write.c b/libbb/safe_write.c new file mode 100644 index 000000000..c81f1247f --- /dev/null +++ b/libbb/safe_write.c @@ -0,0 +1,26 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include "libbb.h" + + + +ssize_t safe_write(int fd, const void *buf, size_t count) +{ + ssize_t n; + + do { + n = write(fd, buf, count); + } while (n < 0 && errno == EINTR); + + return n; +} diff --git a/libbb/setup_environment.c b/libbb/setup_environment.c new file mode 100644 index 000000000..874a58efa --- /dev/null +++ b/libbb/setup_environment.c @@ -0,0 +1,83 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright 1989 - 1991, Julianne Frances Haugh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of Julianne F. Haugh nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY JULIE HAUGH AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL JULIE HAUGH OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "libbb.h" + + + +#define DEFAULT_LOGIN_PATH "/bin:/usr/bin" +#define DEFAULT_ROOT_LOGIN_PATH "/usr/sbin:/bin:/usr/bin:/sbin" + +void setup_environment(const char *shell, int loginshell, int changeenv, const struct passwd *pw) +{ + if (loginshell) { + const char *term; + + /* Change the current working directory to be the home directory + * of the user. It is a fatal error for this process to be unable + * to change to that directory. There is no "default" home + * directory. + * Some systems default to HOME=/ + */ + if (chdir(pw->pw_dir)) { + xchdir("/"); + fputs("warning: cannot change to home directory\n", stderr); + } + + /* Leave TERM unchanged. Set HOME, SHELL, USER, LOGNAME, PATH. + Unset all other environment variables. */ + term = getenv("TERM"); + clearenv(); + if (term) + xsetenv("TERM", term); + xsetenv("HOME", pw->pw_dir); + xsetenv("SHELL", shell); + xsetenv("USER", pw->pw_name); + xsetenv("LOGNAME", pw->pw_name); + xsetenv("PATH", (pw->pw_uid ? DEFAULT_LOGIN_PATH : DEFAULT_ROOT_LOGIN_PATH)); + } + else if (changeenv) { + /* Set HOME, SHELL, and if not becoming a super-user, + USER and LOGNAME. */ + xsetenv("HOME", pw->pw_dir); + xsetenv("SHELL", shell); + if (pw->pw_uid) { + xsetenv("USER", pw->pw_name); + xsetenv("LOGNAME", pw->pw_name); + } + } +} diff --git a/libbb/sha1.c b/libbb/sha1.c new file mode 100644 index 000000000..34813e24a --- /dev/null +++ b/libbb/sha1.c @@ -0,0 +1,178 @@ +/* vi: set sw=4 ts=4: */ +/* + * Based on shasum from http://www.netsw.org/crypto/hash/ + * Majorly hacked up to use Dr Brian Gladman's sha1 code + * + * Copyright (C) 2002 Dr Brian Gladman , Worcester, UK. + * Copyright (C) 2003 Glenn L. McGrath + * Copyright (C) 2003 Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * --------------------------------------------------------------------------- + * Issue Date: 10/11/2002 + * + * This is a byte oriented version of SHA1 that operates on arrays of bytes + * stored in memory. It runs at 22 cycles per byte on a Pentium P4 processor + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "libbb.h" + +#define SHA1_BLOCK_SIZE 64 +#define SHA1_DIGEST_SIZE 20 +#define SHA1_HASH_SIZE SHA1_DIGEST_SIZE +#define SHA2_GOOD 0 +#define SHA2_BAD 1 + +#define rotl32(x,n) (((x) << n) | ((x) >> (32 - n))) + +#define SHA1_MASK (SHA1_BLOCK_SIZE - 1) + +/* reverse byte order in 32-bit words */ +#define ch(x,y,z) ((z) ^ ((x) & ((y) ^ (z)))) +#define parity(x,y,z) ((x) ^ (y) ^ (z)) +#define maj(x,y,z) (((x) & (y)) | ((z) & ((x) | (y)))) + +/* A normal version as set out in the FIPS. This version uses */ +/* partial loop unrolling and is optimised for the Pentium 4 */ +#define rnd(f,k) \ + do { \ + t = a; a = rotl32(a,5) + f(b,c,d) + e + k + w[i]; \ + e = d; d = c; c = rotl32(b, 30); b = t; \ + } while(0) + +static void sha1_compile(sha1_ctx_t *ctx) +{ + uint32_t w[80], i, a, b, c, d, e, t; + + /* note that words are compiled from the buffer into 32-bit */ + /* words in big-endian order so an order reversal is needed */ + /* here on little endian machines */ + for (i = 0; i < SHA1_BLOCK_SIZE / 4; ++i) + w[i] = htonl(ctx->wbuf[i]); + + for (i = SHA1_BLOCK_SIZE / 4; i < 80; ++i) + w[i] = rotl32(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1); + + a = ctx->hash[0]; + b = ctx->hash[1]; + c = ctx->hash[2]; + d = ctx->hash[3]; + e = ctx->hash[4]; + + for (i = 0; i < 20; ++i) { + rnd(ch, 0x5a827999); + } + + for (i = 20; i < 40; ++i) { + rnd(parity, 0x6ed9eba1); + } + + for (i = 40; i < 60; ++i) { + rnd(maj, 0x8f1bbcdc); + } + + for (i = 60; i < 80; ++i) { + rnd(parity, 0xca62c1d6); + } + + ctx->hash[0] += a; + ctx->hash[1] += b; + ctx->hash[2] += c; + ctx->hash[3] += d; + ctx->hash[4] += e; +} + +void sha1_begin(sha1_ctx_t *ctx) +{ + ctx->count[0] = ctx->count[1] = 0; + ctx->hash[0] = 0x67452301; + ctx->hash[1] = 0xefcdab89; + ctx->hash[2] = 0x98badcfe; + ctx->hash[3] = 0x10325476; + ctx->hash[4] = 0xc3d2e1f0; +} + +/* SHA1 hash data in an array of bytes into hash buffer and call the */ +/* hash_compile function as required. */ +void sha1_hash(const void *data, size_t length, sha1_ctx_t *ctx) +{ + uint32_t pos = (uint32_t) (ctx->count[0] & SHA1_MASK); + uint32_t freeb = SHA1_BLOCK_SIZE - pos; + const unsigned char *sp = data; + + if ((ctx->count[0] += length) < length) + ++(ctx->count[1]); + + while (length >= freeb) { /* tranfer whole blocks while possible */ + memcpy(((unsigned char *) ctx->wbuf) + pos, sp, freeb); + sp += freeb; + length -= freeb; + freeb = SHA1_BLOCK_SIZE; + pos = 0; + sha1_compile(ctx); + } + + memcpy(((unsigned char *) ctx->wbuf) + pos, sp, length); +} + +void *sha1_end(void *resbuf, sha1_ctx_t *ctx) +{ + /* SHA1 Final padding and digest calculation */ +#if BB_BIG_ENDIAN + static uint32_t mask[4] = { 0x00000000, 0xff000000, 0xffff0000, 0xffffff00 }; + static uint32_t bits[4] = { 0x80000000, 0x00800000, 0x00008000, 0x00000080 }; +#else + static uint32_t mask[4] = { 0x00000000, 0x000000ff, 0x0000ffff, 0x00ffffff }; + static uint32_t bits[4] = { 0x00000080, 0x00008000, 0x00800000, 0x80000000 }; +#endif + + uint8_t *hval = resbuf; + uint32_t i, cnt = (uint32_t) (ctx->count[0] & SHA1_MASK); + + /* mask out the rest of any partial 32-bit word and then set */ + /* the next byte to 0x80. On big-endian machines any bytes in */ + /* the buffer will be at the top end of 32 bit words, on little */ + /* endian machines they will be at the bottom. Hence the AND */ + /* and OR masks above are reversed for little endian systems */ + ctx->wbuf[cnt >> 2] = + (ctx->wbuf[cnt >> 2] & mask[cnt & 3]) | bits[cnt & 3]; + + /* we need 9 or more empty positions, one for the padding byte */ + /* (above) and eight for the length count. If there is not */ + /* enough space pad and empty the buffer */ + if (cnt > SHA1_BLOCK_SIZE - 9) { + if (cnt < 60) + ctx->wbuf[15] = 0; + sha1_compile(ctx); + cnt = 0; + } else /* compute a word index for the empty buffer positions */ + cnt = (cnt >> 2) + 1; + + while (cnt < 14) /* and zero pad all but last two positions */ + ctx->wbuf[cnt++] = 0; + + /* assemble the eight byte counter in the buffer in big-endian */ + /* format */ + + ctx->wbuf[14] = htonl((ctx->count[1] << 3) | (ctx->count[0] >> 29)); + ctx->wbuf[15] = htonl(ctx->count[0] << 3); + + sha1_compile(ctx); + + /* extract the hash value as bytes in case the hash buffer is */ + /* misaligned for 32-bit words */ + + for (i = 0; i < SHA1_DIGEST_SIZE; ++i) + hval[i] = (unsigned char) (ctx->hash[i >> 2] >> 8 * (~i & 3)); + + return resbuf; +} diff --git a/libbb/simplify_path.c b/libbb/simplify_path.c new file mode 100644 index 000000000..b714c66b6 --- /dev/null +++ b/libbb/simplify_path.c @@ -0,0 +1,50 @@ +/* vi: set sw=4 ts=4: */ +/* + * bb_simplify_path implementation for busybox + * + * Copyright (C) 2001 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +char *bb_simplify_path(const char *path) +{ + char *s, *start, *p; + + if (path[0] == '/') + start = xstrdup(path); + else { + s = xgetcwd(NULL); + start = concat_path_file(s, path); + free(s); + } + p = s = start; + + do { + if (*p == '/') { + if (*s == '/') { /* skip duplicate (or initial) slash */ + continue; + } else if (*s == '.') { + if (s[1] == '/' || s[1] == 0) { /* remove extra '.' */ + continue; + } else if ((s[1] == '.') && (s[2] == '/' || s[2] == 0)) { + ++s; + if (p > start) { + while (*--p != '/'); /* omit previous dir */ + } + continue; + } + } + } + *++p = *s; + } while (*++s); + + if ((p == start) || (*p != '/')) { /* not a trailing slash */ + ++p; /* so keep last character */ + } + *p = 0; + + return start; +} diff --git a/libbb/skip_whitespace.c b/libbb/skip_whitespace.c new file mode 100644 index 000000000..02c1f5828 --- /dev/null +++ b/libbb/skip_whitespace.c @@ -0,0 +1,18 @@ +/* vi: set sw=4 ts=4: */ +/* + * skip_whitespace implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "libbb.h" + +char *skip_whitespace(const char *s) +{ + while (isspace(*s)) ++s; + + return (char *) s; +} diff --git a/libbb/speed_table.c b/libbb/speed_table.c new file mode 100644 index 000000000..6137b7731 --- /dev/null +++ b/libbb/speed_table.c @@ -0,0 +1,117 @@ +/* vi: set sw=4 ts=4: */ +/* + * compact speed_t <-> speed functions for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "libbb.h" + +struct speed_map { + unsigned short speed; + unsigned short value; +}; + +static const struct speed_map speeds[] = { + {B0, 0}, + {B50, 50}, + {B75, 75}, + {B110, 110}, + {B134, 134}, + {B150, 150}, + {B200, 200}, + {B300, 300}, + {B600, 600}, + {B1200, 1200}, + {B1800, 1800}, + {B2400, 2400}, + {B4800, 4800}, + {B9600, 9600}, +#ifdef B19200 + {B19200, 19200}, +#elif defined(EXTA) + {EXTA, 19200}, +#endif +#ifdef B38400 + {B38400, 38400/256 + 0x8000U}, +#elif defined(EXTB) + {EXTB, 38400/256 + 0x8000U}, +#endif +#ifdef B57600 + {B57600, 57600/256 + 0x8000U}, +#endif +#ifdef B115200 + {B115200, 115200/256 + 0x8000U}, +#endif +#ifdef B230400 + {B230400, 230400/256 + 0x8000U}, +#endif +#ifdef B460800 + {B460800, 460800/256 + 0x8000U}, +#endif +}; + +enum { NUM_SPEEDS = (sizeof(speeds) / sizeof(struct speed_map)) }; + +unsigned int tty_baud_to_value(speed_t speed) +{ + int i = 0; + + do { + if (speed == speeds[i].speed) { + if (speeds[i].value & 0x8000U) { + return ((unsigned long) (speeds[i].value) & 0x7fffU) * 256; + } + return speeds[i].value; + } + } while (++i < NUM_SPEEDS); + + return 0; +} + +speed_t tty_value_to_baud(unsigned int value) +{ + int i = 0; + + do { + if (value == tty_baud_to_value(speeds[i].speed)) { + return speeds[i].speed; + } + } while (++i < NUM_SPEEDS); + + return (speed_t) - 1; +} + +#if 0 +/* testing code */ +#include + +int main(void) +{ + unsigned long v; + speed_t s; + + for (v = 0 ; v < 500000 ; v++) { + s = tty_value_to_baud(v); + if (s == (speed_t) -1) { + continue; + } + printf("v = %lu -- s = %0lo\n", v, (unsigned long) s); + } + + printf("-------------------------------\n"); + + for (s = 0 ; s < 010017+1 ; s++) { + v = tty_baud_to_value(s); + if (!v) { + continue; + } + printf("v = %lu -- s = %0lo\n", v, (unsigned long) s); + } + + return 0; +} +#endif diff --git a/libbb/trim.c b/libbb/trim.c new file mode 100644 index 000000000..d36391540 --- /dev/null +++ b/libbb/trim.c @@ -0,0 +1,31 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) many different people. + * If you wrote this, please acknowledge your work. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include "libbb.h" + + +void trim(char *s) +{ + size_t len = strlen(s); + size_t lws; + + /* trim trailing whitespace */ + while (len && isspace(s[len-1])) --len; + + /* trim leading whitespace */ + if(len) { + lws = strspn(s, " \n\r\t\v"); + memmove(s, s + lws, len -= lws); + } + s[len] = 0; +} diff --git a/libbb/u_signal_names.c b/libbb/u_signal_names.c new file mode 100644 index 000000000..88311dd9c --- /dev/null +++ b/libbb/u_signal_names.c @@ -0,0 +1,58 @@ +/* vi: set sw=4 ts=4: */ +/* + * Signal name/number conversion routines. + * + * Copyright 2006 Rob Landley + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +static const struct signal_name { + int number; + char name[5]; +} signals[] = { + // SUSv3 says kill must support these, and specifies the numerical values, + // http://www.opengroup.org/onlinepubs/009695399/utilities/kill.html + {0, "0"}, {1, "HUP"}, {2, "INT"}, {3, "QUIT"}, {6, "ABRT"}, {9, "KILL"}, + {14, "ALRM"}, {15, "TERM"}, + // And Posix adds the following: + {SIGILL, "ILL"}, {SIGTRAP, "TRAP"}, {SIGFPE, "FPE"}, {SIGUSR1, "USR1"}, + {SIGSEGV, "SEGV"}, {SIGUSR2, "USR2"}, {SIGPIPE, "PIPE"}, {SIGCHLD, "CHLD"}, + {SIGCONT, "CONT"}, {SIGSTOP, "STOP"}, {SIGTSTP, "TSTP"}, {SIGTTIN, "TTIN"}, + {SIGTTOU, "TTOU"} +}; + +// Convert signal name to number. + +int get_signum(const char *name) +{ + int i; + + i = atoi(name); + if (i) return i; + for (i = 0; i < sizeof(signals) / sizeof(struct signal_name); i++) + if (!strcasecmp(signals[i].name, name) || + (!strncasecmp(signals[i].name, "SIG", 3) + && !strcasecmp(signals[i].name+3, signals[i].name))) + return signals[i].number; + return -1; +} + +// Convert signal number to name + +const char *get_signame(int number) +{ + int i; + static char buf[8]; + + for (i=0; i < sizeof(signals) / sizeof(struct signal_name); i++) { + if (number == signals[i].number) { + return signals[i].name; + } + } + + itoa_to_buf(number, buf, 8); + return buf; +} diff --git a/libbb/uuencode.c b/libbb/uuencode.c new file mode 100644 index 000000000..38401205b --- /dev/null +++ b/libbb/uuencode.c @@ -0,0 +1,63 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright 2006 Rob Landley + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +/* Conversion table. for base 64 */ +const char bb_uuenc_tbl_base64[65] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/', + '=' /* termination character */ +}; + +const char bb_uuenc_tbl_std[65] = { + '`', '!', '"', '#', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', ':', ';', '<', '=', '>', '?', + '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', + 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', + '`' /* termination character */ +}; + +/* + * Encode the string S of length LENGTH to base64 format and place it + * to STORE. STORE will be 0-terminated, and must point to a writable + * buffer of at least 1+BASE64_LENGTH(length) bytes. + * where BASE64_LENGTH(len) = (4 * ((LENGTH + 2) / 3)) + */ +void bb_uuencode(const unsigned char *s, char *store, const int length, const char *tbl) +{ + int i; + char *p = store; + + /* Transform the 3x8 bits to 4x6 bits, as required by base64. */ + for (i = 0; i < length; i += 3) { + *p++ = tbl[s[0] >> 2]; + *p++ = tbl[((s[0] & 3) << 4) + (s[1] >> 4)]; + *p++ = tbl[((s[1] & 0xf) << 2) + (s[2] >> 6)]; + *p++ = tbl[s[2] & 0x3f]; + s += 3; + } + /* Pad the result if necessary... */ + if (i == length + 1) { + *(p - 1) = tbl[64]; + } + else if (i == length + 2) { + *(p - 1) = *(p - 2) = tbl[64]; + } + /* ...and zero-terminate it. */ + *p = '\0'; +} diff --git a/libbb/vdprintf.c b/libbb/vdprintf.c new file mode 100644 index 000000000..ffcb7a444 --- /dev/null +++ b/libbb/vdprintf.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include "libbb.h" + + + +#if (__GLIBC__ < 2) +int vdprintf(int d, const char *format, va_list ap) +{ + char buf[BUF_SIZE]; + int len; + + len = vsprintf(buf, format, ap); + return write(d, buf, len); +} +#endif diff --git a/libbb/verror_msg.c b/libbb/verror_msg.c new file mode 100644 index 000000000..0f018c517 --- /dev/null +++ b/libbb/verror_msg.c @@ -0,0 +1,46 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include + +int logmode = LOGMODE_STDIO; +const char *msg_eol = "\n"; + +void bb_verror_msg(const char *s, va_list p, const char* strerr) +{ + /* va_copy is used because it is not portable + * to use va_list p twice */ + va_list p2; + va_copy(p2, p); + + if (logmode & LOGMODE_STDIO) { + fflush(stdout); + fprintf(stderr, "%s: ", applet_name); + vfprintf(stderr, s, p); + if (!strerr) + fputs(msg_eol, stderr); + else + fprintf(stderr, "%s%s%s", + s ? ": " : "", + strerr, msg_eol); + } + if (ENABLE_FEATURE_SYSLOG && (logmode & LOGMODE_SYSLOG)) { + if (!strerr) + vsyslog(LOG_ERR, s, p2); + else { + char *msg; + if (vasprintf(&msg, s, p2) < 0) + bb_error_msg_and_die(bb_msg_memory_exhausted); + syslog(LOG_ERR, "%s: %s", msg, strerr); + free(msg); + } + } + va_end(p2); +} diff --git a/libbb/vfork_daemon_rexec.c b/libbb/vfork_daemon_rexec.c new file mode 100644 index 000000000..ebd32f8cd --- /dev/null +++ b/libbb/vfork_daemon_rexec.c @@ -0,0 +1,67 @@ +/* vi: set sw=4 ts=4: */ +/* + * Rexec program for system have fork() as vfork() with foreground option + * + * Copyright (C) Vladimir N. Oleynik + * Copyright (C) 2003 Russ Dill + * + * daemon() portion taken from uClibc: + * + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Modified for uClibc by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include "libbb.h" + + +#ifdef BB_NOMMU +void vfork_daemon_rexec(int nochdir, int noclose, + int argc, char **argv, char *foreground_opt) +{ + int fd; + char **vfork_args; + int a = 0; + + setsid(); + + if (!nochdir) + xchdir("/"); + + if (!noclose && (fd = open(bb_dev_null, O_RDWR, 0)) != -1) { + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + if (fd > 2) + close(fd); + } + + vfork_args = xcalloc(sizeof(char *), argc + 3); + vfork_args[a++] = CONFIG_BUSYBOX_EXEC_PATH; + while(*argv) { + vfork_args[a++] = *argv; + argv++; + } + vfork_args[a] = foreground_opt; + switch (vfork()) { + case 0: /* child */ + /* Make certain we are not a session leader, or else we + * might reacquire a controlling terminal */ + if (vfork()) + _exit(0); + execv(vfork_args[0], vfork_args); + bb_perror_msg_and_die("execv %s", vfork_args[0]); + case -1: /* error */ + bb_perror_msg_and_die("vfork"); + default: /* parent */ + exit(0); + } +} +#endif /* BB_NOMMU */ diff --git a/libbb/vherror_msg.c b/libbb/vherror_msg.c new file mode 100644 index 000000000..04446a090 --- /dev/null +++ b/libbb/vherror_msg.c @@ -0,0 +1,15 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +void bb_vherror_msg(const char *s, va_list p) +{ + bb_verror_msg(s, p, hstrerror(h_errno)); +} diff --git a/libbb/vinfo_msg.c b/libbb/vinfo_msg.c new file mode 100644 index 000000000..fa2798625 --- /dev/null +++ b/libbb/vinfo_msg.c @@ -0,0 +1,26 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" +#include + +void bb_vinfo_msg(const char *s, va_list p) +{ + /* va_copy is used because it is not portable + * to use va_list p twice */ + va_list p2; + va_copy(p2, p); + if (logmode & LOGMODE_STDIO) { + vprintf(s, p); + fputs(msg_eol, stdout); + } + if (ENABLE_FEATURE_SYSLOG && (logmode & LOGMODE_SYSLOG)) + vsyslog(LOG_INFO, s, p2); + va_end(p2); +} diff --git a/libbb/vperror_msg.c b/libbb/vperror_msg.c new file mode 100644 index 000000000..c3f79c23b --- /dev/null +++ b/libbb/vperror_msg.c @@ -0,0 +1,15 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +void bb_vperror_msg(const char *s, va_list p) +{ + bb_verror_msg(s, p, strerror(errno)); +} diff --git a/libbb/warn_ignoring_args.c b/libbb/warn_ignoring_args.c new file mode 100644 index 000000000..be78a4414 --- /dev/null +++ b/libbb/warn_ignoring_args.c @@ -0,0 +1,17 @@ +/* vi: set sw=4 ts=4: */ +/* + * warn_ignoring_args implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +void bb_warn_ignoring_args(int n) +{ + if (n) { + bb_error_msg("ignoring all arguments"); + } +} diff --git a/libbb/wfopen.c b/libbb/wfopen.c new file mode 100644 index 000000000..26e6a13e2 --- /dev/null +++ b/libbb/wfopen.c @@ -0,0 +1,20 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +FILE *fopen_or_warn(const char *path, const char *mode) +{ + FILE *fp = fopen(path, mode); + if (!fp) { + bb_perror_msg("%s", path); + errno = 0; + } + return fp; +} diff --git a/libbb/wfopen_input.c b/libbb/wfopen_input.c new file mode 100644 index 000000000..3da855fe6 --- /dev/null +++ b/libbb/wfopen_input.c @@ -0,0 +1,31 @@ +/* vi: set sw=4 ts=4: */ +/* + * wfopen_input implementation for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* A number of applets need to open a file for reading, where the filename + * is a command line arg. Since often that arg is '-' (meaning stdin), + * we avoid testing everywhere by consolidating things in this routine. + * + * Note: We also consider "" to main stdin (for 'cmp' at least). + */ + +#include "libbb.h" + +FILE *fopen_or_warn_stdin(const char *filename) +{ + FILE *fp = stdin; + + if (filename != bb_msg_standard_input + && filename[0] + && (filename[0] != '-' || filename[1]) + ) { + fp = fopen_or_warn(filename, "r"); + } + + return fp; +} diff --git a/libbb/xatonum.c b/libbb/xatonum.c new file mode 100644 index 000000000..35607c317 --- /dev/null +++ b/libbb/xatonum.c @@ -0,0 +1,69 @@ +/* vi: set sw=4 ts=4: */ +/* + * ascii-to-numbers implementations for busybox + * + * Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +#define type long long +#define xstrtou(rest) xstrtoull##rest +#define xstrto(rest) xstrtoll##rest +#define xatou(rest) xatoull##rest +#define xato(rest) xatoll##rest +#define XSTR_UTYPE_MAX ULLONG_MAX +#define XSTR_TYPE_MAX LLONG_MAX +#define XSTR_TYPE_MIN LLONG_MIN +#define XSTR_STRTOU strtoull +#include "xatonum_template.c" + +#if ULONG_MAX != ULLONG_MAX +#define type long +#define xstrtou(rest) xstrtoul##rest +#define xstrto(rest) xstrtol##rest +#define xatou(rest) xatoul##rest +#define xato(rest) xatol##rest +#define XSTR_UTYPE_MAX ULONG_MAX +#define XSTR_TYPE_MAX LONG_MAX +#define XSTR_TYPE_MIN LONG_MIN +#define XSTR_STRTOU strtoul +#include "xatonum_template.c" +#endif + +#if UINT_MAX != ULONG_MAX +extern inline unsigned bb_strtoui(const char *str, char **end, int b) +{ + unsigned long v = strtoul(str, end, b); + if (v > UINT_MAX) { + errno = ERANGE; + return UINT_MAX; + } + return v; +} +#define type int +#define xstrtou(rest) xstrtou##rest +#define xstrto(rest) xstrtoi##rest +#define xatou(rest) xatou##rest +#define xato(rest) xatoi##rest +#define XSTR_UTYPE_MAX UINT_MAX +#define XSTR_TYPE_MAX INT_MAX +#define XSTR_TYPE_MIN INT_MIN +/* libc has no strtoui, so we need to create/use our own */ +#define XSTR_STRTOU bb_strtoui +#include "xatonum_template.c" +#endif + +/* A few special cases */ + +int xatoi_u(const char *numstr) +{ + return xatou_range(numstr, 0, INT_MAX); +} + +uint16_t xatou16(const char *numstr) +{ + return xatou_range(numstr, 0, 0xffff); +} diff --git a/libbb/xatonum_template.c b/libbb/xatonum_template.c new file mode 100644 index 000000000..53ba544eb --- /dev/null +++ b/libbb/xatonum_template.c @@ -0,0 +1,185 @@ +/* +You need to define the following (example): + +#define type long +#define xstrtou(rest) xstrtoul##rest +#define xstrto(rest) xstrtol##rest +#define xatou(rest) xatoul##rest +#define xato(rest) xatol##rest +#define XSTR_UTYPE_MAX ULONG_MAX +#define XSTR_TYPE_MAX LONG_MAX +#define XSTR_TYPE_MIN LONG_MIN +#define XSTR_STRTOU strtoul +*/ + +unsigned type xstrtou(_range_sfx)(const char *numstr, int base, + unsigned type lower, + unsigned type upper, + const struct suffix_mult *suffixes) +{ + unsigned type r; + int old_errno; + char *e; + + /* Disallow '-' and any leading whitespace. Speed isn't critical here + * since we're parsing commandline args. So make sure we get the + * actual isspace function rather than a lnumstrer macro implementaion. */ + if ((*numstr == '-') || (isspace)(*numstr)) + goto inval; + + /* Since this is a lib function, we're not allowed to reset errno to 0. + * Doing so could break an app that is deferring checking of errno. + * So, save the old value so that we can restore it if successful. */ + old_errno = errno; + errno = 0; + r = XSTR_STRTOU(numstr, &e, base); + /* Do the initial validity check. Note: The standards do not + * guarantee that errno is set if no digits were found. So we + * must test for this explicitly. */ + if (errno || (numstr == e)) + goto inval; /* error / no digits / illegal trailing chars */ + + errno = old_errno; /* Ok. So restore errno. */ + + /* Do optional suffix parsing. Allow 'empty' suffix tables. + * Note that we also allow nul suffixes with associated multipliers, + * to allow for scaling of the numstr by some default multiplier. */ + if (suffixes) { + while (suffixes->suffix) { + if (strcmp(suffixes->suffix, e) == 0) { + if (XSTR_UTYPE_MAX / suffixes->mult < r) + goto range; /* overflow! */ + ++e; + r *= suffixes->mult; + break; + } + ++suffixes; + } + } + + /* Note: trailing space is an error. + It would be easy enough to allow though if desired. */ + if (*e) + goto inval; + /* Finally, check for range limits. */ + if (r >= lower && r <= upper) + return r; + range: + bb_error_msg_and_die("number %s is not in %llu..%llu range", + numstr, (unsigned long long)lower, + (unsigned long long)upper); + inval: + bb_error_msg_and_die("invalid number '%s'", numstr); +} + +unsigned type xstrtou(_range)(const char *numstr, int base, + unsigned type lower, + unsigned type upper) +{ + return xstrtou(_range_sfx)(numstr, base, lower, upper, NULL); +} + +unsigned type xstrtou(_sfx)(const char *numstr, int base, + const struct suffix_mult *suffixes) +{ + return xstrtou(_range_sfx)(numstr, base, 0, XSTR_UTYPE_MAX, suffixes); +} + +unsigned type xstrtou()(const char *numstr, int base) +{ + return xstrtou(_range_sfx)(numstr, base, 0, XSTR_UTYPE_MAX, NULL); +} + +unsigned type xatou(_range_sfx)(const char *numstr, + unsigned type lower, + unsigned type upper, + const struct suffix_mult *suffixes) +{ + return xstrtou(_range_sfx)(numstr, 10, lower, upper, suffixes); +} + +unsigned type xatou(_range)(const char *numstr, + unsigned type lower, + unsigned type upper) +{ + return xstrtou(_range_sfx)(numstr, 10, lower, upper, NULL); +} + +unsigned type xatou(_sfx)(const char *numstr, + const struct suffix_mult *suffixes) +{ + return xstrtou(_range_sfx)(numstr, 10, 0, XSTR_UTYPE_MAX, suffixes); +} + +unsigned type xatou()(const char *numstr) +{ + return xatou(_sfx)(numstr, NULL); +} + +/* Signed ones */ + +type xstrto(_range_sfx)(const char *numstr, int base, + type lower, + type upper, + const struct suffix_mult *suffixes) +{ + unsigned type u = XSTR_TYPE_MAX; + type r; + const char *p = numstr; + + if ((p[0] == '-') && (p[1] != '+')) { + ++p; + ++u; /* two's complement */ + } + + r = xstrtou(_range_sfx)(p, base, 0, u, suffixes); + + if (*numstr == '-') { + r = -r; + } + + if (r < lower || r > upper) { + bb_error_msg_and_die("number %s is not in %lld..%lld range", + numstr, (long long)lower, (long long)upper); + } + + return r; +} + +type xstrto(_range)(const char *numstr, int base, type lower, type upper) +{ + return xstrto(_range_sfx)(numstr, base, lower, upper, NULL); +} + +type xato(_range_sfx)(const char *numstr, + type lower, + type upper, + const struct suffix_mult *suffixes) +{ + return xstrto(_range_sfx)(numstr, 10, lower, upper, suffixes); +} + +type xato(_range)(const char *numstr, type lower, type upper) +{ + return xstrto(_range_sfx)(numstr, 10, lower, upper, NULL); +} + +type xato(_sfx)(const char *numstr, const struct suffix_mult *suffixes) +{ + return xstrto(_range_sfx)(numstr, 10, XSTR_TYPE_MIN, XSTR_TYPE_MAX, suffixes); +} + +type xato()(const char *numstr) +{ + return xstrto(_range_sfx)(numstr, 10, XSTR_TYPE_MIN, XSTR_TYPE_MAX, NULL); +} + +#undef type +#undef xstrtou +#undef xstrto +#undef xatou +#undef xato +#undef XSTR_UTYPE_MAX +#undef XSTR_TYPE_MAX +#undef XSTR_TYPE_MIN +#undef XSTR_STRTOU diff --git a/libbb/xconnect.c b/libbb/xconnect.c new file mode 100644 index 000000000..b85648007 --- /dev/null +++ b/libbb/xconnect.c @@ -0,0 +1,153 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Connect to host at port using address resolution from getaddrinfo + * + */ + +#include "libbb.h" + +/* Return network byte ordered port number for a service. + * If "port" is a number use it as the port. + * If "port" is a name it is looked up in /etc/services, if it isnt found return + * default_port + */ +unsigned short bb_lookup_port(const char *port, const char *protocol, unsigned short default_port) +{ + unsigned short port_nr = htons(default_port); + if (port) { + char *endptr; + int old_errno; + long port_long; + + /* Since this is a lib function, we're not allowed to reset errno to 0. + * Doing so could break an app that is deferring checking of errno. */ + old_errno = errno; + errno = 0; + port_long = strtol(port, &endptr, 10); + if (errno != 0 || *endptr!='\0' || endptr==port || port_long < 0 || port_long > 65535) { + struct servent *tserv = getservbyname(port, protocol); + if (tserv) { + port_nr = tserv->s_port; + } + } else { + port_nr = htons(port_long); + } + errno = old_errno; + } + return port_nr; +} + +void bb_lookup_host(struct sockaddr_in *s_in, const char *host) +{ + struct hostent *he; + + memset(s_in, 0, sizeof(struct sockaddr_in)); + s_in->sin_family = AF_INET; + he = xgethostbyname(host); + memcpy(&(s_in->sin_addr), he->h_addr_list[0], he->h_length); +} + +void xconnect(int s, const struct sockaddr *s_addr, socklen_t addrlen) +{ + if (connect(s, s_addr, addrlen) < 0) { + if (ENABLE_FEATURE_CLEAN_UP) close(s); + if (s_addr->sa_family == AF_INET) + bb_perror_msg_and_die("cannot connect to remote host (%s)", + inet_ntoa(((struct sockaddr_in *)s_addr)->sin_addr)); + bb_perror_msg_and_die("cannot connect to remote host"); + } +} + +int xconnect_tcp_v4(struct sockaddr_in *s_addr) +{ + int s = xsocket(AF_INET, SOCK_STREAM, 0); + xconnect(s, (struct sockaddr*) s_addr, sizeof(*s_addr)); + return s; +} + +static const int one = 1; +int setsockopt_reuseaddr(int fd) +{ + return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); +} +int setsockopt_broadcast(int fd) +{ + return setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &one, sizeof(one)); +} + +int dotted2sockaddr(const char *dotted, struct sockaddr* sp, int socklen) +{ + union { + struct in_addr a4; +#if ENABLE_FEATURE_IPV6 + struct in6_addr a6; +#endif + } a; + + /* TODO maybe: port spec? like n.n.n.n:nn */ + +#if ENABLE_FEATURE_IPV6 + if (socklen >= sizeof(struct sockaddr_in6) + && inet_pton(AF_INET6, dotted, &a.a6) > 0 + ) { + ((struct sockaddr_in6*)sp)->sin6_family = AF_INET6; + ((struct sockaddr_in6*)sp)->sin6_addr = a.a6; + /* ((struct sockaddr_in6*)sp)->sin6_port = */ + return 0; /* success */ + } +#endif + if (socklen >= sizeof(struct sockaddr_in) + && inet_pton(AF_INET, dotted, &a.a4) > 0 + ) { + ((struct sockaddr_in*)sp)->sin_family = AF_INET; + ((struct sockaddr_in*)sp)->sin_addr = a.a4; + /* ((struct sockaddr_in*)sp)->sin_port = */ + return 0; /* success */ + } + return 1; +} + +int xsocket_stream_ip4or6(sa_family_t *fp) +{ + int fd; +#if ENABLE_FEATURE_IPV6 + fd = socket(AF_INET6, SOCK_STREAM, 0); + if (fp) *fp = AF_INET6; + if (fd < 0) +#endif + { + fd = xsocket(AF_INET, SOCK_STREAM, 0); + if (fp) *fp = AF_INET; + } + return fd; +} + +int create_and_bind_socket_ip4or6(const char *hostaddr, int port) +{ + int fd; + sockaddr_inet sa; + + memset(&sa, 0, sizeof(sa)); + if (hostaddr) { + if (dotted2sockaddr(hostaddr, &sa.sa, sizeof(sa))) + bb_error_msg_and_die("bad address '%s'", hostaddr); + /* user specified bind addr dictates family */ + fd = xsocket(sa.sa.sa_family, SOCK_STREAM, 0); + } else + fd = xsocket_stream_ip4or6(&sa.sa.sa_family); + setsockopt_reuseaddr(fd); + + /* if (port >= 0) { */ +#if ENABLE_FEATURE_IPV6 + if (sa.sa.sa_family == AF_INET6 /* && !sa.sin6.sin6_port */) + sa.sin6.sin6_port = htons(port); +#endif + if (sa.sa.sa_family == AF_INET /* && !sa.sin.sin_port */) + sa.sin.sin_port = htons(port); + /* } */ + + xbind(fd, &sa.sa, sizeof(sa)); + return fd; +} diff --git a/libbb/xfuncs.c b/libbb/xfuncs.c new file mode 100644 index 000000000..313e32814 --- /dev/null +++ b/libbb/xfuncs.c @@ -0,0 +1,521 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) 1999-2004 by Erik Andersen + * Copyright (C) 2006 Rob Landley + * Copyright (C) 2006 Denis Vlasenko + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +/* All the functions starting with "x" call bb_error_msg_and_die() if they + * fail, so callers never need to check for errors. If it returned, it + * succeeded. */ + +#ifndef DMALLOC +/* dmalloc provides variants of these that do abort() on failure. + * Since dmalloc's prototypes overwrite the impls here as they are + * included after these prototypes in libbb.h, all is well. + */ +// Die if we can't allocate size bytes of memory. +void *xmalloc(size_t size) +{ + void *ptr = malloc(size); + if (ptr == NULL && size != 0) + bb_error_msg_and_die(bb_msg_memory_exhausted); + return ptr; +} + +// Die if we can't resize previously allocated memory. (This returns a pointer +// to the new memory, which may or may not be the same as the old memory. +// It'll copy the contents to a new chunk and free the old one if necessary.) +void *xrealloc(void *ptr, size_t size) +{ + ptr = realloc(ptr, size); + if (ptr == NULL && size != 0) + bb_error_msg_and_die(bb_msg_memory_exhausted); + return ptr; +} +#endif /* DMALLOC */ + +// Die if we can't allocate and zero size bytes of memory. +void *xzalloc(size_t size) +{ + void *ptr = xmalloc(size); + memset(ptr, 0, size); + return ptr; +} + +// Die if we can't copy a string to freshly allocated memory. +char * xstrdup(const char *s) +{ + char *t; + + if (s == NULL) + return NULL; + + t = strdup(s); + + if (t == NULL) + bb_error_msg_and_die(bb_msg_memory_exhausted); + + return t; +} + +// Die if we can't allocate n+1 bytes (space for the null terminator) and copy +// the (possibly truncated to length n) string into it. +char * xstrndup(const char *s, int n) +{ + int m; + char *t; + + if (ENABLE_DEBUG && s == NULL) + bb_error_msg_and_die("xstrndup bug"); + + /* We can just xmalloc(n+1) and strncpy into it, */ + /* but think about xstrndup("abc", 10000) wastage! */ + m = n; + t = (char*) s; + while (m) { + if (!*t) break; + m--; t++; + } + n = n - m; + t = xmalloc(n + 1); + t[n] = '\0'; + + return memcpy(t,s,n); +} + +// Die if we can't open a file and return a FILE * to it. +// Notice we haven't got xfread(), This is for use with fscanf() and friends. +FILE *xfopen(const char *path, const char *mode) +{ + FILE *fp = fopen(path, mode); + if (fp == NULL) + bb_perror_msg_and_die("%s", path); + return fp; +} + +// Die if we can't open an existing file and return an fd. +int xopen(const char *pathname, int flags) +{ + //if (ENABLE_DEBUG && (flags & O_CREAT)) + // bb_error_msg_and_die("xopen() with O_CREAT"); + + return xopen3(pathname, flags, 0666); +} + +// Die if we can't open a new file and return an fd. +int xopen3(const char *pathname, int flags, int mode) +{ + int ret; + + ret = open(pathname, flags, mode); + if (ret < 0) { + bb_perror_msg_and_die("%s", pathname); + } + return ret; +} + +/* +int ndelay_off(int fd) +{ + return fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0) & ~O_NONBLOCK); +} +*/ +// Turn on nonblocking I/O on a fd +int ndelay_on(int fd) +{ + return fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0) | O_NONBLOCK); +} + +// Die with an error message if we can't write the entire buffer. +void xwrite(int fd, const void *buf, size_t count) +{ + if (count) { + ssize_t size = full_write(fd, buf, count); + if (size != count) + bb_error_msg_and_die("short write"); + } +} + +// Die with an error message if we can't lseek to the right spot. +off_t xlseek(int fd, off_t offset, int whence) +{ + off_t off = lseek(fd, offset, whence); + if (off == (off_t)-1) { + if (whence == SEEK_SET) + bb_perror_msg_and_die("lseek(%"OFF_FMT"u)", offset); + bb_perror_msg_and_die("lseek"); + } + return off; +} + +// Die with supplied filename if this FILE * has ferror set. +void die_if_ferror(FILE *fp, const char *fn) +{ + if (ferror(fp)) { + bb_error_msg_and_die("%s: I/O error", fn); + } +} + +// Die with an error message if stdout has ferror set. +void die_if_ferror_stdout(void) +{ + die_if_ferror(stdout, bb_msg_standard_output); +} + +// Die with an error message if we have trouble flushing stdout. +void xfflush_stdout(void) +{ + if (fflush(stdout)) { + bb_perror_msg_and_die(bb_msg_standard_output); + } +} + +// This does a fork/exec in one call, using vfork(). Return PID of new child, +// -1 for failure. Runs argv[0], searching path if that has no / in it. +pid_t spawn(char **argv) +{ + static int failed; + pid_t pid; + void *app = ENABLE_FEATURE_SH_STANDALONE_SHELL ? find_applet_by_name(argv[0]) : 0; + + // Be nice to nommu machines. + failed = 0; + pid = vfork(); + if (pid < 0) return pid; + if (!pid) { + execvp(app ? CONFIG_BUSYBOX_EXEC_PATH : *argv, argv); + + // We're sharing a stack with blocked parent, let parent know we failed + // and then exit to unblock parent (but don't run atexit() stuff, which + // would screw up parent.) + + failed = -1; + _exit(0); + } + return failed ? failed : pid; +} + +// Die with an error message if we can't spawn a child process. +pid_t xspawn(char **argv) +{ + pid_t pid = spawn(argv); + if (pid < 0) bb_perror_msg_and_die("%s", *argv); + return pid; +} + +// Wait for the specified child PID to exit, returning child's error return. +int wait4pid(int pid) +{ + int status; + + if (pid == -1 || waitpid(pid, &status, 0) == -1) return -1; + if (WIFEXITED(status)) return WEXITSTATUS(status); + if (WIFSIGNALED(status)) return WTERMSIG(status); + return 0; +} + +void xsetenv(const char *key, const char *value) +{ + if (setenv(key, value, 1)) + bb_error_msg_and_die(bb_msg_memory_exhausted); +} + +// Converts unsigned long long value into compact 4-char +// representation. Examples: "1234", "1.2k", " 27M", "123T" +// Fifth char is always '\0' +void smart_ulltoa5(unsigned long long ul, char buf[5]) +{ + char *fmt; + char c; + unsigned v,idx = 0; + ul *= 10; + if (ul > 9999*10) { // do not scale if 9999 or less + while (ul >= 10000) { + ul /= 1024; + idx++; + } + } + v = ul; // ullong divisions are expensive, avoid them + + fmt = " 123456789"; + if (!idx) { // 9999 or less: use 1234 format + c = buf[0] = " 123456789"[v/10000]; + if (c!=' ') fmt = "0123456789"; + c = buf[1] = fmt[v/1000%10]; + if (c!=' ') fmt = "0123456789"; + buf[2] = fmt[v/100%10]; + buf[3] = "0123456789"[v/10%10]; + } else { + if (v>=10*10) { // scaled value is >=10: use 123M format + c = buf[0] = " 123456789"[v/1000]; + if (c!=' ') fmt = "0123456789"; + buf[1] = fmt[v/100%10]; + buf[2] = "0123456789"[v/10%10]; + } else { // scaled value is <10: use 1.2M format + buf[0] = "0123456789"[v/10]; + buf[1] = '.'; + buf[2] = "0123456789"[v%10]; + } + // see http://en.wikipedia.org/wiki/Tera + buf[3] = " kMGTPEZY"[idx]; + } + buf[4] = '\0'; +} + +// Convert unsigned integer to ascii, writing into supplied buffer. A +// truncated result is always null terminated (unless buflen is 0), and +// contains the first few digits of the result ala strncpy. +void BUG_sizeof_unsigned_not_4(void); +void utoa_to_buf(unsigned n, char *buf, unsigned buflen) +{ + unsigned i, out, res; + if (sizeof(unsigned) != 4) + BUG_sizeof_unsigned_not_4(); + if (buflen) { + out = 0; + for (i = 1000000000; i; i /= 10) { + res = n / i; + if (res || out || i == 1) { + if (!--buflen) break; + out++; + n -= res*i; + *buf++ = '0' + res; + } + } + *buf = '\0'; + } +} + +// Convert signed integer to ascii, like utoa_to_buf() +void itoa_to_buf(int n, char *buf, unsigned buflen) +{ + if (buflen && n<0) { + n = -n; + *buf++ = '-'; + buflen--; + } + utoa_to_buf((unsigned)n, buf, buflen); +} + +// The following two functions use a static buffer, so calling either one a +// second time will overwrite previous results. +// +// The largest 32 bit integer is -2 billion plus null terminator, or 12 bytes. +// Int should always be 32 bits on any remotely Unix-like system, see +// http://www.unix.org/whitepapers/64bit.html for the reasons why. + +static char local_buf[12]; + +// Convert unsigned integer to ascii using a static buffer (returned). +char *utoa(unsigned n) +{ + utoa_to_buf(n, local_buf, sizeof(local_buf)); + + return local_buf; +} + +// Convert signed integer to ascii using a static buffer (returned). +char *itoa(int n) +{ + itoa_to_buf(n, local_buf, sizeof(local_buf)); + + return local_buf; +} + +// Die with an error message if we can't set gid. (Because resource limits may +// limit this user to a given number of processes, and if that fills up the +// setgid() will fail and we'll _still_be_root_, which is bad.) +void xsetgid(gid_t gid) +{ + if (setgid(gid)) bb_error_msg_and_die("setgid"); +} + +// Die with an error message if we can't set uid. (See xsetgid() for why.) +void xsetuid(uid_t uid) +{ + if (setuid(uid)) bb_error_msg_and_die("setuid"); +} + +// Return how long the file at fd is, if there's any way to determine it. +off_t fdlength(int fd) +{ + off_t bottom = 0, top = 0, pos; + long size; + + // If the ioctl works for this, return it. + + if (ioctl(fd, BLKGETSIZE, &size) >= 0) return size*512; + + // FIXME: explain why lseek(SEEK_END) is not used here! + + // If not, do a binary search for the last location we can read. (Some + // block devices don't do BLKGETSIZE right.) + + do { + char temp; + + pos = bottom + (top - bottom) / 2; + + // If we can read from the current location, it's bigger. + + if (lseek(fd, pos, SEEK_SET)>=0 && safe_read(fd, &temp, 1)==1) { + if (bottom == top) bottom = top = (top+1) * 2; + else bottom = pos; + + // If we can't, it's smaller. + + } else { + if (bottom == top) { + if (!top) return 0; + bottom = top/2; + } + else top = pos; + } + } while (bottom + 1 != top); + + return pos + 1; +} + +// Die with an error message if we can't malloc() enough space and do an +// sprintf() into that space. +char *xasprintf(const char *format, ...) +{ + va_list p; + int r; + char *string_ptr; + +#if 1 + // GNU extension + va_start(p, format); + r = vasprintf(&string_ptr, format, p); + va_end(p); +#else + // Bloat for systems that haven't got the GNU extension. + va_start(p, format); + r = vsnprintf(NULL, 0, format, p); + va_end(p); + string_ptr = xmalloc(r+1); + va_start(p, format); + r = vsnprintf(string_ptr, r+1, format, p); + va_end(p); +#endif + + if (r < 0) bb_error_msg_and_die(bb_msg_memory_exhausted); + return string_ptr; +} + +// Die with an error message if we can't copy an entire FILE * to stdout, then +// close that file. +void xprint_and_close_file(FILE *file) +{ + fflush(stdout); + // copyfd outputs error messages for us. + if (bb_copyfd_eof(fileno(file), 1) == -1) + exit(xfunc_error_retval); + + fclose(file); +} + +// Die if we can't chdir to a new path. +void xchdir(const char *path) +{ + if (chdir(path)) + bb_perror_msg_and_die("chdir(%s)", path); +} + +// Print a warning message if opendir() fails, but don't die. +DIR *warn_opendir(const char *path) +{ + DIR *dp; + + if ((dp = opendir(path)) == NULL) { + bb_perror_msg("cannot open '%s'", path); + return NULL; + } + return dp; +} + +// Die with an error message if opendir() fails. +DIR *xopendir(const char *path) +{ + DIR *dp; + + if ((dp = opendir(path)) == NULL) + bb_perror_msg_and_die("cannot open '%s'", path); + return dp; +} + +#ifndef BB_NOMMU +// Die with an error message if we can't daemonize. +void xdaemon(int nochdir, int noclose) +{ + if (daemon(nochdir, noclose)) + bb_perror_msg_and_die("daemon"); +} +#endif + +// Die with an error message if we can't open a new socket. +int xsocket(int domain, int type, int protocol) +{ + int r = socket(domain, type, protocol); + + if (r < 0) bb_perror_msg_and_die("socket"); + + return r; +} + +// Die with an error message if we can't bind a socket to an address. +void xbind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen) +{ + if (bind(sockfd, my_addr, addrlen)) bb_perror_msg_and_die("bind"); +} + +// Die with an error message if we can't listen for connections on a socket. +void xlisten(int s, int backlog) +{ + if (listen(s, backlog)) bb_perror_msg_and_die("listen"); +} + +// xstat() - a stat() which dies on failure with meaningful error message +void xstat(char *name, struct stat *stat_buf) +{ + if (stat(name, stat_buf)) + bb_perror_msg_and_die("can't stat '%s'", name); +} + +/* It is perfectly ok to pass in a NULL for either width or for + * height, in which case that value will not be set. */ +int get_terminal_width_height(int fd, int *width, int *height) +{ + struct winsize win = { 0, 0, 0, 0 }; + int ret = ioctl(fd, TIOCGWINSZ, &win); + + if (height) { + if (!win.ws_row) { + char *s = getenv("LINES"); + if (s) win.ws_row = atoi(s); + } + if (win.ws_row <= 1 || win.ws_row >= 30000) + win.ws_row = 24; + *height = (int) win.ws_row; + } + + if (width) { + if (!win.ws_col) { + char *s = getenv("COLUMNS"); + if (s) win.ws_col = atoi(s); + } + if (win.ws_col <= 1 || win.ws_col >= 30000) + win.ws_col = 80; + *width = (int) win.ws_col; + } + + return ret; +} diff --git a/libbb/xgetcwd.c b/libbb/xgetcwd.c new file mode 100644 index 000000000..0ac450d3b --- /dev/null +++ b/libbb/xgetcwd.c @@ -0,0 +1,44 @@ +/* vi: set sw=4 ts=4: */ +/* + * xgetcwd.c -- return current directory with unlimited length + * Copyright (C) 1992, 1996 Free Software Foundation, Inc. + * Written by David MacKenzie . + * + * Special function for busybox written by Vladimir Oleynik +*/ + +#include "libbb.h" + +/* Amount to increase buffer size by in each try. */ +#define PATH_INCR 32 + +/* Return the current directory, newly allocated, arbitrarily long. + Return NULL and set errno on error. + If argument is not NULL (previous usage allocate memory), call free() +*/ + +char * +xgetcwd(char *cwd) +{ + char *ret; + unsigned path_max; + + path_max = (unsigned) PATH_MAX; + path_max += 2; /* The getcwd docs say to do this. */ + + if (cwd==0) + cwd = xmalloc(path_max); + + while ((ret = getcwd(cwd, path_max)) == NULL && errno == ERANGE) { + path_max += PATH_INCR; + cwd = xrealloc(cwd, path_max); + } + + if (ret == NULL) { + free(cwd); + bb_perror_msg("getcwd"); + return NULL; + } + + return cwd; +} diff --git a/libbb/xgethostbyname.c b/libbb/xgethostbyname.c new file mode 100644 index 000000000..c3158c339 --- /dev/null +++ b/libbb/xgethostbyname.c @@ -0,0 +1,19 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini xgethostbyname implementation. + * + * Copyright (C) 2001 Matt Kraai . + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "libbb.h" + +struct hostent *xgethostbyname(const char *name) +{ + struct hostent *retval = gethostbyname(name); + if (!retval) + bb_herror_msg_and_die("%s", name); + return retval; +} diff --git a/libbb/xgethostbyname2.c b/libbb/xgethostbyname2.c new file mode 100644 index 000000000..83d538669 --- /dev/null +++ b/libbb/xgethostbyname2.c @@ -0,0 +1,22 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini xgethostbyname2 implementation. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "libbb.h" + + +#ifdef CONFIG_FEATURE_IPV6 +struct hostent *xgethostbyname2(const char *name, int af) +{ + struct hostent *retval; + + if ((retval = gethostbyname2(name, af)) == NULL) + bb_herror_msg_and_die("%s", name); + + return retval; +} +#endif diff --git a/libbb/xreadlink.c b/libbb/xreadlink.c new file mode 100644 index 000000000..14863d9d5 --- /dev/null +++ b/libbb/xreadlink.c @@ -0,0 +1,38 @@ +/* vi: set sw=4 ts=4: */ +/* + * xreadlink.c - safe implementation of readlink. + * Returns a NULL on failure... + */ + +#include + +/* + * NOTE: This function returns a malloced char* that you will have to free + * yourself. You have been warned. + */ + +#include +#include "libbb.h" + +char *xreadlink(const char *path) +{ + enum { GROWBY = 80 }; /* how large we will grow strings by */ + + char *buf = NULL; + int bufsize = 0, readsize = 0; + + do { + buf = xrealloc(buf, bufsize += GROWBY); + readsize = readlink(path, buf, bufsize); /* 1st try */ + if (readsize == -1) { + bb_perror_msg("%s", path); + free(buf); + return NULL; + } + } + while (bufsize < readsize + 1); + + buf[readsize] = '\0'; + + return buf; +} diff --git a/libbb/xregcomp.c b/libbb/xregcomp.c new file mode 100644 index 000000000..4bcb9aedf --- /dev/null +++ b/libbb/xregcomp.c @@ -0,0 +1,26 @@ +/* vi: set sw=4 ts=4: */ +/* + * Utility routines. + * + * Copyright (C) many different people. + * If you wrote this, please acknowledge your work. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "libbb.h" +#include "xregex.h" + + + +void xregcomp(regex_t *preg, const char *regex, int cflags) +{ + int ret; + if ((ret = regcomp(preg, regex, cflags)) != 0) { + int errmsgsz = regerror(ret, preg, NULL, 0); + char *errmsg = xmalloc(errmsgsz); + regerror(ret, preg, errmsg, errmsgsz); + bb_error_msg_and_die("xregcomp: %s", errmsg); + } +} diff --git a/libpwdgrp/Kbuild b/libpwdgrp/Kbuild new file mode 100644 index 000000000..9e60ef1e5 --- /dev/null +++ b/libpwdgrp/Kbuild @@ -0,0 +1,7 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:=pwd_grp.o uidgid_get.o diff --git a/libpwdgrp/pwd_grp.c b/libpwdgrp/pwd_grp.c new file mode 100644 index 000000000..ac65d4c5b --- /dev/null +++ b/libpwdgrp/pwd_grp.c @@ -0,0 +1,969 @@ +/* vi: set sw=4 ts=4: */ +/* Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPL v2, or later. See file LICENSE in this tarball. + */ + +/* Nov 6, 2003 Initial version. + * + * NOTE: This implementation is quite strict about requiring all + * field seperators. It also does not allow leading whitespace + * except when processing the numeric fields. glibc is more + * lenient. See the various glibc difference comments below. + * + * TODO: + * Move to dynamic allocation of (currently statically allocated) + * buffers; especially for the group-related functions since + * large group member lists will cause error returns. + * + */ + +#include "libbb.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef _PATH_SHADOW +#define _PATH_SHADOW "/etc/shadow" +#endif +#ifndef _PATH_PASSWD +#define _PATH_PASSWD "/etc/passwd" +#endif +#ifndef _PATH_GROUP +#define _PATH_GROUP "/etc/group" +#endif + +/**********************************************************************/ +/* Sizes for statically allocated buffers. */ + +/* If you change these values, also change _SC_GETPW_R_SIZE_MAX and + * _SC_GETGR_R_SIZE_MAX in libc/unistd/sysconf.c to match */ +#define PWD_BUFFER_SIZE 256 +#define GRP_BUFFER_SIZE 256 + +/**********************************************************************/ +/* Prototypes for internal functions. */ + +extern int __parsepwent(void *pw, char *line); +extern int __parsegrent(void *gr, char *line); +extern int __parsespent(void *sp, char *line); + +extern int __pgsreader(int (*__parserfunc)(void *d, char *line), void *data, + char *__restrict line_buff, size_t buflen, FILE *f); + +/**********************************************************************/ +/* For the various fget??ent_r funcs, return + * + * 0: success + * ENOENT: end-of-file encountered + * ERANGE: buflen too small + * other error values possible. See __pgsreader. + * + * Also, *result == resultbuf on success and NULL on failure. + * + * NOTE: glibc difference - For the ENOENT case, glibc also sets errno. + * We do not, as it really isn't an error if we reach the end-of-file. + * Doing so is analogous to having fgetc() set errno on EOF. + */ +/**********************************************************************/ + +int fgetpwent_r(FILE *__restrict stream, struct passwd *__restrict resultbuf, + char *__restrict buffer, size_t buflen, + struct passwd **__restrict result) +{ + int rv; + + *result = NULL; + + if (!(rv = __pgsreader(__parsepwent, resultbuf, buffer, buflen, stream))) { + *result = resultbuf; + } + + return rv; +} + +int fgetgrent_r(FILE *__restrict stream, struct group *__restrict resultbuf, + char *__restrict buffer, size_t buflen, + struct group **__restrict result) +{ + int rv; + + *result = NULL; + + if (!(rv = __pgsreader(__parsegrent, resultbuf, buffer, buflen, stream))) { + *result = resultbuf; + } + + return rv; +} + +int fgetspent_r(FILE *__restrict stream, struct spwd *__restrict resultbuf, + char *__restrict buffer, size_t buflen, + struct spwd **__restrict result) +{ + int rv; + + *result = NULL; + + if (!(rv = __pgsreader(__parsespent, resultbuf, buffer, buflen, stream))) { + *result = resultbuf; + } + + return rv; +} + +/**********************************************************************/ +/* For the various fget??ent funcs, return NULL on failure and a + * pointer to the appropriate struct (statically allocated) on success. + */ +/**********************************************************************/ + +struct passwd *fgetpwent(FILE *stream) +{ + static char buffer[PWD_BUFFER_SIZE]; + static struct passwd resultbuf; + struct passwd *result; + + fgetpwent_r(stream, &resultbuf, buffer, sizeof(buffer), &result); + return result; +} + +struct group *fgetgrent(FILE *stream) +{ + static char buffer[GRP_BUFFER_SIZE]; + static struct group resultbuf; + struct group *result; + + fgetgrent_r(stream, &resultbuf, buffer, sizeof(buffer), &result); + return result; +} + +extern int fgetspent_r(FILE *__restrict stream, struct spwd *__restrict resultbuf, + char *__restrict buffer, size_t buflen, + struct spwd **__restrict result); +struct spwd *fgetspent(FILE *stream) +{ + static char buffer[PWD_BUFFER_SIZE]; + static struct spwd resultbuf; + struct spwd *result; + + fgetspent_r(stream, &resultbuf, buffer, sizeof(buffer), &result); + return result; +} + +int sgetspent_r(const char *string, struct spwd *result_buf, + char *buffer, size_t buflen, struct spwd **result) +{ + int rv = ERANGE; + + *result = NULL; + + if (buflen < PWD_BUFFER_SIZE) { + DO_ERANGE: + errno=rv; + goto DONE; + } + + if (string != buffer) { + if (strlen(string) >= buflen) { + goto DO_ERANGE; + } + strcpy(buffer, string); + } + + if (!(rv = __parsespent(result_buf, buffer))) { + *result = result_buf; + } + + DONE: + return rv; +} + +/**********************************************************************/ + +#ifdef GETXXKEY_R_FUNC +#error GETXXKEY_R_FUNC is already defined! +#endif + +#define GETXXKEY_R_FUNC getpwnam_R +#define GETXXKEY_R_PARSER __parsepwent +#define GETXXKEY_R_ENTTYPE struct passwd +#define GETXXKEY_R_TEST(ENT) (!strcmp((ENT)->pw_name, key)) +#define DO_GETXXKEY_R_KEYTYPE const char *__restrict +#define DO_GETXXKEY_R_PATHNAME _PATH_PASSWD +#include "pwd_grp_internal.c" + +#define GETXXKEY_R_FUNC getgrnam_R +#define GETXXKEY_R_PARSER __parsegrent +#define GETXXKEY_R_ENTTYPE struct group +#define GETXXKEY_R_TEST(ENT) (!strcmp((ENT)->gr_name, key)) +#define DO_GETXXKEY_R_KEYTYPE const char *__restrict +#define DO_GETXXKEY_R_PATHNAME _PATH_GROUP +#include "pwd_grp_internal.c" + +#define GETXXKEY_R_FUNC getspnam_R +#define GETXXKEY_R_PARSER __parsespent +#define GETXXKEY_R_ENTTYPE struct spwd +#define GETXXKEY_R_TEST(ENT) (!strcmp((ENT)->sp_namp, key)) +#define DO_GETXXKEY_R_KEYTYPE const char *__restrict +#define DO_GETXXKEY_R_PATHNAME _PATH_SHADOW +#include "pwd_grp_internal.c" + +#define GETXXKEY_R_FUNC getpwuid_R +#define GETXXKEY_R_PARSER __parsepwent +#define GETXXKEY_R_ENTTYPE struct passwd +#define GETXXKEY_R_TEST(ENT) ((ENT)->pw_uid == key) +#define DO_GETXXKEY_R_KEYTYPE uid_t +#define DO_GETXXKEY_R_PATHNAME _PATH_PASSWD +#include "pwd_grp_internal.c" + +#define GETXXKEY_R_FUNC getgrgid_R +#define GETXXKEY_R_PARSER __parsegrent +#define GETXXKEY_R_ENTTYPE struct group +#define GETXXKEY_R_TEST(ENT) ((ENT)->gr_gid == key) +#define DO_GETXXKEY_R_KEYTYPE gid_t +#define DO_GETXXKEY_R_PATHNAME _PATH_GROUP +#include "pwd_grp_internal.c" + +/**********************************************************************/ + +struct passwd *getpwuid(uid_t uid) +{ + static char buffer[PWD_BUFFER_SIZE]; + static struct passwd resultbuf; + struct passwd *result; + + getpwuid_r(uid, &resultbuf, buffer, sizeof(buffer), &result); + return result; +} + +struct group *getgrgid(gid_t gid) +{ + static char buffer[GRP_BUFFER_SIZE]; + static struct group resultbuf; + struct group *result; + + getgrgid_r(gid, &resultbuf, buffer, sizeof(buffer), &result); + return result; +} + +/* This function is non-standard and is currently not built. It seems + * to have been created as a reentrant version of the non-standard + * functions getspuid. Why getspuid was added, I do not know. */ + +int getspuid_r(uid_t uid, struct spwd *__restrict resultbuf, + char *__restrict buffer, size_t buflen, + struct spwd **__restrict result) +{ + int rv; + struct passwd *pp; + struct passwd password; + char pwd_buff[PWD_BUFFER_SIZE]; + + *result = NULL; + if (!(rv = getpwuid_r(uid, &password, pwd_buff, sizeof(pwd_buff), &pp))) { + rv = getspnam_r(password.pw_name, resultbuf, buffer, buflen, result); + } + + return rv; +} + +/* This function is non-standard and is currently not built. + * Why it was added, I do not know. */ + +struct spwd *getspuid(uid_t uid) +{ + static char buffer[PWD_BUFFER_SIZE]; + static struct spwd resultbuf; + struct spwd *result; + + getspuid_r(uid, &resultbuf, buffer, sizeof(buffer), &result); + return result; +} + +struct passwd *getpwnam(const char *name) +{ + static char buffer[PWD_BUFFER_SIZE]; + static struct passwd resultbuf; + struct passwd *result; + + getpwnam_r(name, &resultbuf, buffer, sizeof(buffer), &result); + return result; +} + +struct group *getgrnam(const char *name) +{ + static char buffer[GRP_BUFFER_SIZE]; + static struct group resultbuf; + struct group *result; + + getgrnam_r(name, &resultbuf, buffer, sizeof(buffer), &result); + return result; +} + +struct spwd *getspnam(const char *name) +{ + static char buffer[PWD_BUFFER_SIZE]; + static struct spwd resultbuf; + struct spwd *result; + + getspnam_r(name, &resultbuf, buffer, sizeof(buffer), &result); + return result; +} + +int getpw(uid_t uid, char *buf) +{ + struct passwd resultbuf; + struct passwd *result; + char buffer[PWD_BUFFER_SIZE]; + + if (!buf) { + errno=EINVAL; + } else if (!getpwuid_r(uid, &resultbuf, buffer, sizeof(buffer), &result)) { + if (sprintf(buf, "%s:%s:%lu:%lu:%s:%s:%s\n", + resultbuf.pw_name, resultbuf.pw_passwd, + (unsigned long)(resultbuf.pw_uid), + (unsigned long)(resultbuf.pw_gid), + resultbuf.pw_gecos, resultbuf.pw_dir, + resultbuf.pw_shell) >= 0 + ) { + return 0; + } + } + + return -1; +} + +/**********************************************************************/ + +#if defined CONFIG_USE_BB_THREADSAFE_SHADOW && defined PTHREAD_MUTEX_INITIALIZER +static pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER; +# define LOCK pthread_mutex_lock(&mylock) +# define UNLOCK pthread_mutex_unlock(&mylock); +#else +# define LOCK ((void) 0) +# define UNLOCK ((void) 0) +#endif + +static FILE *pwf /*= NULL*/; +void setpwent(void) +{ + LOCK; + if (pwf) { + rewind(pwf); + } + UNLOCK; +} + +void endpwent(void) +{ + LOCK; + if (pwf) { + fclose(pwf); + pwf = NULL; + } + UNLOCK; +} + + +int getpwent_r(struct passwd *__restrict resultbuf, + char *__restrict buffer, size_t buflen, + struct passwd **__restrict result) +{ + int rv; + + LOCK; + *result = NULL; /* In case of error... */ + + if (!pwf) { + if (!(pwf = fopen(_PATH_PASSWD, "r"))) { + rv = errno; + goto ERR; + } + } + + if (!(rv = __pgsreader(__parsepwent, resultbuf, + buffer, buflen, pwf))) { + *result = resultbuf; + } + + ERR: + UNLOCK; + return rv; +} + +static FILE *grf /*= NULL*/; +void setgrent(void) +{ + LOCK; + if (grf) { + rewind(grf); + } + UNLOCK; +} + +void endgrent(void) +{ + LOCK; + if (grf) { + fclose(grf); + grf = NULL; + } + UNLOCK; +} + +int getgrent_r(struct group *__restrict resultbuf, + char *__restrict buffer, size_t buflen, + struct group **__restrict result) +{ + int rv; + + LOCK; + *result = NULL; /* In case of error... */ + + if (!grf) { + if (!(grf = fopen(_PATH_GROUP, "r"))) { + rv = errno; + goto ERR; + } + } + + if (!(rv = __pgsreader(__parsegrent, resultbuf, + buffer, buflen, grf))) { + *result = resultbuf; + } + + ERR: + UNLOCK; + return rv; +} + +static FILE *spf /*= NULL*/; +void setspent(void) +{ + LOCK; + if (spf) { + rewind(spf); + } + UNLOCK; +} + +void endspent(void) +{ + LOCK; + if (spf) { + fclose(spf); + spf = NULL; + } + UNLOCK; +} + +int getspent_r(struct spwd *resultbuf, char *buffer, + size_t buflen, struct spwd **result) +{ + int rv; + + LOCK; + *result = NULL; /* In case of error... */ + + if (!spf) { + if (!(spf = fopen(_PATH_SHADOW, "r"))) { + rv = errno; + goto ERR; + } + } + + if (!(rv = __pgsreader(__parsespent, resultbuf, + buffer, buflen, spf))) { + *result = resultbuf; + } + + ERR: + UNLOCK; + return rv; +} + +struct passwd *getpwent(void) +{ + static char line_buff[PWD_BUFFER_SIZE]; + static struct passwd pwd; + struct passwd *result; + + getpwent_r(&pwd, line_buff, sizeof(line_buff), &result); + return result; +} + +struct group *getgrent(void) +{ + static char line_buff[GRP_BUFFER_SIZE]; + static struct group gr; + struct group *result; + + getgrent_r(&gr, line_buff, sizeof(line_buff), &result); + return result; +} + +struct spwd *getspent(void) +{ + static char line_buff[PWD_BUFFER_SIZE]; + static struct spwd spwd; + struct spwd *result; + + getspent_r(&spwd, line_buff, sizeof(line_buff), &result); + return result; +} + +struct spwd *sgetspent(const char *string) +{ + static char line_buff[PWD_BUFFER_SIZE]; + static struct spwd spwd; + struct spwd *result; + + sgetspent_r(string, &spwd, line_buff, sizeof(line_buff), &result); + return result; +} + +int initgroups(const char *user, gid_t gid) +{ + FILE *grfile; + gid_t *group_list; + int num_groups, rv; + char **m; + struct group group; + char buff[PWD_BUFFER_SIZE]; + + rv = -1; + + /* We alloc space for 8 gids at a time. */ + if (((group_list = (gid_t *) malloc(8*sizeof(gid_t *))) != NULL) + && ((grfile = fopen(_PATH_GROUP, "r")) != NULL) + ) { + + *group_list = gid; + num_groups = 1; + + while (!__pgsreader(__parsegrent, &group, buff, sizeof(buff), grfile)) { + assert(group.gr_mem); /* Must have at least a NULL terminator. */ + if (group.gr_gid != gid) { + for (m=group.gr_mem ; *m ; m++) { + if (!strcmp(*m, user)) { + if (!(num_groups & 7)) { + gid_t *tmp = (gid_t *) + realloc(group_list, + (num_groups+8) * sizeof(gid_t *)); + if (!tmp) { + rv = -1; + goto DO_CLOSE; + } + group_list = tmp; + } + group_list[num_groups++] = group.gr_gid; + break; + } + } + } + } + + rv = setgroups(num_groups, group_list); + DO_CLOSE: + fclose(grfile); + } + + /* group_list will be NULL if initial malloc failed, which may trigger + * warnings from various malloc debuggers. */ + free(group_list); + return rv; +} + +int putpwent(const struct passwd *__restrict p, FILE *__restrict f) +{ + int rv = -1; + + if (!p || !f) { + errno=EINVAL; + } else { + /* No extra thread locking is needed above what fprintf does. */ + if (fprintf(f, "%s:%s:%lu:%lu:%s:%s:%s\n", + p->pw_name, p->pw_passwd, + (unsigned long)(p->pw_uid), + (unsigned long)(p->pw_gid), + p->pw_gecos, p->pw_dir, p->pw_shell) >= 0 + ) { + rv = 0; + } + } + + return rv; +} + +int putgrent(const struct group *__restrict p, FILE *__restrict f) +{ + static const char format[] = ",%s"; + char **m; + const char *fmt; + int rv = -1; + + if (!p || !f) { /* Sigh... glibc checks. */ + errno=EINVAL; + } else { + if (fprintf(f, "%s:%s:%lu:", + p->gr_name, p->gr_passwd, + (unsigned long)(p->gr_gid)) >= 0 + ) { + + fmt = format + 1; + + assert(p->gr_mem); + m = p->gr_mem; + + do { + if (!*m) { + if (fputc('\n', f) >= 0) { + rv = 0; + } + break; + } + if (fprintf(f, fmt, *m) < 0) { + break; + } + ++m; + fmt = format; + } while (1); + + } + + } + + return rv; +} + +static const unsigned char _sp_off[] = { + offsetof(struct spwd, sp_lstchg), /* 2 - not a char ptr */ + offsetof(struct spwd, sp_min), /* 3 - not a char ptr */ + offsetof(struct spwd, sp_max), /* 4 - not a char ptr */ + offsetof(struct spwd, sp_warn), /* 5 - not a char ptr */ + offsetof(struct spwd, sp_inact), /* 6 - not a char ptr */ + offsetof(struct spwd, sp_expire), /* 7 - not a char ptr */ +}; + +int putspent(const struct spwd *p, FILE *stream) +{ + static const char ld_format[] = "%ld:"; + const char *f; + long int x; + int i; + int rv = -1; + + /* Unlike putpwent and putgrent, glibc does not check the args. */ + if (fprintf(stream, "%s:%s:", p->sp_namp, + (p->sp_pwdp ? p->sp_pwdp : "")) < 0 + ) { + goto DO_UNLOCK; + } + + for (i=0 ; i < sizeof(_sp_off) ; i++) { + f = ld_format; + if ((x = *(const long int *)(((const char *) p) + _sp_off[i])) == -1) { + f += 3; + } + if (fprintf(stream, f, x) < 0) { + goto DO_UNLOCK; + } + } + + if ((p->sp_flag != ~0UL) && (fprintf(stream, "%lu", p->sp_flag) < 0)) { + goto DO_UNLOCK; + } + + if (fputc('\n', stream) > 0) { + rv = 0; + } + +DO_UNLOCK: + return rv; +} + +/**********************************************************************/ +/* Internal uClibc functions. */ +/**********************************************************************/ + +static const unsigned char pw_off[] = { + offsetof(struct passwd, pw_name), /* 0 */ + offsetof(struct passwd, pw_passwd), /* 1 */ + offsetof(struct passwd, pw_uid), /* 2 - not a char ptr */ + offsetof(struct passwd, pw_gid), /* 3 - not a char ptr */ + offsetof(struct passwd, pw_gecos), /* 4 */ + offsetof(struct passwd, pw_dir), /* 5 */ + offsetof(struct passwd, pw_shell) /* 6 */ +}; + +int __parsepwent(void *data, char *line) +{ + char *endptr; + char *p; + int i; + + i = 0; + do { + p = ((char *) ((struct passwd *) data)) + pw_off[i]; + + if ((i & 6) ^ 2) { /* i!=2 and i!=3 */ + *((char **) p) = line; + if (i==6) { + return 0; + } + /* NOTE: glibc difference - glibc allows omission of + * ':' seperators after the gid field if all remaining + * entries are empty. We require all separators. */ + if (!(line = strchr(line, ':'))) { + break; + } + } else { + unsigned long t = strtoul(line, &endptr, 10); + /* Make sure we had at least one digit, and that the + * failing char is the next field seperator ':'. See + * glibc difference note above. */ + /* TODO: Also check for leading whitespace? */ + if ((endptr == line) || (*endptr != ':')) { + break; + } + line = endptr; + if (i & 1) { /* i == 3 -- gid */ + *((gid_t *) p) = t; + } else { /* i == 2 -- uid */ + *((uid_t *) p) = t; + } + } + + *line++ = 0; + ++i; + } while (1); + + return -1; +} + +/**********************************************************************/ + +static const unsigned char gr_off[] = { + offsetof(struct group, gr_name), /* 0 */ + offsetof(struct group, gr_passwd), /* 1 */ + offsetof(struct group, gr_gid) /* 2 - not a char ptr */ +}; + +int __parsegrent(void *data, char *line) +{ + char *endptr; + char *p; + int i; + char **members; + char *end_of_buf; + + end_of_buf = ((struct group *) data)->gr_name; /* Evil hack! */ + i = 0; + do { + p = ((char *) ((struct group *) data)) + gr_off[i]; + + if (i < 2) { + *((char **) p) = line; + if (!(line = strchr(line, ':'))) { + break; + } + *line++ = 0; + ++i; + } else { + *((gid_t *) p) = strtoul(line, &endptr, 10); + + /* NOTE: glibc difference - glibc allows omission of the + * trailing colon when there is no member list. We treat + * this as an error. */ + + /* Make sure we had at least one digit, and that the + * failing char is the next field seperator ':'. See + * glibc difference note above. */ + if ((endptr == line) || (*endptr != ':')) { + break; + } + + i = 1; /* Count terminating NULL ptr. */ + p = endptr; + + if (p[1]) { /* We have a member list to process. */ + /* Overwrite the last ':' with a ',' before counting. + * This allows us to test for initial ',' and adds + * one ',' so that the ',' count equals the member + * count. */ + *p = ','; + do { + /* NOTE: glibc difference - glibc allows and trims leading + * (but not trailing) space. We treat this as an error. */ + /* NOTE: glibc difference - glibc allows consecutive and + * trailing commas, and ignores "empty string" users. We + * treat this as an error. */ + if (*p == ',') { + ++i; + *p = 0; /* nul-terminate each member string. */ + if (!*++p || (*p == ',') || isspace(*p)) { + goto ERR; + } + } + } while (*++p); + } + + /* Now align (p+1), rounding up. */ + /* Assumes sizeof(char **) is a power of 2. */ + members = (char **)( (((intptr_t) p) + sizeof(char **)) + & ~((intptr_t)(sizeof(char **) - 1)) ); + + if (((char *)(members + i)) > end_of_buf) { /* No space. */ + break; + } + + ((struct group *) data)->gr_mem = members; + + if (--i) { + p = endptr; /* Pointing to char prior to first member. */ + do { + *members++ = ++p; + if (!--i) break; + while (*++p) {} + } while (1); + } + *members = NULL; + + return 0; + } + } while (1); + + ERR: + return -1; +} + +/**********************************************************************/ + +static const unsigned char sp_off[] = { + offsetof(struct spwd, sp_namp), /* 0 */ + offsetof(struct spwd, sp_pwdp), /* 1 */ + offsetof(struct spwd, sp_lstchg), /* 2 - not a char ptr */ + offsetof(struct spwd, sp_min), /* 3 - not a char ptr */ + offsetof(struct spwd, sp_max), /* 4 - not a char ptr */ + offsetof(struct spwd, sp_warn), /* 5 - not a char ptr */ + offsetof(struct spwd, sp_inact), /* 6 - not a char ptr */ + offsetof(struct spwd, sp_expire), /* 7 - not a char ptr */ + offsetof(struct spwd, sp_flag) /* 8 - not a char ptr */ +}; + +int __parsespent(void *data, char * line) +{ + char *endptr; + char *p; + int i; + + i = 0; + do { + p = ((char *) ((struct spwd *) data)) + sp_off[i]; + if (i < 2) { + *((char **) p) = line; + if (!(line = strchr(line, ':'))) { + break; + } + } else { + *((long *) p) = (long) strtoul(line, &endptr, 10); + + if (endptr == line) { + *((long *) p) = ((i != 8) ? -1L : ((long)(~0UL))); + } + + line = endptr; + + if (i == 8) { + if (!*endptr) { + return 0; + } + break; + } + + if (*endptr != ':') { + break; + } + + } + + *line++ = 0; + ++i; + } while (1); + + return EINVAL; +} + +/**********************************************************************/ + +/* Reads until if EOF, or until if finds a line which fits in the buffer + * and for which the parser function succeeds. + * + * Returns 0 on success and ENOENT for end-of-file (glibc concession). + */ + +int __pgsreader(int (*__parserfunc)(void *d, char *line), void *data, + char *__restrict line_buff, size_t buflen, FILE *f) +{ + int line_len; + int skip; + int rv = ERANGE; + + if (buflen < PWD_BUFFER_SIZE) { + errno=rv; + } else { + skip = 0; + do { + if (!fgets(line_buff, buflen, f)) { + if (feof(f)) { + rv = ENOENT; + } + break; + } + + line_len = strlen(line_buff) - 1; /* strlen() must be > 0. */ + if (line_buff[line_len] == '\n') { + line_buff[line_len] = 0; + } else if (line_len + 2 == buflen) { /* line too long */ + ++skip; + continue; + } + + if (skip) { + --skip; + continue; + } + + /* NOTE: glibc difference - glibc strips leading whitespace from + * records. We do not allow leading whitespace. */ + + /* Skip empty lines, comment lines, and lines with leading + * whitespace. */ + if (*line_buff && (*line_buff != '#') && !isspace(*line_buff)) { + if (__parserfunc == __parsegrent) { /* Do evil group hack. */ + /* The group entry parsing function needs to know where + * the end of the buffer is so that it can construct the + * group member ptr table. */ + ((struct group *) data)->gr_name = line_buff + buflen; + } + + if (!__parserfunc(data, line_buff)) { + rv = 0; + break; + } + } + } while (1); + + } + + return rv; +} + +/**********************************************************************/ diff --git a/libpwdgrp/pwd_grp_internal.c b/libpwdgrp/pwd_grp_internal.c new file mode 100644 index 000000000..866ed3699 --- /dev/null +++ b/libpwdgrp/pwd_grp_internal.c @@ -0,0 +1,62 @@ +/* vi: set sw=4 ts=4: */ +/* Copyright (C) 2003 Manuel Novoa III + * + * Licensed under GPL v2, or later. See file LICENSE in this tarball. + */ + +/* Nov 6, 2003 Initial version. + * + * NOTE: This implementation is quite strict about requiring all + * field seperators. It also does not allow leading whitespace + * except when processing the numeric fields. glibc is more + * lenient. See the various glibc difference comments below. + * + * TODO: + * Move to dynamic allocation of (currently statically allocated) + * buffers; especially for the group-related functions since + * large group member lists will cause error returns. + * + */ + +#ifndef GETXXKEY_R_FUNC +#error GETXXKEY_R_FUNC is not defined! +#endif + +int GETXXKEY_R_FUNC(DO_GETXXKEY_R_KEYTYPE key, + GETXXKEY_R_ENTTYPE *__restrict resultbuf, + char *__restrict buffer, size_t buflen, + GETXXKEY_R_ENTTYPE **__restrict result) +{ + FILE *stream; + int rv; + + *result = NULL; + + stream = fopen(DO_GETXXKEY_R_PATHNAME, "r"); + if (!stream) + return errno; + while (1) { + rv = __pgsreader(GETXXKEY_R_PARSER, resultbuf, buffer, buflen, stream); + if (!rv) { + if (GETXXKEY_R_TEST(resultbuf)) { /* Found key? */ + *result = resultbuf; + break; + } + } else { + if (rv == ENOENT) { /* end-of-file encountered. */ + rv = 0; + } + break; + } + } + fclose(stream); + + return rv; +} + +#undef GETXXKEY_R_FUNC +#undef GETXXKEY_R_PARSER +#undef GETXXKEY_R_ENTTYPE +#undef GETXXKEY_R_TEST +#undef DO_GETXXKEY_R_KEYTYPE +#undef DO_GETXXKEY_R_PATHNAME diff --git a/libpwdgrp/uidgid_get.c b/libpwdgrp/uidgid_get.c new file mode 100644 index 000000000..a2d02a84f --- /dev/null +++ b/libpwdgrp/uidgid_get.c @@ -0,0 +1,49 @@ +#include "busybox.h" + +unsigned uidgid_get(struct bb_uidgid_t *u, const char *ug /*, unsigned dogrp */) +{ + struct passwd *pwd; + struct group *gr; + const char *g; + + /* g = 0; if (dogrp) g = strchr(ug, ':'); */ + g = strchr(ug, ':'); + if (g) { + int sz = (++g) - ug; + char buf[sz]; + safe_strncpy(buf, ug, sz); + pwd = getpwnam(buf); + } else + pwd = getpwnam(ug); + if (!pwd) + return 0; + u->uid = pwd->pw_uid; + u->gid = pwd->pw_gid; + if (g) { + gr = getgrnam(g); + if (!gr) return 0; + u->gid = gr->gr_gid; + } + return 1; +} + +#if 0 +#include +int main() +{ + unsigned u; + struct bb_uidgid_t ug; + u = uidgid_get(&ug, "apache"); + printf("%u = %u:%u\n", u, ug.uid, ug.gid); + ug.uid = ug.gid = 1111; + u = uidgid_get(&ug, "apache"); + printf("%u = %u:%u\n", u, ug.uid, ug.gid); + ug.uid = ug.gid = 1111; + u = uidgid_get(&ug, "apache:users"); + printf("%u = %u:%u\n", u, ug.uid, ug.gid); + ug.uid = ug.gid = 1111; + u = uidgid_get(&ug, "apache:users"); + printf("%u = %u:%u\n", u, ug.uid, ug.gid); + return 0; +} +#endif diff --git a/loginutils/Config.in b/loginutils/Config.in new file mode 100644 index 000000000..2ad141511 --- /dev/null +++ b/loginutils/Config.in @@ -0,0 +1,183 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Login/Password Management Utilities" + +config FEATURE_SHADOWPASSWDS + bool "Support for shadow passwords" + default n + help + Build support for shadow password in /etc/shadow. This file is only + readable by root and thus the encrypted passwords are no longer + publicly readable. + +config USE_BB_SHADOW + bool " Use busybox shadow password functions" + default y + depends on USE_BB_PWD_GRP && FEATURE_SHADOWPASSWDS + help + If you leave this disabled, busybox will use the system's shadow + password handling functions. And if you are using the GNU C library + (glibc), you will then need to install the /etc/nsswitch.conf + configuration file and the required /lib/libnss_* libraries in + order for the shadow password functions to work. This generally + makes your embedded system quite a bit larger. + + Enabling this option will cause busybox to directly access the + system's /etc/shadow file when handling shadow passwords. This + makes your system smaller and I will get fewer emails asking about + how glibc NSS works). When this option is enabled, you will not be + able to use PAM to access shadow passwords from remote LDAP + password servers and whatnot. + +config USE_BB_PWD_GRP + bool "Use internal password and group functions rather than system functions" + default n + help + If you leave this disabled, busybox will use the system's password + and group functions. And if you are using the GNU C library + (glibc), you will then need to install the /etc/nsswitch.conf + configuration file and the required /lib/libnss_* libraries in + order for the password and group functions to work. This generally + makes your embedded system quite a bit larger. + + Enabling this option will cause busybox to directly access the + system's /etc/password, /etc/group files (and your system will be + smaller, and I will get fewer emails asking about how glibc NSS + works). When this option is enabled, you will not be able to use + PAM to access remote LDAP password servers and whatnot. And if you + want hostname resolution to work with glibc, you still need the + /lib/libnss_* libraries. + + If you enable this option, it will add about 1.5k to busybox. + +config ADDGROUP + bool "addgroup" + default n + help + Utility for creating a new group account. + +config DELGROUP + bool "delgroup" + default n + help + Utility for deleting a group account. + +config ADDUSER + bool "adduser" + default n + help + Utility for creating a new user account. + +config DELUSER + bool "deluser" + default n + help + Utility for deleting a user account. + +config GETTY + bool "getty" + default n + select FEATURE_SYSLOG + help + getty lets you log in on a tty, it is normally invoked by init. + +config FEATURE_UTMP + bool "Support utmp file" + depends on GETTY || LOGIN || SU || WHO + default n + help + The file /var/run/utmp is used to track who is currently logged in. + +config FEATURE_WTMP + bool "Support wtmp file" + depends on GETTY || LOGIN || SU || LAST + default n + select FEATURE_UTMP + help + The file /var/run/wtmp is used to track when user's have logged into + and logged out of the system. + +config LOGIN + bool "login" + default n + select FEATURE_SUID + select FEATURE_SYSLOG + help + login is used when signing onto a system. + + Note that Busybox binary must be setuid root for this applet to + work properly. + +config LOGIN_SCRIPTS + bool "Support for login scripts" + depends on LOGIN + default n + help + Enable this if you want login to execute $LOGIN_PRE_SUID_SCRIPT + just prior to switching from root to logged-in user. + +config FEATURE_SECURETTY + bool "Support for /etc/securetty" + default y + depends on LOGIN + help + The file /etc/securetty is used by (some versions of) login(1). + The file contains the device names of tty lines (one per line, + without leading /dev/) on which root is allowed to login. + +config PASSWD + bool "passwd" + default n + select FEATURE_SUID + select FEATURE_SYSLOG + help + passwd changes passwords for user and group accounts. A normal user + may only change the password for his/her own account, the super user + may change the password for any account. The administrator of a group + may change the password for the group. + + Note that Busybox binary must be setuid root for this applet to + work properly. + +config SU + bool "su" + default n + select FEATURE_SUID + select FEATURE_SYSLOG + help + su is used to become another user during a login session. + Invoked without a username, su defaults to becoming the super user. + + Note that Busybox binary must be setuid root for this applet to + work properly. + +config SU_SYSLOG + bool "Support for syslog in su" + default y + depends on SU + help + Enables support for syslog in su. + +config SULOGIN + bool "sulogin" + default n + select FEATURE_SYSLOG + help + sulogin is invoked when the system goes into single user + mode (this is done through an entry in inittab). + +config VLOCK + bool "vlock" + default n + select FEATURE_SUID + help + Build the "vlock" applet which allows you to lock (virtual) terminals. + + Note that Busybox binary must be setuid root for this applet to + work properly. + +endmenu + diff --git a/loginutils/Kbuild b/loginutils/Kbuild new file mode 100644 index 000000000..6c9d193e1 --- /dev/null +++ b/loginutils/Kbuild @@ -0,0 +1,17 @@ +# 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_ADDGROUP) += addgroup.o +lib-$(CONFIG_ADDUSER) += adduser.o +lib-$(CONFIG_GETTY) += getty.o +lib-$(CONFIG_LOGIN) += login.o +lib-$(CONFIG_PASSWD) += passwd.o +lib-$(CONFIG_SU) += su.o +lib-$(CONFIG_SULOGIN) += sulogin.o +lib-$(CONFIG_VLOCK) += vlock.o +lib-$(CONFIG_DELUSER) += deluser.o +lib-$(CONFIG_DELGROUP) += deluser.o diff --git a/loginutils/addgroup.c b/loginutils/addgroup.c new file mode 100644 index 000000000..0172e6041 --- /dev/null +++ b/loginutils/addgroup.c @@ -0,0 +1,107 @@ +/* vi: set sw=4 ts=4: */ +/* + * addgroup - add users to /etc/passwd and /etc/shadow + * + * Copyright (C) 1999 by Lineo, inc. and John Beppu + * Copyright (C) 1999,2000,2001 by John Beppu + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + */ + +#include "busybox.h" + +/* make sure gr_name isn't taken, make sure gid is kosher + * return 1 on failure */ +static int group_study(struct group *g) +{ + FILE *etc_group; + gid_t desired; + + struct group *grp; + const int max = 65000; + + etc_group = xfopen(bb_path_group_file, "r"); + + /* make sure gr_name isn't taken, make sure gid is kosher */ + desired = g->gr_gid; + while ((grp = fgetgrent(etc_group))) { + if ((strcmp(grp->gr_name, g->gr_name)) == 0) { + bb_error_msg_and_die("%s: group already in use", g->gr_name); + } + if ((desired) && grp->gr_gid == desired) { + bb_error_msg_and_die("%d: gid already in use", + desired); + } + if ((grp->gr_gid > g->gr_gid) && (grp->gr_gid < max)) { + g->gr_gid = grp->gr_gid; + } + } + fclose(etc_group); + + /* gid */ + if (desired) { + g->gr_gid = desired; + } else { + g->gr_gid++; + } + /* return 1; */ + return 0; +} + +/* append a new user to the passwd file */ +static int addgroup(char *group, gid_t gid, const char *user) +{ + FILE *file; + struct group gr; + + /* make sure gid and group haven't already been allocated */ + gr.gr_gid = gid; + gr.gr_name = group; + if (group_study(&gr)) + return 1; + + /* add entry to group */ + file = xfopen(bb_path_group_file, "a"); + /* group:passwd:gid:userlist */ + fprintf(file, "%s:%s:%d:%s\n", group, "x", gr.gr_gid, user); + fclose(file); + +#if ENABLE_FEATURE_SHADOWPASSWDS + file = xfopen(bb_path_gshadow_file, "a"); + fprintf(file, "%s:!::\n", group); + fclose(file); +#endif + + /* return 1; */ + return 0; +} + +/* + * addgroup will take a login_name as its first parameter. + * + * gid + * + * can be customized via command-line parameters. + * ________________________________________________________________________ */ +int addgroup_main(int argc, char **argv) +{ + char *group; + gid_t gid = 0; + + /* check for min, max and missing args and exit on error */ + opt_complementary = "-1:?2:?"; + if (getopt32(argc, argv, "g:", &group)) { + gid = xatoul_range(group, 0, (gid_t)ULONG_MAX); + } + /* move past the commandline options */ + argv += optind; + + /* need to be root */ + if (geteuid()) { + bb_error_msg_and_die(bb_msg_perm_denied_are_you_root); + } + + /* werk */ + return addgroup(argv[0], gid, (argv[1]) ? argv[1] : ""); +} diff --git a/loginutils/adduser.c b/loginutils/adduser.c new file mode 100644 index 000000000..44516ef5a --- /dev/null +++ b/loginutils/adduser.c @@ -0,0 +1,194 @@ +/* vi: set sw=4 ts=4: */ +/* + * adduser - add users to /etc/passwd and /etc/shadow + * + * Copyright (C) 1999 by Lineo, inc. and John Beppu + * Copyright (C) 1999,2000,2001 by John Beppu + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" + +#define DONT_SET_PASS (1 << 4) +#define DONT_MAKE_HOME (1 << 6) + + +/* remix */ +/* EDR recoded such that the uid may be passed in *p */ +static int passwd_study(const char *filename, struct passwd *p) +{ + struct passwd *pw; + FILE *passwd; + + const int min = 500; + const int max = 65000; + + passwd = xfopen(filename, "r"); + + /* EDR if uid is out of bounds, set to min */ + if ((p->pw_uid > max) || (p->pw_uid < min)) + p->pw_uid = min; + + /* stuff to do: + * make sure login isn't taken; + * find free uid and gid; + */ + while ((pw = fgetpwent(passwd))) { + if (strcmp(pw->pw_name, p->pw_name) == 0) { + /* return 0; */ + return 1; + } + if ((pw->pw_uid >= p->pw_uid) && (pw->pw_uid < max) + && (pw->pw_uid >= min)) { + p->pw_uid = pw->pw_uid + 1; + } + } + + if (p->pw_gid == 0) { + /* EDR check for an already existing gid */ + while (getgrgid(p->pw_uid) != NULL) + p->pw_uid++; + + /* EDR also check for an existing group definition */ + if (getgrnam(p->pw_name) != NULL) + return 3; + + /* EDR create new gid always = uid */ + p->pw_gid = p->pw_uid; + } + + /* EDR bounds check */ + if ((p->pw_uid > max) || (p->pw_uid < min)) + return 2; + + /* return 1; */ + return 0; +} + +static void addgroup_wrapper(struct passwd *p) +{ + char *cmd; + + cmd = xasprintf("addgroup -g %d \"%s\"", p->pw_gid, p->pw_name); + system(cmd); + free(cmd); +} + +static void passwd_wrapper(const char *login) ATTRIBUTE_NORETURN; + +static void passwd_wrapper(const char *login) +{ + static const char prog[] = "passwd"; + execlp(prog, prog, login, NULL); + bb_error_msg_and_die("failed to execute '%s', you must set the password for '%s' manually", prog, login); +} + +/* putpwent(3) remix */ +static int adduser(struct passwd *p, unsigned long flags) +{ + FILE *file; + int addgroup = !p->pw_gid; + + /* make sure everything is kosher and setup uid && gid */ + file = xfopen(bb_path_passwd_file, "a"); + fseek(file, 0, SEEK_END); + + switch (passwd_study(bb_path_passwd_file, p)) { + case 1: + bb_error_msg_and_die("%s: login already in use", p->pw_name); + case 2: + bb_error_msg_and_die("illegal uid or no uids left"); + case 3: + bb_error_msg_and_die("%s: group name already in use", p->pw_name); + } + + /* add to passwd */ + if (putpwent(p, file) == -1) { + bb_perror_nomsg_and_die(); + } + fclose(file); + +#if ENABLE_FEATURE_SHADOWPASSWDS + /* add to shadow if necessary */ + file = xfopen(bb_path_shadow_file, "a"); + fseek(file, 0, SEEK_END); + fprintf(file, "%s:!:%ld:%d:%d:%d:::\n", + p->pw_name, /* username */ + time(NULL) / 86400, /* sp->sp_lstchg */ + 0, /* sp->sp_min */ + 99999, /* sp->sp_max */ + 7); /* sp->sp_warn */ + fclose(file); +#endif + + /* add to group */ + /* addgroup should be responsible for dealing w/ gshadow */ + /* if using a pre-existing group, don't create one */ + if (addgroup) addgroup_wrapper(p); + + /* Clear the umask for this process so it doesn't + * * screw up the permissions on the mkdir and chown. */ + umask(0); + if (!(flags & DONT_MAKE_HOME)) { + /* Set the owner and group so it is owned by the new user, + then fix up the permissions to 2755. Can't do it before + since chown will clear the setgid bit */ + if (mkdir(p->pw_dir, 0755) + || chown(p->pw_dir, p->pw_uid, p->pw_gid) + || chmod(p->pw_dir, 02755)) { + bb_perror_msg("%s", p->pw_dir); + } + } + + if (!(flags & DONT_SET_PASS)) { + /* interactively set passwd */ + passwd_wrapper(p->pw_name); + } + + return 0; +} + +/* + * adduser will take a login_name as its first parameter. + * + * home + * shell + * gecos + * + * can be customized via command-line parameters. + * ________________________________________________________________________ */ +int adduser_main(int argc, char **argv) +{ + struct passwd pw; + const char *usegroup = NULL; + unsigned long flags; + + pw.pw_gecos = "Linux User,,,"; + pw.pw_shell = (char *)DEFAULT_SHELL; + pw.pw_dir = NULL; + + /* check for min, max and missing args and exit on error */ + opt_complementary = "-1:?1:?"; + flags = getopt32(argc, argv, "h:g:s:G:DSH", &pw.pw_dir, &pw.pw_gecos, &pw.pw_shell, &usegroup); + + /* got root? */ + if(geteuid()) { + bb_error_msg_and_die(bb_msg_perm_denied_are_you_root); + } + + /* create string for $HOME if not specified already */ + if (!pw.pw_dir) { + snprintf(bb_common_bufsiz1, BUFSIZ, "/home/%s", argv[optind]); + pw.pw_dir = &bb_common_bufsiz1[0]; + } + + /* create a passwd struct */ + pw.pw_name = argv[optind]; + pw.pw_passwd = "x"; + pw.pw_uid = 0; + pw.pw_gid = (usegroup) ? bb_xgetgrnam(usegroup) : 0; /* exits on failure */ + + /* grand finale */ + return adduser(&pw, flags); +} diff --git a/loginutils/deluser.c b/loginutils/deluser.c new file mode 100644 index 000000000..869b22013 --- /dev/null +++ b/loginutils/deluser.c @@ -0,0 +1,83 @@ +/* vi: set sw=4 ts=4: */ +/* + * deluser (remove lusers from the system ;) for TinyLogin + * + * Copyright (C) 1999 by Lineo, inc. and John Beppu + * Copyright (C) 1999,2000,2001 by John Beppu + * Unified with delgroup by Tito Ragusa + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + * + */ + +#include "busybox.h" + +static void del_line_matching(const char *login, const char *filename) +{ + char *line; + FILE *passwd; + int len = strlen(login); + int found = 0; + llist_t *plist = NULL; + + passwd = fopen_or_warn(filename, "r"); + if (!passwd) return; + + while ((line = xmalloc_fgets(passwd))) { + if (!strncmp(line, login, len) + && line[len] == ':' + ) { + found++; + free(line); + } else { + llist_add_to_end(&plist, line); + } + } + + if (!ENABLE_FEATURE_CLEAN_UP) { + if (!found) { + bb_error_msg("can't find '%s' in '%s'", login, filename); + return; + } + passwd = fopen_or_warn(filename, "w"); + if (passwd) + while ((line = llist_pop(&plist))) + fputs(line, passwd); + } else { + if (!found) { + bb_error_msg("can't find '%s' in '%s'", login, filename); + goto clean_up; + } + fclose(passwd); + passwd = fopen_or_warn(filename, "w"); + if (passwd) { + clean_up: + while ((line = llist_pop(&plist))) { + if (found) fputs(line, passwd); + free(line); + } + fclose(passwd); + } + } +} + +int deluser_main(int argc, char **argv) +{ + if (argc != 2) + bb_show_usage(); + + if (ENABLE_DELUSER + && (!ENABLE_DELGROUP || applet_name[3] == 'u') + ) { + del_line_matching(argv[1], bb_path_passwd_file); + if (ENABLE_FEATURE_SHADOWPASSWDS) + del_line_matching(argv[1], bb_path_shadow_file); + } + del_line_matching(argv[1], bb_path_group_file); + if (ENABLE_FEATURE_SHADOWPASSWDS) + del_line_matching(argv[1], bb_path_gshadow_file); + + return EXIT_SUCCESS; +} + +/* $Id: deluser.c,v 1.4 2003/07/14 20:20:45 andersen Exp $ */ diff --git a/loginutils/getty.c b/loginutils/getty.c new file mode 100644 index 000000000..a85e52306 --- /dev/null +++ b/loginutils/getty.c @@ -0,0 +1,862 @@ +/* vi: set sw=4 ts=4: */ +/* agetty.c - another getty program for Linux. By W. Z. Venema 1989 + * Ported to Linux by Peter Orbaek + * This program is freely distributable. The entire man-page used to + * be here. Now read the real man-page agetty.8 instead. + * + * option added by Eric Rasmussen - 12/28/95 + * + * 1999-02-22 Arkadiusz Mi¶kiewicz + * - added Native Language Support + + * 1999-05-05 Thorsten Kranzkowski + * - enable hardware flow control before displaying /etc/issue + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + */ + +#include "busybox.h" +#include + +#if ENABLE_FEATURE_UTMP +#include +#endif + +/* + * Some heuristics to find out what environment we are in: if it is not + * System V, assume it is SunOS 4. + */ +#ifdef LOGIN_PROCESS /* defined in System V utmp.h */ +#define SYSV_STYLE /* select System V style getty */ +#include +#include +#if ENABLE_FEATURE_WTMP +extern void updwtmp(const char *filename, const struct utmp *ut); +static void update_utmp(char *line); +#endif +#endif /* LOGIN_PROCESS */ + +/* + * Things you may want to modify. + * + * You may disagree with the default line-editing etc. characters defined + * below. Note, however, that DEL cannot be used for interrupt generation + * and for line editing at the same time. + */ + +/* I doubt there are systems which still need this */ +#undef HANDLE_ALLCAPS + +#define _PATH_LOGIN "/bin/login" + +/* If ISSUE is not defined, getty will never display the contents of the + * /etc/issue file. You will not want to spit out large "issue" files at the + * wrong baud rate. + */ +#define ISSUE "/etc/issue" /* displayed before the login prompt */ + +/* Some shorthands for control characters. */ +#define CTL(x) (x ^ 0100) /* Assumes ASCII dialect */ +#define CR CTL('M') /* carriage return */ +#define NL CTL('J') /* line feed */ +#define BS CTL('H') /* back space */ +#define DEL CTL('?') /* delete */ + +/* Defaults for line-editing etc. characters; you may want to change this. */ +#define DEF_ERASE DEL /* default erase character */ +#define DEF_INTR CTL('C') /* default interrupt character */ +#define DEF_QUIT CTL('\\') /* default quit char */ +#define DEF_KILL CTL('U') /* default kill char */ +#define DEF_EOF CTL('D') /* default EOF char */ +#define DEF_EOL '\n' +#define DEF_SWITCH 0 /* default switch char */ + +/* + * When multiple baud rates are specified on the command line, the first one + * we will try is the first one specified. + */ +#define FIRST_SPEED 0 + +/* Storage for command-line options. */ + +#define MAX_SPEED 10 /* max. nr. of baud rates */ + +struct options { + int flags; /* toggle switches, see below */ + unsigned timeout; /* time-out period */ + char *login; /* login program */ + char *tty; /* name of tty */ + char *initstring; /* modem init string */ + char *issue; /* alternative issue file */ + int numspeed; /* number of baud rates to try */ + int speeds[MAX_SPEED]; /* baud rates to be tried */ +}; + +static const char opt_string[] = "I:LH:f:hil:mt:wn"; +#define F_INITSTRING (1<<0) /* initstring is set */ +#define F_LOCAL (1<<1) /* force local */ +#define F_FAKEHOST (1<<2) /* force fakehost */ +#define F_CUSTISSUE (1<<3) /* give alternative issue file */ +#define F_RTSCTS (1<<4) /* enable RTS/CTS flow control */ +#define F_ISSUE (1<<5) /* display /etc/issue */ +#define F_LOGIN (1<<6) /* non-default login program */ +#define F_PARSE (1<<7) /* process modem status messages */ +#define F_TIMEOUT (1<<8) /* time out */ +#define F_WAITCRLF (1<<9) /* wait for CR or LF */ +#define F_NOPROMPT (1<<10) /* don't ask for login name! */ + +/* Storage for things detected while the login name was read. */ +struct chardata { + unsigned char erase; /* erase character */ + unsigned char kill; /* kill character */ + unsigned char eol; /* end-of-line character */ + unsigned char parity; /* what parity did we see */ +#ifdef HANDLE_ALLCAPS + unsigned char capslock; /* upper case without lower case */ +#endif +}; + +/* Initial values for the above. */ +static const struct chardata init_chardata = { + DEF_ERASE, /* default erase character */ + DEF_KILL, /* default kill character */ + 13, /* default eol char */ + 0, /* space parity */ +#ifdef HANDLE_ALLCAPS + 0, /* no capslock */ +#endif +}; + +/* The following is used for understandable diagnostics. */ + +/* Fake hostname for ut_host specified on command line. */ +static char *fakehost = NULL; + +/* ... */ +#ifdef DEBUGGING +#define debug(s) fprintf(dbf,s); fflush(dbf) +#define DEBUGTERM "/dev/ttyp0" +static FILE *dbf; +#else +#define debug(s) /* nothing */ +#endif + + +/* bcode - convert speed string to speed code; return 0 on failure */ +static int bcode(const char *s) +{ + int r; + unsigned value = bb_strtou(s, NULL, 10); + if (errno) { + return -1; + } + r = tty_value_to_baud(value); + if (r > 0) { + return r; + } + return 0; +} + + +/* parse_speeds - parse alternate baud rates */ +static void parse_speeds(struct options *op, char *arg) +{ + char *cp; + + debug("entered parse_speeds\n"); + for (cp = strtok(arg, ","); cp != 0; cp = strtok((char *) 0, ",")) { + if ((op->speeds[op->numspeed++] = bcode(cp)) <= 0) + bb_error_msg_and_die("bad speed: %s", cp); + if (op->numspeed > MAX_SPEED) + bb_error_msg_and_die("too many alternate speeds"); + } + debug("exiting parsespeeds\n"); +} + + +/* parse_args - parse command-line arguments */ +static void parse_args(int argc, char **argv, struct options *op) +{ + char *ts; + + op->flags = getopt32(argc, argv, opt_string, + &(op->initstring), &fakehost, &(op->issue), + &(op->login), &ts); + if (op->flags & F_INITSTRING) { + const char *p = op->initstring; + char *q; + + q = op->initstring = xstrdup(op->initstring); + /* copy optarg into op->initstring decoding \ddd + octal codes into chars */ + while (*p) { + if (*p == '\\') { + p++; + *q++ = bb_process_escape_sequence(&p); + } else { + *q++ = *p++; + } + } + *q = '\0'; + } + op->flags ^= F_ISSUE; /* revert flag show /etc/issue */ + if (op->flags & F_TIMEOUT) { + op->timeout = xatoul_range(ts, 1, INT_MAX); + } + argv += optind; + argc -= optind; + debug("after getopt loop\n"); + if (argc < 2) /* check parameter count */ + bb_show_usage(); + + /* we loosen up a bit and accept both "baudrate tty" and "tty baudrate" */ + if ('0' <= argv[0][0] && argv[0][0] <= '9') { + /* a number first, assume it's a speed (BSD style) */ + parse_speeds(op, argv[0]); /* baud rate(s) */ + op->tty = argv[1]; /* tty name */ + } else { + op->tty = argv[0]; /* tty name */ + parse_speeds(op, argv[1]); /* baud rate(s) */ + } + + if (argv[2]) + setenv("TERM", argv[2], 1); + + debug("exiting parseargs\n"); +} + +static void xdup2(int srcfd, int dstfd, const char *tty) +{ + if (dup2(srcfd, dstfd) == -1) + bb_perror_msg_and_die("%s: dup", tty); +} + +/* open_tty - set up tty as standard { input, output, error } */ +static void open_tty(char *tty, struct termios *tp, int local) +{ + int chdir_to_root = 0; + + /* Set up new standard input, unless we are given an already opened port. */ + + if (strcmp(tty, "-")) { + struct stat st; + int fd; + + /* Sanity checks... */ + + xchdir("/dev"); + chdir_to_root = 1; + xstat(tty, &st); + if ((st.st_mode & S_IFMT) != S_IFCHR) + bb_error_msg_and_die("%s: not a character device", tty); + + /* Open the tty as standard input. */ + + debug("open(2)\n"); + fd = xopen(tty, O_RDWR | O_NONBLOCK); + if (fd) { + xdup2(fd, 0, tty); + close(fd); + } + } else { + /* + * Standard input should already be connected to an open port. Make + * sure it is open for read/write. + */ + + if ((fcntl(0, F_GETFL, 0) & O_RDWR) != O_RDWR) + bb_error_msg_and_die("%s: not open for read/write", tty); + } + + /* Replace current standard output/error fd's with new ones */ + debug("duping\n"); + xdup2(0, 1, tty); + xdup2(0, 2, tty); + + /* + * The following ioctl will fail if stdin is not a tty, but also when + * there is noise on the modem control lines. In the latter case, the + * common course of action is (1) fix your cables (2) give the modem more + * time to properly reset after hanging up. SunOS users can achieve (2) + * by patching the SunOS kernel variable "zsadtrlow" to a larger value; + * 5 seconds seems to be a good value. + */ + + if (ioctl(0, TCGETS, tp) < 0) + bb_perror_msg_and_die("%s: ioctl(TCGETS)", tty); + + /* + * It seems to be a terminal. Set proper protections and ownership. Mode + * 0622 is suitable for SYSV <4 because /bin/login does not change + * protections. SunOS 4 login will change the protections to 0620 (write + * access for group tty) after the login has succeeded. + */ + +#ifdef DEBIAN +#warning Debian /dev/vcs[a]NN hack is deprecated and will be removed + { + /* tty to root.dialout 660 */ + struct group *gr; + int id; + + gr = getgrnam("dialout"); + id = gr ? gr->gr_gid : 0; + chown(tty, 0, id); + chmod(tty, 0660); + + /* vcs,vcsa to root.sys 600 */ + if (!strncmp(tty, "tty", 3) && isdigit(tty[3])) { + char *vcs, *vcsa; + + vcs = xstrdup(tty); + vcsa = xmalloc(strlen(tty) + 2); + strcpy(vcs, "vcs"); + strcpy(vcs + 3, tty + 3); + strcpy(vcsa, "vcsa"); + strcpy(vcsa + 4, tty + 3); + + id = (gr = getgrnam("sys")) ? gr->gr_gid : 0; + chown(vcs, 0, id); + chmod(vcs, 0600); + chown(vcsa, 0, id); + chmod(vcs, 0600); + + free(vcs); + free(vcsa); + } + } +#else + chown(tty, 0, 0); /* root, sys */ + chmod(tty, 0622); /* crw--w--w- */ +#endif + if (chdir_to_root) + xchdir("/"); +} + +/* termios_init - initialize termios settings */ +static void termios_init(struct termios *tp, int speed, struct options *op) +{ + /* + * Initial termios settings: 8-bit characters, raw-mode, blocking i/o. + * Special characters are set after we have read the login name; all + * reads will be done in raw mode anyway. Errors will be dealt with + * later on. + */ +#ifdef __linux__ + /* flush input and output queues, important for modems! */ + ioctl(0, TCFLSH, TCIOFLUSH); +#endif + + tp->c_cflag = CS8 | HUPCL | CREAD | speed; + if (op->flags & F_LOCAL) { + tp->c_cflag |= CLOCAL; + } + + tp->c_iflag = tp->c_lflag = tp->c_line = 0; + tp->c_oflag = OPOST | ONLCR; + tp->c_cc[VMIN] = 1; + tp->c_cc[VTIME] = 0; + + /* Optionally enable hardware flow control */ + +#ifdef CRTSCTS + if (op->flags & F_RTSCTS) + tp->c_cflag |= CRTSCTS; +#endif + + ioctl(0, TCSETS, tp); + + /* go to blocking input even in local mode */ + fcntl(0, F_SETFL, fcntl(0, F_GETFL, 0) & ~O_NONBLOCK); + + debug("term_io 2\n"); +} + +/* auto_baud - extract baud rate from modem status message */ +static void auto_baud(char *buf, unsigned size_buf, struct termios *tp) +{ + int speed; + int vmin; + unsigned iflag; + char *bp; + int nread; + + /* + * This works only if the modem produces its status code AFTER raising + * the DCD line, and if the computer is fast enough to set the proper + * baud rate before the message has gone by. We expect a message of the + * following format: + * + * + * + * The number is interpreted as the baud rate of the incoming call. If the + * modem does not tell us the baud rate within one second, we will keep + * using the current baud rate. It is advisable to enable BREAK + * processing (comma-separated list of baud rates) if the processing of + * modem status messages is enabled. + */ + + /* + * Use 7-bit characters, don't block if input queue is empty. Errors will + * be dealt with later on. + */ + + iflag = tp->c_iflag; + tp->c_iflag |= ISTRIP; /* enable 8th-bit stripping */ + vmin = tp->c_cc[VMIN]; + tp->c_cc[VMIN] = 0; /* don't block if queue empty */ + ioctl(0, TCSETS, tp); + + /* + * Wait for a while, then read everything the modem has said so far and + * try to extract the speed of the dial-in call. + */ + + sleep(1); + nread = read(0, buf, size_buf - 1); + if (nread > 0) { + buf[nread] = '\0'; + for (bp = buf; bp < buf + nread; bp++) { + if (isascii(*bp) && isdigit(*bp)) { + speed = bcode(bp); + if (speed) { + tp->c_cflag &= ~CBAUD; + tp->c_cflag |= speed; + } + break; + } + } + } + /* Restore terminal settings. Errors will be dealt with later on. */ + + tp->c_iflag = iflag; + tp->c_cc[VMIN] = vmin; + ioctl(0, TCSETS, tp); +} + +/* next_speed - select next baud rate */ +static void next_speed(struct termios *tp, struct options *op) +{ + static int baud_index = FIRST_SPEED; /* current speed index */ + + baud_index = (baud_index + 1) % op->numspeed; + tp->c_cflag &= ~CBAUD; + tp->c_cflag |= op->speeds[baud_index]; + ioctl(0, TCSETS, tp); +} + + +/* do_prompt - show login prompt, optionally preceded by /etc/issue contents */ +static void do_prompt(struct options *op, struct termios *tp) +{ +#ifdef ISSUE + print_login_issue(op->issue, op->tty); +#endif + print_login_prompt(); +} + +#ifdef HANDLE_ALLCAPS +/* caps_lock - string contains upper case without lower case */ +/* returns 1 if true, 0 if false */ +static int caps_lock(const char *s) +{ + while (*s) + if (islower(*s++)) + return 0; + return 1; +} +#endif + +/* get_logname - get user name, establish parity, speed, erase, kill, eol */ +/* return NULL on failure, logname on success */ +static char *get_logname(char *logname, unsigned size_logname, + struct options *op, struct chardata *cp, struct termios *tp) +{ + char *bp; + char c; /* input character, full eight bits */ + char ascval; /* low 7 bits of input character */ + int bits; /* # of "1" bits per character */ + int mask; /* mask with 1 bit up */ + static const char erase[][3] = { /* backspace-space-backspace */ + "\010\040\010", /* space parity */ + "\010\040\010", /* odd parity */ + "\210\240\210", /* even parity */ + "\210\240\210", /* no parity */ + }; + + /* Initialize kill, erase, parity etc. (also after switching speeds). */ + + *cp = init_chardata; + + /* Flush pending input (esp. after parsing or switching the baud rate). */ + + sleep(1); + ioctl(0, TCFLSH, TCIFLUSH); + + /* Prompt for and read a login name. */ + + logname[0] = '\0'; + while (!logname[0]) { + + /* Write issue file and prompt, with "parity" bit == 0. */ + + do_prompt(op, tp); + + /* Read name, watch for break, parity, erase, kill, end-of-line. */ + + bp = logname; + cp->eol = '\0'; + while (cp->eol == '\0') { + + /* Do not report trivial EINTR/EIO errors. */ + if (read(0, &c, 1) < 1) { + if (errno == EINTR || errno == EIO) + exit(0); + bb_perror_msg_and_die("%s: read", op->tty); + } + + /* Do BREAK handling elsewhere. */ + if (c == '\0' && op->numspeed > 1) + return NULL; + + /* Do parity bit handling. */ + ascval = c & 0177; + if (c != ascval) { /* "parity" bit on ? */ + bits = 1; + mask = 1; + while (mask & 0177) { + if (mask & ascval) + bits++; /* count "1" bits */ + mask <<= 1; + } + /* ... |= 2 - even, 1 - odd */ + cp->parity |= 2 - (bits & 1); + } + + /* Do erase, kill and end-of-line processing. */ + switch (ascval) { + case CR: + case NL: + *bp = '\0'; /* terminate logname */ + cp->eol = ascval; /* set end-of-line char */ + break; + case BS: + case DEL: + case '#': + cp->erase = ascval; /* set erase character */ + if (bp > logname) { + write(1, erase[cp->parity], 3); + bp--; + } + break; + case CTL('U'): + case '@': + cp->kill = ascval; /* set kill character */ + while (bp > logname) { + write(1, erase[cp->parity], 3); + bp--; + } + break; + case CTL('D'): + exit(0); + default: + if (!isascii(ascval) || !isprint(ascval)) { + /* ignore garbage characters */ + } else if (bp - logname >= size_logname - 1) { + bb_error_msg_and_die("%s: input overrun", op->tty); + } else { + write(1, &c, 1); /* echo the character */ + *bp++ = ascval; /* and store it */ + } + break; + } + } + } + /* Handle names with upper case and no lower case. */ + +#ifdef HANDLE_ALLCAPS + cp->capslock = caps_lock(logname); + if (cp->capslock) { + for (bp = logname; *bp; bp++) + if (isupper(*bp)) + *bp = tolower(*bp); /* map name to lower case */ + } +#endif + return logname; +} + +/* termios_final - set the final tty mode bits */ +static void termios_final(struct options *op, struct termios *tp, struct chardata *cp) +{ + /* General terminal-independent stuff. */ + + tp->c_iflag |= IXON | IXOFF; /* 2-way flow control */ + tp->c_lflag |= ICANON | ISIG | ECHO | ECHOE | ECHOK | ECHOKE; + /* no longer| ECHOCTL | ECHOPRT */ + tp->c_oflag |= OPOST; + /* tp->c_cflag = 0; */ + tp->c_cc[VINTR] = DEF_INTR; /* default interrupt */ + tp->c_cc[VQUIT] = DEF_QUIT; /* default quit */ + tp->c_cc[VEOF] = DEF_EOF; /* default EOF character */ + tp->c_cc[VEOL] = DEF_EOL; + tp->c_cc[VSWTC] = DEF_SWITCH; /* default switch character */ + + /* Account for special characters seen in input. */ + + if (cp->eol == CR) { + tp->c_iflag |= ICRNL; /* map CR in input to NL */ + tp->c_oflag |= ONLCR; /* map NL in output to CR-NL */ + } + tp->c_cc[VERASE] = cp->erase; /* set erase character */ + tp->c_cc[VKILL] = cp->kill; /* set kill character */ + + /* Account for the presence or absence of parity bits in input. */ + + switch (cp->parity) { + case 0: /* space (always 0) parity */ + break; + case 1: /* odd parity */ + tp->c_cflag |= PARODD; + /* FALLTHROUGH */ + case 2: /* even parity */ + tp->c_cflag |= PARENB; + tp->c_iflag |= INPCK | ISTRIP; + /* FALLTHROUGH */ + case (1 | 2): /* no parity bit */ + tp->c_cflag &= ~CSIZE; + tp->c_cflag |= CS7; + break; + } + /* Account for upper case without lower case. */ + +#ifdef HANDLE_ALLCAPS + if (cp->capslock) { + tp->c_iflag |= IUCLC; + tp->c_lflag |= XCASE; + tp->c_oflag |= OLCUC; + } +#endif + /* Optionally enable hardware flow control */ + +#ifdef CRTSCTS + if (op->flags & F_RTSCTS) + tp->c_cflag |= CRTSCTS; +#endif + + /* Finally, make the new settings effective */ + + if (ioctl(0, TCSETS, tp) < 0) + bb_perror_msg_and_die("%s: ioctl(TCSETS)", op->tty); +} + + +#ifdef SYSV_STYLE +#if ENABLE_FEATURE_UTMP +/* update_utmp - update our utmp entry */ +static void update_utmp(char *line) +{ + struct utmp ut; + struct utmp *utp; + time_t t; + int mypid = getpid(); + + /* + * The utmp file holds miscellaneous information about things started by + * /sbin/init and other system-related events. Our purpose is to update + * the utmp entry for the current process, in particular the process type + * and the tty line we are listening to. Return successfully only if the + * utmp file can be opened for update, and if we are able to find our + * entry in the utmp file. + */ + if (access(_PATH_UTMP, R_OK|W_OK) == -1) { + close(creat(_PATH_UTMP, 0664)); + } + utmpname(_PATH_UTMP); + setutent(); + while ((utp = getutent()) + && !(utp->ut_type == INIT_PROCESS && utp->ut_pid == mypid)) + /* nothing */; + + if (utp) { + memcpy(&ut, utp, sizeof(ut)); + } else { + /* some inits don't initialize utmp... */ + memset(&ut, 0, sizeof(ut)); + safe_strncpy(ut.ut_id, line + 3, sizeof(ut.ut_id)); + } + /* endutent(); */ + + strcpy(ut.ut_user, "LOGIN"); + safe_strncpy(ut.ut_line, line, sizeof(ut.ut_line)); + if (fakehost) + safe_strncpy(ut.ut_host, fakehost, sizeof(ut.ut_host)); + time(&t); + ut.ut_time = t; + ut.ut_type = LOGIN_PROCESS; + ut.ut_pid = mypid; + + pututline(&ut); + endutent(); + +#if ENABLE_FEATURE_WTMP + if (access(bb_path_wtmp_file, R_OK|W_OK) == -1) + close(creat(bb_path_wtmp_file, 0664)); + updwtmp(bb_path_wtmp_file, &ut); +#endif +} + +#endif /* CONFIG_FEATURE_UTMP */ +#endif /* SYSV_STYLE */ + + +int getty_main(int argc, char **argv) +{ + int nullfd; + char *logname = NULL; /* login name, given to /bin/login */ + /* Merging these into "struct local" may _seem_ to reduce + * parameter passing, but today's gcc will inline + * statics which are called once anyway, so don't do that */ + struct chardata chardata; /* set by get_logname() */ + struct termios termios; /* terminal mode bits */ + struct options options = { + 0, /* show /etc/issue (SYSV_STYLE) */ + 0, /* no timeout */ + _PATH_LOGIN, /* default login program */ + "tty1", /* default tty line */ + "", /* modem init string */ +#ifdef ISSUE + ISSUE, /* default issue file */ +#else + NULL, +#endif + 0, /* no baud rates known yet */ + }; + + /* Already too late because of theoretical + * possibility of getty --help somehow triggered + * inadvertently before we reach this. Oh well. */ + close(0); + close(1); + close(2); + logmode = LOGMODE_NONE; +#ifdef __linux__ + setsid(); +#endif + /* Was "/dev/console". Why should we spam *system console* + * if there is a problem with getty on /dev/ttyS15?... */ + nullfd = xopen(bb_dev_null, O_RDWR); + if (nullfd) { + dup2(nullfd, 0); + close(nullfd); + } + dup2(0, 1); + dup2(0, 2); + /* We want special flavor of error_msg_and_die */ + die_sleep = 10; + msg_eol = "\r\n"; + openlog(applet_name, LOG_PID, LOG_AUTH); + logmode = LOGMODE_BOTH; + +#ifdef DEBUGGING + dbf = xfopen(DEBUGTERM, "w"); + + { + int i; + + for (i = 1; i < argc; i++) { + debug(argv[i]); + debug("\n"); + } + } +#endif + + /* Parse command-line arguments. */ + parse_args(argc, argv, &options); + +#ifdef SYSV_STYLE +#if ENABLE_FEATURE_UTMP + /* Update the utmp file. */ + update_utmp(options.tty); +#endif +#endif + + debug("calling open_tty\n"); + /* Open the tty as standard { input, output, error }. */ + open_tty(options.tty, &termios, options.flags & F_LOCAL); + +#ifdef __linux__ + { + int iv; + + iv = getpid(); + ioctl(0, TIOCSPGRP, &iv); + } +#endif + /* Initialize the termios settings (raw mode, eight-bit, blocking i/o). */ + debug("calling termios_init\n"); + termios_init(&termios, options.speeds[FIRST_SPEED], &options); + + /* write the modem init string and DON'T flush the buffers */ + if (options.flags & F_INITSTRING) { + debug("writing init string\n"); + write(1, options.initstring, strlen(options.initstring)); + } + + if (!(options.flags & F_LOCAL)) { + /* go to blocking write mode unless -L is specified */ + fcntl(1, F_SETFL, fcntl(1, F_GETFL, 0) & ~O_NONBLOCK); + } + + /* Optionally detect the baud rate from the modem status message. */ + debug("before autobaud\n"); + if (options.flags & F_PARSE) + auto_baud(bb_common_bufsiz1, sizeof(bb_common_bufsiz1), &termios); + + /* Set the optional timer. */ + if (options.timeout) + alarm(options.timeout); + + /* optionally wait for CR or LF before writing /etc/issue */ + if (options.flags & F_WAITCRLF) { + char ch; + + debug("waiting for cr-lf\n"); + while (read(0, &ch, 1) == 1) { + ch &= 0x7f; /* strip "parity bit" */ +#ifdef DEBUGGING + fprintf(dbf, "read %c\n", ch); +#endif + if (ch == '\n' || ch == '\r') + break; + } + } + + chardata = init_chardata; + if (!(options.flags & F_NOPROMPT)) { + /* Read the login name. */ + debug("reading login name\n"); + logname = get_logname(bb_common_bufsiz1, sizeof(bb_common_bufsiz1), + &options, &chardata, &termios); + while (logname == NULL) + next_speed(&termios, &options); + } + + /* Disable timer. */ + + if (options.timeout) + alarm(0); + + /* Finalize the termios settings. */ + + termios_final(&options, &termios, &chardata); + + /* Now the newline character should be properly written. */ + + write(1, "\n", 1); + + /* Let the login program take care of password validation. */ + + execl(options.login, options.login, "--", logname, (char *) 0); + bb_error_msg_and_die("%s: can't exec %s", options.tty, options.login); +} diff --git a/loginutils/login.c b/loginutils/login.c new file mode 100644 index 000000000..bd3c112b9 --- /dev/null +++ b/loginutils/login.c @@ -0,0 +1,402 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include +#include +#include + +#ifdef CONFIG_SELINUX +#include /* for is_selinux_enabled() */ +#include /* for get_default_context() */ +#include /* for security class definitions */ +#include +#endif + +enum { + TIMEOUT = 60, + EMPTY_USERNAME_COUNT = 10, + USERNAME_SIZE = 32, + TTYNAME_SIZE = 32, +}; + +static char full_tty[TTYNAME_SIZE]; +static char* short_tty = full_tty; + +#if ENABLE_FEATURE_UTMP +/* vv Taken from tinylogin utmp.c vv */ +/* + * read_or_build_utent - see if utmp file is correct for this process + * + * System V is very picky about the contents of the utmp file + * and requires that a slot for the current process exist. + * The utmp file is scanned for an entry with the same process + * ID. If no entry exists the process exits with a message. + * + * The "picky" flag is for network and other logins that may + * use special flags. It allows the pid checks to be overridden. + * This means that getty should never invoke login with any + * command line flags. + */ + +static struct utmp utent; + +static void read_or_build_utent(int picky) +{ + struct utmp *ut; + pid_t pid = getpid(); + + setutent(); + + /* First, try to find a valid utmp entry for this process. */ + while ((ut = getutent())) + if (ut->ut_pid == pid && ut->ut_line[0] && ut->ut_id[0] && + (ut->ut_type == LOGIN_PROCESS || ut->ut_type == USER_PROCESS)) + break; + + /* If there is one, just use it, otherwise create a new one. */ + if (ut) { + utent = *ut; + } else { + if (picky) + bb_error_msg_and_die("no utmp entry found"); + + memset(&utent, 0, sizeof(utent)); + utent.ut_type = LOGIN_PROCESS; + utent.ut_pid = pid; + strncpy(utent.ut_line, short_tty, sizeof(utent.ut_line)); + /* This one is only 4 chars wide. Try to fit something + * remotely meaningful by skipping "tty"... */ + strncpy(utent.ut_id, short_tty + 3, sizeof(utent.ut_id)); + strncpy(utent.ut_user, "LOGIN", sizeof(utent.ut_user)); + utent.ut_time = time(NULL); + } + if (!picky) /* root login */ + memset(utent.ut_host, 0, sizeof(utent.ut_host)); +} + +/* + * write_utent - put a USER_PROCESS entry in the utmp file + * + * write_utent changes the type of the current utmp entry to + * USER_PROCESS. the wtmp file will be updated as well. + */ +static void write_utent(const char *username) +{ + utent.ut_type = USER_PROCESS; + strncpy(utent.ut_user, username, sizeof(utent.ut_user)); + utent.ut_time = time(NULL); + /* other fields already filled in by read_or_build_utent above */ + setutent(); + pututline(&utent); + endutent(); +#if ENABLE_FEATURE_WTMP + if (access(bb_path_wtmp_file, R_OK|W_OK) == -1) { + close(creat(bb_path_wtmp_file, 0664)); + } + updwtmp(bb_path_wtmp_file, &utent); +#endif +} +#else /* !ENABLE_FEATURE_UTMP */ +static inline void read_or_build_utent(int ATTRIBUTE_UNUSED picky) {} +static inline void write_utent(const char ATTRIBUTE_UNUSED *username) {} +#endif /* !ENABLE_FEATURE_UTMP */ + +static void die_if_nologin_and_non_root(int amroot) +{ + FILE *fp; + int c; + + if (access(bb_path_nologin_file, F_OK)) + return; + + fp = fopen(bb_path_nologin_file, "r"); + if (fp) { + while ((c = getc(fp)) != EOF) + putchar((c=='\n') ? '\r' : c); + fflush(stdout); + fclose(fp); + } else + puts("\r\nSystem closed for routine maintenance\r"); + if (!amroot) + exit(1); + puts("\r\n[Disconnect bypassed -- root login allowed.]\r"); +} + +#if ENABLE_FEATURE_SECURETTY +static int check_securetty(void) +{ + FILE *fp; + int i; + char buf[BUFSIZ]; + + fp = fopen(bb_path_securetty_file, "r"); + if (!fp) { + /* A missing securetty file is not an error. */ + return 1; + } + while (fgets(buf, sizeof(buf)-1, fp)) { + for(i = strlen(buf)-1; i>=0; --i) { + if (!isspace(buf[i])) + break; + } + buf[++i] = '\0'; + if ((buf[0]=='\0') || (buf[0]=='#')) + continue; + if (strcmp(buf, short_tty) == 0) { + fclose(fp); + return 1; + } + } + fclose(fp); + return 0; +} +#else +static inline int check_securetty(void) { return 1; } +#endif + +static void get_username_or_die(char *buf, int size_buf) +{ + int c, cntdown; + cntdown = EMPTY_USERNAME_COUNT; +prompt: + /* skip whitespace */ + print_login_prompt(); + do { + c = getchar(); + if (c == EOF) exit(1); + if (c == '\n') { + if (!--cntdown) exit(1); + goto prompt; + } + } while (isspace(c)); + + *buf++ = c; + if (!fgets(buf, size_buf-2, stdin)) + exit(1); + if (!strchr(buf, '\n')) + exit(1); + while (isgraph(*buf)) buf++; + *buf = '\0'; +} + +static void motd(void) +{ + FILE *fp; + int c; + + fp = fopen(bb_path_motd_file, "r"); + if (fp) { + while ((c = getc(fp)) != EOF) + putchar(c); + fclose(fp); + } +} + +static void nonblock(int fd) +{ + fcntl(fd, F_SETFL, O_NONBLOCK | fcntl(fd, F_GETFL)); +} + +static void alarm_handler(int sig ATTRIBUTE_UNUSED) +{ + /* This is the escape hatch! Poor serial line users and the like + * arrive here when their connection is broken. + * We don't want to block here */ + nonblock(1); + nonblock(2); + bb_info_msg("\r\nLogin timed out after %d seconds\r", TIMEOUT); + exit(EXIT_SUCCESS); +} + +int login_main(int argc, char **argv) +{ + enum { + LOGIN_OPT_f = (1<<0), + LOGIN_OPT_h = (1<<1), + LOGIN_OPT_p = (1<<2), + }; + char fromhost[512]; + char username[USERNAME_SIZE]; + const char *tmp; + int amroot; + unsigned opt; + int count = 0; + struct passwd *pw; + char *opt_host = NULL; + char *opt_user = NULL; + USE_SELINUX(security_context_t user_sid = NULL;) + + username[0] = '\0'; + amroot = (getuid() == 0); + signal(SIGALRM, alarm_handler); + alarm(TIMEOUT); + + opt = getopt32(argc, argv, "f:h:p", &opt_user, &opt_host); + if (opt & LOGIN_OPT_f) { + if (!amroot) + bb_error_msg_and_die("-f is for root only"); + safe_strncpy(username, opt_user, sizeof(username)); + } + if (optind < argc) /* user from command line (getty) */ + safe_strncpy(username, argv[optind], sizeof(username)); + + /* Let's find out and memorize our tty */ + if (!isatty(0) || !isatty(1) || !isatty(2)) + return EXIT_FAILURE; /* Must be a terminal */ + safe_strncpy(full_tty, "UNKNOWN", sizeof(full_tty)); + tmp = ttyname(0); + if (tmp) { + safe_strncpy(full_tty, tmp, sizeof(full_tty)); + if (strncmp(full_tty, "/dev/", 5) == 0) + short_tty = full_tty + 5; + } + + read_or_build_utent(!amroot); + + if (opt_host) { + USE_FEATURE_UTMP( + safe_strncpy(utent.ut_host, opt_host, sizeof(utent.ut_host)); + ) + snprintf(fromhost, sizeof(fromhost)-1, " on '%.100s' from " + "'%.200s'", short_tty, opt_host); + } + else + snprintf(fromhost, sizeof(fromhost)-1, " on '%.100s'", short_tty); + + bb_setpgrp; + + openlog(applet_name, LOG_PID | LOG_CONS | LOG_NOWAIT, LOG_AUTH); + + while (1) { + if (!username[0]) + get_username_or_die(username, sizeof(username)); + + pw = getpwnam(username); + if (!pw) { + safe_strncpy(username, "UNKNOWN", sizeof(username)); + goto auth_failed; + } + + if (pw->pw_passwd[0] == '!' || pw->pw_passwd[0] == '*') + goto auth_failed; + + if (opt & LOGIN_OPT_f) + break; /* -f USER: success without asking passwd */ + + if (pw->pw_uid == 0 && !check_securetty()) + goto auth_failed; + + /* Don't check the password if password entry is empty (!) */ + if (!pw->pw_passwd[0]) + break; + + /* authorization takes place here */ + if (correct_password(pw)) + break; + +auth_failed: + opt &= ~LOGIN_OPT_f; + bb_do_delay(FAIL_DELAY); + puts("Login incorrect"); + if (++count == 3) { + syslog(LOG_WARNING, "invalid password for '%s'%s", + username, fromhost); + return EXIT_FAILURE; + } + username[0] = '\0'; + } + + alarm(0); + die_if_nologin_and_non_root(pw->pw_uid == 0); + + write_utent(username); + +#ifdef CONFIG_SELINUX + if (is_selinux_enabled()) { + security_context_t old_tty_sid, new_tty_sid; + + if (get_default_context(username, NULL, &user_sid)) { + bb_error_msg_and_die("cannot get SID for %s", + username); + } + if (getfilecon(full_tty, &old_tty_sid) < 0) { + bb_perror_msg_and_die("getfilecon(%s) failed", + full_tty); + } + if (security_compute_relabel(user_sid, old_tty_sid, + SECCLASS_CHR_FILE, &new_tty_sid) != 0) { + bb_perror_msg_and_die("security_change_sid(%s) failed", + full_tty); + } + if (setfilecon(full_tty, new_tty_sid) != 0) { + bb_perror_msg_and_die("chsid(%s, %s) failed", + full_tty, new_tty_sid); + } + } +#endif + /* Try these, but don't complain if they fail. + * _f_chown is safe wrt race t=ttyname(0);...;chown(t); */ + fchown(0, pw->pw_uid, pw->pw_gid); + fchmod(0, 0600); + + /* TODO: be nommu-friendly, use spawn? */ + if (ENABLE_LOGIN_SCRIPTS) { + char *script = getenv("LOGIN_PRE_SUID_SCRIPT"); + if (script) { + char *t_argv[2] = { script, NULL }; + switch (fork()) { + case -1: break; + case 0: /* child */ + xchdir("/"); + setenv("LOGIN_TTY", full_tty, 1); + setenv("LOGIN_USER", pw->pw_name, 1); + setenv("LOGIN_UID", utoa(pw->pw_uid), 1); + setenv("LOGIN_GID", utoa(pw->pw_gid), 1); + setenv("LOGIN_SHELL", pw->pw_shell, 1); + execvp(script, t_argv); + exit(1); + default: /* parent */ + wait(NULL); + } + } + } + + change_identity(pw); + tmp = pw->pw_shell; + if (!tmp || !*tmp) + tmp = DEFAULT_SHELL; + setup_environment(tmp, 1, !(opt & LOGIN_OPT_p), pw); + + motd(); + + if (pw->pw_uid == 0) + syslog(LOG_INFO, "root login%s", fromhost); +#ifdef CONFIG_SELINUX + /* well, a simple setexeccon() here would do the job as well, + * but let's play the game for now */ + set_current_security_context(user_sid); +#endif + + // util-linux login also does: + // /* start new session */ + // setsid(); + // /* TIOCSCTTY: steal tty from other process group */ + // if (ioctl(0, TIOCSCTTY, 1)) error_msg... + + /* set signals to defaults */ + signal(SIGALRM, SIG_DFL); + /* Is this correct? This way user can ctrl-c out of /etc/profile, + * potentially creating security breach (tested with bash 3.0). + * But without this, bash 3.0 will not enable ctrl-c either. + * Maybe bash is buggy? + * Need to find out what standards say about /bin/login - + * should it leave SIGINT etc enabled or disabled? */ + signal(SIGINT, SIG_DFL); + + run_shell(tmp, 1, 0, 0); /* exec the shell finally */ + + return EXIT_FAILURE; +} diff --git a/loginutils/passwd.c b/loginutils/passwd.c new file mode 100644 index 000000000..bcb7f2b26 --- /dev/null +++ b/loginutils/passwd.c @@ -0,0 +1,339 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include + + +static void nuke_str(char *str) +{ + if (str) memset(str, 0, strlen(str)); +} + + +static int i64c(int i) +{ + i &= 0x3f; + if (i == 0) + return '.'; + if (i == 1) + return '/'; + if (i < 12) + return ('0' - 2 + i); + if (i < 38) + return ('A' - 12 + i); + return ('a' - 38 + i); +} + + +static void crypt_make_salt(char *p, int cnt) +{ + unsigned x = x; /* it's pointless to initialize it anyway :) */ + + x += getpid() + time(NULL) + clock(); + do { + /* x = (x*1664525 + 1013904223) % 2^32 generator is lame + * (low-order bit is not "random", etc...), + * but for our purposes it is good enough */ + x = x*1664525 + 1013904223; + /* BTW, Park and Miller's "minimal standard generator" is + * x = x*16807 % ((2^31)-1) + * It has no problem with visibly alternating lowest bit + * but is also weak in cryptographic sense + needs div, + * which needs more code (and slower) on many CPUs */ + *p++ = i64c(x >> 16); + *p++ = i64c(x >> 22); + } while (--cnt); + *p = '\0'; +} + + +static char* new_password(const struct passwd *pw, uid_t myuid, int algo) +{ + char salt[sizeof("$N$XXXXXXXX")]; /* "$N$XXXXXXXX" or "XX" */ + char *orig = ""; + char *newp = NULL; + char *cipher = NULL; + char *cp = NULL; + char *ret = NULL; /* failure so far */ + + if (myuid && pw->pw_passwd[0]) { + orig = bb_askpass(0, "Old password:"); /* returns ptr to static */ + if (!orig) + goto err_ret; + cipher = pw_encrypt(orig, pw->pw_passwd); /* returns ptr to static */ + if (strcmp(cipher, pw->pw_passwd) != 0) { + syslog(LOG_WARNING, "incorrect password for '%s'", + pw->pw_name); + bb_do_delay(FAIL_DELAY); + puts("Incorrect password"); + goto err_ret; + } + } + orig = xstrdup(orig); /* or else bb_askpass() will destroy it */ + newp = bb_askpass(0, "New password:"); /* returns ptr to static */ + if (!newp) + goto err_ret; + newp = xstrdup(newp); /* we are going to bb_askpass() again, so save it */ + if (obscure(orig, newp, pw) && myuid) + goto err_ret; /* non-root is not allowed to have weak passwd */ + + cp = bb_askpass(0, "Retype password:"); + if (!cp) + goto err_ret; + if (strcmp(cp, newp)) { + puts("Passwords don't match"); + goto err_ret; + } + + /*memset(salt, 0, sizeof(salt)); - why?*/ + crypt_make_salt(salt, 1); /* des */ + if (algo) { /* MD5 */ + strcpy(salt, "$1$"); + crypt_make_salt(salt + 3, 4); + } + ret = xstrdup(pw_encrypt(newp, salt)); /* returns ptr to static */ + /* whee, success! */ + + err_ret: + nuke_str(orig); + if (ENABLE_FEATURE_CLEAN_UP) free(orig); + nuke_str(newp); + if (ENABLE_FEATURE_CLEAN_UP) free(newp); + nuke_str(cipher); + nuke_str(cp); + return ret; +} + + +#if 0 +static int get_algo(char *a) +{ + /* standard: MD5 */ + int x = 1; + if (strcasecmp(a, "des") == 0) + x = 0; + return x; +} +#endif + + +static int update_passwd(const char *filename, const char *username, + const char *new_pw) +{ + struct stat sb; + struct flock lock; + FILE *old_fp; + FILE *new_fp; + char *new_name; + char *last_char; + unsigned user_len; + int old_fd; + int new_fd; + int i; + int ret = 1; /* failure */ + + logmode = LOGMODE_STDIO; + /* New passwd file, "/etc/passwd+" for now */ + new_name = xasprintf("%s+", filename); + last_char = &new_name[strlen(new_name)-1]; + username = xasprintf("%s:", username); + user_len = strlen(username); + + old_fp = fopen(filename, "r+"); + if (!old_fp) + goto free_mem; + old_fd = fileno(old_fp); + + /* Try to create "/etc/passwd+". Wait if it exists. */ + i = 30; + do { + // FIXME: on last iteration try w/o O_EXCL but with O_TRUNC? + new_fd = open(new_name, O_WRONLY|O_CREAT|O_EXCL,0600); + if (new_fd >= 0) goto created; + if (errno != EEXIST) break; + usleep(100000); /* 0.1 sec */ + } while (--i); + bb_perror_msg("cannot create '%s'", new_name); + goto close_old_fp; + created: + if (!fstat(old_fd, &sb)) { + fchmod(new_fd, sb.st_mode & 0777); /* ignore errors */ + fchown(new_fd, sb.st_uid, sb.st_gid); + } + new_fp = fdopen(new_fd, "w"); + if (!new_fp) { + close(new_fd); + goto unlink_new; + } + + /* Backup file is "/etc/passwd-" */ + last_char[0] = '-'; + /* Delete old one, create new as a hardlink to current */ + i = (unlink(new_name) && errno != ENOENT); + if (i || link(filename, new_name)) + bb_perror_msg("warning: cannot create backup copy '%s'", new_name); + last_char[0] = '+'; + + /* Lock the password file before updating */ + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + if (fcntl(old_fd, F_SETLK, &lock) < 0) + bb_perror_msg("warning: cannot lock '%s'", filename); + lock.l_type = F_UNLCK; + + /* Read current password file, write updated one */ + while (1) { + char *line = xmalloc_fgets(old_fp); + if (!line) break; /* EOF/error */ + if (strncmp(username, line, user_len) == 0) { + /* we have a match with "username:"... */ + const char *cp = line + user_len; + /* now cp -> old passwd, skip it: */ + cp = strchr(cp, ':'); + if (!cp) cp = ""; + /* now cp -> ':' after old passwd or -> "" */ + fprintf(new_fp, "%s%s%s", username, new_pw, cp); + /* Erase password in memory */ + } else + fputs(line, new_fp); + free(line); + } + fcntl(old_fd, F_SETLK, &lock); + + /* We do want all of them to execute, thus | instead of || */ + if ((ferror(old_fp) | fflush(new_fp) | fsync(new_fd) | fclose(new_fp)) + || rename(new_name, filename) + ) { + /* At least one of those failed */ + goto unlink_new; + } + ret = 0; /* whee, success! */ + + unlink_new: + if (ret) unlink(new_name); + + close_old_fp: + fclose(old_fp); + + free_mem: + if (ENABLE_FEATURE_CLEAN_UP) free(new_name); + if (ENABLE_FEATURE_CLEAN_UP) free((char*)username); + logmode = LOGMODE_BOTH; + return ret; +} + + +int passwd_main(int argc, char **argv) +{ + enum { + OPT_algo = 0x1, /* -a - password algorithm */ + OPT_lock = 0x2, /* -l - lock account */ + OPT_unlock = 0x4, /* -u - unlock account */ + OPT_delete = 0x8, /* -d - delete password */ + OPT_lud = 0xe, + STATE_ALGO_md5 = 0x10, + /*STATE_ALGO_des = 0x20, not needed yet */ + }; + unsigned opt; + char *opt_a = ""; + const char *filename; + char *myname; + char *name; + char *newp; + struct passwd *pw; + uid_t myuid; + struct rlimit rlimit_fsize; + char c; + + logmode = LOGMODE_BOTH; + openlog(applet_name, LOG_NOWAIT, LOG_AUTH); + opt = getopt32(argc, argv, "a:lud", &opt_a); + argc -= optind; + argv += optind; + + if (strcasecmp(opt_a, "des") != 0) /* -a */ + opt |= STATE_ALGO_md5; + //else + // opt |= STATE_ALGO_des; + myuid = getuid(); + if ((opt & OPT_lud) && (!argc || myuid)) + bb_show_usage(); + + myname = xstrdup(bb_getpwuid(NULL, myuid, -1)); + name = argc ? argv[0] : myname; + + pw = getpwnam(name); + if (!pw) bb_error_msg_and_die("unknown user %s", name); + if (myuid && pw->pw_uid != myuid) { + /* LOGMODE_BOTH */ + bb_error_msg_and_die("%s can't change password for %s", myname, name); + } + + filename = bb_path_passwd_file; + if (ENABLE_FEATURE_SHADOWPASSWDS) { + struct spwd *sp = getspnam(name); + if (!sp) { + /* LOGMODE_BOTH */ + bb_error_msg("no record of %s in %s, using %s", + name, bb_path_shadow_file, + bb_path_passwd_file); + } else { + filename = bb_path_shadow_file; + pw->pw_passwd = sp->sp_pwdp; + } + } + + /* Decide what the new password will be */ + newp = NULL; + c = pw->pw_passwd[0] - '!'; + if (!(opt & OPT_lud)) { + if (myuid && !c) { /* passwd starts with '!' */ + /* LOGMODE_BOTH */ + bb_error_msg_and_die("cannot change " + "locked password for %s", name); + } + printf("Changing password for %s\n", name); + newp = new_password(pw, myuid, opt & STATE_ALGO_md5); + if (!newp) { + logmode = LOGMODE_STDIO; + bb_error_msg_and_die("password for %s is unchanged", name); + } + } else if (opt & OPT_lock) { + if (!c) goto skip; /* passwd starts with '!' */ + newp = xasprintf("!%s", pw->pw_passwd); + } else if (opt & OPT_unlock) { + if (c) goto skip; /* not '!' */ + newp = xstrdup(&pw->pw_passwd[1]); + } else if (opt & OPT_delete) { + newp = xstrdup(""); + } + + rlimit_fsize.rlim_cur = rlimit_fsize.rlim_max = 512L * 30000; + setrlimit(RLIMIT_FSIZE, &rlimit_fsize); + signal(SIGHUP, SIG_IGN); + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + umask(077); + xsetuid(0); + if (update_passwd(filename, name, newp) != 0) { + /* LOGMODE_BOTH */ + bb_error_msg_and_die("cannot update password file %s", + filename); + } + /* LOGMODE_BOTH */ + bb_info_msg("Password for %s changed by %s", name, myname); + + if (ENABLE_FEATURE_CLEAN_UP) free(newp); +skip: + if (!newp) { + bb_error_msg_and_die("password for %s is already %slocked", + name, (opt & OPT_unlock) ? "un" : ""); + } + if (ENABLE_FEATURE_CLEAN_UP) free(myname); + return 0; +} diff --git a/loginutils/su.c b/loginutils/su.c new file mode 100644 index 000000000..a23ee932b --- /dev/null +++ b/loginutils/su.c @@ -0,0 +1,90 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini su implementation for busybox + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include + +int su_main(int argc, char **argv) +{ + unsigned long flags; + char *opt_shell = 0; + char *opt_command = 0; + char *opt_username = "root"; + char **opt_args = 0; + struct passwd *pw; + uid_t cur_uid = getuid(); + const char *tty; + char *old_user; + + flags = getopt32(argc, argv, "mplc:s:", &opt_command, &opt_shell); +#define SU_OPT_mp (3) +#define SU_OPT_l (4) + + if (optind < argc && argv[optind][0] == '-' && argv[optind][1] == 0) { + flags |= SU_OPT_l; + ++optind; + } + + /* get user if specified */ + if (optind < argc) opt_username = argv [optind++]; + + if (optind < argc) opt_args = argv + optind; + + if (ENABLE_SU_SYSLOG) { + /* The utmp entry (via getlogin) is probably the best way to identify + the user, especially if someone su's from a su-shell. + But getlogin can fail -- usually due to lack of utmp entry. + in this case resort to getpwuid. */ + old_user = xstrdup(USE_FEATURE_UTMP(getlogin() ? : ) (pw = getpwuid(cur_uid)) ? pw->pw_name : ""); + tty = ttyname(2) ? : "none"; + openlog(applet_name, 0, LOG_AUTH); + } + + pw = getpwnam(opt_username); + if (!pw) bb_error_msg_and_die("unknown id: %s", opt_username); + + /* Make sure pw->pw_shell is non-NULL. It may be NULL when NEW_USER + is a username that is retrieved via NIS (YP), but that doesn't have + a default shell listed. */ + if (!pw->pw_shell || !pw->pw_shell[0]) pw->pw_shell = (char *)DEFAULT_SHELL; + + if ((cur_uid == 0) || correct_password(pw)) { + if (ENABLE_SU_SYSLOG) + syslog(LOG_NOTICE, "+ %s %s:%s", tty, old_user, opt_username); + } else { + if (ENABLE_SU_SYSLOG) + syslog(LOG_NOTICE, "- %s %s:%s", tty, old_user, opt_username); + bb_error_msg_and_die("incorrect password"); + } + + if (ENABLE_FEATURE_CLEAN_UP && ENABLE_SU_SYSLOG) { + closelog(); + free(old_user); + } + + if (!opt_shell && (flags & SU_OPT_mp)) opt_shell = getenv("SHELL"); + + if (opt_shell && cur_uid && restricted_shell(pw->pw_shell)) { + /* The user being su'd to has a nonstandard shell, and so is + probably a uucp account or has restricted access. Don't + compromise the account by allowing access with a standard + shell. */ + bb_error_msg("using restricted shell"); + opt_shell = 0; + } + + if (!opt_shell) opt_shell = pw->pw_shell; + + change_identity(pw); + setup_environment(opt_shell, flags & SU_OPT_l, !(flags & SU_OPT_mp), pw); + USE_SELINUX(set_current_security_context(NULL);) + + /* Never returns */ + run_shell(opt_shell, flags & SU_OPT_l, opt_command, (const char**)opt_args); + + return EXIT_FAILURE; +} diff --git a/loginutils/sulogin.c b/loginutils/sulogin.c new file mode 100644 index 000000000..c07264e7b --- /dev/null +++ b/loginutils/sulogin.c @@ -0,0 +1,121 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini sulogin implementation for busybox + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include + +#include "busybox.h" + +static const char * const forbid[] = { + "ENV", + "BASH_ENV", + "HOME", + "IFS", + "PATH", + "SHELL", + "LD_LIBRARY_PATH", + "LD_PRELOAD", + "LD_TRACE_LOADED_OBJECTS", + "LD_BIND_NOW", + "LD_AOUT_LIBRARY_PATH", + "LD_AOUT_PRELOAD", + "LD_NOWARN", + "LD_KEEPDIR", + (char *) 0 +}; + + +static void catchalarm(int ATTRIBUTE_UNUSED junk) +{ + exit(EXIT_FAILURE); +} + + +int sulogin_main(int argc, char **argv) +{ + char *cp; + int timeout = 0; + char *timeout_arg; + const char * const *p; + struct passwd *pwd; + struct spwd *spwd; + const char *shell; + + logmode = LOGMODE_BOTH; + openlog(applet_name, 0, LOG_AUTH); + + if (getopt32(argc, argv, "t:", &timeout_arg)) { + timeout = xatoi_u(timeout_arg); + } + + if (argv[optind]) { + close(0); + close(1); + dup(xopen(argv[optind], O_RDWR)); + close(2); + dup(0); + } + + if (!isatty(0) || !isatty(1) || !isatty(2)) { + logmode = LOGMODE_SYSLOG; + bb_error_msg_and_die("not a tty"); + } + + /* Clear out anything dangerous from the environment */ + for (p = forbid; *p; p++) + unsetenv(*p); + + signal(SIGALRM, catchalarm); + + pwd = getpwuid(0); + if (!pwd) { + goto auth_error; + } + + if (ENABLE_FEATURE_SHADOWPASSWDS) { + spwd = getspnam(pwd->pw_name); + if (!spwd) { + goto auth_error; + } + pwd->pw_passwd = spwd->sp_pwdp; + } + + while (1) { + /* cp points to a static buffer that is zeroed every time */ + cp = bb_askpass(timeout, + "Give root password for system maintenance\n" + "(or type Control-D for normal startup):"); + + if (!cp || !*cp) { + bb_info_msg("Normal startup"); + return 0; + } + if (strcmp(pw_encrypt(cp, pwd->pw_passwd), pwd->pw_passwd) == 0) { + break; + } + bb_do_delay(FAIL_DELAY); + bb_error_msg("login incorrect"); + } + memset(cp, 0, strlen(cp)); + signal(SIGALRM, SIG_DFL); + + bb_info_msg("System Maintenance Mode"); + + USE_SELINUX(renew_current_security_context()); + + shell = getenv("SUSHELL"); + if (!shell) shell = getenv("sushell"); + if (!shell) { + shell = "/bin/sh"; + if (pwd->pw_shell[0]) + shell = pwd->pw_shell; + } + run_shell(shell, 1, 0, 0); + /* never returns */ + +auth_error: + bb_error_msg_and_die("no password entry for 'root'"); +} diff --git a/loginutils/vlock.c b/loginutils/vlock.c new file mode 100644 index 000000000..7cc812fbb --- /dev/null +++ b/loginutils/vlock.c @@ -0,0 +1,118 @@ +/* vi: set sw=4 ts=4: */ + +/* + * vlock implementation for busybox + * + * Copyright (C) 2000 by spoon + * Written by spoon + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* Shoutz to Michael K. Johnson , author of the + * original vlock. I snagged a bunch of his code to write this + * minimalistic vlock. + */ +/* Fixed by Erik Andersen to do passwords the tinylogin way... + * It now works with md5, sha1, etc passwords. */ + +#include "busybox.h" +#include + +static struct passwd *pw; +static struct vt_mode ovtm; +static struct termios oterm; +static int vfd; +static unsigned long o_lock_all; + +static void release_vt(int signo) +{ + ioctl(vfd, VT_RELDISP, !o_lock_all); +} + +static void acquire_vt(int signo) +{ + ioctl(vfd, VT_RELDISP, VT_ACKACQ); +} + +static void restore_terminal(void) +{ + ioctl(vfd, VT_SETMODE, &ovtm); + tcsetattr(STDIN_FILENO, TCSANOW, &oterm); +} + +int vlock_main(int argc, char **argv) +{ + sigset_t sig; + struct sigaction sa; + struct vt_mode vtm; + struct termios term; + uid_t uid = getuid(); + + pw = getpwuid(uid); + if (pw == NULL) + bb_error_msg_and_die("unknown uid %d", uid); + + if (argc > 2) { + bb_show_usage(); + } + + o_lock_all = getopt32(argc, argv, "a"); + + vfd = xopen(CURRENT_TTY, O_RDWR); + + if (ioctl(vfd, VT_GETMODE, &vtm) < 0) { + bb_perror_msg_and_die("VT_GETMODE"); + } + + /* mask a bunch of signals */ + sigprocmask(SIG_SETMASK, NULL, &sig); + sigdelset(&sig, SIGUSR1); + sigdelset(&sig, SIGUSR2); + sigaddset(&sig, SIGTSTP); + sigaddset(&sig, SIGTTIN); + sigaddset(&sig, SIGTTOU); + sigaddset(&sig, SIGHUP); + sigaddset(&sig, SIGCHLD); + sigaddset(&sig, SIGQUIT); + sigaddset(&sig, SIGINT); + + sigemptyset(&(sa.sa_mask)); + sa.sa_flags = SA_RESTART; + sa.sa_handler = release_vt; + sigaction(SIGUSR1, &sa, NULL); + sa.sa_handler = acquire_vt; + sigaction(SIGUSR2, &sa, NULL); + + /* need to handle some signals so that we don't get killed by them */ + sa.sa_handler = SIG_IGN; + sigaction(SIGHUP, &sa, NULL); + sigaction(SIGQUIT, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGTSTP, &sa, NULL); + + ovtm = vtm; + vtm.mode = VT_PROCESS; + vtm.relsig = SIGUSR1; + vtm.acqsig = SIGUSR2; + ioctl(vfd, VT_SETMODE, &vtm); + + tcgetattr(STDIN_FILENO, &oterm); + term = oterm; + term.c_iflag &= ~BRKINT; + term.c_iflag |= IGNBRK; + term.c_lflag &= ~ISIG; + term.c_lflag &= ~(ECHO | ECHOCTL); + tcsetattr(STDIN_FILENO, TCSANOW, &term); + + do { + printf("Virtual Console%s locked by %s.\n", (o_lock_all) ? "s" : "", pw->pw_name); + if (correct_password(pw)) { + break; + } + bb_do_delay(FAIL_DELAY); + puts("Password incorrect"); + } while (1); + restore_terminal(); + fflush_stdout_and_exit(0); +} diff --git a/miscutils/Config.in b/miscutils/Config.in new file mode 100644 index 000000000..90e2b6fb1 --- /dev/null +++ b/miscutils/Config.in @@ -0,0 +1,374 @@ +# +# 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 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 + Note that Busybox binary must be setuid root for this applet to + work properly. + +config DEBUG_CROND_OPTION + bool "Support debug option -d" + depends on CROND + default n + help + Support option -d to enter debug mode. + +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. + +config DC + bool "dc" + default n + help + Dc is a reverse-polish desk calculator which supports unlimited + precision arithmetic. + +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 EJECT + bool "eject" + default n + help + Used to eject cdroms. (defaults to /dev/cdrom) + +config LAST + bool "last" + default n + select FEATURE_WTMP + help + 'last' displays a list of the last users that logged into the system. + +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_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_FLAGCS + bool "Enable flag changes" + default n + depends on LESS + help + This enables the ability to change command-line flags within + less itself. + +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 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 MAKEDEVS + default FEATURE_MAKEDEVS_TABLE + +config FEATURE_MAKEDEVS_LEAF + bool "leaf" + +config FEATURE_MAKEDEVS_TABLE + bool "table" + +endchoice + +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 NMETER + bool "nmeter" + default n + help + nmeter prints various system parameters continuously. + +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 + 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 boundprocess 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 STRINGS + bool "strings" + default n + help + strings prints the printable character sequences for each file + specified. + +config SETSID + bool "setsid" + default n + help + setsid runs a program in a new session + +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 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 000000000..8d1b9f484 --- /dev/null +++ b/miscutils/Kbuild @@ -0,0 +1,30 @@ +# 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_CROND) += crond.o +lib-$(CONFIG_CRONTAB) += crontab.o +lib-$(CONFIG_BBCONFIG) += bbconfig.o +lib-$(CONFIG_DC) += dc.o +lib-$(CONFIG_DEVFSD) += devfsd.o +lib-$(CONFIG_EJECT) += eject.o +lib-$(CONFIG_HDPARM) += hdparm.o +lib-$(CONFIG_LAST) += last.o +lib-$(CONFIG_LESS) += less.o +lib-$(CONFIG_MAKEDEVS) += makedevs.o +lib-$(CONFIG_MOUNTPOINT) += mountpoint.o +lib-$(CONFIG_MT) += mt.o +lib-$(CONFIG_NMETER) += nmeter.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_WATCHDOG) += watchdog.o diff --git a/miscutils/adjtimex.c b/miscutils/adjtimex.c new file mode 100644 index 000000000..b35538a84 --- /dev/null +++ b/miscutils/adjtimex.c @@ -0,0 +1,117 @@ +/* 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 "busybox.h" +#include + +static const struct {int bit; const char *name;} statlist[] = { + { STA_PLL, "PLL" }, + { STA_PPSFREQ, "PPSFREQ" }, + { STA_PPSTIME, "PPSTIME" }, + { STA_FLL, "FFL" }, + { STA_INS, "INS" }, + { STA_DEL, "DEL" }, + { STA_UNSYNC, "UNSYNC" }, + { STA_FREQHOLD, "FREQHOLD" }, + { STA_PPSSIGNAL, "PPSSIGNAL" }, + { STA_PPSJITTER, "PPSJITTER" }, + { STA_PPSWANDER, "PPSWANDER" }, + { STA_PPSERROR, "PPSERROR" }, + { STA_CLOCKERR, "CLOCKERR" }, + { 0, NULL } }; + +static const char * const ret_code_descript[] = { + "clock synchronized", + "insert leap second", + "delete leap second", + "leap second in progress", + "leap second has occurred", + "clock not synchronized" }; + +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, sep; + const char *descript; + txc.modes=0; + + opt = getopt32(argc, argv, "qo:f:p:t:", + &opt_o, &opt_f, &opt_p, &opt_t); + //if (opt & 0x1) // -q + if (opt & 0x2) { // -o + txc.offset = xatoi(opt_o); + txc.modes |= ADJ_OFFSET_SINGLESHOT; + } + if (opt & 0x4) { // -f + txc.freq = xatou(opt_f); + txc.modes |= ADJ_FREQUENCY; + } + if (opt & 0x8) { // -p + txc.constant = xatoi(opt_p); + txc.modes |= ADJ_TIMECONST; + } + if (opt & 0x10) { // -t + txc.tick = xatoi(opt_t); + txc.modes |= ADJ_TICK; + } + if (argc != optind) { /* no valid non-option parameters */ + bb_show_usage(); + } + + ret = adjtimex(&txc); + + if (ret < 0) perror("adjtimex"); + + if (!(opt & OPT_quiet) && ret>=0) { + 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" */ + sep=0; + for (i=0; statlist[i].name; i++) { + if (txc.status & statlist[i].bit) { + if (sep) fputs(" | ",stdout); + fputs(statlist[i].name,stdout); + sep=1; + } + } + + descript = "error"; + if (ret >= 0 && ret <= 5) descript = 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 (ret<0); +} diff --git a/miscutils/bbconfig.c b/miscutils/bbconfig.c new file mode 100644 index 000000000..ce14d4a1f --- /dev/null +++ b/miscutils/bbconfig.c @@ -0,0 +1,11 @@ +/* vi: set sw=4 ts=4: */ +/* This file was released into the public domain by Paul Fox. + */ +#include "busybox.h" +#include "bbconfigopts.h" + +int bbconfig_main(int argc, char **argv) +{ + printf(bbconfig_config); + return 0; +} diff --git a/miscutils/crond.c b/miscutils/crond.c new file mode 100644 index 000000000..fa7964e4e --- /dev/null +++ b/miscutils/crond.c @@ -0,0 +1,1016 @@ +/* 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) + * Vladimir Oleynik (C) 2002 + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#define VERSION "2.3.2" + +#include "busybox.h" +#include + +#define arysize(ary) (sizeof(ary)/sizeof((ary)[0])) + +#ifndef CRONTABS +#define CRONTABS "/var/spool/cron/crontabs" +#endif +#ifndef TMPDIR +#define TMPDIR "/var/spool/cron" +#endif +#ifndef SENDMAIL +#define SENDMAIL "/usr/sbin/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 */ + int cf_Ready; /* bool: one or more jobs ready */ + int cf_Running; /* bool: one or more jobs running */ + int 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) */ + int cl_MailFlag; /* running pid is for mail */ + int cl_MailPos; /* 'empty file' size */ + char cl_Mins[60]; /* 0-59 */ + char cl_Hrs[24]; /* 0-23 */ + char cl_Days[32]; /* 1-31 */ + char cl_Mons[12]; /* 0-11 */ + char cl_Dow[7]; /* 0-6, beginning sunday */ +} CronLine; + +#define RUN_RANOUT 1 +#define RUN_RUNNING 2 +#define RUN_FAILED 3 + +#define DaemonUid 0 + +#if ENABLE_DEBUG_CROND_OPTION +static unsigned DebugOpt; +#endif + +static unsigned LogLevel = 8; +static const char *LogFile; +static const char *CDir = CRONTABS; + +static void startlogger(void); + +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); + +static CronFile *FileBase; + + +static void crondlog(const char *ctl, ...) +{ + va_list va; + const char *fmt; + int level = (int) (ctl[0] & 0xf); + int type = level == 20 ? + LOG_ERR : ((ctl[0] & 0100) ? LOG_WARNING : LOG_NOTICE); + + + va_start(va, ctl); + fmt = ctl + 1; + if (level >= LogLevel) { + +#if ENABLE_DEBUG_CROND_OPTION + if (DebugOpt) { + vfprintf(stderr, fmt, va); + } else +#endif + if (LogFile == 0) { + vsyslog(type, fmt, va); + } else { + int logfd = open(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600); + if (logfd >= 0) { + vdprintf(logfd, fmt, va); + close(logfd); +#if ENABLE_DEBUG_CROND_OPTION + } else { + bb_perror_msg("can't open log file"); +#endif + } + } + } + va_end(va); + if (ctl[0] & 0200) { + exit(20); + } +} + +int crond_main(int ac, char **av) +{ + unsigned opt; + char *lopt, *Lopt, *copt; + +#if ENABLE_DEBUG_CROND_OPTION + char *dopt; + + opt_complementary = "f-b:b-f:S-L:L-S:d-l"; +#else + opt_complementary = "f-b:b-f:S-L:L-S"; +#endif + + opterr = 0; /* disable getopt 'errors' message. */ + opt = getopt32(ac, av, "l:L:fbSc:" +#if ENABLE_DEBUG_CROND_OPTION + "d:" +#endif + , &lopt, &Lopt, &copt +#if ENABLE_DEBUG_CROND_OPTION + , &dopt +#endif + ); + if (opt & 1) { + LogLevel = xatou(lopt); + } + if (opt & 2) { + if (*Lopt != 0) { + LogFile = Lopt; + } + } + if (opt & 32) { + if (*copt != 0) { + CDir = copt; + } + } +#if ENABLE_DEBUG_CROND_OPTION + if (opt & 64) { + DebugOpt = xatou(dopt); + LogLevel = 0; + } +#endif + + /* + * change directory + */ + + xchdir(CDir); + signal(SIGHUP, SIG_IGN); /* hmm.. but, if kill -HUP original + * version - his died. ;( + */ + /* + * close stdin and stdout, stderr. + * close unused descriptors - don't need. + * optional detach from controlling terminal + */ + + if (!(opt & 4)) { +#ifdef BB_NOMMU + /* reexec for vfork() do continue parent */ + vfork_daemon_rexec(1, 0, ac, av, "-f"); +#else + xdaemon(1, 0); +#endif + } + + (void) startlogger(); /* need if syslog mode selected */ + + /* + * main loop - synchronize to 1 second after the minute, minimum sleep + * of 1 second. + */ + + crondlog("\011%s " VERSION " dillon, started, log level %d\n", + applet_name, LogLevel); + + SynchronizeDir(); + + { + time_t t1 = time(NULL); + time_t t2; + long dt; + int rescan = 60; + short sleep_time = 60; + + for (;;) { + sleep((sleep_time + 1) - (short) (time(NULL) % sleep_time)); + + t2 = time(NULL); + dt = t2 - 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 ENABLE_DEBUG_CROND_OPTION + if (DebugOpt) + crondlog("\005Wakeup dt=%d\n", dt); +#endif + if (dt < -60 * 60 || dt > 60 * 60) { + t1 = t2; + crondlog("\111time disparity of %d minutes detected\n", 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 */ +} + +static int ChangeUser(const char *user) +{ + struct passwd *pas; + const char *err_msg; + + /* + * Obtain password entry and change privileges + */ + pas = getpwnam(user); + if (pas == 0) { + crondlog("\011failed to get uid for %s", user); + return -1; + } + setenv("USER", pas->pw_name, 1); + setenv("HOME", pas->pw_dir, 1); + setenv("SHELL", DEFAULT_SHELL, 1); + + /* + * Change running state to the user in question + */ + err_msg = change_identity_e2str(pas); + if (err_msg) { + crondlog("\011%s for user %s", err_msg, user); + return -1; + } + if (chdir(pas->pw_dir) < 0) { + crondlog("\011chdir failed: %s: %m", pas->pw_dir); + if (chdir(TMPDIR) < 0) { + crondlog("\011chdir failed: %s: %m", TMPDIR); + return -1; + } + } + return pas->pw_uid; +} + +static void startlogger(void) +{ + if (LogFile == 0) { + openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON); + } +#if ENABLE_DEBUG_CROND_OPTION + else { /* test logfile */ + int logfd; + + if ((logfd = open(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600)) >= 0) { + close(logfd); + } else { + bb_perror_msg("failed to open log file '%s': ", LogFile); + } + } +#endif +} + + +static const char *const DowAry[] = { + "sun", + "mon", + "tue", + "wed", + "thu", + "fri", + "sat", + + "Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + NULL +}; + +static const char *const MonAry[] = { + "jan", + "feb", + "mar", + "apr", + "may", + "jun", + "jul", + "aug", + "sep", + "oct", + "nov", + "dec", + + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + NULL +}; + +static char *ParseField(char *user, char *ary, int modvalue, int off, + const char *const *names, char *ptr) +{ + char *base = ptr; + int n1 = -1; + int n2 = -1; + + if (base == NULL) { + return NULL; + } + + while (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') { + 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 (*ptr >= '0' && *ptr <= '9') { + 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) { + if (strncmp(ptr, names[i], strlen(names[i])) == 0) { + break; + } + } + if (names[i]) { + ptr += strlen(names[i]); + if (n1 < 0) { + n1 = i; + } else { + n2 = i; + } + skip = 1; + } + } + + /* handle optional range '-' */ + + if (skip == 0) { + crondlog("\111failed user %s parsing %s\n", user, base); + return NULL; + } + 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; + } + } + while (n1 != n2 && --failsafe); + + if (failsafe == 0) { + crondlog("\111failed user %s parsing %s\n", user, base); + return NULL; + } + } + if (*ptr != ',') { + break; + } + ++ptr; + n1 = -1; + n2 = -1; + } + + if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') { + crondlog("\111failed user %s parsing %s\n", user, base); + return NULL; + } + + while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') { + ++ptr; + } +#if ENABLE_DEBUG_CROND_OPTION + if (DebugOpt) { + int i; + + for (i = 0; i < modvalue; ++i) { + crondlog("\005%d", ary[i]); + } + crondlog("\005\n"); + } +#endif + + return ptr; +} + +static void FixDayDow(CronLine * line) +{ + int i; + int weekUsed = 0; + int daysUsed = 0; + + for (i = 0; i < (int)(arysize(line->cl_Dow)); ++i) { + if (line->cl_Dow[i] == 0) { + weekUsed = 1; + break; + } + } + for (i = 0; i < (int)(arysize(line->cl_Days)); ++i) { + if (line->cl_Days[i] == 0) { + daysUsed = 1; + break; + } + } + if (weekUsed && !daysUsed) { + memset(line->cl_Days, 0, sizeof(line->cl_Days)); + } + if (daysUsed && !weekUsed) { + memset(line->cl_Dow, 0, sizeof(line->cl_Dow)); + } +} + + + +static void SynchronizeFile(const char *fileName) +{ + int maxEntries = MAXLINES; + int maxLines; + char buf[1024]; + + if (strcmp(fileName, "root") == 0) { + maxEntries = 65535; + } + maxLines = maxEntries * 10; + + if (fileName) { + FILE *fi; + + DeleteFile(fileName); + + fi = fopen(fileName, "r"); + if (fi != NULL) { + struct stat sbuf; + + if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) { + CronFile *file = calloc(1, sizeof(CronFile)); + CronLine **pline; + + file->cf_User = strdup(fileName); + pline = &file->cf_LineBase; + + while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) { + CronLine line; + char *ptr; + + trim(buf); + if (buf[0] == 0 || buf[0] == '#') { + continue; + } + if (--maxEntries == 0) { + break; + } + memset(&line, 0, sizeof(line)); + +#if ENABLE_DEBUG_CROND_OPTION + if (DebugOpt) { + crondlog("\111User %s Entry %s\n", fileName, buf); + } +#endif + + /* parse date ranges */ + ptr = ParseField(file->cf_User, line.cl_Mins, 60, 0, NULL, buf); + ptr = ParseField(file->cf_User, line.cl_Hrs, 24, 0, NULL, ptr); + ptr = ParseField(file->cf_User, line.cl_Days, 32, 0, NULL, ptr); + ptr = ParseField(file->cf_User, line.cl_Mons, 12, -1, MonAry, ptr); + ptr = ParseField(file->cf_User, line.cl_Dow, 7, 0, DowAry, ptr); + + /* check failure */ + if (ptr == NULL) { + continue; + } + + /* + * fix days and dow - if one is not * and the other + * is *, the other is set to 0, and vise-versa + */ + + FixDayDow(&line); + + *pline = calloc(1, sizeof(CronLine)); + **pline = line; + + /* copy command */ + (*pline)->cl_Shell = strdup(ptr); + +#if ENABLE_DEBUG_CROND_OPTION + if (DebugOpt) { + crondlog("\111 Command %s\n", ptr); + } +#endif + + pline = &((*pline)->cl_Next); + } + *pline = NULL; + + file->cf_Next = FileBase; + FileBase = file; + + if (maxLines == 0 || maxEntries == 0) { + crondlog("\111Maximum number of lines reached for user %s\n", fileName); + } + } + fclose(fi); + } + } +} + +static void CheckUpdates(void) +{ + FILE *fi; + char buf[256]; + + fi = fopen(CRONUPDATE, "r"); + if (fi != NULL) { + remove(CRONUPDATE); + while (fgets(buf, sizeof(buf), fi) != NULL) { + SynchronizeFile(strtok(buf, " \t\r\n")); + } + fclose(fi); + } +} + +static void SynchronizeDir(void) +{ + /* Attempt to delete the database. */ + + for (;;) { + CronFile *file; + + for (file = FileBase; file && file->cf_Deleted; file = file->cf_Next); + if (file == NULL) { + break; + } + DeleteFile(file->cf_User); + } + + /* + * Remove cron update file + * + * Re-chdir, in case directory was renamed & deleted, or otherwise + * screwed up. + * + * scan directory and add associated users + */ + + remove(CRONUPDATE); + if (chdir(CDir) < 0) { + crondlog("\311cannot find %s\n", CDir); + } + { + DIR *dir = opendir("."); + struct dirent *den; + + if (dir) { + while ((den = readdir(dir))) { + if (strchr(den->d_name, '.') != NULL) { + continue; + } + if (getpwnam(den->d_name)) { + SynchronizeFile(den->d_name); + } else { + crondlog("\007ignoring %s\n", den->d_name); + } + } + closedir(dir); + } else { + crondlog("\311cannot open current dir!\n"); + } + } +} + + +/* + * 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) { + if (t > t1) { + struct tm *tp = localtime(&t); + CronFile *file; + CronLine *line; + + for (file = FileBase; file; file = file->cf_Next) { +#if ENABLE_DEBUG_CROND_OPTION + if (DebugOpt) + crondlog("\005FILE %s:\n", file->cf_User); +#endif + if (file->cf_Deleted) + continue; + for (line = file->cf_LineBase; line; line = line->cl_Next) { +#if ENABLE_DEBUG_CROND_OPTION + if (DebugOpt) + crondlog("\005 LINE %s\n", line->cl_Shell); +#endif + 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 ENABLE_DEBUG_CROND_OPTION + if (DebugOpt) { + crondlog("\005 JobToDo: %d %s\n", + line->cl_Pid, line->cl_Shell); + } +#endif + if (line->cl_Pid > 0) { + crondlog("\010 process already running: %s %s\n", + 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) { + file->cf_Ready = 0; + + for (line = file->cf_LineBase; line; line = line->cl_Next) { + if (line->cl_Pid < 0) { + + RunJob(file->cf_User, line); + + crondlog("\010USER %s pid %3d cmd %s\n", + file->cf_User, 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) { + if (line->cl_Pid > 0) { + int status; + int r = wait4(line->cl_Pid, &status, WNOHANG, NULL); + + 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 +static void +ForkJob(const char *user, CronLine * line, int mailFd, + const char *prog, const char *cmd, const char *arg, const char *mailf) +{ + /* Fork as the user in question and run program */ + pid_t pid = fork(); + + line->cl_Pid = pid; + if (pid == 0) { + /* CHILD */ + + /* Change running state to the user in question */ + + if (ChangeUser(user) < 0) { + exit(0); + } +#if ENABLE_DEBUG_CROND_OPTION + if (DebugOpt) { + crondlog("\005Child Running %s\n", prog); + } +#endif + + if (mailFd >= 0) { + dup2(mailFd, mailf != NULL); + dup2((mailf ? mailFd : 1), 2); + close(mailFd); + } + execl(prog, prog, cmd, arg, NULL); + crondlog("\024cannot exec, user %s cmd %s %s %s\n", user, prog, cmd, arg); + if (mailf) { + fdprintf(1, "Exec failed: %s -c %s\n", prog, arg); + } + exit(0); + } else if (pid < 0) { + /* FORK FAILED */ + crondlog("\024cannot fork, user %s\n", user); + line->cl_Pid = 0; + if (mailf) { + remove(mailf); + } + } else if (mailf) { + /* PARENT, FORK SUCCESS + * rename mail-file based on pid of process + */ + char mailFile2[128]; + + snprintf(mailFile2, sizeof(mailFile2), TMPDIR "/cron.%s.%d", user, pid); + rename(mailf, mailFile2); + } + /* + * 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; + + line->cl_Pid = 0; + line->cl_MailFlag = 0; + + /* open mail file - owner root so nobody can screw with it. */ + + snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", 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", user, + line->cl_Shell); + line->cl_MailPos = lseek(mailFd, 0, SEEK_CUR); + } else { + crondlog("\024cannot create mail file user %s file %s, output to /dev/null\n", user, mailFile); + } + + 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), TMPDIR "/cron.%s.%d", user, line->cl_Pid); + line->cl_Pid = 0; + + if (line->cl_MailFlag != 1) { + 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); + remove(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; + } + ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL); +} +#else +/* crond without sendmail */ + +static void RunJob(const char *user, CronLine * line) +{ + /* Fork as the user in question and run program */ + pid_t pid = fork(); + + if (pid == 0) { + /* CHILD */ + + /* Change running state to the user in question */ + + if (ChangeUser(user) < 0) { + exit(0); + } +#if ENABLE_DEBUG_CROND_OPTION + if (DebugOpt) { + crondlog("\005Child Running %s\n", DEFAULT_SHELL); + } +#endif + + execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_Shell, NULL); + crondlog("\024cannot exec, user %s cmd %s -c %s\n", user, + DEFAULT_SHELL, line->cl_Shell); + exit(0); + } else if (pid < 0) { + /* FORK FAILED */ + crondlog("\024cannot, user %s\n", user); + 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 000000000..39d3aae41 --- /dev/null +++ b/miscutils/crontab.c @@ -0,0 +1,344 @@ +/* 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 "busybox.h" + +#ifndef CRONTABS +#define CRONTABS "/var/spool/cron/crontabs" +#endif +#ifndef TMPDIR +#define TMPDIR "/var/spool/cron" +#endif +#ifndef CRONUPDATE +#define CRONUPDATE "cron.update" +#endif +#ifndef PATH_VI +#define PATH_VI "/bin/vi" /* location of vi */ +#endif + +static const char *CDir = CRONTABS; + +static void EditFile(const char *user, const char *file); +static int GetReplaceStream(const char *user, const char *file); +static int ChangeUser(const char *user, short dochdir); + +int crontab_main(int ac, char **av) +{ + enum { NONE, EDIT, LIST, REPLACE, DELETE } option = NONE; + const struct passwd *pas; + const char *repFile = NULL; + int repFd = 0; + int i; + char caller[256]; /* user that ran program */ + char buf[1024]; + int UserId; + + UserId = getuid(); + pas = getpwuid(UserId); + if (pas == NULL) + bb_perror_msg_and_die("getpwuid"); + + safe_strncpy(caller, pas->pw_name, sizeof(caller)); + + i = 1; + if (ac > 1) { + if (av[1][0] == '-' && av[1][1] == 0) { + option = REPLACE; + ++i; + } else if (av[1][0] != '-') { + option = REPLACE; + ++i; + repFile = av[1]; + } + } + + for (; i < ac; ++i) { + char *ptr = av[i]; + + if (*ptr != '-') + break; + ptr += 2; + + switch (ptr[-1]) { + case 'l': + if (ptr[-1] == 'l') + option = LIST; + /* fall through */ + case 'e': + if (ptr[-1] == 'e') + option = EDIT; + /* fall through */ + case 'd': + if (ptr[-1] == 'd') + option = DELETE; + /* fall through */ + case 'u': + if (i + 1 < ac && av[i+1][0] != '-') { + ++i; + if (getuid() == geteuid()) { + pas = getpwnam(av[i]); + if (pas) { + UserId = pas->pw_uid; + } else { + bb_error_msg_and_die("user %s unknown", av[i]); + } + } else { + bb_error_msg_and_die("only the superuser may specify a user"); + } + } + break; + case 'c': + if (getuid() == geteuid()) { + CDir = (*ptr) ? ptr : av[++i]; + } else { + bb_error_msg_and_die("-c option: superuser only"); + } + break; + default: + i = ac; + break; + } + } + if (i != ac || option == NONE) + bb_show_usage(); + + /* + * Get password entry + */ + + pas = getpwuid(UserId); + if (pas == NULL) + bb_perror_msg_and_die("getpwuid"); + + /* + * If there is a replacement file, obtain a secure descriptor to it. + */ + + if (repFile) { + repFd = GetReplaceStream(caller, repFile); + if (repFd < 0) + bb_error_msg_and_die("cannot read replacement file"); + } + + /* + * Change directory to our crontab directory + */ + + xchdir(CDir); + + /* + * Handle options as appropriate + */ + + switch (option) { + case LIST: + { + FILE *fi; + + fi = fopen(pas->pw_name, "r"); + if (fi) { + while (fgets(buf, sizeof(buf), fi) != NULL) + fputs(buf, stdout); + fclose(fi); + } else { + bb_error_msg("no crontab for %s", pas->pw_name); + } + } + break; + case EDIT: + { +/* FIXME: messy code here! we have file copying helpers for this! */ + FILE *fi; + int fd; + int n; + char tmp[128]; + + snprintf(tmp, sizeof(tmp), TMPDIR "/crontab.%d", getpid()); + fd = xopen3(tmp, O_RDWR|O_CREAT|O_TRUNC|O_EXCL, 0600); +/* race, use fchown */ + chown(tmp, getuid(), getgid()); + fi = fopen(pas->pw_name, "r"); + if (fi) { + while ((n = fread(buf, 1, sizeof(buf), fi)) > 0) + full_write(fd, buf, n); + } + EditFile(caller, tmp); + remove(tmp); + lseek(fd, 0L, SEEK_SET); + repFd = fd; + } + option = REPLACE; + /* fall through */ + case REPLACE: + { +/* same here */ + char path[1024]; + int fd; + int n; + + snprintf(path, sizeof(path), "%s.new", pas->pw_name); + fd = open(path, O_CREAT|O_TRUNC|O_APPEND|O_WRONLY, 0600); + if (fd >= 0) { + while ((n = read(repFd, buf, sizeof(buf))) > 0) { + full_write(fd, buf, n); + } + close(fd); + rename(path, pas->pw_name); + } else { + bb_error_msg("cannot create %s/%s", CDir, path); + } + close(repFd); + } + break; + case DELETE: + remove(pas->pw_name); + break; + case NONE: + default: + break; + } + + /* + * Bump notification file. Handle window where crond picks file up + * before we can write our entry out. + */ + + if (option == REPLACE || option == DELETE) { + FILE *fo; + struct stat st; + + while ((fo = fopen(CRONUPDATE, "a"))) { + fprintf(fo, "%s\n", pas->pw_name); + fflush(fo); + if (fstat(fileno(fo), &st) != 0 || st.st_nlink != 0) { + fclose(fo); + break; + } + fclose(fo); + /* loop */ + } + if (fo == NULL) { + bb_error_msg("cannot append to %s/%s", CDir, CRONUPDATE); + } + } + return 0; +} + +static int GetReplaceStream(const char *user, const char *file) +{ + int filedes[2]; + int pid; + int fd; + int n; + char buf[1024]; + + if (pipe(filedes) < 0) { + perror("pipe"); + return -1; + } + pid = fork(); + if (pid < 0) { + perror("fork"); + return -1; + } + if (pid > 0) { + /* + * PARENT + */ + + close(filedes[1]); + if (read(filedes[0], buf, 1) != 1) { + close(filedes[0]); + filedes[0] = -1; + } + return filedes[0]; + } + + /* + * CHILD + */ + + close(filedes[0]); + + if (ChangeUser(user, 0) < 0) + exit(0); + + xfunc_error_retval = 0; + fd = xopen(file, O_RDONLY); + buf[0] = 0; + write(filedes[1], buf, 1); + while ((n = read(fd, buf, sizeof(buf))) > 0) { + write(filedes[1], buf, n); + } + exit(0); +} + +static void EditFile(const char *user, const char *file) +{ + int pid = fork(); + + if (pid == 0) { + /* + * CHILD - change user and run editor + */ + char *ptr; + char visual[1024]; + + if (ChangeUser(user, 1) < 0) + exit(0); + ptr = getenv("VISUAL"); + if (ptr == NULL || strlen(ptr) > 256) + ptr = PATH_VI; + + snprintf(visual, sizeof(visual), "%s %s", ptr, file); + execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", visual, NULL); + perror("exec"); + exit(0); + } + if (pid < 0) { + /* + * PARENT - failure + */ + bb_perror_msg_and_die("fork"); + } + wait4(pid, NULL, 0, NULL); +} + +static int ChangeUser(const char *user, short dochdir) +{ + struct passwd *pas; + + /* + * Obtain password entry and change privileges + */ + + pas = getpwnam(user); + if (pas == NULL) { + bb_perror_msg_and_die("failed to get uid for %s", user); + } + setenv("USER", pas->pw_name, 1); + setenv("HOME", pas->pw_dir, 1); + setenv("SHELL", DEFAULT_SHELL, 1); + + /* + * Change running state to the user in question + */ + change_identity(pas); + + if (dochdir) { + if (chdir(pas->pw_dir) < 0) { + bb_perror_msg("chdir(%s) by %s failed", pas->pw_dir, user); + xchdir(TMPDIR); + } + } + return pas->pw_uid; +} diff --git a/miscutils/dc.c b/miscutils/dc.c new file mode 100644 index 000000000..7b6405754 --- /dev/null +++ b/miscutils/dc.c @@ -0,0 +1,231 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include +#include +#include +#include +#include +#include + +/* Tiny RPN calculator, because "expr" didn't give me bitwise operations. */ + +static double stack[100]; +static unsigned int pointer; +static unsigned char base; + +static void push(double a) +{ + if (pointer >= (sizeof(stack) / sizeof(*stack))) + 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()); +} + +static void power(void) +{ + double topower = pop(); + + push(pow(pop(), topower)); +} + +static void divide(void) +{ + double divisor = pop(); + + push(pop() / divisor); +} + +static void mod(void) +{ + unsigned int d = pop(); + + push((unsigned int) pop() % d); +} + +static void and(void) +{ + push((unsigned int) pop() & (unsigned int) pop()); +} + +static void or(void) +{ + push((unsigned int) pop() | (unsigned int) pop()); +} + +static void eor(void) +{ + push((unsigned int) pop() ^ (unsigned int) pop()); +} + +static void not(void) +{ + push(~(unsigned int) pop()); +} + +static void set_output_base(void) +{ + base=(unsigned char)pop(); + if ((base != 10) && (base != 16)) { + fprintf(stderr, "Error: base = %d is not supported.\n", base); + base=10; + } +} + +static void print_base(double print) +{ + if (base == 16) + printf("%x\n", (unsigned int)print); + else + printf("%g\n", print); +} + +static void print_stack_no_pop(void) +{ + unsigned int i=pointer; + while (i) + print_base(stack[--i]); +} + +static void print_no_pop(void) +{ + print_base(stack[pointer-1]); +} + +struct op { + const char *name; + void (*function) (void); +}; + +static const struct op operators[] = { + {"+", add}, + {"add", add}, + {"-", sub}, + {"sub", sub}, + {"*", mul}, + {"mul", mul}, + {"/", divide}, + {"div", divide}, + {"**", power}, + {"exp", power}, + {"pow", power}, + {"%", 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}, + {0, 0} +}; + +static void stack_machine(const char *argument) +{ + char *endPointer = 0; + 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 *start = NULL; + char *current; + + current = skip_whitespace(*buffer); + if (*current != 0) { + start = current; + while (!isspace(*current) && *current != 0) { current++; } + *buffer = current; + } + return start; +} + +/* In Perl one might say, scalar m|\s*(\S+)\s*|g */ +static int number_of_tokens(char *buffer) +{ + int i = 0; + char *b = buffer; + while (get_token(&b)) { i++; } + return i; +} + +int dc_main(int argc, char **argv) +{ + /* take stuff from stdin if no args are given */ + if (argc <= 1) { + int i, len; + char *line = NULL; + char *cursor = NULL; + char *token = NULL; + while ((line = xmalloc_getline(stdin))) { + cursor = line; + len = number_of_tokens(line); + for (i = 0; i < len; i++) { + token = get_token(&cursor); + *cursor++ = 0; + stack_machine(token); + } + free(line); + } + } else { + if (*argv[1]=='-') + bb_show_usage(); + while (argc >= 2) { + stack_machine(argv[1]); + argv++; + argc--; + } + } + return EXIT_SUCCESS; +} diff --git a/miscutils/devfsd.c b/miscutils/devfsd.c new file mode 100644 index 000000000..f39bb7e3f --- /dev/null +++ b/miscutils/devfsd.c @@ -0,0 +1,2032 @@ +/* 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 "busybox.h" +#include "xregex.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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, char *); + +/* busybox functions */ +static void msg_logger(int pri, const char * fmt, ... )__attribute__ ((format (printf, 2, 3))); +static void msg_logger_and_die(int pri, const char * fmt, ... )__attribute__ ((noreturn, format (printf, 2, 3))); +static void do_ioctl_and_die(int fd, int request, unsigned long event_mask_flag); +static void fork_and_execute(int die, char *arg0, char **arg ); +static int get_uid_gid ( int, const char *); +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, char *ptr); +static unsigned int scan_dev_name(const char *d, unsigned int n, char *ptr); + +/* Structs and vars */ +static struct config_entry_struct *first_config = NULL; +static struct config_entry_struct *last_config = NULL; +static const char *mount_point = NULL; +static volatile int caught_signal = FALSE; +static volatile int caught_sighup = FALSE; +static struct initial_symlink_struct +{ + char *dest; + 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 * const bb_msg_proto_rev = "protocol revision"; +static const char * const bb_msg_bad_config = "bad %s config file: %s"; +static const char * const bb_msg_small_buffer = "buffer too small"; +static const char * const bb_msg_variable_not_found = "variable: %s not found"; + +/* Busybox functions */ +static void msg_logger(int pri, const char * fmt, ... ) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + ret = access ("/dev/log", F_OK); + if (ret == 0) { + openlog(applet_name, 0, LOG_DAEMON); + vsyslog( pri , fmt, ap); + /* Man: A trailing newline is added when needed. */ + closelog(); + } + /* ENABLE_DEVFSD_VERBOSE is always enabled if msg_logger is used */ + if ((ENABLE_DEVFSD_VERBOSE && ret) || ENABLE_DEBUG) { + bb_error_msg(fmt, ap); + } + va_end(ap); +} + +static void msg_logger_and_die(int pri, const char* fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + msg_logger(pri, fmt, ap); + va_end(ap); + exit(EXIT_FAILURE); +} + +/* Busybox stuff */ +#if defined(CONFIG_DEVFSD_VERBOSE) || defined(CONFIG_DEBUG) +#define devfsd_error_msg(fmt, args...) bb_error_msg(fmt, ## args) +#define devfsd_perror_msg_and_die(fmt, args...) bb_perror_msg_and_die(fmt, ## args) +#define devfsd_error_msg_and_die(fmt, args...) bb_error_msg_and_die(fmt, ## args) +#if defined(CONFIG_DEBUG) +#define debug_msg_logger(x, fmt, args...) msg_logger(x, fmt, ## args) +#else +#define debug_msg_logger(x, fmt, args...) +#endif +#else +#define debug_msg_logger(x, fmt, args...) +#define msg_logger(p, fmt, args...) +#define msg_logger_and_die(p, fmt, args...) exit(1) +#define devfsd_perror_msg_and_die(fmt, args...) exit(1) +#define devfsd_error_msg_and_die(fmt, args...) exit(1) +#define devfsd_error_msg(fmt, args...) +#endif + +static void do_ioctl_and_die(int fd, int request, unsigned long event_mask_flag) +{ + if (ioctl (fd, request, event_mask_flag) == -1) + msg_logger_and_die(LOG_ERR, "ioctl"); +} + +static void fork_and_execute(int die, char *arg0, char **arg ) +{ + switch ( fork () ) + { + case 0: + /* Child */ + break; + case -1: + /* Parent: Error : die or return */ + msg_logger(LOG_ERR,(char *) bb_msg_memory_exhausted); + if(die) + exit(EXIT_FAILURE); + return; + default: + /* Parent : ok : return or exit */ + if(arg0 != NULL) + { + wait (NULL); + return; + } + exit (EXIT_SUCCESS); + } + /* Child : if arg0 != NULL do execvp */ + if(arg0 != NULL ) + { + execvp (arg0, arg); + msg_logger_and_die(LOG_ERR, "execvp"); + } +} + +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, 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, 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) +{ + 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(); + } + } + + /* strip last / from mount point, so we don't need to check for it later */ + while( argv[1][1]!='\0' && argv[1][strlen(argv[1])-1] == '/' ) + argv[1][strlen(argv[1]) -1] = '\0'; + + mount_point = argv[1]; + + if (chdir (mount_point) != 0) + devfsd_perror_msg_and_die(mount_point); + + fd = xopen (".devfsd", O_RDONLY); + + if (fcntl (fd, F_SETFD, FD_CLOEXEC) != 0) + devfsd_perror_msg_and_die("FD_CLOEXEC"); + + if (ioctl (fd, DEVFSDIOC_GET_PROTO_REV, &proto_rev) == -1) + msg_logger_and_die(LOG_ERR, "ioctl"); + + /*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) */ + do_ioctl_and_die(fd, DEVFSDIOC_SET_EVENT_MASK, 0); + + sigemptyset (&new_action.sa_mask); + new_action.sa_flags = 0; + + /* Set up SIGHUP and SIGUSR1 handlers */ + new_action.sa_handler = signal_handler; + if (sigaction (SIGHUP, &new_action, NULL) != 0 || sigaction (SIGUSR1, &new_action, NULL) != 0 ) + devfsd_error_msg_and_die( "sigaction"); + + 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 (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 (0); + if (do_daemon) + { + /* Release so that the child can grab it */ + do_ioctl_and_die(fd, DEVFSDIOC_RELEASE_EVENT_QUEUE, 0); + fork_and_execute(DIE, NULL, NULL); + setsid (); /* Prevent hangups and become pgrp leader */ + } 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 (CONFIG_FILE, FALSE, &event_mask); + if (do_scan) + dir_operation(SERVICE,mount_point,0,NULL); + } +} /* 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; + + debug_msg_logger(LOG_INFO, "%s: %s", __FUNCTION__, path); + + if (stat (path, &statbuf) == 0 ) + { + /* Don't read 0 length files: ignored */ + /*if( statbuf.st_size == 0 ) + return;*/ + if ( S_ISDIR (statbuf.st_mode) ) + { + /* strip last / from dirname so we don't need to check for it later */ + while( path && path[1]!='\0' && path[strlen(path)-1] == '/') + path[strlen(path) -1] = '\0'; + + dir_operation(READ_CONFIG, path, 0, event_mask); + return; + } + if ( ( fp = fopen (path, "r") ) != NULL ) + { + while (fgets (buf, STRING_LENGTH, fp) != NULL) + { + /* Skip whitespace */ + for (line = buf; isspace (*line); ++line) + /*VOID*/; + 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) + msg_logger_and_die(LOG_ERR, "read config file: %s: %m", path); + } + return; +} /* 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]; + char * msg=""; + char *ptr; + int i; + + /* !!!! Only Uppercase Keywords in devsfd.conf */ + static const char *const options[] = { + "CLEAR_CONFIG", "INCLUDE", "OPTIONAL_INCLUDE", + "RESTORE", "PERMISSIONS", "MODLOAD", "EXECUTE", + "COPY", "IGNORE", "MKOLDCOMPAT", "MKNEWCOMPAT", + "RMOLDCOMPAT", "RMNEWCOMPAT", 0 + }; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + 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_str_array(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 ); + msg_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 = xmalloc (sizeof *new); + memset (new, 0, 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_str_array(options, what ); + + switch(i) + { + case 4: /* "PERMISSIONS" */ + new->action.what = AC_PERMISSIONS; + /* Get user and group */ + if ( ( ptr = strchr (p[0], '.') ) == 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; + unsigned long tmp_event_mask; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + /* Tell devfs what events we care about */ + tmp_event_mask = event_mask; + do_ioctl_and_die(fd, DEVFSDIOC_SET_EVENT_MASK, tmp_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; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + if (ENABLE_DEBUG && info->overrun_count > 0) + debug_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 */; + + debug_msg_logger(LOG_INFO, "%s: action.what %d", __FUNCTION__, entry->action.what); + + 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; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + 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) + { + msg_logger(LOG_ERR, "Can't chmod or chown: %s: %m",info->devname); + } +} /* End Function action_permissions */ + +static void action_modload (const struct devfsd_notify_struct *info, + const struct config_entry_struct *entry ATTRIBUTE_UNUSED) +/* [SUMMARY] Load a module. + The devfs change. + The config file entry. + [RETURNS] Nothing. +*/ +{ + char *argv[6]; + char device[STRING_LENGTH]; + + argv[0] = MODPROBE; + argv[1] = MODPROBE_SWITCH_1; /* "-k" */ + argv[2] = MODPROBE_SWITCH_2; /* "-C" */ + argv[3] = CONFIG_MODULES_DEVFS; + argv[4] = device; + argv[5] = NULL; + + snprintf (device, sizeof (device), "/dev/%s", info->devname); + debug_msg_logger(LOG_INFO, "%s: %s %s %s %s %s",__FUNCTION__, argv[0],argv[1],argv[2],argv[3],argv[4]); + fork_and_execute(DIE, argv[0], argv); +} /* 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]; + + debug_msg_logger(LOG_INFO ,__FUNCTION__); + 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; + fork_and_execute(NO_DIE, argv[0], 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; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + 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)) + debug_msg_logger(LOG_ERR, "copy_inode: %s to %s: %m", source, destination); + return; +} /* 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; + char *ptr=NULL; + 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 = strrchr (info->devname, '/') + 1; + i=scan_dev_name(info->devname, info->namelen, ptr); + + debug_msg_logger(LOG_INFO, "%s: scan_dev_name = %d", __FUNCTION__, i); + + /* 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; + + debug_msg_logger( LOG_INFO, "%s: %s", __FUNCTION__, compat_name); + + /* 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) + debug_msg_logger(LOG_ERR, "unlink: %s: %m", compat_name); + break; + /*esac*/ + } /* switch(action) */ +} /* End Function action_compat */ + +static void restore(char *spath, struct stat source_stat, int rootlen) +{ + char dpath[STRING_LENGTH]; + struct stat dest_stat; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + dest_stat.st_mode = 0; + snprintf (dpath, sizeof dpath, "%s%s", mount_point, spath + rootlen); + lstat (dpath, &dest_stat); + + 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]; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + if ( (source_stat->st_mode & S_IFMT) == (dest_stat->st_mode & S_IFMT) ) + { + /* Same type */ + if ( S_ISLNK (source_stat->st_mode) ) + { + if (( source_len = readlink (sourcepath, source_link, STRING_LENGTH - 1) ) < 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: + if ( ( fd = socket (AF_UNIX, SOCK_STREAM, 0) ) < 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: + if ( ( val = readlink (sourcepath, symlink_val, STRING_LENGTH - 1) ) < 0 ) + break; + symlink_val[val] = '\0'; + if (symlink (symlink_val, destpath) == 0) + return TRUE; + break; + case S_IFREG: + if ( ( fd = open (destpath, O_RDONLY | O_CREAT, new_mode & ~S_IFMT) ) < 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; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + 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 char *msg; + + if (ENABLE_DEVFSD_VERBOSE) + msg="user"; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + if(ENABLE_DEBUG && flag != UID && flag != GID) + msg_logger_and_die(LOG_ERR,"%s: flag != UID && flag != GID", __FUNCTION__); + + 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; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + 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) +{ + debug_msg_logger(LOG_INFO, __FUNCTION__); + + caught_signal = TRUE; + if (sig == SIGHUP) + caught_sighup = TRUE; + + msg_logger(LOG_INFO, "Caught signal %d", sig); +} /* End Function signal_handler */ + +static const char *get_variable (const char *variable, void *info) +{ + struct get_variable_info *gv_info = info; + static char hostname[STRING_LENGTH], sbuf[STRING_LENGTH]; + const char *field_names[] = { "hostname", "mntpt", "devpath", "devname", + "uid", "gid", "mode", hostname, mount_point, + gv_info->devpath, gv_info->devname, 0 }; + int i; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + if (gethostname (hostname, STRING_LENGTH - 1) != 0) + msg_logger_and_die(LOG_ERR, "gethostname: %m"); + + /* Here on error we should do exit(RV_SYS_ERROR), instead we do exit(EXIT_FAILURE) */ + hostname[STRING_LENGTH - 1] = '\0'; + + /* 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) + { + debug_msg_logger(LOG_INFO, "%s: i=%d %s", __FUNCTION__, i ,field_names[i+7]); + 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); + + debug_msg_logger(LOG_INFO, "%s: %s", __FUNCTION__, sbuf); + + return sbuf; +} /* End Function get_variable */ + +static void service(struct stat statbuf, char *path) +{ + struct devfsd_notify_struct info; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + 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[STRING_LENGTH]; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + if((dp = opendir( dir_name))==NULL) + { + debug_msg_logger(LOG_ERR, "opendir: %s: %m", dir_name); + return; + } + + while ( (de = readdir (dp) ) != NULL ) + { + + if(de->d_name && *de->d_name == '.' && (!de->d_name[1] || (de->d_name[1] == '.' && !de->d_name[2]))) + continue; + snprintf (path, sizeof (path), "%s/%s", dir_name, de->d_name); + debug_msg_logger(LOG_ERR, "%s: %s", __FUNCTION__, path); + + if (lstat (path, &statbuf) != 0) + { + debug_msg_logger(LOG_ERR, "%s: %s: %m", __FUNCTION__, path); + continue; + } + 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; + } + } + 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. +*/ +{ + debug_msg_logger(LOG_INFO, __FUNCTION__); + + if ( !make_dir_tree (newpath) ) + return -1; + + if (symlink (oldpath, newpath) != 0) { + if (errno != EEXIST) { + debug_msg_logger(LOG_ERR, "%s: %s to %s: %m", __FUNCTION__, oldpath, newpath); + 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. +*/ +{ + debug_msg_logger(LOG_INFO, __FUNCTION__); + + if (bb_make_directory( dirname((char *)path), -1, FILEUTILS_RECUR )==-1) + { + debug_msg_logger(LOG_ERR, "%s: %s: %m",__FUNCTION__, path); + 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]; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + 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; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + /* 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 +{ + char *match; /* The string to match to (up to length) */ + 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; + 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 + }; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + 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 = (strrchr (devname, '/') + 1); + i = scan_dev_name(devname, namelen, ptr); + + if( i > 0 && i < 13) + compat_name = buffer; + else + return NULL; + + debug_msg_logger(LOG_INFO, "%s: scan_dev_name = %d", __FUNCTION__, i); + + /* 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]); + } + + if(ENABLE_DEBUG && compat_name!=NULL) + msg_logger(LOG_INFO, "%s: compat_name %s", __FUNCTION__, compat_name); + + 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; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + /* 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, + 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; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + 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]; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + 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 */ + if ( ( env = getenv ("HOME") ) == NULL ) + { + msg_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; + if ( ( pwent = getpwnam (tmp) ) == NULL ) + { + msg_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: + msg_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]; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + 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; + if ( ( env = get_variable_v2 (tmp, func, info) ) == NULL ) + { + msg_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] != '-' ) + { + msg_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': + msg_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}" */ + if ( ( env = get_variable_v2 (tmp, func, info) ) != 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: + msg_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; + + debug_msg_logger(LOG_INFO, __FUNCTION__); + + 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/eject.c b/miscutils/eject.c new file mode 100644 index 000000000..ff23b1666 --- /dev/null +++ b/miscutils/eject.c @@ -0,0 +1,60 @@ +/* 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 "busybox.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 FLAG_CLOSE 1 +#define FLAG_SMART 2 + +int eject_main(int argc, char **argv) +{ + unsigned long flags; + char *device; + int dev, cmd; + + opt_complementary = "?:?1:t--T:T--t"; + flags = getopt32(argc, argv, "tT"); + device = 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 = xopen(device, O_RDONLY|O_NONBLOCK); + cmd = CDROMEJECT; + if (flags & FLAG_CLOSE + || (flags & FLAG_SMART && ioctl(dev, CDROM_DRIVE_STATUS) == CDS_TRAY_OPEN)) + cmd = CDROMCLOSETRAY; + if (ioctl(dev, cmd)) { + bb_perror_msg_and_die("%s", device); + } + + if (ENABLE_FEATURE_CLEAN_UP) + close(dev); + + return EXIT_SUCCESS; +} diff --git a/miscutils/hdparm.c b/miscutils/hdparm.c new file mode 100644 index 000000000..90c163973 --- /dev/null +++ b/miscutils/hdparm.c @@ -0,0 +1,2184 @@ +/* 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 "busybox.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 + +#ifdef CONFIG_FEATURE_HDPARM_GET_IDENTITY +static const char * const pkt_str[] = { + "Direct-access device", /* word 0, bits 12-8 = 00 */ + "Sequential-access device", /* word 0, bits 12-8 = 01 */ + "Printer", /* word 0, bits 12-8 = 02 */ + "Processor", /* word 0, bits 12-8 = 03 */ + "Write-once device", /* word 0, bits 12-8 = 04 */ + "CD-ROM", /* word 0, bits 12-8 = 05 */ + "Scanner", /* word 0, bits 12-8 = 06 */ + "Optical memory", /* word 0, bits 12-8 = 07 */ + "Medium changer", /* word 0, bits 12-8 = 08 */ + "Communications device", /* word 0, bits 12-8 = 09 */ + "ACS-IT8 device", /* word 0, bits 12-8 = 0a */ + "ACS-IT8 device", /* word 0, bits 12-8 = 0b */ + "Array controller", /* word 0, bits 12-8 = 0c */ + "Enclosure services", /* word 0, bits 12-8 = 0d */ + "Reduced block command device", /* word 0, bits 12-8 = 0e */ + "Optical card reader/writer", /* word 0, bits 12-8 = 0f */ + "", /* word 0, bits 12-8 = 10 */ + "", /* word 0, bits 12-8 = 11 */ + "", /* word 0, bits 12-8 = 12 */ + "", /* word 0, bits 12-8 = 13 */ + "", /* word 0, bits 12-8 = 14 */ + "", /* word 0, bits 12-8 = 15 */ + "", /* word 0, bits 12-8 = 16 */ + "", /* word 0, bits 12-8 = 17 */ + "", /* word 0, bits 12-8 = 18 */ + "", /* word 0, bits 12-8 = 19 */ + "", /* word 0, bits 12-8 = 1a */ + "", /* word 0, bits 12-8 = 1b */ + "", /* word 0, bits 12-8 = 1c */ + "", /* word 0, bits 12-8 = 1d */ + "", /* word 0, bits 12-8 = 1e */ + "Unknown", /* word 0, bits 12-8 = 1f */ +}; + +static const char * const ata1_cfg_str[] = { /* word 0 in ATA-1 mode */ + "Reserved", /* bit 0 */ + "hard sectored", /* bit 1 */ + "soft sectored", /* bit 2 */ + "not MFM encoded ", /* bit 3 */ + "head switch time > 15us", /* bit 4 */ + "spindle motor control option", /* bit 5 */ + "fixed drive", /* bit 6 */ + "removable drive", /* bit 7 */ + "disk xfer rate <= 5Mbs", /* bit 8 */ + "disk xfer rate > 5Mbs, <= 10Mbs", /* bit 9 */ + "disk xfer rate > 5Mbs", /* bit 10 */ + "rotational speed tol.", /* bit 11 */ + "data strobe offset option", /* bit 12 */ + "track offset option", /* bit 13 */ + "format speed tolerance gap reqd", /* bit 14 */ + "ATAPI" /* bit 14 */ +}; +#endif + +/* 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 +#ifdef CONFIG_FEATURE_HDPARM_GET_IDENTITY +static const char *minor_str[MINOR_MAX+2] = { /* word 81 value: */ + "Unspecified", /* 0x0000 */ + "ATA-1 X3T9.2 781D prior to rev.4", /* 0x0001 */ + "ATA-1 published, ANSI X3.221-1994", /* 0x0002 */ + "ATA-1 X3T9.2 781D rev.4", /* 0x0003 */ + "ATA-2 published, ANSI X3.279-1996", /* 0x0004 */ + "ATA-2 X3T10 948D prior to rev.2k", /* 0x0005 */ + "ATA-3 X3T10 2008D rev.1", /* 0x0006 */ + "ATA-2 X3T10 948D rev.2k", /* 0x0007 */ + "ATA-3 X3T10 2008D rev.0", /* 0x0008 */ + "ATA-2 X3T10 948D rev.3", /* 0x0009 */ + "ATA-3 published, ANSI X3.298-199x", /* 0x000a */ + "ATA-3 X3T10 2008D rev.6", /* 0x000b */ + "ATA-3 X3T13 2008D rev.7 and 7a", /* 0x000c */ + "ATA/ATAPI-4 X3T13 1153D rev.6", /* 0x000d */ + "ATA/ATAPI-4 T13 1153D rev.13", /* 0x000e */ + "ATA/ATAPI-4 X3T13 1153D rev.7", /* 0x000f */ + "ATA/ATAPI-4 T13 1153D rev.18", /* 0x0010 */ + "ATA/ATAPI-4 T13 1153D rev.15", /* 0x0011 */ + "ATA/ATAPI-4 published, ANSI INCITS 317-1998", /* 0x0012 */ + "ATA/ATAPI-5 T13 1321D rev.3", + "ATA/ATAPI-4 T13 1153D rev.14", /* 0x0014 */ + "ATA/ATAPI-5 T13 1321D rev.1", /* 0x0015 */ + "ATA/ATAPI-5 published, ANSI INCITS 340-2000", /* 0x0016 */ + "ATA/ATAPI-4 T13 1153D rev.17", /* 0x0017 */ + "ATA/ATAPI-6 T13 1410D rev.0", /* 0x0018 */ + "ATA/ATAPI-6 T13 1410D rev.3a", /* 0x0019 */ + "ATA/ATAPI-7 T13 1532D rev.1", /* 0x001a */ + "ATA/ATAPI-6 T13 1410D rev.2", /* 0x001b */ + "ATA/ATAPI-6 T13 1410D rev.1", /* 0x001c */ + "ATA/ATAPI-7 published, ANSI INCITS 397-2005", /* 0x001d */ + "ATA/ATAPI-7 T13 1532D rev.0", /* 0x001e */ + "Reserved" /* 0x001f */ + "Reserved" /* 0x0020 */ + "ATA/ATAPI-7 T13 1532D rev.4a", /* 0x0021 */ + "ATA/ATAPI-6 published, ANSI INCITS 361-2002", /* 0x0022 */ + "Reserved" /* 0x0023-0xfffe*/ +}; +#endif +static const char actual_ver[MINOR_MAX+2] = { + /* word 81 value: */ + 0, /* 0x0000 WARNING: */ + 1, /* 0x0001 WARNING: */ + 1, /* 0x0002 WARNING: */ + 1, /* 0x0003 WARNING: */ + 2, /* 0x0004 WARNING: This array */ + 2, /* 0x0005 WARNING: corresponds */ + 3, /* 0x0006 WARNING: *exactly* */ + 2, /* 0x0007 WARNING: to the ATA/ */ + 3, /* 0x0008 WARNING: ATAPI version */ + 2, /* 0x0009 WARNING: listed in */ + 3, /* 0x000a WARNING: the */ + 3, /* 0x000b WARNING: minor_str */ + 3, /* 0x000c WARNING: array */ + 4, /* 0x000d WARNING: above. */ + 4, /* 0x000e WARNING: */ + 4, /* 0x000f WARNING: if you change */ + 4, /* 0x0010 WARNING: that one, */ + 4, /* 0x0011 WARNING: change this one */ + 4, /* 0x0012 WARNING: too!!! */ + 5, /* 0x0013 WARNING: */ + 4, /* 0x0014 WARNING: */ + 5, /* 0x0015 WARNING: */ + 5, /* 0x0016 WARNING: */ + 4, /* 0x0017 WARNING: */ + 6, /* 0x0018 WARNING: */ + 6, /* 0x0019 WARNING: */ + 7, /* 0x001a WARNING: */ + 6, /* 0x001b WARNING: */ + 6, /* 0x001c WARNING: */ + 7, /* 0x001d WARNING: */ + 7, /* 0x001e WARNING: */ + 0, /* 0x001f WARNING: */ + 0, /* 0x0020 WARNING: */ + 7, /* 0x0021 WARNING: */ + 6, /* 0x0022 WARNING: */ + 0 /* 0x0023-0xfffe */ +}; + +/* 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 + +#ifdef CONFIG_FEATURE_HDPARM_GET_IDENTITY +static const char * const cmd_feat_str[] = { + "", /* word 82 bit 15: obsolete */ + "NOP cmd", /* word 82 bit 14 */ + "READ BUFFER cmd", /* word 82 bit 13 */ + "WRITE BUFFER cmd", /* word 82 bit 12 */ + "", /* word 82 bit 11: obsolete */ + "Host Protected Area feature set", /* word 82 bit 10 */ + "DEVICE RESET cmd", /* word 82 bit 9 */ + "SERVICE interrupt", /* word 82 bit 8 */ + "Release interrupt", /* word 82 bit 7 */ + "Look-ahead", /* word 82 bit 6 */ + "Write cache", /* word 82 bit 5 */ + "PACKET command feature set", /* word 82 bit 4 */ + "Power Management feature set", /* word 82 bit 3 */ + "Removable Media feature set", /* word 82 bit 2 */ + "Security Mode feature set", /* word 82 bit 1 */ + "SMART feature set", /* word 82 bit 0 */ + /* --------------*/ + "", /* word 83 bit 15: !valid bit */ + "", /* word 83 bit 14: valid bit */ + "FLUSH CACHE EXT cmd", /* word 83 bit 13 */ + "Mandatory FLUSH CACHE cmd ", /* word 83 bit 12 */ + "Device Configuration Overlay feature set ", + "48-bit Address feature set ", /* word 83 bit 10 */ + "", + "SET MAX security extension", /* word 83 bit 8 */ + "Address Offset Reserved Area Boot", /* word 83 bit 7 */ + "SET FEATURES subcommand required to spinup after power up", + "Power-Up In Standby feature set", /* word 83 bit 5 */ + "Removable Media Status Notification feature set", + "Adv. Power Management feature set",/* word 83 bit 3 */ + "CFA feature set", /* word 83 bit 2 */ + "READ/WRITE DMA QUEUED", /* word 83 bit 1 */ + "DOWNLOAD MICROCODE cmd", /* word 83 bit 0 */ + /* --------------*/ + "", /* word 84 bit 15: !valid bit */ + "", /* word 84 bit 14: valid bit */ + "", /* word 84 bit 13: reserved */ + "", /* word 84 bit 12: reserved */ + "", /* word 84 bit 11: reserved */ + "", /* word 84 bit 10: reserved */ + "", /* word 84 bit 9: reserved */ + "", /* word 84 bit 8: reserved */ + "", /* word 84 bit 7: reserved */ + "", /* word 84 bit 6: reserved */ + "General Purpose Logging feature set", /* word 84 bit 5 */ + "", /* word 84 bit 4: reserved */ + "Media Card Pass Through Command feature set ", + "Media serial number ", /* word 84 bit 2 */ + "SMART self-test ", /* word 84 bit 1 */ + "SMART error logging " /* word 84 bit 0 */ +}; + +static void identify(uint16_t *id_supplied) ATTRIBUTE_NORETURN; +static void identify_from_stdin(void) ATTRIBUTE_NORETURN; +#else +void identify_from_stdin(void); +#endif + + +/* 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 +#ifdef CONFIG_FEATURE_HDPARM_GET_IDENTITY +static const char * const secu_str[] = { + "supported", /* word 128, bit 0 */ + "enabled", /* word 128, bit 1 */ + "locked", /* word 128, bit 2 */ + "frozen", /* word 128, bit 3 */ + "expired: security count", /* word 128, bit 4 */ + "supported: enhanced erase" /* word 128, bit 5 */ +}; +#endif + +/* 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_MB 64 +#define TIMING_BUF_MB 1 +#define TIMING_BUF_BYTES (TIMING_BUF_MB * 1024 * 1024) +#define TIMING_BUF_COUNT (timing_MB / TIMING_BUF_MB) +#define BUFCACHE_FACTOR 2 + +#undef DO_FLUSHCACHE /* under construction: force cache flush on -W0 */ + +/* Busybox messages and functions */ +static int bb_ioctl(int fd, int request, void *argp, const char *string) +{ + int e = ioctl(fd, request, argp); + if (e && string) + bb_perror_msg(" %s", string); + return e; +} + +static int bb_ioctl_alt(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(fd, cmd, args, string); +} + +static void on_off(unsigned int value); + +static void print_flag_on_off(unsigned long get_arg, const char *s, unsigned long arg) +{ + + if (get_arg) + { + printf(" setting %s to %ld", s, arg); + on_off(arg); + } +} + +static void bb_ioctl_on_off(int fd, int request, void *argp, const char *string, + const char * str) +{ + if (ioctl(fd, request, &argp) != 0) + bb_perror_msg(" %s", string); + else + { + printf(" %s\t= %2ld", str, (unsigned long) argp); + on_off((unsigned long) argp); + } +} + +#ifdef CONFIG_FEATURE_HDPARM_GET_IDENTITY +static void print_ascii(uint16_t *p, uint8_t length); + +static void xprint_ascii(uint16_t *val ,int i, char * string, int n) +{ + if (val[i]) + { + printf("\t%-20s",string); + print_ascii(&val[i], n); + } +} +#endif +/* end of busybox specific stuff */ + +#ifdef CONFIG_FEATURE_HDPARM_GET_IDENTITY +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 void print_ascii(uint16_t *p, uint8_t length) { + uint8_t ii; + char cl; + + /* find first non-space & print it */ + for (ii = 0; ii< length; ii++) + { + if (((char) 0x00ff&((*p)>>8)) != ' ') + break; + if ((cl = (char) 0x00ff&(*p)) != ' ') + { + if (cl != '\0') printf("%c",cl); + p++; + ii++; + break; + } + p++; + } + /* print the rest */ + for (; ii< length; ii++) + { + if (!(*p)) + break; /* some older devices have NULLs */ + printf("%c%c",(char)0x00ff&((*p)>>8),(char)(*p)&0x00ff); + p++; + } + puts(""); +} + +// Parse 512 byte disk identification block and print much crap. + +static void identify(uint16_t *id_supplied) +{ + uint16_t buf[256]; + uint16_t *val, 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; + + // Adjust for endianness if necessary. + + if (BB_BIG_ENDIAN) { + swab(id_supplied, buf, sizeof(buf)); + val = buf; + } else val = id_supplied; + + chksum &= 0xff; + + /* check if we recognise the device type */ + puts(""); + 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 ", pkt_str[eqpt]); + 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 ",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 + puts(""); + } + 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; + } + } + printf("%s\n", (!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",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 + puts(""); + } + + /* 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 + puts(""); + } + 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]); + puts(""); + } + } + + /* 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("(?)"); + puts(""); + + 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]); + puts(""); + } + } + + /* 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; + } + puts(""); + } + else if (((min_std < 5) || (eqpt == CDROM)) && (val[PIO_MODE] & MODE) ) + { + for (ii = 0; ii <= val[PIO_MODE]>>8; ii++) + printf("pio%d ",ii); + puts(""); + } + else + printf("unknown\n"); + + 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]); + puts(""); + } + } + + 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++) + { + if ((jj & 0x8000) && (*cmd_feat_str[ii] != '\0')) + { + printf("\t%s\t%s\n", (kk & 0x8000) ? " *" : "", cmd_feat_str[ii]); + } + 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", 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" : "", 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) + { + printf("\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 "); + puts(""); + } + } + + /* reset result */ + jj = val[HWRST_RSLT]; + if ((jj & VALID) == VALID_VAL) + { + if (!(oo = (jj & RST0))) + 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 + +static int get_identity, get_geom; +static int do_flush; +static int do_ctimings, do_timings; +static unsigned long set_readahead, get_readahead, Xreadahead; +static unsigned long set_readonly, get_readonly, readonly; +static unsigned long set_unmask, get_unmask, unmask; +static unsigned long set_mult, get_mult, mult; +#ifdef CONFIG_FEATURE_HDPARM_HDIO_GETSET_DMA +static unsigned long set_dma, get_dma, dma; +#endif +static unsigned long set_dma_q, get_dma_q, dma_q; +static unsigned long set_nowerr, get_nowerr, nowerr; +static unsigned long set_keep, get_keep, keep; +static unsigned long set_io32bit, get_io32bit, io32bit; +static unsigned long set_piomode, noisy_piomode; +static int piomode; +#ifdef HDIO_DRIVE_CMD +static unsigned long set_dkeep, get_dkeep, dkeep; +static unsigned long set_standby, get_standby, standby_requested; +static unsigned long set_xfermode, get_xfermode; +static int xfermode_requested; +static unsigned long set_lookahead, get_lookahead, lookahead; +static unsigned long set_prefetch, get_prefetch, prefetch; +static unsigned long set_defects, get_defects, defects; +static unsigned long set_wcache, get_wcache, wcache; +static unsigned long set_doorlock, get_doorlock, doorlock; +static unsigned long set_seagate, get_seagate; +static unsigned long set_standbynow, get_standbynow; +static unsigned long set_sleepnow, get_sleepnow; +static unsigned long get_powermode; +static unsigned long set_apmmode, get_apmmode, apmmode; +#endif +#ifdef CONFIG_FEATURE_HDPARM_GET_IDENTITY +static int get_IDentity; +#endif +#ifdef CONFIG_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF +static unsigned long unregister_hwif; +static unsigned long hwif; +#endif +#ifdef CONFIG_FEATURE_HDPARM_HDIO_SCAN_HWIF +static unsigned long scan_hwif; +static unsigned long hwif_data; +static unsigned long hwif_ctrl; +static unsigned long hwif_irq; +#endif +#ifdef CONFIG_FEATURE_HDPARM_HDIO_TRISTATE_HWIF +static unsigned long set_busstate, get_busstate, busstate; +#endif +static int reread_partn; + +#ifdef CONFIG_FEATURE_HDPARM_HDIO_DRIVE_RESET +static int perform_reset; +#endif /* CONFIG_FEATURE_HDPARM_HDIO_DRIVE_RESET */ +#ifdef CONFIG_FEATURE_HDPARM_HDIO_TRISTATE_HWIF +static unsigned long perform_tristate, tristate; +#endif /* CONFIG_FEATURE_HDPARM_HDIO_TRISTATE_HWIF */ + +// 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. + +#ifdef CONFIG_FEATURE_HDPARM_GET_IDENTITY +static const char * const cfg_str[] = +{ "", "HardSect", "SoftSect", "NotMFM", + "HdSw>15uSec", "SpinMotCtl", "Fixed", "Removeable", + "DTR<=5Mbs", "DTR>5Mbs", "DTR>10Mbs", "RotSpdTol>.5%", + "dStbOff", "TrkOff", "FmtGapReq", "nonMagnetic" +}; + +static const char * const BuffType[] = {"Unknown", "1Sect", "DualPort", "DualPortCache"}; + +static void dump_identity(const struct hd_driveid *id) +{ + int i; + const unsigned short int *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, 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"); + } + puts(""); + + 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) ? + (long unsigned int)(id->cur_capacity0 << 16) | id->cur_capacity1 : + (long unsigned int)(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) + { + if (id->eide_pio_modes & 1) printf("pio3 "); + if (id->eide_pio_modes & 2) printf("pio4 "); + if (id->eide_pio_modes &~3) printf("pio? "); + } + if (id->capability&1) + { + if (id->dma_1word | id->dma_mword) + { + printf("\n DMA modes: "); + if (id->dma_1word & 0x100) printf("*"); + if (id->dma_1word & 1) printf("sdma0 "); + if (id->dma_1word & 0x200) printf("*"); + if (id->dma_1word & 2) printf("sdma1 "); + if (id->dma_1word & 0x400) printf("*"); + if (id->dma_1word & 4) printf("sdma2 "); + if (id->dma_1word & 0xf800) printf("*"); + if (id->dma_1word & 0xf8) printf("sdma? "); + if (id->dma_mword & 0x100) printf("*"); + if (id->dma_mword & 1) printf("mdma0 "); + if (id->dma_mword & 0x200) printf("*"); + if (id->dma_mword & 2) printf("mdma1 "); + if (id->dma_mword & 0x400) printf("*"); + if (id->dma_mword & 4) printf("mdma2 "); + if (id->dma_mword & 0xf800) printf("*"); + if (id->dma_mword & 0xf8) printf("mdma? "); + } + } + if (((id->capability&8) || (id->field_valid&2)) && id->field_valid&4) + { + printf("\n UDMA modes: "); + if (id->dma_ultra & 0x100) printf("*"); + if (id->dma_ultra & 0x001) printf("udma0 "); + if (id->dma_ultra & 0x200) printf("*"); + if (id->dma_ultra & 0x002) printf("udma1 "); + if (id->dma_ultra & 0x400) printf("*"); + if (id->dma_ultra & 0x004) printf("udma2 "); +#ifdef __NEW_HD_DRIVE_ID + if (id->hw_config & 0x2000) + { +#else /* !__NEW_HD_DRIVE_ID */ + if (id->word93 & 0x2000) + { +#endif /* __NEW_HD_DRIVE_ID */ + if (id->dma_ultra & 0x0800) printf("*"); + if (id->dma_ultra & 0x0008) printf("udma3 "); + if (id->dma_ultra & 0x1000) printf("*"); + if (id->dma_ultra & 0x0010) printf("udma4 "); + if (id->dma_ultra & 0x2000) printf("*"); + if (id->dma_ultra & 0x0020) printf("udma5 "); + if (id->dma_ultra & 0x4000) printf("*"); + if (id->dma_ultra & 0x0040) printf("udma6 "); + if (id->dma_ultra & 0x8000) printf("*"); + if (id->dma_ultra & 0x0080) printf("udma7 "); + } + } + printf("\n AdvancedPM=%s",((id_regs[83]&8)==0)?"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) ? 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<= e) /* more than 1MB/s */ + printf("%2d MB in %5.2f seconds =%6.2f %cB/sec\n", t, e, t / e, 'M'); + else + printf("%2d MB in %5.2f seconds =%6.2f %cB/sec\n", t, e, t / e * 1024, 'k'); +} + +static int do_blkgetsize (int fd, unsigned long long *blksize64) +{ + int rc; + unsigned int blksize32 = 0; + + if (0 == ioctl(fd, BLKGETSIZE64, blksize64)) { // returns bytes + *blksize64 /= 512; + return 0; + } + rc = ioctl(fd, BLKGETSIZE, &blksize32); // returns sectors + if (rc) + bb_perror_msg("BLKGETSIZE"); + *blksize64 = blksize32; + return rc; +} + +static void do_time(int flag, int fd) +/* + flag = 0 time_cache + flag = 1 time_device +*/ +{ + struct itimerval e1, e2; + double elapsed, elapsed2; + unsigned int max_iterations = 1024, total_MB, iterations; + unsigned long long blksize; + RESERVE_CONFIG_BUFFER(buf, TIMING_BUF_BYTES); + + if (mlock(buf, TIMING_BUF_BYTES)) { + bb_perror_msg("mlock"); + goto quit2; + } + + if (0 == do_blkgetsize(fd, &blksize)) { + max_iterations = blksize / (2 * 1024) / TIMING_BUF_MB; + } + + /* Clear out the device request queues & give them time to complete */ + sync(); + sleep(3); + + setitimer(ITIMER_REAL, &(struct itimerval){{1000,0},{1000,0}}, NULL); + + if (flag == 0) /* Time cache */ + { + if (seek_to_zero (fd)) return; + if (read_big_block (fd, buf)) return; + printf(" Timing cached reads: "); + fflush(stdout); + + /* Now do the timing */ + iterations = 0; + getitimer(ITIMER_REAL, &e1); + do { + ++iterations; + if (seek_to_zero (fd) || read_big_block (fd, buf)) + goto quit; + getitimer(ITIMER_REAL, &e2); + elapsed = (e1.it_value.tv_sec - e2.it_value.tv_sec) + + ((e1.it_value.tv_usec - e2.it_value.tv_usec) / 1000000.0); + } while (elapsed < 2.0); + total_MB = iterations * TIMING_BUF_MB; + + /* Now remove the lseek() and getitimer() overheads from the elapsed time */ + getitimer(ITIMER_REAL, &e1); + do { + if (seek_to_zero (fd)) + goto quit; + getitimer(ITIMER_REAL, &e2); + elapsed2 = (e1.it_value.tv_sec - e2.it_value.tv_sec) + + ((e1.it_value.tv_usec - e2.it_value.tv_usec) / 1000000.0); + } while (--iterations); + + elapsed -= elapsed2; + print_timing(BUFCACHE_FACTOR * total_MB, elapsed); + flush_buffer_cache(fd); + sleep(1); + } + else /* Time device */ + { + printf(" Timing buffered disk reads: "); + fflush(stdout); + /* + * getitimer() is used rather than gettimeofday() because + * it is much more consistent (on my machine, at least). + */ + /* Now do the timings for real */ + iterations = 0; + getitimer(ITIMER_REAL, &e1); + do { + ++iterations; + if (read_big_block (fd, buf)) + goto quit; + getitimer(ITIMER_REAL, &e2); + elapsed = (e1.it_value.tv_sec - e2.it_value.tv_sec) + + ((e1.it_value.tv_usec - e2.it_value.tv_usec) / 1000000.0); + } while (elapsed < 3.0 && iterations < max_iterations); + + total_MB = iterations * TIMING_BUF_MB; + print_timing(total_MB, elapsed); + } +quit: + munlock(buf, TIMING_BUF_BYTES); +quit2: + RELEASE_CONFIG_BUFFER(buf); +} + +static void on_off (unsigned int value) +{ + printf(value ? " (on)\n" : " (off)\n"); +} + +#ifdef CONFIG_FEATURE_HDPARM_HDIO_TRISTATE_HWIF +static void bus_state_value(unsigned int 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(unsigned int standby) +{ + unsigned int t; + + printf(" ("); + if (standby == 0) + printf("off"); + else if (standby == 252) + printf("21 minutes"); + else if (standby == 253) + printf("vendor-specific"); + else if (standby == 254) + printf("Reserved"); + else if (standby == 255) + printf("21 minutes + 15 seconds"); + else { + if (standby <= 240) { + t = standby * 5; + printf("%u minutes + %u seconds", t / 60, t % 60); + } else if (standby <= 251) { + t = (standby - 240) * 30; + printf("%u hours + %u minutes", t / 60, t % 60); + } else + printf("illegal value"); + } + printf(")\n"); +} + +struct xfermode_entry { + int val; + const char *name; +}; + +static const struct xfermode_entry xfermode_table[] = { + { 8, "pio0" }, + { 9, "pio1" }, + { 10, "pio2" }, + { 11, "pio3" }, + { 12, "pio4" }, + { 13, "pio5" }, + { 14, "pio6" }, + { 15, "pio7" }, + { 16, "sdma0" }, + { 17, "sdma1" }, + { 18, "sdma2" }, + { 19, "sdma3" }, + { 20, "sdma4" }, + { 21, "sdma5" }, + { 22, "sdma6" }, + { 23, "sdma7" }, + { 32, "mdma0" }, + { 33, "mdma1" }, + { 34, "mdma2" }, + { 35, "mdma3" }, + { 36, "mdma4" }, + { 37, "mdma5" }, + { 38, "mdma6" }, + { 39, "mdma7" }, + { 64, "udma0" }, + { 65, "udma1" }, + { 66, "udma2" }, + { 67, "udma3" }, + { 68, "udma4" }, + { 69, "udma5" }, + { 70, "udma6" }, + { 71, "udma7" }, + { 0, NULL } +}; + +static int translate_xfermode(char * name) +{ + const struct xfermode_entry *tmp; + char *endptr; + int val = -1; + + + for (tmp = xfermode_table; tmp->name != NULL; ++tmp) + { + if (!strcmp(name, tmp->name)) + return tmp->val; + } + + val = strtol(name, &endptr, 10); + if (*endptr == '\0') + return val; + + return -1; +} + +static void interpret_xfermode(unsigned int 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(unsigned long flag, char *s, unsigned long value) +{ + if (flag) + printf(" setting %s to %ld\n", s, value); +} + +static void process_dev(char *devname) +{ + int fd; + static 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); + printf("\n%s:\n", devname); + + if (set_readahead) + { + print_flag(get_readahead,"fs readahead", Xreadahead); + bb_ioctl(fd, BLKRASET,(int *)Xreadahead,"BLKRASET"); + } +#ifdef CONFIG_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF + if (unregister_hwif) + { + printf(" attempting to unregister hwif#%lu\n", hwif); + bb_ioctl(fd, HDIO_UNREGISTER_HWIF,(int *)(unsigned long)hwif,"HDIO_UNREGISTER_HWIF"); + } +#endif +#ifdef CONFIG_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; + bb_ioctl(fd, HDIO_SCAN_HWIF, args, "HDIO_SCAN_HWIF"); + 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)); + } + bb_ioctl(fd, HDIO_SET_PIO_MODE, (int *)(unsigned long)piomode, "HDIO_SET_PIO_MODE"); + } + if (set_io32bit) + { + print_flag(get_io32bit,"32-bit IO_support flag", io32bit); + bb_ioctl(fd, HDIO_SET_32BIT, (int *)io32bit, "HDIO_SET_32BIT"); + } + if (set_mult) + { + print_flag(get_mult, "multcount", mult); +#ifdef HDIO_DRIVE_CMD + bb_ioctl(fd, HDIO_SET_MULTCOUNT, &mult, "HDIO_SET_MULTCOUNT"); +#else + force_operation |= (!bb_ioctl(fd, HDIO_SET_MULTCOUNT, &mult, "HDIO_SET_MULTCOUNT")); +#endif + } + if (set_readonly) + { + print_flag_on_off(get_readonly,"readonly", readonly); + bb_ioctl(fd, BLKROSET, &readonly, "BLKROSET"); + } + if (set_unmask) + { + print_flag_on_off(get_unmask,"unmaskirq", unmask); + bb_ioctl(fd, HDIO_SET_UNMASKINTR, (int *)unmask, "HDIO_SET_UNMASKINTR"); + } +#ifdef CONFIG_FEATURE_HDPARM_HDIO_GETSET_DMA + if (set_dma) + { + print_flag_on_off(get_dma, "using_dma", dma); + bb_ioctl(fd, HDIO_SET_DMA, (int *)dma, "HDIO_SET_DMA"); + } +#endif /* CONFIG_FEATURE_HDPARM_HDIO_GETSET_DMA */ + if (set_dma_q) + { + print_flag_on_off(get_dma_q,"DMA queue_depth", dma_q); + bb_ioctl(fd, HDIO_SET_QDMA, (int *)dma_q, "HDIO_SET_QDMA"); + } + if (set_nowerr) + { + print_flag_on_off(get_nowerr,"nowerr", nowerr); + bb_ioctl(fd, HDIO_SET_NOWERR, (int *)nowerr,"HDIO_SET_NOWERR"); + } + if (set_keep) + { + print_flag_on_off(get_keep,"keep_settings", keep); + bb_ioctl(fd, HDIO_SET_KEEPSETTINGS, (int *)keep,"HDIO_SET_KEEPSETTINGS"); + } +#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); + bb_ioctl(fd, HDIO_DRIVE_CMD, &args,"HDIO_DRIVE_CMD(doorlock)"); + 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; + bb_ioctl(fd, HDIO_DRIVE_CMD, &args,"HDIO_DRIVE_CMD(keepsettings)"); + } + if (set_defects) + { + args[2] = defects ? 0x04 : 0x84; + print_flag(get_defects,"drive defect-mgmt", defects); + bb_ioctl(fd, HDIO_DRIVE_CMD, &args,"HDIO_DRIVE_CMD(defectmgmt)"); + } + if (set_prefetch) + { + args[1] = prefetch; + args[2] = 0xab; + print_flag(get_prefetch,"drive prefetch", prefetch); + bb_ioctl(fd, HDIO_DRIVE_CMD, &args, "HDIO_DRIVE_CMD(setprefetch)"); + 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); + } + bb_ioctl(fd, HDIO_DRIVE_CMD, &args,"HDIO_DRIVE_CMD(setxfermode)"); + args[1] = 0; + } + if (set_lookahead) + { + args[2] = lookahead ? 0xaa : 0x55; + print_flag_on_off(get_lookahead,"drive read-lookahead", lookahead); + bb_ioctl(fd, HDIO_DRIVE_CMD, &args, "HDIO_DRIVE_CMD(setreadahead)"); + } + 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); + bb_ioctl(fd, HDIO_DRIVE_CMD, &args,"HDIO_DRIVE_CMD"); + args[1] = 0; + } + if (set_wcache) + { +#ifdef DO_FLUSHCACHE +#ifndef WIN_FLUSHCACHE +#define WIN_FLUSHCACHE 0xe7 +#endif + static unsigned char flushcache[4] = {WIN_FLUSHCACHE,0,0,0}; +#endif /* DO_FLUSHCACHE */ + args[2] = wcache ? 0x02 : 0x82; + print_flag_on_off(get_wcache,"drive write-caching", wcache); +#ifdef DO_FLUSHCACHE + if (!wcache) + bb_ioctl(fd, HDIO_DRIVE_CMD, &flushcache, "HDIO_DRIVE_CMD(flushcache)"); +#endif /* DO_FLUSHCACHE */ + bb_ioctl(fd, HDIO_DRIVE_CMD, &args, "HDIO_DRIVE_CMD(setcache)"); +#ifdef DO_FLUSHCACHE + if (!wcache) + bb_ioctl(fd, HDIO_DRIVE_CMD, &flushcache, "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; + bb_ioctl_alt(fd, HDIO_DRIVE_CMD, args, WIN_STANDBYNOW2, "HDIO_DRIVE_CMD(standby)"); + } + 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; + bb_ioctl_alt(fd, HDIO_DRIVE_CMD, args, WIN_SLEEPNOW2, "HDIO_DRIVE_CMD(sleep)"); + } + if (set_seagate) + { + args[0] = 0xfb; + if (get_seagate) printf(" disabling Seagate auto powersaving mode\n"); + bb_ioctl(fd, HDIO_DRIVE_CMD, &args, "HDIO_DRIVE_CMD(seagatepwrsave)"); + } + if (set_standby) + { + args[0] = WIN_SETIDLE1; + args[1] = standby_requested; + if (get_standby) + { + print_flag(1,"standby", standby_requested); + interpret_standby(standby_requested); + } + bb_ioctl(fd, HDIO_DRIVE_CMD, &args, "HDIO_DRIVE_CMD(setidle1)"); + args[1] = 0; + } +#else /* HDIO_DRIVE_CMD */ + if (force_operation) + { + char buf[512]; + flush_buffer_cache(fd); + if (-1 == read(fd, buf, sizeof(buf))) + bb_perror_msg("read(%d bytes) failed (rc=%d)", sizeof(buf), -1); + } +#endif /* HDIO_DRIVE_CMD */ + + if (get_mult || get_identity) + { + multcount = -1; + if (ioctl(fd, HDIO_GET_MULTCOUNT, &multcount)) + { + if (get_mult) + bb_perror_msg("HDIO_GET_MULTCOUNT"); + } + else if (get_mult) + { + printf(fmt, "multcount", multcount); + on_off(multcount); + } + } + if (get_io32bit) + { + if (!bb_ioctl(fd, HDIO_GET_32BIT, &parm, "HDIO_GET_32BIT")) + { + 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) + { + bb_ioctl_on_off(fd, HDIO_GET_UNMASKINTR,(unsigned long *)parm, + "HDIO_GET_UNMASKINTR","unmaskirq"); + } + + +#ifdef CONFIG_FEATURE_HDPARM_HDIO_GETSET_DMA + if (get_dma) { + if (!bb_ioctl(fd, HDIO_GET_DMA, &parm, "HDIO_GET_DMA")) + { + printf(fmt, "using_dma", parm); + if (parm == 8) + printf(" (DMA-Assisted-PIO)\n"); + else + on_off(parm); + } + } +#endif + if (get_dma_q) + { + bb_ioctl_on_off (fd, HDIO_GET_QDMA,(unsigned long *)parm, + "HDIO_GET_QDMA","queue_depth"); + } + if (get_keep) + { + bb_ioctl_on_off (fd, HDIO_GET_KEEPSETTINGS,(unsigned long *)parm, + "HDIO_GET_KEEPSETTINGS","keepsettings"); + } + + if (get_nowerr) + { + bb_ioctl_on_off (fd, HDIO_GET_NOWERR,(unsigned long *)&parm, + "HDIO_GET_NOWERR","nowerr"); + } + if (get_readonly) + { + bb_ioctl_on_off(fd, BLKROGET,(unsigned long *)parm, + "BLKROGET","readonly"); + } + if (get_readahead) + { + bb_ioctl_on_off (fd, BLKRAGET, (unsigned long *) parm, + "BLKRAGET","readahead"); + } + if (get_geom) + { + if (!bb_ioctl(fd, BLKGETSIZE, &parm, "BLKGETSIZE")) + { + struct hd_geometry g; + + if (!bb_ioctl(fd, HDIO_GETGEO, &g, "HDIO_GETGEO")) + 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 (bb_ioctl_alt(fd, HDIO_DRIVE_CMD, args, WIN_CHECKPOWERMODE2, 0)) + { + 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 +#ifdef CONFIG_FEATURE_HDPARM_HDIO_DRIVE_RESET + if (perform_reset) + { + bb_ioctl(fd, HDIO_DRIVE_RESET, NULL, "HDIO_DRIVE_RESET"); + } +#endif /* CONFIG_FEATURE_HDPARM_HDIO_DRIVE_RESET */ +#ifdef CONFIG_FEATURE_HDPARM_HDIO_TRISTATE_HWIF + if (perform_tristate) + { + args[0] = 0; + args[1] = tristate; + bb_ioctl(fd, HDIO_TRISTATE_HWIF, &args, "HDIO_TRISTATE_HWIF"); + } +#endif /* CONFIG_FEATURE_HDPARM_HDIO_TRISTATE_HWIF */ +#ifdef CONFIG_FEATURE_HDPARM_GET_IDENTITY + if (get_identity) + { + static 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 + bb_perror_msg("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 (!bb_ioctl_alt(fd, HDIO_DRIVE_CMD, args1, WIN_PIDENTIFY, "HDIO_DRIVE_CMD(identify)")) + identify((void *)(args1 + 4)); + } +#endif +#ifdef CONFIG_FEATURE_HDPARM_HDIO_TRISTATE_HWIF + if (set_busstate) + { + if (get_busstate) + { + print_flag(1, "bus state", busstate); + bus_state_value(busstate); + } + bb_ioctl(fd, HDIO_SET_BUSSTATE, (int *)(unsigned long)busstate, "HDIO_SET_BUSSTATE"); + } + if (get_busstate) + { + if (!bb_ioctl(fd, HDIO_GET_BUSSTATE, &parm, "HDIO_GET_BUSSTATE")) + { + printf(fmt, "bus state", parm); + bus_state_value(parm); + } + } +#endif + if (reread_partn) + bb_ioctl(fd, BLKRRPART, NULL, "BLKRRPART"); + + + if (do_ctimings) + do_time(0,fd); /*time cache */ + if (do_timings) + do_time(1,fd); /*time device */ + if (do_flush) + flush_buffer_cache(fd); + close(fd); +} + +#ifdef CONFIG_FEATURE_HDPARM_GET_IDENTITY +static int fromhex(unsigned char c) +{ + if (c >= 'a' && c <= 'f') + return 10 + (c - 'a'); + if (c >= '0' && c <= '9') + return (c - '0'); + bb_error_msg_and_die("bad char: '%c' 0x%02x", c, c); +} + +static void identify_from_stdin(void) +{ + uint16_t sbuf[256]; + unsigned char buf[1280], *b = (unsigned char *)buf; + int i, count = read(0, buf, 1280); + + if (count != 1280) + bb_error_msg_and_die("read(%d bytes) failed (rc=%d)", 1280, count); + + // 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); +} +#endif + +/* busybox specific stuff */ +static void parse_opts(unsigned long *get, unsigned long *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, unsigned long *get, unsigned long *set, int *value) +{ + if (flag) { + *get = 1; + if (optarg) { + *set = ((*value = translate_xfermode(optarg)) > -1); + } + } +} + +/*------- getopt short options --------*/ +static const char hdparm_options[] = "gfu::n::p:r::m::c::k::a::B:tTh" + 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) ATTRIBUTE_NORETURN; +int hdparm_main(int argc, char **argv) +{ + int c; + int flagcount = 0; + + while ((c = getopt(argc, argv, hdparm_options)) >= 0) { + flagcount++; + if (c == 'h') bb_show_usage(); /* EXIT */ + 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, INT_MAX); + 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); + } + argc -= optind; + argv += optind; + + if (argc < 1) { + if (ENABLE_FEATURE_HDPARM_GET_IDENTITY && !isatty(STDIN_FILENO)) + identify_from_stdin(); /* EXIT */ + else bb_show_usage(); + } + + while (argc--) { + process_dev(*argv); + argv++; + } + exit(EXIT_SUCCESS); +} diff --git a/miscutils/last.c b/miscutils/last.c new file mode 100644 index 000000000..668f0c17d --- /dev/null +++ b/miscutils/last.c @@ -0,0 +1,91 @@ +/* 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 "busybox.h" +#include + +#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 + +int last_main(int argc, char **argv) +{ + struct utmp ut; + int n, file = STDIN_FILENO; + time_t t_tmp; + + 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"); + while ((n = safe_read(file, (void*)&ut, sizeof(struct utmp))) != 0) { + + if (n != sizeof(struct utmp)) { + bb_perror_msg_and_die("short read"); + } + + if (strncmp(ut.ut_line, "~", 1) == 0) { + 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", 7) == 0) + ut.ut_type = RUN_LVL; + } else { + if (!ut.ut_name[0] || strcmp(ut.ut_name, "LOGIN") == 0 || + ut.ut_name[0] == 0) + { + /* Don't bother. This means we can't find how long + * someone was logged in for. Oh well. */ + continue; + } + if (ut.ut_type != DEAD_PROCESS && + ut.ut_name[0] && ut.ut_line[0]) + { + ut.ut_type = USER_PROCESS; + } + if (strcmp(ut.ut_name, "date") == 0) { + if (ut.ut_line[0] == '|') ut.ut_type = OLD_TIME; + if (ut.ut_line[0] == '{') 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: + continue; + case BOOT_TIME: + strcpy(ut.ut_line, "system boot"); + break; + } + } + 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); + } + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/miscutils/less.c b/miscutils/less.c new file mode 100644 index 000000000..03ffd78ed --- /dev/null +++ b/miscutils/less.c @@ -0,0 +1,1144 @@ +/* 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. + */ + +/* + * This program needs a lot of development, so consider it in a beta stage + * at best. + * + * 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. + * - Check for binary files, and prompt the user if a binary file + * is detected. + * - Allow horizontal scrolling. Currently, lines simply continue onto + * the next line, per the terminal's discretion + * + * Notes: + * - filename is an array and not a pointer because that avoids all sorts + * of complications involving the fact that something that is pointed to + * will be changed if the pointer is changed. + * - the inp file pointer is used so that keyboard input works after + * redirected input has been read from stdin +*/ + +#include "busybox.h" + +#ifdef CONFIG_FEATURE_LESS_REGEXP +#include "xregex.h" +#endif + + +/* These are the escape sequences corresponding to special keys */ +#define REAL_KEY_UP 'A' +#define REAL_KEY_DOWN 'B' +#define REAL_KEY_RIGHT 'C' +#define REAL_KEY_LEFT 'D' +#define REAL_PAGE_UP '5' +#define REAL_PAGE_DOWN '6' +#define REAL_KEY_HOME '7' +#define REAL_KEY_END '8' + +/* These are the special codes assigned by this program to the special keys */ +#define KEY_UP 20 +#define KEY_DOWN 21 +#define KEY_RIGHT 22 +#define KEY_LEFT 23 +#define PAGE_UP 24 +#define PAGE_DOWN 25 +#define KEY_HOME 26 +#define KEY_END 27 + +/* 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" + +/* Maximum number of lines in a file */ +#define MAXLINES 10000 + +static int height; +static int width; +static char **files; +static char filename[256]; +static char **buffer; +static char **flines; +static int current_file = 1; +static int line_pos; +static int num_flines; +static int num_files = 1; + +/* Command line options */ +static unsigned flags; +#define FLAG_E 1 +#define FLAG_M (1<<1) +#define FLAG_m (1<<2) +#define FLAG_N (1<<3) +#define FLAG_TILDE (1<<4) +/* hijack command line options variable for internal state vars */ +#define LESS_STATE_INP_STDIN (1<<5) +#define LESS_STATE_PAST_EOF (1<<6) +#define LESS_STATE_MATCH_BACKWARDS (1<<7) +/* INP_STDIN is used to change behaviour when input comes from stdin */ + +#ifdef CONFIG_FEATURE_LESS_MARKS +static int mark_lines[15][2]; +static int num_marks; +#endif + +#ifdef CONFIG_FEATURE_LESS_REGEXP +static int match_found; +static int *match_lines; +static int match_pos; +static int num_matches; +static regex_t old_pattern; +#endif + +/* Needed termios structures */ +static struct termios term_orig, term_vi; + +/* File pointer to get input from */ +static FILE *inp; + +/* Reset terminal input to normal */ +static void set_tty_cooked(void) +{ + fflush(stdout); + tcsetattr(fileno(inp), TCSANOW, &term_orig); +} + +/* Exit the program gracefully */ +static void tless_exit(int code) +{ + /* TODO: We really should save the terminal state when we start, + and restore it when we exit. Less does this with the + "ti" and "te" termcap commands; can this be done with + only termios.h? */ + + putchar('\n'); + fflush_stdout_and_exit(code); +} + +/* 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 tless_getch(void) +{ + int input; + /* Set terminal input to raw mode (taken from vi.c) */ + tcsetattr(fileno(inp), TCSANOW, &term_vi); + + input = getc(inp); + /* Detect escape sequences (i.e. arrow keys) and handle + them accordingly */ + + if (input == '\033' && getc(inp) == '[') { + unsigned int i; + input = getc(inp); + set_tty_cooked(); + + i = input - REAL_KEY_UP; + if (i < 4) + return 20 + i; + else if ((i = input - REAL_PAGE_UP) < 4) + return 24 + i; + } + /* The input is a normal ASCII value */ + else { + set_tty_cooked(); + return input; + } + return 0; +} + +/* Move the cursor to a position (x,y), where (0,0) is the + top-left corner of the console */ +static void move_cursor(int x, int y) +{ + printf("\033[%i;%iH", x, y); +} + +static void clear_line(void) +{ + move_cursor(height, 0); + printf("\033[K"); +} + +/* This adds line numbers to every line, as the -N flag necessitates */ +static void add_linenumbers(void) +{ + int i; + + for (i = 0; i <= num_flines; i++) { + char *new = xasprintf("%5d %s", i + 1, flines[i]); + free(flines[i]); + flines[i] = new; + } +} + +static void data_readlines(void) +{ + int i; + char current_line[256]; + FILE *fp; + + fp = (flags & LESS_STATE_INP_STDIN) ? stdin : xfopen(filename, "r"); + flines = NULL; + for (i = 0; (feof(fp)==0) && (i <= MAXLINES); i++) { + strcpy(current_line, ""); + fgets(current_line, 256, fp); + if (fp != stdin) + die_if_ferror(fp, filename); + flines = xrealloc(flines, (i+1) * sizeof(char *)); + flines[i] = xstrdup(current_line); + } + num_flines = i - 2; + + /* Reset variables for a new file */ + + line_pos = 0; + flags &= ~LESS_STATE_PAST_EOF; + + fclose(fp); + + if (inp == NULL) + inp = (flags & LESS_STATE_INP_STDIN) ? xfopen(CURRENT_TTY, "r") : stdin; + + if (flags & FLAG_N) + add_linenumbers(); +} + +#ifdef CONFIG_FEATURE_LESS_FLAGS + +/* Interestingly, writing calc_percent as a function and not a prototype saves around 32 bytes + * on my build. */ +static int calc_percent(void) +{ + return ((100 * (line_pos + height - 2) / num_flines) + 1); +} + +/* Print a status line if -M was specified */ +static void m_status_print(void) +{ + int percentage; + + if (!(flags & LESS_STATE_PAST_EOF)) { + if (!line_pos) { + if (num_files > 1) + printf("%s%s %s%i%s%i%s%i-%i/%i ", HIGHLIGHT, + filename, "(file ", current_file, " of ", num_files, ") lines ", + line_pos + 1, line_pos + height - 1, num_flines + 1); + else { + printf("%s%s lines %i-%i/%i ", HIGHLIGHT, + filename, line_pos + 1, line_pos + height - 1, + num_flines + 1); + } + } + else { + printf("%s %s lines %i-%i/%i ", HIGHLIGHT, filename, + line_pos + 1, line_pos + height - 1, num_flines + 1); + } + + if (line_pos == num_flines - height + 2) { + printf("(END) %s", NORMAL); + if ((num_files > 1) && (current_file != num_files)) + printf("%s- Next: %s%s", HIGHLIGHT, files[current_file], NORMAL); + } + else { + percentage = calc_percent(); + printf("%i%% %s", percentage, NORMAL); + } + } + else { + printf("%s%s lines %i-%i/%i (END) ", HIGHLIGHT, filename, + line_pos + 1, num_flines + 1, num_flines + 1); + if ((num_files > 1) && (current_file != num_files)) + printf("- Next: %s", files[current_file]); + printf("%s", NORMAL); + } +} + +/* Print a status line if -m was specified */ +static void medium_status_print(void) +{ + int percentage; + percentage = calc_percent(); + + if (!line_pos) + printf("%s%s %i%%%s", HIGHLIGHT, filename, percentage, NORMAL); + else if (line_pos == num_flines - height + 2) + printf("%s(END)%s", HIGHLIGHT, NORMAL); + else + printf("%s%i%%%s", HIGHLIGHT, percentage, NORMAL); +} +#endif + +/* Print the status line */ +static void status_print(void) +{ + /* Change the status if flags have been set */ +#ifdef CONFIG_FEATURE_LESS_FLAGS + if (flags & FLAG_M) + m_status_print(); + else if (flags & FLAG_m) + medium_status_print(); + /* No flags set */ + else { +#endif + if (!line_pos) { + printf("%s%s %s", HIGHLIGHT, filename, NORMAL); + if (num_files > 1) + printf("%s%s%i%s%i%s%s", HIGHLIGHT, "(file ", + current_file, " of ", num_files, ")", NORMAL); + } + else if (line_pos == num_flines - height + 2) { + printf("%s%s %s", HIGHLIGHT, "(END)", NORMAL); + if ((num_files > 1) && (current_file != num_files)) + printf("%s%s%s%s", HIGHLIGHT, "- Next: ", files[current_file], NORMAL); + } + else { + putchar(':'); + } +#ifdef CONFIG_FEATURE_LESS_FLAGS + } +#endif +} + +/* Print the buffer */ +static void buffer_print(void) +{ + int i; + + printf("%s", CLEAR); + if (num_flines >= height - 2) { + for (i = 0; i < height - 1; i++) + printf("%s", buffer[i]); + } + else { + for (i = 1; i < (height - 1 - num_flines); i++) + putchar('\n'); + for (i = 0; i < height - 1; i++) + printf("%s", buffer[i]); + } + + status_print(); +} + +/* Initialise the buffer */ +static void buffer_init(void) +{ + int i; + + if (buffer == NULL) { + /* malloc the number of lines needed for the buffer */ + buffer = xrealloc(buffer, height * sizeof(char *)); + } else { + for (i = 0; i < (height - 1); i++) + free(buffer[i]); + } + + /* Fill the buffer until the end of the file or the + end of the buffer is reached */ + for (i = 0; (i < (height - 1)) && (i <= num_flines); i++) { + buffer[i] = xstrdup(flines[i]); + } + + /* If the buffer still isn't full, fill it with blank lines */ + for (; i < (height - 1); i++) { + buffer[i] = xstrdup(""); + } +} + +/* Move the buffer up and down in the file in order to scroll */ +static void buffer_down(int nlines) +{ + int i; + + if (!(flags & LESS_STATE_PAST_EOF)) { + if (line_pos + (height - 3) + nlines < num_flines) { + line_pos += nlines; + for (i = 0; i < (height - 1); i++) { + free(buffer[i]); + buffer[i] = xstrdup(flines[line_pos + i]); + } + } + else { + /* As the number of lines requested was too large, we just move + to the end of the file */ + while (line_pos + (height - 3) + 1 < num_flines) { + line_pos += 1; + for (i = 0; i < (height - 1); i++) { + free(buffer[i]); + buffer[i] = xstrdup(flines[line_pos + i]); + } + } + } + + /* We exit if the -E flag has been set */ + if ((flags & FLAG_E) && (line_pos + (height - 2) == num_flines)) + tless_exit(0); + } +} + +static void buffer_up(int nlines) +{ + int i; + int tilde_line; + + if (!(flags & LESS_STATE_PAST_EOF)) { + if (line_pos - nlines >= 0) { + line_pos -= nlines; + for (i = 0; i < (height - 1); i++) { + free(buffer[i]); + buffer[i] = xstrdup(flines[line_pos + i]); + } + } + else { + /* As the requested number of lines to move was too large, we + move one line up at a time until we can't. */ + while (line_pos != 0) { + line_pos -= 1; + for (i = 0; i < (height - 1); i++) { + free(buffer[i]); + buffer[i] = xstrdup(flines[line_pos + i]); + } + } + } + } + else { + /* Work out where the tildes start */ + tilde_line = num_flines - line_pos + 3; + + line_pos -= nlines; + /* Going backwards nlines lines has taken us to a point where + nothing is past the EOF, so we revert to normal. */ + if (line_pos < num_flines - height + 3) { + flags &= ~LESS_STATE_PAST_EOF; + buffer_up(nlines); + } + else { + /* We only move part of the buffer, as the rest + is past the EOF */ + for (i = 0; i < (height - 1); i++) { + free(buffer[i]); + if (i < tilde_line - nlines + 1) + buffer[i] = xstrdup(flines[line_pos + i]); + else { + if (line_pos >= num_flines - height + 2) + buffer[i] = xstrdup("~\n"); + } + } + } + } +} + +static void buffer_line(int linenum) +{ + int i; + flags &= ~LESS_STATE_PAST_EOF; + + if (linenum < 0 || linenum > num_flines) { + clear_line(); + printf("%s%s%i%s", HIGHLIGHT, "Cannot seek to line number ", linenum + 1, NORMAL); + } + else if (linenum < (num_flines - height - 2)) { + for (i = 0; i < (height - 1); i++) { + free(buffer[i]); + buffer[i] = xstrdup(flines[linenum + i]); + } + line_pos = linenum; + buffer_print(); + } + else { + for (i = 0; i < (height - 1); i++) { + free(buffer[i]); + if (linenum + i < num_flines + 2) + buffer[i] = xstrdup(flines[linenum + i]); + else + buffer[i] = xstrdup((flags & FLAG_TILDE) ? "\n" : "~\n"); + } + line_pos = linenum; + /* Set past_eof so buffer_down and buffer_up act differently */ + flags |= LESS_STATE_PAST_EOF; + buffer_print(); + } +} + +/* Reinitialise everything for a new file - free the memory and start over */ +static void reinitialise(void) +{ + int i; + + for (i = 0; i <= num_flines; i++) + free(flines[i]); + free(flines); + + data_readlines(); + buffer_init(); + buffer_print(); +} + +static void examine_file(void) +{ + int newline_offset; + + clear_line(); + printf("Examine: "); + fgets(filename, 256, inp); + + /* As fgets adds a newline to the end of an input string, we + need to remove it */ + newline_offset = strlen(filename) - 1; + filename[newline_offset] = '\0'; + + files[num_files] = xstrdup(filename); + current_file = num_files + 1; + num_files++; + + flags &= ~LESS_STATE_INP_STDIN; + reinitialise(); +} + +/* 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; + strcpy(filename, files[current_file - 1]); + reinitialise(); + } + else { + clear_line(); + printf("%s%s%s", HIGHLIGHT, (direction > 0) ? "No next file" : "No previous file", NORMAL); + } +} + +static void remove_current_file(void) +{ + int i; + + if (current_file != 1) { + change_file(-1); + for (i = 3; i <= num_files; i++) + files[i - 2] = files[i - 1]; + num_files--; + buffer_print(); + } + else { + change_file(1); + for (i = 2; i <= num_files; i++) + files[i - 2] = files[i - 1]; + num_files--; + current_file--; + buffer_print(); + } +} + +static void colon_process(void) +{ + int keypress; + + /* Clear the current line and print a prompt */ + clear_line(); + printf(" :"); + + keypress = tless_getch(); + switch (keypress) { + case 'd': + remove_current_file(); + break; + case 'e': + examine_file(); + break; +#ifdef CONFIG_FEATURE_LESS_FLAGS + case 'f': + clear_line(); + m_status_print(); + break; +#endif + case 'n': + change_file(1); + break; + case 'p': + change_file(-1); + break; + case 'q': + tless_exit(0); + break; + case 'x': + change_file(0); + break; + default: + break; + } +} + +#ifdef CONFIG_FEATURE_LESS_REGEXP +/* The below two regular expression handler functions NEED development. */ + +/* Get a regular expression from the user, and then go through the current + file line by line, running a processing regex function on each one. */ + +static char *process_regex_on_line(char *line, regex_t *pattern, int action) +{ + /* This function takes the regex and applies it to the line. + Each part of the line that matches has the HIGHLIGHT + and NORMAL escape sequences placed around it by + insert_highlights if action = 1, or has the escape sequences + removed if action = 0, and then the line is returned. */ + int match_status; + char *line2 = xmalloc((sizeof(char) * (strlen(line) + 1)) + 64); + char *growline = ""; + regmatch_t match_structs; + + line2 = xstrdup(line); + + match_found = 0; + match_status = regexec(pattern, line2, 1, &match_structs, 0); + + while (match_status == 0) { + if (match_found == 0) + match_found = 1; + + if (action) { + growline = xasprintf("%s%.*s%s%.*s%s", growline, + match_structs.rm_so, line2, HIGHLIGHT, + match_structs.rm_eo - match_structs.rm_so, + line2 + match_structs.rm_so, NORMAL); + } + else { + growline = xasprintf("%s%.*s%.*s", growline, + match_structs.rm_so - 4, line2, + match_structs.rm_eo - match_structs.rm_so, + line2 + match_structs.rm_so); + } + + line2 += match_structs.rm_eo; + match_status = regexec(pattern, line2, 1, &match_structs, REG_NOTBOL); + } + + growline = xasprintf("%s%s", growline, line2); + + return (match_found ? growline : line); + + free(growline); + free(line2); +} + +static void goto_match(int match) +{ + /* This goes to a specific match - all line positions of matches are + stored within the match_lines[] array. */ + if ((match < num_matches) && (match >= 0)) { + buffer_line(match_lines[match]); + match_pos = match; + } +} + +static void regex_process(void) +{ + char uncomp_regex[100]; + char *current_line; + int i; + int j = 0; + regex_t pattern; + /* Get the uncompiled regular expression from the user */ + clear_line(); + putchar((flags & LESS_STATE_MATCH_BACKWARDS) ? '?' : '/'); + uncomp_regex[0] = 0; + fgets(uncomp_regex, sizeof(uncomp_regex), inp); + + if (strlen(uncomp_regex) == 1) { + if (num_matches) + goto_match((flags & LESS_STATE_MATCH_BACKWARDS) + ? match_pos - 1 : match_pos + 1); + else + buffer_print(); + return; + } + uncomp_regex[strlen(uncomp_regex) - 1] = '\0'; + + /* Compile the regex and check for errors */ + xregcomp(&pattern, uncomp_regex, 0); + + if (num_matches) { + /* Get rid of all the highlights we added previously */ + for (i = 0; i <= num_flines; i++) { + current_line = process_regex_on_line(flines[i], &old_pattern, 0); + flines[i] = xstrdup(current_line); + } + } + old_pattern = pattern; + + /* Reset variables */ + match_lines = xrealloc(match_lines, sizeof(int)); + match_lines[0] = -1; + match_pos = 0; + num_matches = 0; + match_found = 0; + /* Run the regex on each line of the current file here */ + for (i = 0; i <= num_flines; i++) { + current_line = process_regex_on_line(flines[i], &pattern, 1); + flines[i] = xstrdup(current_line); + if (match_found) { + match_lines = xrealloc(match_lines, (j + 1) * sizeof(int)); + match_lines[j] = i; + j++; + } + } + + num_matches = j; + if ((match_lines[0] != -1) && (num_flines > height - 2)) { + if (flags & LESS_STATE_MATCH_BACKWARDS) { + for (i = 0; i < num_matches; i++) { + if (match_lines[i] > line_pos) { + match_pos = i - 1; + buffer_line(match_lines[match_pos]); + break; + } + } + } + else + buffer_line(match_lines[0]); + } + else + buffer_init(); +} +#endif + +static void number_process(int first_digit) +{ + int i = 1; + int num; + char num_input[80]; + char keypress; + char *endptr; + + 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 (max 80 chars)*/ + while((i < 80) && (num_input[i] = tless_getch()) && isdigit(num_input[i])) { + putchar(num_input[i]); + i++; + } + + /* Take the final letter out of the digits string */ + keypress = num_input[i]; + num_input[i] = '\0'; + num = strtol(num_input, &endptr, 10); + if (endptr==num_input || *endptr!='\0' || num < 1 || num > MAXLINES) { + buffer_print(); + return; + } + + /* We now know the number and the letter entered, so we process them */ + switch (keypress) { + case KEY_DOWN: case 'z': case 'd': case 'e': case ' ': case '\015': + buffer_down(num); + break; + case KEY_UP: case 'b': case 'w': case 'y': case 'u': + buffer_up(num); + break; + case 'g': case '<': case 'G': case '>': + if (num_flines >= height - 2) + buffer_line(num - 1); + break; + case 'p': case '%': + buffer_line(((num / 100) * num_flines) - 1); + break; +#ifdef CONFIG_FEATURE_LESS_REGEXP + case 'n': + goto_match(match_pos + num); + break; + case '/': + flags &= ~LESS_STATE_MATCH_BACKWARDS; + regex_process(); + break; + case '?': + flags |= LESS_STATE_MATCH_BACKWARDS; + regex_process(); + break; +#endif + default: + break; + } +} + +#ifdef CONFIG_FEATURE_LESS_FLAGCS +static void flag_change(void) +{ + int keypress; + + clear_line(); + putchar('-'); + keypress = tless_getch(); + + switch (keypress) { + case 'M': + flags ^= FLAG_M; + break; + case 'm': + flags ^= FLAG_m; + break; + case 'E': + flags ^= FLAG_E; + break; + case '~': + flags ^= FLAG_TILDE; + break; + default: + break; + } +} + +static void show_flag_status(void) +{ + int keypress; + int flag_val; + + clear_line(); + putchar('_'); + keypress = tless_getch(); + + switch (keypress) { + case 'M': + flag_val = flags & FLAG_M; + break; + case 'm': + flag_val = flags & FLAG_m; + break; + case '~': + flag_val = flags & FLAG_TILDE; + break; + case 'N': + flag_val = flags & FLAG_N; + break; + case 'E': + flag_val = flags & FLAG_E; + break; + default: + flag_val = 0; + break; + } + + clear_line(); + printf("%s%s%i%s", HIGHLIGHT, "The status of the flag is: ", flag_val != 0, NORMAL); +} +#endif + +static void full_repaint(void) +{ + int temp_line_pos = line_pos; + data_readlines(); + buffer_init(); + buffer_line(temp_line_pos); +} + + +static void save_input_to_file(void) +{ + char current_line[256]; + int i; + FILE *fp; + + clear_line(); + printf("Log file: "); + fgets(current_line, 256, inp); + current_line[strlen(current_line) - 1] = '\0'; + if (strlen(current_line) > 1) { + fp = xfopen(current_line, "w"); + for (i = 0; i < num_flines; i++) + fprintf(fp, "%s", flines[i]); + fclose(fp); + buffer_print(); + } + else + printf("%s%s%s", HIGHLIGHT, "No log file", NORMAL); +} + +#ifdef CONFIG_FEATURE_LESS_MARKS +static void add_mark(void) +{ + int letter; + + clear_line(); + printf("Mark: "); + letter = tless_getch(); + + 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] = line_pos; + num_marks++; + } + else { + clear_line(); + printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL); + } +} + +static void goto_mark(void) +{ + int letter; + int i; + + clear_line(); + printf("Go to mark: "); + letter = tless_getch(); + 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])) + printf("%s%s%s", HIGHLIGHT, "Mark not set", NORMAL); + } + else + printf("%s%s%s", HIGHLIGHT, "Invalid mark letter", NORMAL); +} +#endif + + +#ifdef CONFIG_FEATURE_LESS_BRACKETS + +static char opp_bracket(char bracket) +{ + switch (bracket) { + case '{': case '[': + return bracket + 2; + case '(': + return ')'; + case '}': case ']': + return bracket - 2; + case ')': + return '('; + default: + return 0; + } +} + +static void match_right_bracket(char bracket) +{ + int bracket_line = -1; + int i; + + clear_line(); + + if (strchr(flines[line_pos], bracket) == NULL) + printf("%s%s%s", HIGHLIGHT, "No bracket in top line", NORMAL); + else { + for (i = line_pos + 1; i < num_flines; i++) { + if (strchr(flines[i], opp_bracket(bracket)) != NULL) { + bracket_line = i; + break; + } + } + + if (bracket_line == -1) + printf("%s%s%s", HIGHLIGHT, "No matching bracket found", NORMAL); + + buffer_line(bracket_line - height + 2); + } +} + +static void match_left_bracket(char bracket) +{ + int bracket_line = -1; + int i; + + clear_line(); + + if (strchr(flines[line_pos + height - 2], bracket) == NULL) { + printf("%s%s%s", HIGHLIGHT, "No bracket in bottom line", NORMAL); + printf("%s", flines[line_pos + height]); + sleep(4); + } + else { + for (i = line_pos + height - 2; i >= 0; i--) { + if (strchr(flines[i], opp_bracket(bracket)) != NULL) { + bracket_line = i; + break; + } + } + + if (bracket_line == -1) + printf("%s%s%s", HIGHLIGHT, "No matching bracket found", NORMAL); + + buffer_line(bracket_line); + } +} + +#endif /* CONFIG_FEATURE_LESS_BRACKETS */ + +static void keypress_process(int keypress) +{ + switch (keypress) { + case KEY_DOWN: case 'e': case 'j': case '\015': + buffer_down(1); + buffer_print(); + break; + case KEY_UP: case 'y': case 'k': + buffer_up(1); + buffer_print(); + break; + case PAGE_DOWN: case ' ': case 'z': + buffer_down(height - 1); + buffer_print(); + break; + case PAGE_UP: case 'w': case 'b': + buffer_up(height - 1); + buffer_print(); + break; + case 'd': + buffer_down((height - 1) / 2); + buffer_print(); + break; + case 'u': + buffer_up((height - 1) / 2); + buffer_print(); + break; + case KEY_HOME: case 'g': case 'p': case '<': case '%': + buffer_line(0); + break; + case KEY_END: case 'G': case '>': + buffer_line(num_flines - height + 2); + break; + case 'q': case 'Q': + tless_exit(0); + break; +#ifdef CONFIG_FEATURE_LESS_MARKS + case 'm': + add_mark(); + buffer_print(); + break; + case '\'': + goto_mark(); + buffer_print(); + break; +#endif + case 'r': + buffer_print(); + break; + case 'R': + full_repaint(); + break; + case 's': + if (flags & LESS_STATE_INP_STDIN) + save_input_to_file(); + break; + case 'E': + examine_file(); + break; +#ifdef CONFIG_FEATURE_LESS_FLAGS + case '=': + clear_line(); + m_status_print(); + break; +#endif +#ifdef CONFIG_FEATURE_LESS_REGEXP + case '/': + flags &= ~LESS_STATE_MATCH_BACKWARDS; + regex_process(); + break; + case 'n': + goto_match(match_pos + 1); + break; + case 'N': + goto_match(match_pos - 1); + break; + case '?': + flags |= LESS_STATE_MATCH_BACKWARDS; + regex_process(); + break; +#endif +#ifdef CONFIG_FEATURE_LESS_FLAGCS + case '-': + flag_change(); + buffer_print(); + break; + case '_': + show_flag_status(); + break; +#endif +#ifdef CONFIG_FEATURE_LESS_BRACKETS + case '{': case '(': case '[': + match_right_bracket(keypress); + break; + case '}': case ')': case ']': + match_left_bracket(keypress); + break; +#endif + case ':': + colon_process(); + break; + default: + break; + } + + if (isdigit(keypress)) + number_process(keypress); +} + +int less_main(int argc, char **argv) { + + int keypress; + + flags = getopt32(argc, argv, "EMmN~"); + + argc -= optind; + argv += optind; + files = argv; + num_files = argc; + + if (!num_files) { + if (ttyname(STDIN_FILENO) == NULL) + flags |= LESS_STATE_INP_STDIN; + else { + bb_error_msg("missing filename"); + bb_show_usage(); + } + } + + strcpy(filename, (flags & LESS_STATE_INP_STDIN) ? bb_msg_standard_input : files[0]); + get_terminal_width_height(0, &width, &height); + data_readlines(); + tcgetattr(fileno(inp), &term_orig); + term_vi = term_orig; + term_vi.c_lflag &= (~ICANON & ~ECHO); + term_vi.c_iflag &= (~IXON & ~ICRNL); + term_vi.c_oflag &= (~ONLCR); + term_vi.c_cc[VMIN] = 1; + term_vi.c_cc[VTIME] = 0; + buffer_init(); + buffer_print(); + + while (1) { + keypress = tless_getch(); + keypress_process(keypress); + } +} diff --git a/miscutils/makedevs.c b/miscutils/makedevs.c new file mode 100644 index 000000000..3bc1559c7 --- /dev/null +++ b/miscutils/makedevs.c @@ -0,0 +1,230 @@ +/* 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 "busybox.h" + +#ifdef CONFIG_FEATURE_MAKEDEVS_LEAF +int makedevs_main(int argc, char **argv) +{ + mode_t mode; + char *basedev, *type, *nodname, buf[255]; + int Smajor, Sminor, S, E; + + if (argc < 7 || *argv[1]=='-') + bb_show_usage(); + + basedev = argv[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 = argc == 8 ? 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) { + int sz; + + sz = snprintf(buf, sizeof(buf), "%s%d", basedev, S); + if(sz<0 || sz>=sizeof(buf)) /* libc different */ + bb_error_msg_and_die("%s too large", basedev); + + /* if mode != S_IFCHR and != S_IFBLK third param in mknod() ignored */ + + if (mknod(nodname, mode, makedev(Smajor, Sminor))) + bb_error_msg("failed to create: %s", nodname); + + if (nodname == basedev) /* ex. /dev/hda - to /dev/hda1 ... */ + nodname = buf; + S++; + Sminor++; + } + + return 0; +} + +#elif defined CONFIG_FEATURE_MAKEDEVS_TABLE + +/* Licensed under the GPL v2 or later, see the file LICENSE in this tarball. */ + +int makedevs_main(int argc, char **argv) +{ + FILE *table = stdin; + char *rootdir = NULL; + char *line = NULL; + int linenum = 0; + int ret = EXIT_SUCCESS; + + getopt32(argc, argv, "d:", &line); + if (line) + table = xfopen(line, "r"); + + if (optind >= argc || (rootdir=argv[optind])==NULL) { + bb_error_msg_and_die("root directory not specified"); + } + + xchdir(rootdir); + + umask(0); + + printf("rootdir=%s\n", rootdir); + if (line) { + printf("table='%s'\n", line); + } else { + printf("table=\n"); + } + + while ((line = xmalloc_getline(table))) { + char type; + unsigned int mode = 0755; + unsigned int major = 0; + unsigned int minor = 0; + unsigned int count = 0; + unsigned int increment = 0; + unsigned int start = 0; + char name[41]; + char user[41]; + char group[41]; + char *full_name; + uid_t uid; + gid_t gid; + + linenum++; + + if ((2 > sscanf(line, "%40s %c %o %40s %40s %u %u %u %u %u", name, + &type, &mode, user, group, &major, + &minor, &start, &increment, &count)) || + ((major | minor | start | count | increment) > 255)) + { + if (*line=='\0' || *line=='#' || isspace(*line)) + continue; + bb_error_msg("line %d invalid: '%s'", linenum, line); + ret = EXIT_FAILURE; + continue; + } + if (name[0] == '#') { + continue; + } + + gid = (*group) ? get_ug_id(group, bb_xgetgrnam) : getgid(); + uid = (*user) ? get_ug_id(user, bb_xgetpwnam) : getuid(); + full_name = concat_path_file(rootdir, name); + + if (type == 'd') { + bb_make_directory(full_name, mode | S_IFDIR, FILEUTILS_RECUR); + if (chown(full_name, uid, gid) == -1) { + bb_perror_msg("line %d: chown failed for %s", linenum, full_name); + ret = EXIT_FAILURE; + goto loop; + } + if ((mode != -1) && (chmod(full_name, mode) < 0)){ + bb_perror_msg("line %d: chmod failed for %s", linenum, full_name); + ret = EXIT_FAILURE; + goto loop; + } + } 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; + goto loop; + } + if (chown(full_name, uid, gid) == -1) { + bb_perror_msg("line %d: chown failed for %s", linenum, full_name); + ret = EXIT_FAILURE; + goto loop; + } + if ((mode != -1) && (chmod(full_name, mode) < 0)){ + bb_perror_msg("line %d: chmod failed for %s", linenum, full_name); + ret = EXIT_FAILURE; + goto loop; + } + } else + { + dev_t rdev; + + 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; + goto loop; + } + + if (count > 0) { + int i; + char *full_name_inc; + + full_name_inc = xmalloc(strlen(full_name) + 4); + for (i = start; i < count; i++) { + sprintf(full_name_inc, "%s%d", full_name, i); + rdev = (major << 8) + minor + (i * increment - start); + if (mknod(full_name_inc, mode, rdev) == -1) { + bb_perror_msg("line %d: cannot create node %s", linenum, full_name_inc); + ret = EXIT_FAILURE; + } + else if (chown(full_name_inc, uid, gid) == -1) { + bb_perror_msg("line %d: chown failed for %s", linenum, full_name_inc); + ret = EXIT_FAILURE; + } + if ((mode != -1) && (chmod(full_name_inc, mode) < 0)){ + bb_perror_msg("line %d: chmod failed for %s", linenum, full_name_inc); + ret = EXIT_FAILURE; + } + } + free(full_name_inc); + } else { + rdev = (major << 8) + minor; + if (mknod(full_name, mode, rdev) == -1) { + bb_perror_msg("line %d: cannot create node %s", linenum, full_name); + ret = EXIT_FAILURE; + } + else if (chown(full_name, uid, gid) == -1) { + bb_perror_msg("line %d: chown failed for %s", linenum, full_name); + ret = EXIT_FAILURE; + } + if ((mode != -1) && (chmod(full_name, mode) < 0)){ + bb_perror_msg("line %d: chmod failed for %s", linenum, full_name); + ret = EXIT_FAILURE; + } + } + } +loop: + free(line); + free(full_name); + } + fclose(table); + + return ret; +} + +#else +# error makedevs configuration error, either leaf or table must be selected +#endif diff --git a/miscutils/mountpoint.c b/miscutils/mountpoint.c new file mode 100644 index 000000000..b1bcab698 --- /dev/null +++ b/miscutils/mountpoint.c @@ -0,0 +1,65 @@ +/* vi: set sw=4 ts=4: */ +/* + * mountpoint implementation for busybox + * + * Copyright (C) 2005 Bernhard Fischer + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Based on sysvinit's mountpoint + */ + +#include "busybox.h" + +int mountpoint_main(int argc, char **argv) +{ + struct stat st; + char *arg; + int opt = getopt32(argc, 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) + 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_perror_msg("%s", arg); + return EXIT_FAILURE; +} diff --git a/miscutils/mt.c b/miscutils/mt.c new file mode 100644 index 000000000..9ecec82a1 --- /dev/null +++ b/miscutils/mt.c @@ -0,0 +1,120 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include + +struct mt_opcodes { + char *name; + short value; +}; + +/* missing: eod/seod, stoptions, stwrthreshold, densities */ +static const struct mt_opcodes opcodes[] = { + {"bsf", MTBSF}, + {"bsfm", MTBSFM}, + {"bsr", MTBSR}, + {"bss", MTBSS}, + {"datacompression", MTCOMPRESSION}, + {"eom", MTEOM}, + {"erase", MTERASE}, + {"fsf", MTFSF}, + {"fsfm", MTFSFM}, + {"fsr", MTFSR}, + {"fss", MTFSS}, + {"load", MTLOAD}, + {"lock", MTLOCK}, + {"mkpart", MTMKPART}, + {"nop", MTNOP}, + {"offline", MTOFFL}, + {"rewoffline", MTOFFL}, + {"ras1", MTRAS1}, + {"ras2", MTRAS2}, + {"ras3", MTRAS3}, + {"reset", MTRESET}, + {"retension", MTRETEN}, + {"rewind", MTREW}, + {"seek", MTSEEK}, + {"setblk", MTSETBLK}, + {"setdensity", MTSETDENSITY}, + {"drvbuffer", MTSETDRVBUFFER}, + {"setpart", MTSETPART}, + {"tell", MTTELL}, + {"wset", MTWSM}, + {"unload", MTUNLOAD}, + {"unlock", MTUNLOCK}, + {"eof", MTWEOF}, + {"weof", MTWEOF}, + {0, 0} +}; + +int mt_main(int argc, char **argv) +{ + const char *file = "/dev/tape"; + const struct mt_opcodes *code = opcodes; + struct mtop op; + struct mtpos position; + int fd, mode; + + if (argc < 2) { + bb_show_usage(); + } + + if (strcmp(argv[1], "-f") == 0) { + if (argc < 4) { + bb_show_usage(); + } + file = argv[2]; + argv += 2; + argc -= 2; + } + + while (code->name != 0) { + if (strcmp(code->name, argv[1]) == 0) + break; + code++; + } + + if (code->name == 0) { + bb_error_msg("unrecognized opcode %s", argv[1]); + return EXIT_FAILURE; + } + + op.mt_op = code->value; + if (argc >= 3) + op.mt_count = xatoi_u(argv[2]); + else + op.mt_count = 1; /* One, not zero, right? */ + + switch (code->value) { + case MTWEOF: + case MTERASE: + case MTWSM: + case MTSETDRVBUFFER: + mode = O_WRONLY; + break; + + default: + mode = O_RDONLY; + break; + } + + fd = xopen(file, mode); + + switch (code->value) { + case MTTELL: + if (ioctl(fd, MTIOCPOS, &position) < 0) + bb_perror_msg_and_die("%s", file); + printf("At block %d.\n", (int) position.mt_blkno); + break; + + default: + if (ioctl(fd, MTIOCTOP, &op) != 0) + bb_perror_msg_and_die("%s", file); + break; + } + + return EXIT_SUCCESS; +} diff --git a/miscutils/nmeter.c b/miscutils/nmeter.c new file mode 100644 index 000000000..b09877137 --- /dev/null +++ b/miscutils/nmeter.c @@ -0,0 +1,848 @@ +/* +** Licensed under the GPL v2, see the file LICENSE in this tarball +** +** Based on nanotop.c from floppyfw project +** +** Contact me: vda.linux@googlemail.com */ + +//TODO: +// simplify code +// /proc/locks +// /proc/stat: +// disk_io: (3,0):(22272,17897,410702,4375,54750) +// btime 1059401962 + +#include "busybox.h" +#include + +typedef unsigned long long ullong; + +enum { proc_file_size = 4096 }; + +typedef struct proc_file { + char *name; + int gen; + char *file; +} proc_file; + +static proc_file proc_stat = { "/proc/stat", -1 }; +static proc_file proc_loadavg = { "/proc/loadavg", -1 }; +static proc_file proc_net_dev = { "/proc/net/dev", -1 }; +static proc_file proc_meminfo = { "/proc/meminfo", -1 }; +static proc_file proc_diskstats = { "/proc/diskstats", -1 }; +// Sample # +static int gen = -1; +// Linux 2.6? (otherwise assumes 2.4) +static int is26 = 0; +static struct timeval tv; +static int delta = 1000000; +static int deltanz = 1000000; +static int need_seconds = 0; +static char *final_str = "\n"; + +// We depend on this being a char[], not char* - we take sizeof() of it +#define outbuf bb_common_bufsiz1 +static char *cur_outbuf = outbuf; + + +static inline void reset_outbuf(void) +{ + cur_outbuf = outbuf; +} + +static inline int outbuf_count(void) +{ + return cur_outbuf - outbuf; +} + +static void print_outbuf(void) +{ + int sz = cur_outbuf - outbuf; + if (sz > 0) { + write(1, outbuf, sz); + cur_outbuf = outbuf; + } +} + +static void put(const char *s) +{ + int sz = strlen(s); + if (sz > outbuf + sizeof(outbuf) - cur_outbuf) + sz = outbuf + sizeof(outbuf) - cur_outbuf; + memcpy(cur_outbuf, s, sz); + cur_outbuf += sz; +} + +static void put_c(char c) +{ + if (cur_outbuf < outbuf + sizeof(outbuf)) + *cur_outbuf++ = c; +} + +static void put_question_marks(int count) +{ + while (count--) + put_c('?'); +} + +static int readfile_z(char *buf, int sz, const char* fname) +{ + sz = open_read_close(fname, buf, sz-1); + if (sz < 0) { + buf[0] = '\0'; + return 1; + } + buf[sz] = '\0'; + return 0; +} + +static const char* get_file(proc_file *pf) +{ + if (pf->gen != gen) { + pf->gen = gen; + // We allocate proc_file_size bytes. This wastes memory, + // but allows us to allocate only once (at first sample) + // per proc file, and reuse buffer for each sample + if (!pf->file) + pf->file = (char*)xmalloc(proc_file_size); + readfile_z(pf->file, proc_file_size, pf->name); + } + return pf->file; +} + +static inline ullong read_after_slash(const char *p) +{ + p = strchr(p, '/'); + if (!p) return 0; + return strtoull(p+1, NULL, 10); +} + +enum conv_type { conv_decimal, conv_slash }; + +// Reads decimal values from line. Values start after key, for example: +// "cpu 649369 0 341297 4336769..." - key is "cpu" here. +// Values are stored in vec[]. arg_ptr has list of positions +// we are interested in: for example: 1,2,5 - we want 1st, 2nd and 5th value. +static int vrdval(const char* p, const char* key, + enum conv_type conv, ullong *vec, va_list arg_ptr) +{ + int indexline; + int indexnext; + + p = strstr(p, key); + if (!p) return 1; + + p += strlen(key); + indexline = 1; + indexnext = va_arg(arg_ptr, int); + while (1) { + while (*p == ' ' || *p == '\t') p++; + if (*p == '\n' || *p == '\0') break; + + if (indexline == indexnext) { // read this value + *vec++ = conv==conv_decimal ? + strtoull(p, NULL, 10) : + read_after_slash(p); + indexnext = va_arg(arg_ptr, int); + } + while (*p > ' ') p++; // skip over value + indexline++; + } + return 0; +} + +// Parses files with lines like "cpu0 21727 0 15718 1813856 9461 10485 0 0": +// rdval(file_contents, "string_to_find", result_vector, value#, value#...) +// value# start with 1 +static int rdval(const char* p, const char* key, ullong *vec, ...) +{ + va_list arg_ptr; + int result; + + va_start(arg_ptr, vec); + result = vrdval(p, key, conv_decimal, vec, arg_ptr); + va_end(arg_ptr); + + return result; +} + +// Parses files with lines like "... ... ... 3/148 ...." +static int rdval_loadavg(const char* p, ullong *vec, ...) +{ + va_list arg_ptr; + int result; + + va_start(arg_ptr, vec); + result = vrdval(p, "", conv_slash, vec, arg_ptr); + va_end(arg_ptr); + + return result; +} + +// Parses /proc/diskstats +// 1 2 3 4 5 6(rd) 7 8 9 10(wr) 11 12 13 14 +// 3 0 hda 51292 14441 841783 926052 25717 79650 843256 3029804 0 148459 3956933 +// 3 1 hda1 0 0 0 0 <- ignore if only 4 fields +static int rdval_diskstats(const char* p, ullong *vec) +{ + ullong rd = 0; // to avoid "warning: 'rd' might be used uninitialized" + int indexline = 0; + vec[0] = 0; + vec[1] = 0; + while (1) { + indexline++; + while (*p == ' ' || *p == '\t') p++; + if (*p == '\0') break; + if (*p == '\n') { + indexline = 0; + p++; + continue; + } + if (indexline == 6) { + rd = strtoull(p, NULL, 10); + } else if (indexline == 10) { + vec[0] += rd; // TODO: *sectorsize (don't know how to find out sectorsize) + vec[1] += strtoull(p, NULL, 10); + while (*p != '\n' && *p != '\0') p++; + continue; + } + while (*p > ' ') p++; // skip over value + } + return 0; +} + +static void scale(ullong ul) +{ + char buf[5]; + smart_ulltoa5(ul, buf); + put(buf); +} + + +#define S_STAT(a) \ +typedef struct a { \ + struct s_stat *next; \ + void (*collect)(struct a *s); \ + const char *label; +#define S_STAT_END(a) } a; + +S_STAT(s_stat) +S_STAT_END(s_stat) + +static void collect_literal(s_stat *s) +{ +} + +static s_stat* init_literal(void) +{ + s_stat *s = xmalloc(sizeof(s_stat)); + s->collect = collect_literal; + return (s_stat*)s; +} + +static s_stat* init_delay(const char *param) +{ + delta = strtol(param, NULL, 0)*1000; + deltanz = delta > 0 ? delta : 1; + need_seconds = (1000000%deltanz) != 0; + return (s_stat*)0; +} + +static s_stat* init_cr(const char *param) +{ + final_str = "\r"; + return (s_stat*)0; +} + + +// user nice system idle iowait irq softirq (last 3 only in 2.6) +//cpu 649369 0 341297 4336769 11640 7122 1183 +//cpuN 649369 0 341297 4336769 11640 7122 1183 +enum { CPU_FIELDCNT = 7 }; +S_STAT(cpu_stat) + ullong old[CPU_FIELDCNT]; + int bar_sz; + char *bar; +S_STAT_END(cpu_stat) + + +static void collect_cpu(cpu_stat *s) +{ + ullong data[CPU_FIELDCNT] = { 0, 0, 0, 0, 0, 0, 0 }; + unsigned frac[CPU_FIELDCNT] = { 0, 0, 0, 0, 0, 0, 0 }; + ullong all = 0; + int norm_all = 0; + int bar_sz = s->bar_sz; + char *bar = s->bar; + int i; + + if (rdval(get_file(&proc_stat), "cpu ", data, 1, 2, 3, 4, 5, 6, 7)) { + put_question_marks(bar_sz); + return; + } + + for (i=0; iold[i]; + if (data[i] < old) old = data[i]; //sanitize + s->old[i] = data[i]; + all += (data[i] -= old); + } + + if (all) { + for (i=0; i max) max = frac[i], pos = i; + } + frac[pos] = 0; //avoid bumping up same value twice + data[pos]++; + norm_all++; + } + + memset(bar, '.', bar_sz); + memset(bar, 'S', data[2]); bar += data[2]; //sys + memset(bar, 'U', data[0]); bar += data[0]; //usr + memset(bar, 'N', data[1]); bar += data[1]; //nice + memset(bar, 'D', data[4]); bar += data[4]; //iowait + memset(bar, 'I', data[5]); bar += data[5]; //irq + memset(bar, 'i', data[6]); bar += data[6]; //softirq + } else { + memset(bar, '?', bar_sz); + } + put(s->bar); +} + + +static s_stat* init_cpu(const char *param) +{ + int sz; + cpu_stat *s = xmalloc(sizeof(cpu_stat)); + s->collect = collect_cpu; + sz = strtol(param, NULL, 0); + if (sz < 10) sz = 10; + if (sz > 1000) sz = 1000; + s->bar = xmalloc(sz+1); + s->bar[sz] = '\0'; + s->bar_sz = sz; + return (s_stat*)s; +} + + +S_STAT(int_stat) + ullong old; + int no; +S_STAT_END(int_stat) + +static void collect_int(int_stat *s) +{ + ullong data[1]; + ullong old; + + if (rdval(get_file(&proc_stat), "intr", data, s->no)) { + put_question_marks(4); + return; + } + + old = s->old; + if (data[0] < old) old = data[0]; //sanitize + s->old = data[0]; + scale(data[0] - old); +} + +static s_stat* init_int(const char *param) +{ + int_stat *s = xmalloc(sizeof(int_stat)); + s->collect = collect_int; + if (param[0]=='\0') { + s->no = 1; + } else { + int n = strtoul(param, NULL, 0); + s->no = n+2; + } + return (s_stat*)s; +} + + +S_STAT(ctx_stat) + ullong old; +S_STAT_END(ctx_stat) + +static void collect_ctx(ctx_stat *s) +{ + ullong data[1]; + ullong old; + + if (rdval(get_file(&proc_stat), "ctxt", data, 1)) { + put_question_marks(4); + return; + } + + old = s->old; + if (data[0] < old) old = data[0]; //sanitize + s->old = data[0]; + scale(data[0] - old); +} + +static s_stat* init_ctx(const char *param) +{ + ctx_stat *s = xmalloc(sizeof(ctx_stat)); + s->collect = collect_ctx; + return (s_stat*)s; +} + + +S_STAT(blk_stat) + const char* lookfor; + ullong old[2]; +S_STAT_END(blk_stat) + +static void collect_blk(blk_stat *s) +{ + ullong data[2]; + int i; + + if (is26) { + i = rdval_diskstats(get_file(&proc_diskstats), data); + } else { + i = rdval(get_file(&proc_stat), s->lookfor, data, 1, 2); + // Linux 2.4 reports bio in Kbytes, convert to sectors: + data[0] *= 2; + data[1] *= 2; + } + if (i) { + put_question_marks(9); + return; + } + + for (i=0; i<2; i++) { + ullong old = s->old[i]; + if (data[i] < old) old = data[i]; //sanitize + s->old[i] = data[i]; + data[i] -= old; + } + scale(data[0]*512); // TODO: *sectorsize + put_c(' '); + scale(data[1]*512); +} + +static s_stat* init_blk(const char *param) +{ + blk_stat *s = xmalloc(sizeof(blk_stat)); + s->collect = collect_blk; + s->lookfor = "page"; + return (s_stat*)s; +} + + +S_STAT(fork_stat) + ullong old; +S_STAT_END(fork_stat) + +static void collect_thread_nr(fork_stat *s) +{ + ullong data[1]; + + if (rdval_loadavg(get_file(&proc_loadavg), data, 4)) { + put_question_marks(4); + return; + } + scale(data[0]); +} + +static void collect_fork(fork_stat *s) +{ + ullong data[1]; + ullong old; + + if (rdval(get_file(&proc_stat), "processes", data, 1)) { + put_question_marks(4); + return; + } + + old = s->old; + if (data[0] < old) old = data[0]; //sanitize + s->old = data[0]; + scale(data[0] - old); +} + +static s_stat* init_fork(const char *param) +{ + fork_stat *s = xmalloc(sizeof(fork_stat)); + if (*param == 'n') { + s->collect = collect_thread_nr; + } else { + s->collect = collect_fork; + } + return (s_stat*)s; +} + + +S_STAT(if_stat) + ullong old[4]; + const char *device; + char *device_colon; +S_STAT_END(if_stat) + +static void collect_if(if_stat *s) +{ + ullong data[4]; + int i; + + if (rdval(get_file(&proc_net_dev), s->device_colon, data, 1, 3, 9, 11)) { + put_question_marks(10); + return; + } + + for (i=0; i<4; i++) { + ullong old = s->old[i]; + if (data[i] < old) old = data[i]; //sanitize + s->old[i] = data[i]; + data[i] -= old; + } + put_c(data[1] ? '*' : ' '); + scale(data[0]); + put_c(data[3] ? '*' : ' '); + scale(data[2]); +} + +static s_stat* init_if(const char *device) +{ + if_stat *s = xmalloc(sizeof(if_stat)); + + if (!device || !device[0]) + bb_show_usage(); + s->collect = collect_if; + + s->device = device; + s->device_colon = xmalloc(strlen(device)+2); + strcpy(s->device_colon, device); + strcat(s->device_colon, ":"); + return (s_stat*)s; +} + + +S_STAT(mem_stat) + char opt; +S_STAT_END(mem_stat) + +// "Memory" value should not include any caches. +// IOW: neither "ls -laR /" nor heavy read/write activity +// should affect it. We'd like to also include any +// long-term allocated kernel-side mem, but it is hard +// to figure out. For now, bufs, cached & slab are +// counted as "free" memory +//2.6.16: +//MemTotal: 773280 kB +//MemFree: 25912 kB - genuinely free +//Buffers: 320672 kB - cache +//Cached: 146396 kB - cache +//SwapCached: 0 kB +//Active: 183064 kB +//Inactive: 356892 kB +//HighTotal: 0 kB +//HighFree: 0 kB +//LowTotal: 773280 kB +//LowFree: 25912 kB +//SwapTotal: 131064 kB +//SwapFree: 131064 kB +//Dirty: 48 kB +//Writeback: 0 kB +//Mapped: 96620 kB +//Slab: 200668 kB - takes 7 Mb on my box fresh after boot, +// but includes dentries and inodes +// (== can take arbitrary amount of mem) +//CommitLimit: 517704 kB +//Committed_AS: 236776 kB +//PageTables: 1248 kB +//VmallocTotal: 516052 kB +//VmallocUsed: 3852 kB +//VmallocChunk: 512096 kB +//HugePages_Total: 0 +//HugePages_Free: 0 +//Hugepagesize: 4096 kB +static void collect_mem(mem_stat *s) +{ + ullong m_total = 0; + ullong m_free = 0; + ullong m_bufs = 0; + ullong m_cached = 0; + ullong m_slab = 0; + + if (rdval(get_file(&proc_meminfo), "MemTotal:", &m_total, 1)) { + put_question_marks(4); + return; + } + if (s->opt == 'f') { + scale(m_total << 10); + return; + } + + if (rdval(proc_meminfo.file, "MemFree:", &m_free , 1) + || rdval(proc_meminfo.file, "Buffers:", &m_bufs , 1) + || rdval(proc_meminfo.file, "Cached:", &m_cached, 1) + || rdval(proc_meminfo.file, "Slab:", &m_slab , 1) + ) { + put_question_marks(4); + return; + } + + m_free += m_bufs + m_cached + m_slab; + switch(s->opt) { + case 'f': + scale(m_free << 10); break; + default: + scale((m_total - m_free) << 10); break; + } +} + +static s_stat* init_mem(const char *param) +{ + mem_stat *s = xmalloc(sizeof(mem_stat)); + s->collect = collect_mem; + s->opt = param[0]; + return (s_stat*)s; +} + + +S_STAT(swp_stat) +S_STAT_END(swp_stat) + +static void collect_swp(swp_stat *s) +{ + ullong s_total[1]; + ullong s_free[1]; + if (rdval(get_file(&proc_meminfo), "SwapTotal:", s_total, 1) + || rdval(proc_meminfo.file, "SwapFree:" , s_free, 1) + ) { + put_question_marks(4); + return; + } + scale((s_total[0]-s_free[0]) << 10); +} + +static s_stat* init_swp(const char *param) +{ + swp_stat *s = xmalloc(sizeof(swp_stat)); + s->collect = collect_swp; + return (s_stat*)s; +} + + +S_STAT(fd_stat) +S_STAT_END(fd_stat) + +static void collect_fd(fd_stat *s) +{ + char file[4096]; + ullong data[2]; + + readfile_z(file, sizeof(file), "/proc/sys/fs/file-nr"); + if (rdval(file, "", data, 1, 2)) { + put_question_marks(4); + return; + } + + scale(data[0] - data[1]); +} + +static s_stat* init_fd(const char *param) +{ + fd_stat *s = xmalloc(sizeof(fd_stat)); + s->collect = collect_fd; + return (s_stat*)s; +} + + +S_STAT(time_stat) + int prec; + int scale; +S_STAT_END(time_stat) + +static void collect_time(time_stat *s) +{ + char buf[sizeof("12:34:56.123456")]; + struct tm* tm; + int us = tv.tv_usec + s->scale/2; + time_t t = tv.tv_sec; + + if (us >= 1000000) { + t++; + us -= 1000000; + } + tm = localtime(&t); + + sprintf(buf, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); + if (s->prec) + sprintf(buf+8, ".%0*d", s->prec, us / s->scale); + put(buf); +} + +static s_stat* init_time(const char *param) +{ + int prec; + time_stat *s = xmalloc(sizeof(time_stat)); + + s->collect = collect_time; + prec = param[0]-'0'; + if (prec < 0) prec = 0; + else if (prec > 6) prec = 6; + s->prec = prec; + s->scale = 1; + while (prec++ < 6) + s->scale *= 10; + return (s_stat*)s; +} + +static void collect_info(s_stat *s) +{ + gen++; + while (s) { + put(s->label); + s->collect(s); + s = s->next; + } +} + + +typedef s_stat* init_func(const char *param); + +static const char options[] = "ncmsfixptbdr"; +static init_func* init_functions[] = { + init_if, + init_cpu, + init_mem, + init_swp, + init_fd, + init_int, + init_ctx, + init_fork, + init_time, + init_blk, + init_delay, + init_cr, +}; + +int nmeter_main(int argc, char* argv[]) +{ + char buf[32]; + s_stat *first = NULL; + s_stat *last = NULL; + s_stat *s; + char *cur, *prev; + + if (argc != 2) + bb_show_usage(); + + if (open_read_close("/proc/version", buf, sizeof(buf)) > 0) + is26 = (strstr(buf, " 2.4.")==NULL); + + // Can use argv[1] directly, but this will mess up + // parameters as seen by e.g. ps. Making a copy... + cur = xstrdup(argv[1]); + while (1) { + char *param, *p; + prev = cur; +again: + cur = strchr(cur, '%'); + if (!cur) + break; + if (cur[1]=='%') { // %% + strcpy(cur, cur+1); + cur++; + goto again; + } + *cur++ = '\0'; // overwrite % + if (cur[0] == '[') { + // format: %[foptstring] + cur++; + p = strchr(options, cur[0]); + param = cur+1; + while (cur[0] != ']') { + if (!cur[0]) + bb_show_usage(); + cur++; + } + *cur++ = '\0'; // overwrite [ + } else { + // format: %NNNNNNf + param = cur; + while (cur[0] >= '0' && cur[0] <= '9') + cur++; + if (!cur[0]) + bb_show_usage(); + p = strchr(options, cur[0]); + *cur++ = '\0'; // overwrite format char + } + if (!p) + bb_show_usage(); + s = init_functions[p-options](param); + if (s) { + s->label = prev; + s->next = 0; + if (!first) + first = s; + else + last->next = s; + last = s; + } else { + // %NNNNd or %r option. remove it from string + strcpy(prev + strlen(prev), cur); + cur = prev; + } + } + if (prev[0]) { + s = init_literal(); + s->label = prev; + s->next = 0; + if (!first) + first = s; + else + last->next = s; + last = s; + } + + // Generate first samples but do not print them, they're bogus + collect_info(first); + reset_outbuf(); + if (delta >= 0) { + gettimeofday(&tv, 0); + usleep(delta > 1000000 ? 1000000 : delta - tv.tv_usec%deltanz); + } + + while (1) { + gettimeofday(&tv, 0); + collect_info(first); + put(final_str); + print_outbuf(); + + // Negative delta -> no usleep at all + // This will hog the CPU but you can have REALLY GOOD + // time resolution ;) + // TODO: detect and avoid useless updates + // (like: nothing happens except time) + if (delta >= 0) { + int rem; + // can be commented out, will sacrifice sleep time precision a bit + gettimeofday(&tv, 0); + if (need_seconds) + rem = delta - ((ullong)tv.tv_sec*1000000+tv.tv_usec)%deltanz; + else + rem = delta - tv.tv_usec%deltanz; + // Sometimes kernel wakes us up just a tiny bit earlier than asked + // Do not go to very short sleep in this case + if (rem < delta/128) { + rem += delta; + } + usleep(rem); + } + } + + return 0; +} diff --git a/miscutils/raidautorun.c b/miscutils/raidautorun.c new file mode 100644 index 000000000..e5bce9a92 --- /dev/null +++ b/miscutils/raidautorun.c @@ -0,0 +1,26 @@ +/* vi: set sw=4 ts=4: */ +/* + * raidautorun implementation for busybox + * + * Copyright (C) 2006 Bernhard Fischer + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + */ + +#include "busybox.h" + +#include +#include + +int raidautorun_main(int argc, char **argv) +{ + if (argc != 2) + bb_show_usage(); + + if (ioctl(xopen(argv[1], O_RDONLY), RAID_AUTORUN, NULL) != 0) { + bb_perror_msg_and_die("ioctl"); + } + + return EXIT_SUCCESS; +} diff --git a/miscutils/readahead.c b/miscutils/readahead.c new file mode 100644 index 000000000..356c40451 --- /dev/null +++ b/miscutils/readahead.c @@ -0,0 +1,34 @@ +/* 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 "busybox.h" + +int readahead_main(int argc, char **argv) +{ + FILE *f; + int retval = EXIT_SUCCESS; + + if (argc == 1) bb_show_usage(); + + while (*++argv) { + if ((f = fopen_or_warn(*argv, "r")) != NULL) { + int r, fd=fileno(f); + + r = readahead(fd, 0, fdlength(fd)); + fclose(f); + if (r >= 0) continue; + } + retval = EXIT_FAILURE; + } + + return retval; +} diff --git a/miscutils/runlevel.c b/miscutils/runlevel.c new file mode 100644 index 000000000..91d49fa55 --- /dev/null +++ b/miscutils/runlevel.c @@ -0,0 +1,42 @@ +/* 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 Fischer + */ + +#include "busybox.h" +#include +#include +#include +#include + +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); + endutent(); + return 0; + } + } + + puts("unknown"); + endutent(); + return 1; +} diff --git a/miscutils/rx.c b/miscutils/rx.c new file mode 100644 index 000000000..9b9f6afd4 --- /dev/null +++ b/miscutils/rx.c @@ -0,0 +1,290 @@ +/* vi: set sw=4 ts=4: */ +/*------------------------------------------------------------------------- + * Filename: xmodem.c + * Version: $Id: rx.c,v 1.2 2004/03/15 08:28:46 andersen Exp $ + * 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 "busybox.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 + +static int read_byte(int fd, unsigned int timeout) { + char buf[1]; + int n; + + alarm(timeout); + + n = read(fd, &buf, 1); + + alarm(0); + + if (n == 1) + return buf[0] & 0xff; + else + return -1; +} + +static int receive(char *error_buf, size_t error_buf_size, + int ttyfd, int filefd) +{ + char blockBuf[1024]; + unsigned int errors = 0; + unsigned int wantBlockNo = 1; + unsigned int length = 0; + int docrc = 1; + char nak = 'C'; + unsigned int timeout = TIMEOUT_LONG; + +#define note_error(fmt,args...) \ + snprintf(error_buf, error_buf_size, fmt,##args) + + /* Flush pending input */ + tcflush(ttyfd, TCIFLUSH); + + /* Ask for CRC; if we get errors, we will go with checksum */ + write(ttyfd, &nak, 1); + + for (;;) { + int blockBegin; + int blockNo, blockNoOnesCompl; + int blockLength; + int cksum = 0; + int crcHi = 0; + int crcLo = 0; + + blockBegin = read_byte(ttyfd, timeout); + if (blockBegin < 0) + goto timeout; + + timeout = TIMEOUT; + nak = NAK; + + switch (blockBegin) { + case SOH: + case STX: + break; + + case EOT: + nak = ACK; + write(ttyfd, &nak, 1); + goto done; + + default: + goto error; + } + + /* block no */ + blockNo = read_byte(ttyfd, TIMEOUT); + if (blockNo < 0) + goto timeout; + + /* block no one's compliment */ + blockNoOnesCompl = read_byte(ttyfd, TIMEOUT); + if (blockNoOnesCompl < 0) + goto timeout; + + if (blockNo != (255 - blockNoOnesCompl)) { + note_error("bad block ones compl"); + goto error; + } + + blockLength = (blockBegin == SOH) ? 128 : 1024; + + { + int i; + + for (i = 0; i < blockLength; i++) { + int cc = read_byte(ttyfd, TIMEOUT); + if (cc < 0) + goto timeout; + blockBuf[i] = cc; + } + } + + if (docrc) { + crcHi = read_byte(ttyfd, TIMEOUT); + if (crcHi < 0) + goto timeout; + + crcLo = read_byte(ttyfd, TIMEOUT); + if (crcLo < 0) + goto timeout; + } else { + cksum = read_byte(ttyfd, TIMEOUT); + if (cksum < 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; + } else if (blockNo != (wantBlockNo & 0xff)) { + note_error("unexpected block no, 0x%08x, expecting 0x%08x", blockNo, wantBlockNo); + goto error; + } + + if (docrc) { + int crc = 0; + int i, j; + int expectedCrcHi; + int expectedCrcLo; + + for (i = 0; i < blockLength; i++) { + crc = crc ^ (int) blockBuf[i] << 8; + for (j = 0; j < 8; j++) + if (crc & 0x8000) + crc = crc << 1 ^ 0x1021; + else + crc = crc << 1; + } + + expectedCrcHi = (crc >> 8) & 0xff; + expectedCrcLo = crc & 0xff; + + if ((crcHi != expectedCrcHi) || + (crcLo != expectedCrcLo)) { + note_error("crc error, expected 0x%02x 0x%02x, got 0x%02x 0x%02x", expectedCrcHi, expectedCrcLo, crcHi, crcLo); + goto error; + } + } else { + unsigned char expectedCksum = 0; + int i; + + for (i = 0; i < blockLength; i++) + expectedCksum += blockBuf[i]; + + if (cksum != expectedCksum) { + note_error("checksum error, expected 0x%02x, got 0x%02x", expectedCksum, cksum); + goto error; + } + } + + wantBlockNo++; + length += blockLength; + + if (full_write(filefd, blockBuf, blockLength) < 0) { + note_error("write to file failed: %m"); + goto fatal; + } + + next: + errors = 0; + nak = ACK; + write(ttyfd, &nak, 1); + continue; + + error: + timeout: + errors++; + if (errors == MAXERRORS) { + /* Abort */ + + // if using crc, try again w/o crc + if (nak == 'C') { + nak = NAK; + errors = 0; + docrc = 0; + goto timeout; + } + + note_error("too many errors; giving up"); + + fatal: + /* 5 CAN followed by 5 BS */ + write(ttyfd, "\030\030\030\030\030\010\010\010\010\010", 10); + return -1; + } + + /* Flush pending input */ + tcflush(ttyfd, TCIFLUSH); + + write(ttyfd, &nak, 1); + } + + done: + return length; + +#undef note_error +} + +static void sigalrm_handler(int ATTRIBUTE_UNUSED signum) +{ +} + +int rx_main(int argc, char **argv) +{ + char *fn; + int ttyfd, filefd; + struct termios tty, orig_tty; + struct sigaction act; + int n; + char error_buf[256]; + + if (argc != 2) + bb_show_usage(); + + fn = argv[1]; + ttyfd = xopen(CURRENT_TTY, O_RDWR); + filefd = xopen(fn, O_RDWR|O_CREAT|O_TRUNC); + + if (tcgetattr(ttyfd, &tty) < 0) + bb_perror_msg_and_die("tcgetattr"); + + orig_tty = tty; + + cfmakeraw(&tty); + tcsetattr(ttyfd, TCSAFLUSH, &tty); + + memset(&act, 0, sizeof(act)); + act.sa_handler = sigalrm_handler; + sigaction(SIGALRM, &act, 0); + + n = receive(error_buf, sizeof(error_buf), ttyfd, filefd); + + close(filefd); + + tcsetattr(ttyfd, TCSAFLUSH, &orig_tty); + + if (n < 0) + bb_error_msg_and_die("\nreceive failed:\n %s", error_buf); + + fflush_stdout_and_exit(EXIT_SUCCESS); +} diff --git a/miscutils/setsid.c b/miscutils/setsid.c new file mode 100644 index 000000000..347b2babd --- /dev/null +++ b/miscutils/setsid.c @@ -0,0 +1,44 @@ +/* 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 Mi¶kiewicz + * - added Native Language Support + * + * 2001-01-18 John Fremlin + * - fork in case we are process group leader + * + * 2004-11-12 Paul Fox + * - busyboxed + */ + +#include "busybox.h" +#include +#include +#include + +int setsid_main(int argc, char *argv[]) +{ + if (argc < 2) + bb_show_usage(); + + if (getpgrp() == getpid()) { + switch (fork()){ + case -1: + bb_perror_msg_and_die("fork"); + case 0: + break; + default: /* parent */ + exit(0); + } + /* child falls through */ + } + + setsid(); /* no error possible */ + + execvp(argv[1], argv + 1); + + bb_perror_msg_and_die("%s", argv[1]); +} diff --git a/miscutils/strings.c b/miscutils/strings.c new file mode 100644 index 000000000..0d5576e9b --- /dev/null +++ b/miscutils/strings.c @@ -0,0 +1,88 @@ +/* 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 "busybox.h" +#include +#include +#include +#include + +#define WHOLE_FILE 1 +#define PRINT_NAME 2 +#define PRINT_OFFSET 4 +#define SIZE 8 + +int strings_main(int argc, char **argv) +{ + int n, c, i = 0, status = EXIT_SUCCESS; + unsigned opt; + unsigned long count; + FILE *file = stdin; + char *string; + const char *fmt = "%s: "; + char *n_arg = "4"; + + opt = getopt32(argc, argv, "afon:", &n_arg); + /* -a is our default behaviour */ + + argc -= optind; + argv += optind; + + n = xatoul_range(n_arg, 1, INT_MAX); + string = xzalloc(n + 1); + n--; + + if (argc == 0) { + fmt = "{%s}: "; + *argv = (char *)bb_msg_standard_input; + goto PIPE; + } + + do { + file = fopen_or_warn(*argv, "r"); + if (file) { +PIPE: + count = 0; + do { + c = fgetc(file); + if (isprint(c) || c == '\t') { + if (i <= n) { + string[i] = c; + } else { + putchar(c); + } + if (i == n) { + if (opt & PRINT_NAME) { + printf(fmt, *argv); + } + if (opt & PRINT_OFFSET) { + printf("%7lo ", count - n); + } + printf("%s", string); + } + i++; + } else { + if (i > n) { + putchar('\n'); + } + i = 0; + } + count++; + } while (c != EOF); + fclose_if_not_stdin(file); + } else { + status = EXIT_FAILURE; + } + } while (--argc > 0); + + 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 000000000..4496aa5b4 --- /dev/null +++ b/miscutils/taskset.c @@ -0,0 +1,96 @@ +/* vi: set sw=4 ts=4: */ +/* + * taskset - retrieve or set a processes' CPU affinity + * Copyright (c) 2006 Bernhard Fischer + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include +#include +#include /* optind */ + +#if ENABLE_FEATURE_TASKSET_FANCY +#define TASKSET_PRINTF_MASK "%s" +#define from_cpuset(x) __from_cpuset(&x) +/* craft a string from the mask */ +static char *__from_cpuset(cpu_set_t *mask) { + int i; + char *ret = 0, *str = xzalloc(9); + + for (i = CPU_SETSIZE - 4; i >= 0; i -= 4) { + char val = 0; + int off; + for (off = 0; off <= 3; ++off) + if (CPU_ISSET(i+off, mask)) + val |= 1< */ + aff = p_opt; + p_opt = argv[optind]; + } + argv += optind; /* me -p */ + pid = xatoul_range(p_opt, 1, ULONG_MAX); /* -p */ + } else + aff = *++argv; /* */ + if (aff) { + unsigned i = 0; + unsigned long l = xstrtol_range(aff, 16, 1, ULONG_MAX); + + CPU_ZERO(&new_mask); + while (i < CPU_SETSIZE && l >= (1<. + Heavily modified by David MacKenzie . + Heavily modified for busybox by Erik Andersen +*/ + +#include "busybox.h" + +#define TV_MSEC tv_usec / 1000 + +/* Information on the resources used by a child process. */ +typedef struct { + int waitstatus; + struct rusage ru; + struct timeval start, elapsed; /* 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. */ + +#ifndef TICKS_PER_SEC +#define TICKS_PER_SEC 100 +#endif + +/* The number of milliseconds in one `tick' used by the `rusage' structure. */ +#define MSEC_PER_TICK (1000 / TICKS_PER_SEC) + +/* Return the number of clock ticks that occur in M milliseconds. */ +#define MSEC_TO_TICKS(m) ((m) / MSEC_PER_TICK) + +#define UL unsigned long + +static const char *const default_format = "real\t%E\nuser\t%u\nsys\t%T"; + +/* The output format for the -p option .*/ +static const char *const posix_format = "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 *const long_format = + "\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 int resuse_end(pid_t pid, resource_t * resp) +{ + int status; + + 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(&status, 0, &resp->ru)) != pid) { + if (caught == -1) + return 0; + } + + gettimeofday(&resp->elapsed, (struct timezone *) 0); + resp->elapsed.tv_sec -= resp->start.tv_sec; + if (resp->elapsed.tv_usec < resp->start.tv_usec) { + /* Manually carry a one from the seconds field. */ + resp->elapsed.tv_usec += 1000000; + --resp->elapsed.tv_sec; + } + resp->elapsed.tv_usec -= resp->start.tv_usec; + + resp->waitstatus = status; + + return 1; +} + +/* Print ARGV to FP, with each entry in ARGV separated by FILLER. */ +static void fprintargv(FILE * fp, char *const *argv, const char *filler) +{ + char *const *av; + + av = argv; + fputs(*av, fp); + while (*++av) { + fputs(filler, fp); + fputs(*av, fp); + } + if (ferror(fp)) + bb_error_msg_and_die(bb_msg_write_error); +} + +/* 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 long pages) +{ + static unsigned long ps = 0; + unsigned long tmp; + static long size = LONG_MAX; + + /* Initialization. */ + if (ps == 0) + ps = (long) getpagesize(); + + /* Conversion. */ + if (pages > (LONG_MAX / ps)) { /* Could overflow. */ + tmp = pages / 1024; /* Smaller first, */ + size = tmp * ps; /* then larger. */ + } else { /* Could underflow. */ + tmp = pages * ps; /* Larger first, */ + size = tmp / 1024; /* then smaller. */ + } + return size; +} + +/* summarize: Report on the system use of a command. + + Copy the FMT argument to FP 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. + + FP is the stream to print to. + 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. */ + +static void summarize(FILE * fp, const char *fmt, char **command, + resource_t * resp) +{ + unsigned long r; /* Elapsed real milliseconds. */ + unsigned long v; /* Elapsed virtual (CPU) milliseconds. */ + + if (WIFSTOPPED(resp->waitstatus)) + fprintf(fp, "Command stopped by signal %d\n", + WSTOPSIG(resp->waitstatus)); + else if (WIFSIGNALED(resp->waitstatus)) + fprintf(fp, "Command terminated by signal %d\n", + WTERMSIG(resp->waitstatus)); + else if (WIFEXITED(resp->waitstatus) && WEXITSTATUS(resp->waitstatus)) + fprintf(fp, "Command exited with non-zero status %d\n", + WEXITSTATUS(resp->waitstatus)); + + /* Convert all times to milliseconds. Occasionally, one of these values + comes out as zero. Dividing by zero causes problems, so we first + check the time value. If it is zero, then we take `evasive action' + instead of calculating a value. */ + + r = resp->elapsed.tv_sec * 1000 + resp->elapsed.tv_usec / 1000; + + v = resp->ru.ru_utime.tv_sec * 1000 + resp->ru.ru_utime.TV_MSEC + + resp->ru.ru_stime.tv_sec * 1000 + resp->ru.ru_stime.TV_MSEC; + + while (*fmt) { + switch (*fmt) { + case '%': + switch (*++fmt) { + case '%': /* Literal '%'. */ + putc('%', fp); + break; + case 'C': /* The command that got timed. */ + fprintargv(fp, command, " "); + break; + case 'D': /* Average unshared data size. */ + fprintf(fp, "%lu", + MSEC_TO_TICKS(v) == 0 ? 0 : + ptok((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS(v) + + ptok((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS(v)); + break; + case 'E': /* Elapsed real (wall clock) time. */ + if (resp->elapsed.tv_sec >= 3600) /* One hour -> h:m:s. */ + fprintf(fp, "%ldh %ldm %02lds", + resp->elapsed.tv_sec / 3600, + (resp->elapsed.tv_sec % 3600) / 60, + resp->elapsed.tv_sec % 60); + else + fprintf(fp, "%ldm %ld.%02lds", /* -> m:s. */ + resp->elapsed.tv_sec / 60, + resp->elapsed.tv_sec % 60, + resp->elapsed.tv_usec / 10000); + break; + case 'F': /* Major page faults. */ + fprintf(fp, "%ld", resp->ru.ru_majflt); + break; + case 'I': /* Inputs. */ + fprintf(fp, "%ld", resp->ru.ru_inblock); + break; + case 'K': /* Average mem usage == data+stack+text. */ + fprintf(fp, "%lu", + MSEC_TO_TICKS(v) == 0 ? 0 : + ptok((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS(v) + + ptok((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS(v) + + ptok((UL) resp->ru.ru_ixrss) / MSEC_TO_TICKS(v)); + break; + case 'M': /* Maximum resident set size. */ + fprintf(fp, "%lu", ptok((UL) resp->ru.ru_maxrss)); + break; + case 'O': /* Outputs. */ + fprintf(fp, "%ld", resp->ru.ru_oublock); + break; + case 'P': /* Percent of CPU this job got. */ + /* % cpu is (total cpu time)/(elapsed time). */ + if (r > 0) + fprintf(fp, "%lu%%", (v * 100 / r)); + else + fprintf(fp, "?%%"); + break; + case 'R': /* Minor page faults (reclaims). */ + fprintf(fp, "%ld", resp->ru.ru_minflt); + break; + case 'S': /* System time. */ + fprintf(fp, "%ld.%02ld", + resp->ru.ru_stime.tv_sec, + resp->ru.ru_stime.TV_MSEC / 10); + break; + case 'T': /* System time. */ + if (resp->ru.ru_stime.tv_sec >= 3600) /* One hour -> h:m:s. */ + fprintf(fp, "%ldh %ldm %02lds", + resp->ru.ru_stime.tv_sec / 3600, + (resp->ru.ru_stime.tv_sec % 3600) / 60, + resp->ru.ru_stime.tv_sec % 60); + else + fprintf(fp, "%ldm %ld.%02lds", /* -> m:s. */ + resp->ru.ru_stime.tv_sec / 60, + resp->ru.ru_stime.tv_sec % 60, + resp->ru.ru_stime.tv_usec / 10000); + break; + case 'U': /* User time. */ + fprintf(fp, "%ld.%02ld", + resp->ru.ru_utime.tv_sec, + resp->ru.ru_utime.TV_MSEC / 10); + break; + case 'u': /* User time. */ + if (resp->ru.ru_utime.tv_sec >= 3600) /* One hour -> h:m:s. */ + fprintf(fp, "%ldh %ldm %02lds", + resp->ru.ru_utime.tv_sec / 3600, + (resp->ru.ru_utime.tv_sec % 3600) / 60, + resp->ru.ru_utime.tv_sec % 60); + else + fprintf(fp, "%ldm %ld.%02lds", /* -> m:s. */ + resp->ru.ru_utime.tv_sec / 60, + resp->ru.ru_utime.tv_sec % 60, + resp->ru.ru_utime.tv_usec / 10000); + break; + case 'W': /* Times swapped out. */ + fprintf(fp, "%ld", resp->ru.ru_nswap); + break; + case 'X': /* Average shared text size. */ + fprintf(fp, "%lu", + MSEC_TO_TICKS(v) == 0 ? 0 : + ptok((UL) resp->ru.ru_ixrss) / MSEC_TO_TICKS(v)); + break; + case 'Z': /* Page size. */ + fprintf(fp, "%d", getpagesize()); + break; + case 'c': /* Involuntary context switches. */ + fprintf(fp, "%ld", resp->ru.ru_nivcsw); + break; + case 'e': /* Elapsed real time in seconds. */ + fprintf(fp, "%ld.%02ld", + resp->elapsed.tv_sec, resp->elapsed.tv_usec / 10000); + break; + case 'k': /* Signals delivered. */ + fprintf(fp, "%ld", resp->ru.ru_nsignals); + break; + case 'p': /* Average stack segment. */ + fprintf(fp, "%lu", + MSEC_TO_TICKS(v) == 0 ? 0 : + ptok((UL) resp->ru.ru_isrss) / MSEC_TO_TICKS(v)); + break; + case 'r': /* Incoming socket messages received. */ + fprintf(fp, "%ld", resp->ru.ru_msgrcv); + break; + case 's': /* Outgoing socket messages sent. */ + fprintf(fp, "%ld", resp->ru.ru_msgsnd); + break; + case 't': /* Average resident set size. */ + fprintf(fp, "%lu", + MSEC_TO_TICKS(v) == 0 ? 0 : + ptok((UL) resp->ru.ru_idrss) / MSEC_TO_TICKS(v)); + break; + case 'w': /* Voluntary context switches. */ + fprintf(fp, "%ld", resp->ru.ru_nvcsw); + break; + case 'x': /* Exit status. */ + fprintf(fp, "%d", WEXITSTATUS(resp->waitstatus)); + break; + case '\0': + putc('?', fp); + return; + default: + putc('?', fp); + putc(*fmt, fp); + } + ++fmt; + break; + + case '\\': /* Format escape. */ + switch (*++fmt) { + case 't': + putc('\t', fp); + break; + case 'n': + putc('\n', fp); + break; + case '\\': + putc('\\', fp); + break; + default: + putc('?', fp); + putc('\\', fp); + putc(*fmt, fp); + } + ++fmt; + break; + + default: + putc(*fmt++, fp); + } + + if (ferror(fp)) + bb_error_msg_and_die(bb_msg_write_error); + } + putc('\n', fp); + + if (ferror(fp)) + bb_error_msg_and_die(bb_msg_write_error); +} + +/* 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. */ + __sighandler_t interrupt_signal, quit_signal; + + gettimeofday(&resp->start, (struct timezone *) 0); + pid = vfork(); /* Run CMD as child process. */ + if (pid < 0) + bb_error_msg_and_die("cannot fork"); + else 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. */ + execvp(cmd[0], cmd); + bb_error_msg("cannot run %s", cmd[0]); + _exit(errno == ENOENT ? 127 : 126); + } + + /* Have signals kill the child but not self (if possible). */ + interrupt_signal = signal(SIGINT, SIG_IGN); + quit_signal = signal(SIGQUIT, SIG_IGN); + + if (resuse_end(pid, resp) == 0) + bb_error_msg("error waiting for child process"); + + /* Re-enable signals. */ + signal(SIGINT, interrupt_signal); + signal(SIGQUIT, quit_signal); +} + +int time_main(int argc, char **argv) +{ + int gotone; + resource_t res; + const char *output_format = default_format; + + argc--; + argv++; + /* Parse any options -- don't use getopt() here so we don't + * consume the args of our client application... */ + while (argc > 0 && **argv == '-') { + gotone = 0; + while (gotone == 0 && *++(*argv)) { + switch (**argv) { + case 'v': + output_format = long_format; + break; + case 'p': + output_format = posix_format; + break; + default: + bb_show_usage(); + } + argc--; + argv++; + gotone = 1; + } + } + + if (argv == NULL || *argv == NULL) + bb_show_usage(); + + run_command(argv, &res); + summarize(stderr, output_format, argv, &res); + fflush(stderr); + + if (WIFSTOPPED(res.waitstatus)) + exit(WSTOPSIG(res.waitstatus)); + else if (WIFSIGNALED(res.waitstatus)) + exit(WTERMSIG(res.waitstatus)); + else if (WIFEXITED(res.waitstatus)) + exit(WEXITSTATUS(res.waitstatus)); + return 0; +} diff --git a/miscutils/watchdog.c b/miscutils/watchdog.c new file mode 100644 index 000000000..e342c13f3 --- /dev/null +++ b/miscutils/watchdog.c @@ -0,0 +1,65 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini watchdog implementation for busybox + * + * Copyright (C) 2003 Paul Mundt + * Copyright (C) 2006 Bernhard Fischer + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" + +#define OPT_FOREGROUND 0x01 +#define OPT_TIMER 0x02 + +/* Watchdog file descriptor */ +static int fd; + +static void watchdog_shutdown(int ATTRIBUTE_UNUSED unused) +{ + write(fd, "V", 1); /* Magic, see watchdog-api.txt in kernel */ + close(fd); + exit(0); +} + +int watchdog_main(int argc, char **argv) +{ + unsigned opts; + unsigned timer_duration = 30; /* Userspace timer duration, in seconds */ + char *t_arg; + + opts = getopt32(argc, argv, "Ft:", &t_arg); + + if (opts & OPT_TIMER) + timer_duration = xatou(t_arg); + + /* We're only interested in the watchdog device .. */ + if (optind < argc - 1 || argc == 1) + bb_show_usage(); + +#ifdef BB_NOMMU + if (!(opts & OPT_FOREGROUND)) + vfork_daemon_rexec(0, 1, argc, argv, "-F"); +#else + xdaemon(0, 1); +#endif + + signal(SIGHUP, watchdog_shutdown); + signal(SIGINT, watchdog_shutdown); + + fd = xopen(argv[argc - 1], O_WRONLY); + + while (1) { + /* + * Make sure we clear the counter before sleeping, as the counter value + * is undefined at this point -- PFM + */ + write(fd, "\0", 1); + sleep(timer_duration); + } + + watchdog_shutdown(0); + + return EXIT_SUCCESS; +} diff --git a/modutils/Config.in b/modutils/Config.in new file mode 100644 index 000000000..6a6956755 --- /dev/null +++ b/modutils/Config.in @@ -0,0 +1,158 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Linux Module Utilities" + +config INSMOD + bool "insmod" + default n + help + insmod is used to load specified modules in the running kernel. + +config FEATURE_INSMOD_VERSION_CHECKING + bool "Module version checking" + default n + depends on INSMOD && FEATURE_2_4_MODULES + help + Support checking of versions for modules. This is used to + ensure that the kernel and module are made for each other. + +config FEATURE_INSMOD_KSYMOOPS_SYMBOLS + bool "Add module symbols to kernel symbol table" + default n + depends on INSMOD && FEATURE_2_4_MODULES + help + By adding module symbols to the kernel symbol table, Oops messages + occuring within kernel modules can be properly debugged. By enabling + this feature, module symbols will always be added to the kernel symbol + table for properly debugging support. If you are not interested in + Oops messages from kernel modules, say N. + +config FEATURE_INSMOD_LOADINKMEM + bool "In kernel memory optimization (uClinux only)" + default n + depends on INSMOD && FEATURE_2_4_MODULES + help + This is a special uClinux only memory optimization that lets insmod + load the specified kernel module directly into kernel space, reducing + memory usage by preventing the need for two copies of the module + being loaded into memory. + +config FEATURE_INSMOD_LOAD_MAP + bool "Enable load map (-m) option" + default n + depends on INSMOD && ( FEATURE_2_4_MODULES || FEATURE_2_6_MODULES ) + help + Enabling this, one would be able to get a load map + output on stdout. This makes kernel module debugging + easier. + If you don't plan to debug kernel modules, you + don't need this option. + +config FEATURE_INSMOD_LOAD_MAP_FULL + bool "Symbols in load map" + default y + depends on FEATURE_INSMOD_LOAD_MAP + help + Without this option, -m will only output section + load map. With this option, -m will also output + symbols load map. + +config RMMOD + bool "rmmod" + default n + help + rmmod is used to unload specified modules from the kernel. + +config LSMOD + bool "lsmod" + default n + help + lsmod is used to display a list of loaded modules. + +config FEATURE_LSMOD_PRETTY_2_6_OUTPUT + bool "lsmod pretty output for 2.6.x Linux kernels " + default n + depends on LSMOD + help + This option makes output format of lsmod adjusted to + the format of module-init-tools for Linux kernel 2.6. + +config MODPROBE + bool "modprobe" + default n + help + Handle the loading of modules, and their dependencies on a high + level. + + Note that in the state, modprobe does not understand multiple + module options from the configuration file. See option below. + +config FEATURE_MODPROBE_MULTIPLE_OPTIONS + bool + prompt "Multiple options parsing" if NITPICK + default y + depends on MODPROBE + help + Allow modprobe to understand more than one option to pass to + modules. + + This is a WIP, while waiting for a common argument parsing + common amongst all BB applets (shell, modprobe, etc...) and + adds around 600 bytes on x86, 700 bytes on ARM. The code is + biggish and uggly, but just works. + + Saying Y here is not a bad idea if you're not that short + on storage capacity. + +config FEATURE_MODPROBE_FANCY_ALIAS + bool + prompt "Fancy alias parsing" if NITPICK + default y + depends on MODPROBE && FEATURE_2_6_MODULES + help + Say 'y' here to enable parsing of aliases with underscore/dash + mismatch between module name and file name, along with bus-specific + aliases (such as pci:... or usb:... aliases). + +comment "Options common to multiple modutils" + depends on INSMOD || RMMOD || MODPROBE || LSMOD + +config FEATURE_CHECK_TAINTED_MODULE + # Simulate indentation + bool "Support tainted module checking with new kernels" + default y + depends on INSMOD || LSMOD + help + Support checking for tainted modules. These are usually binary + only modules that will make the linux-kernel list ignore your + support request. + This option is required to support GPLONLY modules. + +config FEATURE_2_4_MODULES + # Simulate indentation + bool "Support version 2.2.x to 2.4.x Linux kernels" + default y + depends on INSMOD || RMMOD || MODPROBE + help + Support module loading for 2.2.x and 2.4.x Linux kernels. + +config FEATURE_2_6_MODULES + # Simulate indentation + bool "Support version 2.6.x Linux kernels" + default y + depends on INSMOD || RMMOD || MODPROBE + help + Support module loading for newer 2.6.x Linux kernels. + + +config FEATURE_QUERY_MODULE_INTERFACE + bool + default y + depends on FEATURE_2_4_MODULES && !FEATURE_2_6_MODULES + + +endmenu + diff --git a/modutils/Kbuild b/modutils/Kbuild new file mode 100644 index 000000000..cff02b4f2 --- /dev/null +++ b/modutils/Kbuild @@ -0,0 +1,11 @@ +# 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_INSMOD) += insmod.o +lib-$(CONFIG_LSMOD) += lsmod.o +lib-$(CONFIG_MODPROBE) += modprobe.o +lib-$(CONFIG_RMMOD) += rmmod.o diff --git a/modutils/insmod.c b/modutils/insmod.c new file mode 100644 index 000000000..7b715b9c3 --- /dev/null +++ b/modutils/insmod.c @@ -0,0 +1,4316 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini insmod implementation for busybox + * + * This version of insmod supports ARM, CRIS, H8/300, x86, ia64, x86_64, + * m68k, MIPS, PowerPC, S390, SH3/4/5, Sparc, v850e, and x86_64. + * + * Copyright (C) 1999-2004 by Erik Andersen + * and Ron Alder + * + * Rodney Radford 17-Aug-2004. + * Added x86_64 support. + * + * Miles Bader added NEC V850E support. + * + * Modified by Bryan Rittmeyer to support SH4 + * and (theoretically) SH3. I have only tested SH4 in little endian mode. + * + * Modified by Alcove, Julien Gaulmin and + * Nicolas Ferre to support ARM7TDMI. Only + * very minor changes required to also work with StrongArm and presumably + * all ARM based systems. + * + * Yoshinori Sato 19-May-2004. + * added Renesas H8/300 support. + * + * Paul Mundt 08-Aug-2003. + * Integrated support for sh64 (SH-5), from preliminary modutils + * patches from Benedict Gaster . + * Currently limited to support for 32bit ABI. + * + * Magnus Damm 22-May-2002. + * The plt and got code are now using the same structs. + * Added generic linked list code to fully support PowerPC. + * Replaced the mess in arch_apply_relocation() with architecture blocks. + * The arch_create_got() function got cleaned up with architecture blocks. + * These blocks should be easy maintain and sync with obj_xxx.c in modutils. + * + * Magnus Damm added PowerPC support 20-Feb-2001. + * PowerPC specific code stolen from modutils-2.3.16, + * written by Paul Mackerras, Copyright 1996, 1997 Linux International. + * I've only tested the code on mpc8xx platforms in big-endian mode. + * Did some cleanup and added USE_xxx_ENTRIES... + * + * Quinn Jensen added MIPS support 23-Feb-2001. + * based on modutils-2.4.2 + * MIPS specific support for Elf loading and relocation. + * Copyright 1996, 1997 Linux International. + * Contributed by Ralf Baechle + * + * Based almost entirely on the Linux modutils-2.3.11 implementation. + * Copyright 1996, 1997 Linux International. + * New implementation contributed by Richard Henderson + * Based on original work by Bjorn Ekwall + * Restructured (and partly rewritten) by: + * Björn Ekwall February 1999 + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include +#include + +#if !ENABLE_FEATURE_2_4_MODULES && !ENABLE_FEATURE_2_6_MODULES +#define ENABLE_FEATURE_2_4_MODULES 1 +#endif + +#if !ENABLE_FEATURE_2_4_MODULES +#define insmod_ng_main insmod_main +#endif + +#if ENABLE_FEATURE_2_6_MODULES +extern int insmod_ng_main( int argc, char **argv); +#endif + + +#if ENABLE_FEATURE_2_4_MODULES + + +#if ENABLE_FEATURE_INSMOD_LOADINKMEM +#define LOADBITS 0 +#else +#define LOADBITS 1 +#endif + + +/* Alpha */ +#if defined(__alpha__) +#define MATCH_MACHINE(x) (x == EM_ALPHA) +#define SHT_RELM SHT_RELA +#define Elf64_RelM Elf64_Rela +#define ELFCLASSM ELFCLASS64 +#endif + +/* ARM support */ +#if defined(__arm__) +#define MATCH_MACHINE(x) (x == EM_ARM) +#define SHT_RELM SHT_REL +#define Elf32_RelM Elf32_Rel +#define ELFCLASSM ELFCLASS32 +#define USE_PLT_ENTRIES +#define PLT_ENTRY_SIZE 8 +#define USE_GOT_ENTRIES +#define GOT_ENTRY_SIZE 8 +#define USE_SINGLE +#endif + +/* blackfin */ +#if defined(BFIN) +#define MATCH_MACHINE(x) (x == EM_BLACKFIN) +#define SHT_RELM SHT_RELA +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#endif + +/* CRIS */ +#if defined(__cris__) +#define MATCH_MACHINE(x) (x == EM_CRIS) +#define SHT_RELM SHT_RELA +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#ifndef EM_CRIS +#define EM_CRIS 76 +#define R_CRIS_NONE 0 +#define R_CRIS_32 3 +#endif +#endif + +/* H8/300 */ +#if defined(__H8300H__) || defined(__H8300S__) +#define MATCH_MACHINE(x) (x == EM_H8_300) +#define SHT_RELM SHT_RELA +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#define USE_SINGLE +#define SYMBOL_PREFIX "_" +#endif + +/* PA-RISC / HP-PA */ +#if defined(__hppa__) +#define MATCH_MACHINE(x) (x == EM_PARISC) +#define SHT_RELM SHT_RELA +#if defined(__LP64__) +#define Elf64_RelM Elf64_Rela +#define ELFCLASSM ELFCLASS64 +#else +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#endif +#endif + +/* x86 */ +#if defined(__i386__) +#ifndef EM_486 +#define MATCH_MACHINE(x) (x == EM_386) +#else +#define MATCH_MACHINE(x) (x == EM_386 || x == EM_486) +#endif +#define SHT_RELM SHT_REL +#define Elf32_RelM Elf32_Rel +#define ELFCLASSM ELFCLASS32 +#define USE_GOT_ENTRIES +#define GOT_ENTRY_SIZE 4 +#define USE_SINGLE +#endif + +/* IA64, aka Itanium */ +#if defined(__ia64__) +#define MATCH_MACHINE(x) (x == EM_IA_64) +#define SHT_RELM SHT_RELA +#define Elf64_RelM Elf64_Rela +#define ELFCLASSM ELFCLASS64 +#endif + +/* m68k */ +#if defined(__mc68000__) +#define MATCH_MACHINE(x) (x == EM_68K) +#define SHT_RELM SHT_RELA +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#define USE_GOT_ENTRIES +#define GOT_ENTRY_SIZE 4 +#define USE_SINGLE +#endif + +/* Microblaze */ +#if defined(__microblaze__) +#define USE_SINGLE +#define MATCH_MACHINE(x) (x == EM_XILINX_MICROBLAZE) +#define SHT_RELM SHT_RELA +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#endif + +/* MIPS */ +#if defined(__mips__) +#define MATCH_MACHINE(x) (x == EM_MIPS || x == EM_MIPS_RS3_LE) +#define SHT_RELM SHT_REL +#define Elf32_RelM Elf32_Rel +#define ELFCLASSM ELFCLASS32 +/* Account for ELF spec changes. */ +#ifndef EM_MIPS_RS3_LE +#ifdef EM_MIPS_RS4_BE +#define EM_MIPS_RS3_LE EM_MIPS_RS4_BE +#else +#define EM_MIPS_RS3_LE 10 +#endif +#endif /* !EM_MIPS_RS3_LE */ +#define ARCHDATAM "__dbe_table" +#endif + +/* Nios II */ +#if defined(__nios2__) +#define MATCH_MACHINE(x) (x == EM_ALTERA_NIOS2) +#define SHT_RELM SHT_RELA +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#endif + +/* PowerPC */ +#if defined(__powerpc64__) +#define MATCH_MACHINE(x) (x == EM_PPC64) +#define SHT_RELM SHT_RELA +#define Elf64_RelM Elf64_Rela +#define ELFCLASSM ELFCLASS64 +#elif defined(__powerpc__) +#define MATCH_MACHINE(x) (x == EM_PPC) +#define SHT_RELM SHT_RELA +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#define USE_PLT_ENTRIES +#define PLT_ENTRY_SIZE 16 +#define USE_PLT_LIST +#define LIST_ARCHTYPE ElfW(Addr) +#define USE_LIST +#define ARCHDATAM "__ftr_fixup" +#endif + +/* S390 */ +#if defined(__s390__) +#define MATCH_MACHINE(x) (x == EM_S390) +#define SHT_RELM SHT_RELA +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#define USE_PLT_ENTRIES +#define PLT_ENTRY_SIZE 8 +#define USE_GOT_ENTRIES +#define GOT_ENTRY_SIZE 8 +#define USE_SINGLE +#endif + +/* SuperH */ +#if defined(__sh__) +#define MATCH_MACHINE(x) (x == EM_SH) +#define SHT_RELM SHT_RELA +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#define USE_GOT_ENTRIES +#define GOT_ENTRY_SIZE 4 +#define USE_SINGLE +/* the SH changes have only been tested in =little endian= mode */ +/* I'm not sure about big endian, so let's warn: */ +#if defined(__sh__) && BB_BIG_ENDIAN +# error insmod.c may require changes for use on big endian SH +#endif +/* it may or may not work on the SH1/SH2... Error on those also */ +#if ((!(defined(__SH3__) || defined(__SH4__) || defined(__SH5__)))) && (defined(__sh__)) +#error insmod.c may require changes for SH1 or SH2 use +#endif +#endif + +/* Sparc */ +#if defined(__sparc__) +#define MATCH_MACHINE(x) (x == EM_SPARC) +#define SHT_RELM SHT_RELA +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#endif + +/* v850e */ +#if defined (__v850e__) +#define MATCH_MACHINE(x) ((x) == EM_V850 || (x) == EM_CYGNUS_V850) +#define SHT_RELM SHT_RELA +#define Elf32_RelM Elf32_Rela +#define ELFCLASSM ELFCLASS32 +#define USE_PLT_ENTRIES +#define PLT_ENTRY_SIZE 8 +#define USE_SINGLE +#ifndef EM_CYGNUS_V850 /* grumble */ +#define EM_CYGNUS_V850 0x9080 +#endif +#define SYMBOL_PREFIX "_" +#endif + +/* X86_64 */ +#if defined(__x86_64__) +#define MATCH_MACHINE(x) (x == EM_X86_64) +#define SHT_RELM SHT_RELA +#define USE_GOT_ENTRIES +#define GOT_ENTRY_SIZE 8 +#define USE_SINGLE +#define Elf64_RelM Elf64_Rela +#define ELFCLASSM ELFCLASS64 +#endif + +#ifndef SHT_RELM +#error Sorry, but insmod.c does not yet support this architecture... +#endif + + +//---------------------------------------------------------------------------- +//--------modutils module.h, lines 45-242 +//---------------------------------------------------------------------------- + +/* Definitions for the Linux module syscall interface. + Copyright 1996, 1997 Linux International. + + Contributed by Richard Henderson + + This file is part of the Linux modutils. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + + +#ifndef MODUTILS_MODULE_H +/* Why? static const int MODUTILS_MODULE_H = 1;*/ + +/*======================================================================*/ +/* For sizeof() which are related to the module platform and not to the + environment isnmod is running in, use sizeof_xx instead of sizeof(xx). */ + +#define tgt_sizeof_char sizeof(char) +#define tgt_sizeof_short sizeof(short) +#define tgt_sizeof_int sizeof(int) +#define tgt_sizeof_long sizeof(long) +#define tgt_sizeof_char_p sizeof(char *) +#define tgt_sizeof_void_p sizeof(void *) +#define tgt_long long + +#if defined(__sparc__) && !defined(__sparc_v9__) && defined(ARCH_sparc64) +#undef tgt_sizeof_long +#undef tgt_sizeof_char_p +#undef tgt_sizeof_void_p +#undef tgt_long +enum { + tgt_sizeof_long = 8, + tgt_sizeof_char_p = 8, + tgt_sizeof_void_p = 8 +}; +#define tgt_long long long +#endif + +/*======================================================================*/ +/* The structures used in Linux 2.1. */ + +/* Note: new_module_symbol does not use tgt_long intentionally */ +struct new_module_symbol { + unsigned long value; + unsigned long name; +}; + +struct new_module_persist; + +struct new_module_ref { + unsigned tgt_long dep; /* kernel addresses */ + unsigned tgt_long ref; + unsigned tgt_long next_ref; +}; + +struct new_module { + unsigned tgt_long size_of_struct; /* == sizeof(module) */ + unsigned tgt_long next; + unsigned tgt_long name; + unsigned tgt_long size; + + tgt_long usecount; + unsigned tgt_long flags; /* AUTOCLEAN et al */ + + unsigned nsyms; + unsigned ndeps; + + unsigned tgt_long syms; + unsigned tgt_long deps; + unsigned tgt_long refs; + unsigned tgt_long init; + unsigned tgt_long cleanup; + unsigned tgt_long ex_table_start; + unsigned tgt_long ex_table_end; +#ifdef __alpha__ + unsigned tgt_long gp; +#endif + /* Everything after here is extension. */ + unsigned tgt_long persist_start; + unsigned tgt_long persist_end; + unsigned tgt_long can_unload; + unsigned tgt_long runsize; + const char *kallsyms_start; /* All symbols for kernel debugging */ + const char *kallsyms_end; + const char *archdata_start; /* arch specific data for module */ + const char *archdata_end; + const char *kernel_data; /* Reserved for kernel internal use */ +}; + +#ifdef ARCHDATAM +#define ARCHDATA_SEC_NAME ARCHDATAM +#else +#define ARCHDATA_SEC_NAME "__archdata" +#endif +#define KALLSYMS_SEC_NAME "__kallsyms" + + +struct new_module_info { + unsigned long addr; + unsigned long size; + unsigned long flags; + long usecount; +}; + +/* Bits of module.flags. */ +enum { + NEW_MOD_RUNNING = 1, + NEW_MOD_DELETED = 2, + NEW_MOD_AUTOCLEAN = 4, + NEW_MOD_VISITED = 8, + NEW_MOD_USED_ONCE = 16 +}; + +int init_module(const char *name, const struct new_module *); +int query_module(const char *name, int which, void *buf, + size_t bufsize, size_t *ret); + +/* Values for query_module's which. */ +enum { + QM_MODULES = 1, + QM_DEPS = 2, + QM_REFS = 3, + QM_SYMBOLS = 4, + QM_INFO = 5 +}; + +/*======================================================================*/ +/* The system calls unchanged between 2.0 and 2.1. */ + +unsigned long create_module(const char *, size_t); +int delete_module(const char *); + + +#endif /* module.h */ + +//---------------------------------------------------------------------------- +//--------end of modutils module.h +//---------------------------------------------------------------------------- + + + +//---------------------------------------------------------------------------- +//--------modutils obj.h, lines 253-462 +//---------------------------------------------------------------------------- + +/* Elf object file loading and relocation routines. + Copyright 1996, 1997 Linux International. + + Contributed by Richard Henderson + + This file is part of the Linux modutils. + + 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + + +#ifndef MODUTILS_OBJ_H +/* Why? static const int MODUTILS_OBJ_H = 1; */ + +/* The relocatable object is manipulated using elfin types. */ + +#include +#include +#include + +#ifndef ElfW +# if ELFCLASSM == ELFCLASS32 +# define ElfW(x) Elf32_ ## x +# define ELFW(x) ELF32_ ## x +# else +# define ElfW(x) Elf64_ ## x +# define ELFW(x) ELF64_ ## x +# endif +#endif + +/* For some reason this is missing from some ancient C libraries.... */ +#ifndef ELF32_ST_INFO +# define ELF32_ST_INFO(bind, type) (((bind) << 4) + ((type) & 0xf)) +#endif + +#ifndef ELF64_ST_INFO +# define ELF64_ST_INFO(bind, type) (((bind) << 4) + ((type) & 0xf)) +#endif + +#define ELF_ST_BIND(info) ELFW(ST_BIND)(info) +#define ELF_ST_TYPE(info) ELFW(ST_TYPE)(info) +#define ELF_ST_INFO(bind, type) ELFW(ST_INFO)(bind, type) +#define ELF_R_TYPE(val) ELFW(R_TYPE)(val) +#define ELF_R_SYM(val) ELFW(R_SYM)(val) + +struct obj_string_patch; +struct obj_symbol_patch; + +struct obj_section +{ + ElfW(Shdr) header; + const char *name; + char *contents; + struct obj_section *load_next; + int idx; +}; + +struct obj_symbol +{ + struct obj_symbol *next; /* hash table link */ + const char *name; + unsigned long value; + unsigned long size; + int secidx; /* the defining section index/module */ + int info; + int ksymidx; /* for export to the kernel symtab */ + int referenced; /* actually used in the link */ +}; + +/* Hardcode the hash table size. We shouldn't be needing so many + symbols that we begin to degrade performance, and we get a big win + by giving the compiler a constant divisor. */ + +#define HASH_BUCKETS 521 + +struct obj_file { + ElfW(Ehdr) header; + ElfW(Addr) baseaddr; + struct obj_section **sections; + struct obj_section *load_order; + struct obj_section **load_order_search_start; + struct obj_string_patch *string_patches; + struct obj_symbol_patch *symbol_patches; + int (*symbol_cmp)(const char *, const char *); + unsigned long (*symbol_hash)(const char *); + unsigned long local_symtab_size; + struct obj_symbol **local_symtab; + struct obj_symbol *symtab[HASH_BUCKETS]; +}; + +enum obj_reloc { + obj_reloc_ok, + obj_reloc_overflow, + obj_reloc_dangerous, + obj_reloc_unhandled +}; + +struct obj_string_patch { + struct obj_string_patch *next; + int reloc_secidx; + ElfW(Addr) reloc_offset; + ElfW(Addr) string_offset; +}; + +struct obj_symbol_patch { + struct obj_symbol_patch *next; + int reloc_secidx; + ElfW(Addr) reloc_offset; + struct obj_symbol *sym; +}; + + +/* Generic object manipulation routines. */ + +static unsigned long obj_elf_hash(const char *); + +static unsigned long obj_elf_hash_n(const char *, unsigned long len); + +static struct obj_symbol *obj_find_symbol (struct obj_file *f, + const char *name); + +static ElfW(Addr) obj_symbol_final_value(struct obj_file *f, + struct obj_symbol *sym); + +#if ENABLE_FEATURE_INSMOD_VERSION_CHECKING +static void obj_set_symbol_compare(struct obj_file *f, + int (*cmp)(const char *, const char *), + unsigned long (*hash)(const char *)); +#endif + +static struct obj_section *obj_find_section (struct obj_file *f, + const char *name); + +static void obj_insert_section_load_order (struct obj_file *f, + struct obj_section *sec); + +static struct obj_section *obj_create_alloced_section (struct obj_file *f, + const char *name, + unsigned long align, + unsigned long size); + +static struct obj_section *obj_create_alloced_section_first (struct obj_file *f, + const char *name, + unsigned long align, + unsigned long size); + +static void *obj_extend_section (struct obj_section *sec, unsigned long more); + +static int obj_string_patch(struct obj_file *f, int secidx, ElfW(Addr) offset, + const char *string); + +static int obj_symbol_patch(struct obj_file *f, int secidx, ElfW(Addr) offset, + struct obj_symbol *sym); + +static int obj_check_undefineds(struct obj_file *f); + +static void obj_allocate_commons(struct obj_file *f); + +static unsigned long obj_load_size (struct obj_file *f); + +static int obj_relocate (struct obj_file *f, ElfW(Addr) base); + +static struct obj_file *obj_load(FILE *f, int loadprogbits); + +static int obj_create_image (struct obj_file *f, char *image); + +/* Architecture specific manipulation routines. */ + +static struct obj_file *arch_new_file (void); + +static struct obj_section *arch_new_section (void); + +static struct obj_symbol *arch_new_symbol (void); + +static enum obj_reloc arch_apply_relocation (struct obj_file *f, + struct obj_section *targsec, + struct obj_section *symsec, + struct obj_symbol *sym, + ElfW(RelM) *rel, ElfW(Addr) value); + +static void arch_create_got (struct obj_file *f); +#if ENABLE_FEATURE_CHECK_TAINTED_MODULE +static int obj_gpl_license(struct obj_file *f, const char **license); +#endif /* FEATURE_CHECK_TAINTED_MODULE */ +#endif /* obj.h */ +//---------------------------------------------------------------------------- +//--------end of modutils obj.h +//---------------------------------------------------------------------------- + + +/* SPFX is always a string, so it can be concatenated to string constants. */ +#ifdef SYMBOL_PREFIX +#define SPFX SYMBOL_PREFIX +#else +#define SPFX "" +#endif + + +#define _PATH_MODULES "/lib/modules" +enum { STRVERSIONLEN = 64 }; + +/*======================================================================*/ + +#define OPTION_STR "sLo:fkvqx" USE_FEATURE_INSMOD_LOAD_MAP("m") +enum { + OPT_s = 0x1, // -s /* log to syslog */ + /* Not supported but kernel needs this for request_module(), + as this calls: modprobe -k -s -- + so silently ignore this flag */ + OPT_L = 0x2, // -L /* Stub warning */ + /* Compatibility with modprobe. + In theory, this does locking, but we don't do + that. So be careful and plan your life around not + loading the same module 50 times concurrently. */ + OPT_o = 0x4, // -o /* name the output module */ + OPT_f = 0x8, // -f /* force loading */ + OPT_k = 0x10, // -k /* module loaded by kerneld, auto-cleanable */ + OPT_v = 0x20, // -v /* verbose output */ + OPT_q = 0x40, // -q /* silent */ + OPT_x = 0x80, // -x /* do not export externs */ + OPT_m = 0x100, // -m /* print module load map */ +}; +#define flag_force_load (option_mask32 & OPT_f) +#define flag_autoclean (option_mask32 & OPT_k) +#define flag_verbose (option_mask32 & OPT_v) +#define flag_quiet (option_mask32 & OPT_q) +#define flag_noexport (option_mask32 & OPT_x) +#if ENABLE_FEATURE_INSMOD_LOAD_MAP +#define flag_print_load_map (option_mask32 & OPT_m) +#else +#define flag_print_load_map 0 +#endif + +/*======================================================================*/ + +#if defined(USE_LIST) + +struct arch_list_entry +{ + struct arch_list_entry *next; + LIST_ARCHTYPE addend; + int offset; + int inited : 1; +}; + +#endif + +#if defined(USE_SINGLE) + +struct arch_single_entry +{ + int offset; + int inited : 1; + int allocated : 1; +}; + +#endif + +#if defined(__mips__) +struct mips_hi16 +{ + struct mips_hi16 *next; + ElfW(Addr) *addr; + ElfW(Addr) value; +}; +#endif + +struct arch_file { + struct obj_file root; +#if defined(USE_PLT_ENTRIES) + struct obj_section *plt; +#endif +#if defined(USE_GOT_ENTRIES) + struct obj_section *got; +#endif +#if defined(__mips__) + struct mips_hi16 *mips_hi16_list; +#endif +}; + +struct arch_symbol { + struct obj_symbol root; +#if defined(USE_PLT_ENTRIES) +#if defined(USE_PLT_LIST) + struct arch_list_entry *pltent; +#else + struct arch_single_entry pltent; +#endif +#endif +#if defined(USE_GOT_ENTRIES) + struct arch_single_entry gotent; +#endif +}; + + +struct external_module { + const char *name; + ElfW(Addr) addr; + int used; + size_t nsyms; + struct new_module_symbol *syms; +}; + +static struct new_module_symbol *ksyms; +static size_t nksyms; + +static struct external_module *ext_modules; +static int n_ext_modules; +static int n_ext_modules_used; +extern int delete_module(const char *); + +static char *m_filename; +static char *m_fullName; + + +/*======================================================================*/ + + +static int check_module_name_match(const char *filename, struct stat *statbuf, + void *userdata, int depth) +{ + char *fullname = (char *) userdata; + + if (fullname[0] == '\0') + return FALSE; + else { + char *tmp, *tmp1 = xstrdup(filename); + tmp = bb_get_last_path_component(tmp1); + if (strcmp(tmp, fullname) == 0) { + free(tmp1); + /* Stop searching if we find a match */ + m_filename = xstrdup(filename); + return FALSE; + } + free(tmp1); + } + return TRUE; +} + + +/*======================================================================*/ + +static struct obj_file *arch_new_file(void) +{ + struct arch_file *f; + f = xmalloc(sizeof(*f)); + + memset(f, 0, sizeof(*f)); + + return &f->root; +} + +static struct obj_section *arch_new_section(void) +{ + return xmalloc(sizeof(struct obj_section)); +} + +static struct obj_symbol *arch_new_symbol(void) +{ + struct arch_symbol *sym; + sym = xmalloc(sizeof(*sym)); + + memset(sym, 0, sizeof(*sym)); + + return &sym->root; +} + +static enum obj_reloc +arch_apply_relocation(struct obj_file *f, + struct obj_section *targsec, + struct obj_section *symsec, + struct obj_symbol *sym, + ElfW(RelM) *rel, ElfW(Addr) v) +{ + struct arch_file *ifile = (struct arch_file *) f; + enum obj_reloc ret = obj_reloc_ok; + ElfW(Addr) *loc = (ElfW(Addr) *) (targsec->contents + rel->r_offset); + ElfW(Addr) dot = targsec->header.sh_addr + rel->r_offset; +#if defined(USE_GOT_ENTRIES) || defined(USE_PLT_ENTRIES) + struct arch_symbol *isym = (struct arch_symbol *) sym; +#endif +#if defined(__arm__) || defined(__i386__) || defined(__mc68000__) || defined(__sh__) || defined(__s390__) +#if defined(USE_GOT_ENTRIES) + ElfW(Addr) got = ifile->got ? ifile->got->header.sh_addr : 0; +#endif +#endif +#if defined(USE_PLT_ENTRIES) + ElfW(Addr) plt = ifile->plt ? ifile->plt->header.sh_addr : 0; + unsigned long *ip; +# if defined(USE_PLT_LIST) + struct arch_list_entry *pe; +# else + struct arch_single_entry *pe; +# endif +#endif + + switch (ELF_R_TYPE(rel->r_info)) { + +#if defined(__arm__) + + case R_ARM_NONE: + break; + + case R_ARM_ABS32: + *loc += v; + break; + + case R_ARM_GOT32: + goto bb_use_got; + + case R_ARM_GOTPC: + /* relative reloc, always to _GLOBAL_OFFSET_TABLE_ + * (which is .got) similar to branch, + * but is full 32 bits relative */ + + *loc += got - dot; + break; + + case R_ARM_PC24: + case R_ARM_PLT32: + goto bb_use_plt; + + case R_ARM_GOTOFF: /* address relative to the got */ + *loc += v - got; + break; + +#elif defined(__cris__) + + case R_CRIS_NONE: + break; + + case R_CRIS_32: + /* CRIS keeps the relocation value in the r_addend field and + * should not use whats in *loc at all + */ + *loc = v; + break; + +#elif defined(__H8300H__) || defined(__H8300S__) + + case R_H8_DIR24R8: + loc = (ElfW(Addr) *)((ElfW(Addr))loc - 1); + *loc = (*loc & 0xff000000) | ((*loc & 0xffffff) + v); + break; + case R_H8_DIR24A8: + *loc += v; + break; + case R_H8_DIR32: + case R_H8_DIR32A16: + *loc += v; + break; + case R_H8_PCREL16: + v -= dot + 2; + if ((ElfW(Sword))v > 0x7fff || + (ElfW(Sword))v < -(ElfW(Sword))0x8000) + ret = obj_reloc_overflow; + else + *(unsigned short *)loc = v; + break; + case R_H8_PCREL8: + v -= dot + 1; + if ((ElfW(Sword))v > 0x7f || + (ElfW(Sword))v < -(ElfW(Sword))0x80) + ret = obj_reloc_overflow; + else + *(unsigned char *)loc = v; + break; + +#elif defined(__i386__) + + case R_386_NONE: + break; + + case R_386_32: + *loc += v; + break; + + case R_386_PLT32: + case R_386_PC32: + *loc += v - dot; + break; + + case R_386_GLOB_DAT: + case R_386_JMP_SLOT: + *loc = v; + break; + + case R_386_RELATIVE: + *loc += f->baseaddr; + break; + + case R_386_GOTPC: + *loc += got - dot; + break; + + case R_386_GOT32: + goto bb_use_got; + + case R_386_GOTOFF: + *loc += v - got; + break; + +#elif defined (__microblaze__) + case R_MICROBLAZE_NONE: + case R_MICROBLAZE_64_NONE: + case R_MICROBLAZE_32_SYM_OP_SYM: + case R_MICROBLAZE_32_PCREL: + break; + + case R_MICROBLAZE_64_PCREL: { + /* dot is the address of the current instruction. + * v is the target symbol address. + * So we need to extract the offset in the code, + * adding v, then subtrating the current address + * of this instruction. + * Ex: "IMM 0xFFFE bralid 0x0000" = "bralid 0xFFFE0000" + */ + + /* Get split offset stored in code */ + unsigned int temp = (loc[0] & 0xFFFF) << 16 | + (loc[1] & 0xFFFF); + + /* Adjust relative offset. -4 adjustment required + * because dot points to the IMM insn, but branch + * is computed relative to the branch instruction itself. + */ + temp += v - dot - 4; + + /* Store back into code */ + loc[0] = (loc[0] & 0xFFFF0000) | temp >> 16; + loc[1] = (loc[1] & 0xFFFF0000) | (temp & 0xFFFF); + + break; + } + + case R_MICROBLAZE_32: + *loc += v; + break; + + case R_MICROBLAZE_64: { + /* Get split pointer stored in code */ + unsigned int temp1 = (loc[0] & 0xFFFF) << 16 | + (loc[1] & 0xFFFF); + + /* Add reloc offset */ + temp1+=v; + + /* Store back into code */ + loc[0] = (loc[0] & 0xFFFF0000) | temp1 >> 16; + loc[1] = (loc[1] & 0xFFFF0000) | (temp1 & 0xFFFF); + + break; + } + + case R_MICROBLAZE_32_PCREL_LO: + case R_MICROBLAZE_32_LO: + case R_MICROBLAZE_SRO32: + case R_MICROBLAZE_SRW32: + ret = obj_reloc_unhandled; + break; + +#elif defined(__mc68000__) + + case R_68K_NONE: + break; + + case R_68K_32: + *loc += v; + break; + + case R_68K_8: + if (v > 0xff) { + ret = obj_reloc_overflow; + } + *(char *)loc = v; + break; + + case R_68K_16: + if (v > 0xffff) { + ret = obj_reloc_overflow; + } + *(short *)loc = v; + break; + + case R_68K_PC8: + v -= dot; + if ((ElfW(Sword))v > 0x7f || + (ElfW(Sword))v < -(ElfW(Sword))0x80) { + ret = obj_reloc_overflow; + } + *(char *)loc = v; + break; + + case R_68K_PC16: + v -= dot; + if ((ElfW(Sword))v > 0x7fff || + (ElfW(Sword))v < -(ElfW(Sword))0x8000) { + ret = obj_reloc_overflow; + } + *(short *)loc = v; + break; + + case R_68K_PC32: + *(int *)loc = v - dot; + break; + + case R_68K_GLOB_DAT: + case R_68K_JMP_SLOT: + *loc = v; + break; + + case R_68K_RELATIVE: + *(int *)loc += f->baseaddr; + break; + + case R_68K_GOT32: + goto bb_use_got; + +# ifdef R_68K_GOTOFF + case R_68K_GOTOFF: + *loc += v - got; + break; +# endif + +#elif defined(__mips__) + + case R_MIPS_NONE: + break; + + case R_MIPS_32: + *loc += v; + break; + + case R_MIPS_26: + if (v % 4) + ret = obj_reloc_dangerous; + if ((v & 0xf0000000) != ((dot + 4) & 0xf0000000)) + ret = obj_reloc_overflow; + *loc = + (*loc & ~0x03ffffff) | ((*loc + (v >> 2)) & + 0x03ffffff); + break; + + case R_MIPS_HI16: + { + struct mips_hi16 *n; + + /* We cannot relocate this one now because we don't know the value + of the carry we need to add. Save the information, and let LO16 + do the actual relocation. */ + n = (struct mips_hi16 *) xmalloc(sizeof *n); + n->addr = loc; + n->value = v; + n->next = ifile->mips_hi16_list; + ifile->mips_hi16_list = n; + break; + } + + case R_MIPS_LO16: + { + unsigned long insnlo = *loc; + ElfW(Addr) val, vallo; + + /* Sign extend the addend we extract from the lo insn. */ + vallo = ((insnlo & 0xffff) ^ 0x8000) - 0x8000; + + if (ifile->mips_hi16_list != NULL) { + struct mips_hi16 *l; + + l = ifile->mips_hi16_list; + while (l != NULL) { + struct mips_hi16 *next; + unsigned long insn; + + /* Do the HI16 relocation. Note that we actually don't + need to know anything about the LO16 itself, except where + to find the low 16 bits of the addend needed by the LO16. */ + insn = *l->addr; + val = + ((insn & 0xffff) << 16) + + vallo; + val += v; + + /* Account for the sign extension that will happen in the + low bits. */ + val = + ((val >> 16) + + ((val & 0x8000) != + 0)) & 0xffff; + + insn = (insn & ~0xffff) | val; + *l->addr = insn; + + next = l->next; + free(l); + l = next; + } + + ifile->mips_hi16_list = NULL; + } + + /* Ok, we're done with the HI16 relocs. Now deal with the LO16. */ + val = v + vallo; + insnlo = (insnlo & ~0xffff) | (val & 0xffff); + *loc = insnlo; + break; + } + +#elif defined(__nios2__) + + case R_NIOS2_NONE: + break; + + case R_NIOS2_BFD_RELOC_32: + *loc += v; + break; + + case R_NIOS2_BFD_RELOC_16: + if (v > 0xffff) { + ret = obj_reloc_overflow; + } + *(short *)loc = v; + break; + + case R_NIOS2_BFD_RELOC_8: + if (v > 0xff) { + ret = obj_reloc_overflow; + } + *(char *)loc = v; + break; + + case R_NIOS2_S16: + { + Elf32_Addr word; + + if ((Elf32_Sword)v > 0x7fff || + (Elf32_Sword)v < -(Elf32_Sword)0x8000) { + ret = obj_reloc_overflow; + } + + word = *loc; + *loc = ((((word >> 22) << 16) | (v & 0xffff)) << 6) | + (word & 0x3f); + } + break; + + case R_NIOS2_U16: + { + Elf32_Addr word; + + if (v > 0xffff) { + ret = obj_reloc_overflow; + } + + word = *loc; + *loc = ((((word >> 22) << 16) | (v & 0xffff)) << 6) | + (word & 0x3f); + } + break; + + case R_NIOS2_PCREL16: + { + Elf32_Addr word; + + v -= dot + 4; + if ((Elf32_Sword)v > 0x7fff || + (Elf32_Sword)v < -(Elf32_Sword)0x8000) { + ret = obj_reloc_overflow; + } + + word = *loc; + *loc = ((((word >> 22) << 16) | (v & 0xffff)) << 6) | (word & 0x3f); + } + break; + + case R_NIOS2_GPREL: + { + Elf32_Addr word, gp; + /* get _gp */ + gp = obj_symbol_final_value(f, obj_find_symbol(f, SPFX "_gp")); + v-=gp; + if ((Elf32_Sword)v > 0x7fff || + (Elf32_Sword)v < -(Elf32_Sword)0x8000) { + ret = obj_reloc_overflow; + } + + word = *loc; + *loc = ((((word >> 22) << 16) | (v & 0xffff)) << 6) | (word & 0x3f); + } + break; + + case R_NIOS2_CALL26: + if (v & 3) + ret = obj_reloc_dangerous; + if ((v >> 28) != (dot >> 28)) + ret = obj_reloc_overflow; + *loc = (*loc & 0x3f) | ((v >> 2) << 6); + break; + + case R_NIOS2_IMM5: + { + Elf32_Addr word; + + if (v > 0x1f) { + ret = obj_reloc_overflow; + } + + word = *loc & ~0x7c0; + *loc = word | ((v & 0x1f) << 6); + } + break; + + case R_NIOS2_IMM6: + { + Elf32_Addr word; + + if (v > 0x3f) { + ret = obj_reloc_overflow; + } + + word = *loc & ~0xfc0; + *loc = word | ((v & 0x3f) << 6); + } + break; + + case R_NIOS2_IMM8: + { + Elf32_Addr word; + + if (v > 0xff) { + ret = obj_reloc_overflow; + } + + word = *loc & ~0x3fc0; + *loc = word | ((v & 0xff) << 6); + } + break; + + case R_NIOS2_HI16: + { + Elf32_Addr word; + + word = *loc; + *loc = ((((word >> 22) << 16) | ((v >>16) & 0xffff)) << 6) | + (word & 0x3f); + } + break; + + case R_NIOS2_LO16: + { + Elf32_Addr word; + + word = *loc; + *loc = ((((word >> 22) << 16) | (v & 0xffff)) << 6) | + (word & 0x3f); + } + break; + + case R_NIOS2_HIADJ16: + { + Elf32_Addr word1, word2; + + word1 = *loc; + word2 = ((v >> 16) + ((v >> 15) & 1)) & 0xffff; + *loc = ((((word1 >> 22) << 16) | word2) << 6) | + (word1 & 0x3f); + } + break; + +#elif defined(__powerpc64__) + /* PPC64 needs a 2.6 kernel, 2.4 module relocation irrelevant */ + +#elif defined(__powerpc__) + + case R_PPC_ADDR16_HA: + *(unsigned short *)loc = (v + 0x8000) >> 16; + break; + + case R_PPC_ADDR16_HI: + *(unsigned short *)loc = v >> 16; + break; + + case R_PPC_ADDR16_LO: + *(unsigned short *)loc = v; + break; + + case R_PPC_REL24: + goto bb_use_plt; + + case R_PPC_REL32: + *loc = v - dot; + break; + + case R_PPC_ADDR32: + *loc = v; + break; + +#elif defined(__s390__) + + case R_390_32: + *(unsigned int *) loc += v; + break; + case R_390_16: + *(unsigned short *) loc += v; + break; + case R_390_8: + *(unsigned char *) loc += v; + break; + + case R_390_PC32: + *(unsigned int *) loc += v - dot; + break; + case R_390_PC16DBL: + *(unsigned short *) loc += (v - dot) >> 1; + break; + case R_390_PC16: + *(unsigned short *) loc += v - dot; + break; + + case R_390_PLT32: + case R_390_PLT16DBL: + /* find the plt entry and initialize it. */ + pe = (struct arch_single_entry *) &isym->pltent; + if (pe->inited == 0) { + ip = (unsigned long *)(ifile->plt->contents + pe->offset); + ip[0] = 0x0d105810; /* basr 1,0; lg 1,10(1); br 1 */ + ip[1] = 0x100607f1; + if (ELF_R_TYPE(rel->r_info) == R_390_PLT16DBL) + ip[2] = v - 2; + else + ip[2] = v; + pe->inited = 1; + } + + /* Insert relative distance to target. */ + v = plt + pe->offset - dot; + if (ELF_R_TYPE(rel->r_info) == R_390_PLT32) + *(unsigned int *) loc = (unsigned int) v; + else if (ELF_R_TYPE(rel->r_info) == R_390_PLT16DBL) + *(unsigned short *) loc = (unsigned short) ((v + 2) >> 1); + break; + + case R_390_GLOB_DAT: + case R_390_JMP_SLOT: + *loc = v; + break; + + case R_390_RELATIVE: + *loc += f->baseaddr; + break; + + case R_390_GOTPC: + *(unsigned long *) loc += got - dot; + break; + + case R_390_GOT12: + case R_390_GOT16: + case R_390_GOT32: + if (!isym->gotent.inited) + { + isym->gotent.inited = 1; + *(ElfW(Addr) *)(ifile->got->contents + isym->gotent.offset) = v; + } + if (ELF_R_TYPE(rel->r_info) == R_390_GOT12) + *(unsigned short *) loc |= (*(unsigned short *) loc + isym->gotent.offset) & 0xfff; + else if (ELF_R_TYPE(rel->r_info) == R_390_GOT16) + *(unsigned short *) loc += isym->gotent.offset; + else if (ELF_R_TYPE(rel->r_info) == R_390_GOT32) + *(unsigned int *) loc += isym->gotent.offset; + break; + +# ifndef R_390_GOTOFF32 +# define R_390_GOTOFF32 R_390_GOTOFF +# endif + case R_390_GOTOFF32: + *loc += v - got; + break; + +#elif defined(__sh__) + + case R_SH_NONE: + break; + + case R_SH_DIR32: + *loc += v; + break; + + case R_SH_REL32: + *loc += v - dot; + break; + + case R_SH_PLT32: + *loc = v - dot; + break; + + case R_SH_GLOB_DAT: + case R_SH_JMP_SLOT: + *loc = v; + break; + + case R_SH_RELATIVE: + *loc = f->baseaddr + rel->r_addend; + break; + + case R_SH_GOTPC: + *loc = got - dot + rel->r_addend; + break; + + case R_SH_GOT32: + goto bb_use_got; + + case R_SH_GOTOFF: + *loc = v - got; + break; + +# if defined(__SH5__) + case R_SH_IMM_MEDLOW16: + case R_SH_IMM_LOW16: + { + ElfW(Addr) word; + + if (ELF_R_TYPE(rel->r_info) == R_SH_IMM_MEDLOW16) + v >>= 16; + + /* + * movi and shori have the format: + * + * | op | imm | reg | reserved | + * 31..26 25..10 9.. 4 3 .. 0 + * + * so we simply mask and or in imm. + */ + word = *loc & ~0x3fffc00; + word |= (v & 0xffff) << 10; + + *loc = word; + + break; + } + + case R_SH_IMM_MEDLOW16_PCREL: + case R_SH_IMM_LOW16_PCREL: + { + ElfW(Addr) word; + + word = *loc & ~0x3fffc00; + + v -= dot; + + if (ELF_R_TYPE(rel->r_info) == R_SH_IMM_MEDLOW16_PCREL) + v >>= 16; + + word |= (v & 0xffff) << 10; + + *loc = word; + + break; + } +# endif /* __SH5__ */ + +#elif defined (__v850e__) + + case R_V850_NONE: + break; + + case R_V850_32: + /* We write two shorts instead of a long because even + 32-bit insns only need half-word alignment, but + 32-bit data needs to be long-word aligned. */ + v += ((unsigned short *)loc)[0]; + v += ((unsigned short *)loc)[1] << 16; + ((unsigned short *)loc)[0] = v & 0xffff; + ((unsigned short *)loc)[1] = (v >> 16) & 0xffff; + break; + + case R_V850_22_PCREL: + goto bb_use_plt; + +#elif defined(__x86_64__) + + case R_X86_64_NONE: + break; + + case R_X86_64_64: + *loc += v; + break; + + case R_X86_64_32: + *(unsigned int *) loc += v; + if (v > 0xffffffff) + { + ret = obj_reloc_overflow; /* Kernel module compiled without -mcmodel=kernel. */ + /* error("Possibly is module compiled without -mcmodel=kernel!"); */ + } + break; + + case R_X86_64_32S: + *(signed int *) loc += v; + break; + + case R_X86_64_16: + *(unsigned short *) loc += v; + break; + + case R_X86_64_8: + *(unsigned char *) loc += v; + break; + + case R_X86_64_PC32: + *(unsigned int *) loc += v - dot; + break; + + case R_X86_64_PC16: + *(unsigned short *) loc += v - dot; + break; + + case R_X86_64_PC8: + *(unsigned char *) loc += v - dot; + break; + + case R_X86_64_GLOB_DAT: + case R_X86_64_JUMP_SLOT: + *loc = v; + break; + + case R_X86_64_RELATIVE: + *loc += f->baseaddr; + break; + + case R_X86_64_GOT32: + case R_X86_64_GOTPCREL: + goto bb_use_got; +# if 0 + if (!isym->gotent.reloc_done) + { + isym->gotent.reloc_done = 1; + *(Elf64_Addr *)(ifile->got->contents + isym->gotent.offset) = v; + } + /* XXX are these really correct? */ + if (ELF64_R_TYPE(rel->r_info) == R_X86_64_GOTPCREL) + *(unsigned int *) loc += v + isym->gotent.offset; + else + *loc += isym->gotent.offset; + break; +# endif + +#else +# warning "no idea how to handle relocations on your arch" +#endif + + default: + printf("Warning: unhandled reloc %d\n",(int)ELF_R_TYPE(rel->r_info)); + ret = obj_reloc_unhandled; + break; + +#if defined(USE_PLT_ENTRIES) + +bb_use_plt: + + /* find the plt entry and initialize it if necessary */ + +#if defined(USE_PLT_LIST) + for (pe = isym->pltent; pe != NULL && pe->addend != rel->r_addend;) + pe = pe->next; +#else + pe = &isym->pltent; +#endif + + if (! pe->inited) { + ip = (unsigned long *) (ifile->plt->contents + pe->offset); + + /* generate some machine code */ + +#if defined(__arm__) + ip[0] = 0xe51ff004; /* ldr pc,[pc,#-4] */ + ip[1] = v; /* sym@ */ +#endif +#if defined(__powerpc__) + ip[0] = 0x3d600000 + ((v + 0x8000) >> 16); /* lis r11,sym@ha */ + ip[1] = 0x396b0000 + (v & 0xffff); /* addi r11,r11,sym@l */ + ip[2] = 0x7d6903a6; /* mtctr r11 */ + ip[3] = 0x4e800420; /* bctr */ +#endif +#if defined (__v850e__) + /* We have to trash a register, so we assume that any control + transfer more than 21-bits away must be a function call + (so we can use a call-clobbered register). */ + ip[0] = 0x0621 + ((v & 0xffff) << 16); /* mov sym, r1 ... */ + ip[1] = ((v >> 16) & 0xffff) + 0x610000; /* ...; jmp r1 */ +#endif + pe->inited = 1; + } + + /* relative distance to target */ + v -= dot; + /* if the target is too far away.... */ +#if defined (__arm__) || defined (__powerpc__) + if ((int)v < -0x02000000 || (int)v >= 0x02000000) +#elif defined (__v850e__) + if ((ElfW(Sword))v > 0x1fffff || (ElfW(Sword))v < (ElfW(Sword))-0x200000) +#endif + /* go via the plt */ + v = plt + pe->offset - dot; + +#if defined (__v850e__) + if (v & 1) +#else + if (v & 3) +#endif + ret = obj_reloc_dangerous; + + /* merge the offset into the instruction. */ +#if defined(__arm__) + /* Convert to words. */ + v >>= 2; + + *loc = (*loc & ~0x00ffffff) | ((v + *loc) & 0x00ffffff); +#endif +#if defined(__powerpc__) + *loc = (*loc & ~0x03fffffc) | (v & 0x03fffffc); +#endif +#if defined (__v850e__) + /* We write two shorts instead of a long because even 32-bit insns + only need half-word alignment, but the 32-bit data write needs + to be long-word aligned. */ + ((unsigned short *)loc)[0] = + (*(unsigned short *)loc & 0xffc0) /* opcode + reg */ + | ((v >> 16) & 0x3f); /* offs high part */ + ((unsigned short *)loc)[1] = + (v & 0xffff); /* offs low part */ +#endif + break; +#endif /* USE_PLT_ENTRIES */ + +#if defined(USE_GOT_ENTRIES) +bb_use_got: + + /* needs an entry in the .got: set it, once */ + if (!isym->gotent.inited) { + isym->gotent.inited = 1; + *(ElfW(Addr) *) (ifile->got->contents + isym->gotent.offset) = v; + } + /* make the reloc with_respect_to_.got */ +#if defined(__sh__) + *loc += isym->gotent.offset + rel->r_addend; +#elif defined(__i386__) || defined(__arm__) || defined(__mc68000__) + *loc += isym->gotent.offset; +#endif + break; + +#endif /* USE_GOT_ENTRIES */ + } + + return ret; +} + + +#if defined(USE_LIST) + +static int arch_list_add(ElfW(RelM) *rel, struct arch_list_entry **list, + int offset, int size) +{ + struct arch_list_entry *pe; + + for (pe = *list; pe != NULL; pe = pe->next) { + if (pe->addend == rel->r_addend) { + break; + } + } + + if (pe == NULL) { + pe = xmalloc(sizeof(struct arch_list_entry)); + pe->next = *list; + pe->addend = rel->r_addend; + pe->offset = offset; + pe->inited = 0; + *list = pe; + return size; + } + return 0; +} + +#endif + +#if defined(USE_SINGLE) + +static int arch_single_init(ElfW(RelM) *rel, struct arch_single_entry *single, + int offset, int size) +{ + if (single->allocated == 0) { + single->allocated = 1; + single->offset = offset; + single->inited = 0; + return size; + } + return 0; +} + +#endif + +#if defined(USE_GOT_ENTRIES) || defined(USE_PLT_ENTRIES) + +static struct obj_section *arch_xsect_init(struct obj_file *f, char *name, + int offset, int size) +{ + struct obj_section *myrelsec = obj_find_section(f, name); + + if (offset == 0) { + offset += size; + } + + if (myrelsec) { + obj_extend_section(myrelsec, offset); + } else { + myrelsec = obj_create_alloced_section(f, name, + size, offset); + } + + return myrelsec; +} + +#endif + +static void arch_create_got(struct obj_file *f) +{ +#if defined(USE_GOT_ENTRIES) || defined(USE_PLT_ENTRIES) + struct arch_file *ifile = (struct arch_file *) f; + int i; +#if defined(USE_GOT_ENTRIES) + int got_offset = 0, got_needed = 0, got_allocate; +#endif +#if defined(USE_PLT_ENTRIES) + int plt_offset = 0, plt_needed = 0, plt_allocate; +#endif + struct obj_section *relsec, *symsec, *strsec; + ElfW(RelM) *rel, *relend; + ElfW(Sym) *symtab, *extsym; + const char *strtab, *name; + struct arch_symbol *intsym; + + for (i = 0; i < f->header.e_shnum; ++i) { + relsec = f->sections[i]; + if (relsec->header.sh_type != SHT_RELM) + continue; + + symsec = f->sections[relsec->header.sh_link]; + strsec = f->sections[symsec->header.sh_link]; + + rel = (ElfW(RelM) *) relsec->contents; + relend = rel + (relsec->header.sh_size / sizeof(ElfW(RelM))); + symtab = (ElfW(Sym) *) symsec->contents; + strtab = (const char *) strsec->contents; + + for (; rel < relend; ++rel) { + extsym = &symtab[ELF_R_SYM(rel->r_info)]; + +#if defined(USE_GOT_ENTRIES) + got_allocate = 0; +#endif +#if defined(USE_PLT_ENTRIES) + plt_allocate = 0; +#endif + + switch (ELF_R_TYPE(rel->r_info)) { +#if defined(__arm__) + case R_ARM_PC24: + case R_ARM_PLT32: + plt_allocate = 1; + break; + + case R_ARM_GOTOFF: + case R_ARM_GOTPC: + got_needed = 1; + continue; + + case R_ARM_GOT32: + got_allocate = 1; + break; + +#elif defined(__i386__) + case R_386_GOTPC: + case R_386_GOTOFF: + got_needed = 1; + continue; + + case R_386_GOT32: + got_allocate = 1; + break; + +#elif defined(__powerpc__) + case R_PPC_REL24: + plt_allocate = 1; + break; + +#elif defined(__mc68000__) + case R_68K_GOT32: + got_allocate = 1; + break; + +#ifdef R_68K_GOTOFF + case R_68K_GOTOFF: + got_needed = 1; + continue; +#endif + +#elif defined(__sh__) + case R_SH_GOT32: + got_allocate = 1; + break; + + case R_SH_GOTPC: + case R_SH_GOTOFF: + got_needed = 1; + continue; + +#elif defined (__v850e__) + case R_V850_22_PCREL: + plt_needed = 1; + break; + +#endif + default: + continue; + } + + if (extsym->st_name != 0) { + name = strtab + extsym->st_name; + } else { + name = f->sections[extsym->st_shndx]->name; + } + intsym = (struct arch_symbol *) obj_find_symbol(f, name); +#if defined(USE_GOT_ENTRIES) + if (got_allocate) { + got_offset += arch_single_init( + rel, &intsym->gotent, + got_offset, GOT_ENTRY_SIZE); + + got_needed = 1; + } +#endif +#if defined(USE_PLT_ENTRIES) + if (plt_allocate) { +#if defined(USE_PLT_LIST) + plt_offset += arch_list_add( + rel, &intsym->pltent, + plt_offset, PLT_ENTRY_SIZE); +#else + plt_offset += arch_single_init( + rel, &intsym->pltent, + plt_offset, PLT_ENTRY_SIZE); +#endif + plt_needed = 1; + } +#endif + } + } + +#if defined(USE_GOT_ENTRIES) + if (got_needed) { + ifile->got = arch_xsect_init(f, ".got", got_offset, + GOT_ENTRY_SIZE); + } +#endif + +#if defined(USE_PLT_ENTRIES) + if (plt_needed) { + ifile->plt = arch_xsect_init(f, ".plt", plt_offset, + PLT_ENTRY_SIZE); + } +#endif + +#endif /* defined(USE_GOT_ENTRIES) || defined(USE_PLT_ENTRIES) */ +} + +/*======================================================================*/ + +/* Standard ELF hash function. */ +static unsigned long obj_elf_hash_n(const char *name, unsigned long n) +{ + unsigned long h = 0; + unsigned long g; + unsigned char ch; + + while (n > 0) { + ch = *name++; + h = (h << 4) + ch; + if ((g = (h & 0xf0000000)) != 0) { + h ^= g >> 24; + h &= ~g; + } + n--; + } + return h; +} + +static unsigned long obj_elf_hash(const char *name) +{ + return obj_elf_hash_n(name, strlen(name)); +} + +#if ENABLE_FEATURE_INSMOD_VERSION_CHECKING +/* String comparison for non-co-versioned kernel and module. */ + +static int ncv_strcmp(const char *a, const char *b) +{ + size_t alen = strlen(a), blen = strlen(b); + + if (blen == alen + 10 && b[alen] == '_' && b[alen + 1] == 'R') + return strncmp(a, b, alen); + else if (alen == blen + 10 && a[blen] == '_' && a[blen + 1] == 'R') + return strncmp(a, b, blen); + else + return strcmp(a, b); +} + +/* String hashing for non-co-versioned kernel and module. Here + we are simply forced to drop the crc from the hash. */ + +static unsigned long ncv_symbol_hash(const char *str) +{ + size_t len = strlen(str); + if (len > 10 && str[len - 10] == '_' && str[len - 9] == 'R') + len -= 10; + return obj_elf_hash_n(str, len); +} + +static void +obj_set_symbol_compare(struct obj_file *f, + int (*cmp) (const char *, const char *), + unsigned long (*hash) (const char *)) +{ + if (cmp) + f->symbol_cmp = cmp; + if (hash) { + struct obj_symbol *tmptab[HASH_BUCKETS], *sym, *next; + int i; + + f->symbol_hash = hash; + + memcpy(tmptab, f->symtab, sizeof(tmptab)); + memset(f->symtab, 0, sizeof(f->symtab)); + + for (i = 0; i < HASH_BUCKETS; ++i) + for (sym = tmptab[i]; sym; sym = next) { + unsigned long h = hash(sym->name) % HASH_BUCKETS; + next = sym->next; + sym->next = f->symtab[h]; + f->symtab[h] = sym; + } + } +} + +#endif /* FEATURE_INSMOD_VERSION_CHECKING */ + +static struct obj_symbol * +obj_add_symbol(struct obj_file *f, const char *name, + unsigned long symidx, int info, + int secidx, ElfW(Addr) value, + unsigned long size) +{ + struct obj_symbol *sym; + unsigned long hash = f->symbol_hash(name) % HASH_BUCKETS; + int n_type = ELF_ST_TYPE(info); + int n_binding = ELF_ST_BIND(info); + + for (sym = f->symtab[hash]; sym; sym = sym->next) + if (f->symbol_cmp(sym->name, name) == 0) { + int o_secidx = sym->secidx; + int o_info = sym->info; + int o_type = ELF_ST_TYPE(o_info); + int o_binding = ELF_ST_BIND(o_info); + + /* A redefinition! Is it legal? */ + + if (secidx == SHN_UNDEF) + return sym; + else if (o_secidx == SHN_UNDEF) + goto found; + else if (n_binding == STB_GLOBAL && o_binding == STB_LOCAL) { + /* Cope with local and global symbols of the same name + in the same object file, as might have been created + by ld -r. The only reason locals are now seen at this + level at all is so that we can do semi-sensible things + with parameters. */ + + struct obj_symbol *nsym, **p; + + nsym = arch_new_symbol(); + nsym->next = sym->next; + nsym->ksymidx = -1; + + /* Excise the old (local) symbol from the hash chain. */ + for (p = &f->symtab[hash]; *p != sym; p = &(*p)->next) + continue; + *p = sym = nsym; + goto found; + } else if (n_binding == STB_LOCAL) { + /* Another symbol of the same name has already been defined. + Just add this to the local table. */ + sym = arch_new_symbol(); + sym->next = NULL; + sym->ksymidx = -1; + f->local_symtab[symidx] = sym; + goto found; + } else if (n_binding == STB_WEAK) + return sym; + else if (o_binding == STB_WEAK) + goto found; + /* Don't unify COMMON symbols with object types the programmer + doesn't expect. */ + else if (secidx == SHN_COMMON + && (o_type == STT_NOTYPE || o_type == STT_OBJECT)) + return sym; + else if (o_secidx == SHN_COMMON + && (n_type == STT_NOTYPE || n_type == STT_OBJECT)) + goto found; + else { + /* Don't report an error if the symbol is coming from + the kernel or some external module. */ + if (secidx <= SHN_HIRESERVE) + bb_error_msg("%s multiply defined", name); + return sym; + } + } + + /* Completely new symbol. */ + sym = arch_new_symbol(); + sym->next = f->symtab[hash]; + f->symtab[hash] = sym; + sym->ksymidx = -1; + + if (ELF_ST_BIND(info) == STB_LOCAL && symidx != -1) { + if (symidx >= f->local_symtab_size) + bb_error_msg("local symbol %s with index %ld exceeds local_symtab_size %ld", + name, (long) symidx, (long) f->local_symtab_size); + else + f->local_symtab[symidx] = sym; + } + +found: + sym->name = name; + sym->value = value; + sym->size = size; + sym->secidx = secidx; + sym->info = info; + + return sym; +} + +static struct obj_symbol * +obj_find_symbol(struct obj_file *f, const char *name) +{ + struct obj_symbol *sym; + unsigned long hash = f->symbol_hash(name) % HASH_BUCKETS; + + for (sym = f->symtab[hash]; sym; sym = sym->next) + if (f->symbol_cmp(sym->name, name) == 0) + return sym; + + return NULL; +} + +static ElfW(Addr) obj_symbol_final_value(struct obj_file * f, struct obj_symbol * sym) +{ + if (sym) { + if (sym->secidx >= SHN_LORESERVE) + return sym->value; + + return sym->value + f->sections[sym->secidx]->header.sh_addr; + } else { + /* As a special case, a NULL sym has value zero. */ + return 0; + } +} + +static struct obj_section *obj_find_section(struct obj_file *f, const char *name) +{ + int i, n = f->header.e_shnum; + + for (i = 0; i < n; ++i) + if (strcmp(f->sections[i]->name, name) == 0) + return f->sections[i]; + + return NULL; +} + +static int obj_load_order_prio(struct obj_section *a) +{ + unsigned long af, ac; + + af = a->header.sh_flags; + + ac = 0; + if (a->name[0] != '.' || strlen(a->name) != 10 || + strcmp(a->name + 5, ".init")) + ac |= 32; + if (af & SHF_ALLOC) + ac |= 16; + if (!(af & SHF_WRITE)) + ac |= 8; + if (af & SHF_EXECINSTR) + ac |= 4; + if (a->header.sh_type != SHT_NOBITS) + ac |= 2; + + return ac; +} + +static void +obj_insert_section_load_order(struct obj_file *f, struct obj_section *sec) +{ + struct obj_section **p; + int prio = obj_load_order_prio(sec); + for (p = f->load_order_search_start; *p; p = &(*p)->load_next) + if (obj_load_order_prio(*p) < prio) + break; + sec->load_next = *p; + *p = sec; +} + +static struct obj_section *obj_create_alloced_section(struct obj_file *f, + const char *name, + unsigned long align, + unsigned long size) +{ + int newidx = f->header.e_shnum++; + struct obj_section *sec; + + f->sections = xrealloc(f->sections, (newidx + 1) * sizeof(sec)); + f->sections[newidx] = sec = arch_new_section(); + + memset(sec, 0, sizeof(*sec)); + sec->header.sh_type = SHT_PROGBITS; + sec->header.sh_flags = SHF_WRITE | SHF_ALLOC; + sec->header.sh_size = size; + sec->header.sh_addralign = align; + sec->name = name; + sec->idx = newidx; + if (size) + sec->contents = xmalloc(size); + + obj_insert_section_load_order(f, sec); + + return sec; +} + +static struct obj_section *obj_create_alloced_section_first(struct obj_file *f, + const char *name, + unsigned long align, + unsigned long size) +{ + int newidx = f->header.e_shnum++; + struct obj_section *sec; + + f->sections = xrealloc(f->sections, (newidx + 1) * sizeof(sec)); + f->sections[newidx] = sec = arch_new_section(); + + memset(sec, 0, sizeof(*sec)); + sec->header.sh_type = SHT_PROGBITS; + sec->header.sh_flags = SHF_WRITE | SHF_ALLOC; + sec->header.sh_size = size; + sec->header.sh_addralign = align; + sec->name = name; + sec->idx = newidx; + if (size) + sec->contents = xmalloc(size); + + sec->load_next = f->load_order; + f->load_order = sec; + if (f->load_order_search_start == &f->load_order) + f->load_order_search_start = &sec->load_next; + + return sec; +} + +static void *obj_extend_section(struct obj_section *sec, unsigned long more) +{ + unsigned long oldsize = sec->header.sh_size; + if (more) { + sec->contents = xrealloc(sec->contents, sec->header.sh_size += more); + } + return sec->contents + oldsize; +} + + +/* Conditionally add the symbols from the given symbol set to the + new module. */ + +static int +add_symbols_from( struct obj_file *f, + int idx, struct new_module_symbol *syms, size_t nsyms) +{ + struct new_module_symbol *s; + size_t i; + int used = 0; +#ifdef SYMBOL_PREFIX + char *name_buf = 0; + size_t name_alloced_size = 0; +#endif +#if ENABLE_FEATURE_CHECK_TAINTED_MODULE + int gpl; + + gpl = obj_gpl_license(f, NULL) == 0; +#endif + for (i = 0, s = syms; i < nsyms; ++i, ++s) { + /* Only add symbols that are already marked external. + If we override locals we may cause problems for + argument initialization. We will also create a false + dependency on the module. */ + struct obj_symbol *sym; + char *name; + + /* GPL licensed modules can use symbols exported with + * EXPORT_SYMBOL_GPL, so ignore any GPLONLY_ prefix on the + * exported names. Non-GPL modules never see any GPLONLY_ + * symbols so they cannot fudge it by adding the prefix on + * their references. + */ + if (strncmp((char *)s->name, "GPLONLY_", 8) == 0) { +#if ENABLE_FEATURE_CHECK_TAINTED_MODULE + if (gpl) + s->name += 8; + else +#endif + continue; + } + name = (char *)s->name; + +#ifdef SYMBOL_PREFIX + /* Prepend SYMBOL_PREFIX to the symbol's name (the + kernel exports `C names', but module object files + reference `linker names'). */ + size_t extra = sizeof SYMBOL_PREFIX; + size_t name_size = strlen (name) + extra; + if (name_size > name_alloced_size) { + name_alloced_size = name_size * 2; + name_buf = alloca (name_alloced_size); + } + strcpy (name_buf, SYMBOL_PREFIX); + strcpy (name_buf + extra - 1, name); + name = name_buf; +#endif /* SYMBOL_PREFIX */ + + sym = obj_find_symbol(f, name); + if (sym && !(ELF_ST_BIND(sym->info) == STB_LOCAL)) { +#ifdef SYMBOL_PREFIX + /* Put NAME_BUF into more permanent storage. */ + name = xmalloc (name_size); + strcpy (name, name_buf); +#endif + sym = obj_add_symbol(f, name, -1, + ELF_ST_INFO(STB_GLOBAL, + STT_NOTYPE), + idx, s->value, 0); + /* Did our symbol just get installed? If so, mark the + module as "used". */ + if (sym->secidx == idx) + used = 1; + } + } + + return used; +} + +static void add_kernel_symbols(struct obj_file *f) +{ + struct external_module *m; + int i, nused = 0; + + /* Add module symbols first. */ + + for (i = 0, m = ext_modules; i < n_ext_modules; ++i, ++m) + if (m->nsyms + && add_symbols_from(f, SHN_HIRESERVE + 2 + i, m->syms, + m->nsyms)) m->used = 1, ++nused; + + n_ext_modules_used = nused; + + /* And finally the symbols from the kernel proper. */ + + if (nksyms) + add_symbols_from(f, SHN_HIRESERVE + 1, ksyms, nksyms); +} + +static char *get_modinfo_value(struct obj_file *f, const char *key) +{ + struct obj_section *sec; + char *p, *v, *n, *ep; + size_t klen = strlen(key); + + sec = obj_find_section(f, ".modinfo"); + if (sec == NULL) + return NULL; + p = sec->contents; + ep = p + sec->header.sh_size; + while (p < ep) { + v = strchr(p, '='); + n = strchr(p, '\0'); + if (v) { + if (p + klen == v && strncmp(p, key, klen) == 0) + return v + 1; + } else { + if (p + klen == n && strcmp(p, key) == 0) + return n; + } + p = n + 1; + } + + return NULL; +} + + +/*======================================================================*/ +/* Functions relating to module loading after 2.1.18. */ + +static int +new_process_module_arguments(struct obj_file *f, int argc, char **argv) +{ + while (argc > 0) { + char *p, *q, *key, *sym_name; + struct obj_symbol *sym; + char *contents, *loc; + int min, max, n; + + p = *argv; + if ((q = strchr(p, '=')) == NULL) { + argc--; + continue; + } + + key = alloca(q - p + 6); + memcpy(key, "parm_", 5); + memcpy(key + 5, p, q - p); + key[q - p + 5] = 0; + + p = get_modinfo_value(f, key); + key += 5; + if (p == NULL) { + bb_error_msg("invalid parameter %s", key); + return 0; + } + +#ifdef SYMBOL_PREFIX + sym_name = alloca (strlen (key) + sizeof SYMBOL_PREFIX); + strcpy (sym_name, SYMBOL_PREFIX); + strcat (sym_name, key); +#else + sym_name = key; +#endif + sym = obj_find_symbol(f, sym_name); + + /* Also check that the parameter was not resolved from the kernel. */ + if (sym == NULL || sym->secidx > SHN_HIRESERVE) { + bb_error_msg("symbol for parameter %s not found", key); + return 0; + } + + if (isdigit(*p)) { + min = strtoul(p, &p, 10); + if (*p == '-') + max = strtoul(p + 1, &p, 10); + else + max = min; + } else + min = max = 1; + + contents = f->sections[sym->secidx]->contents; + loc = contents + sym->value; + n = (*++q != '\0'); + + while (1) { + if ((*p == 's') || (*p == 'c')) { + char *str; + + /* Do C quoting if we begin with a ", else slurp the lot. */ + if (*q == '"') { + char *r; + + str = alloca(strlen(q)); + for (r = str, q++; *q != '"'; ++q, ++r) { + if (*q == '\0') { + bb_error_msg("improperly terminated string argument for %s", + key); + return 0; + } else if (*q == '\\') + switch (*++q) { + case 'a': + *r = '\a'; + break; + case 'b': + *r = '\b'; + break; + case 'e': + *r = '\033'; + break; + case 'f': + *r = '\f'; + break; + case 'n': + *r = '\n'; + break; + case 'r': + *r = '\r'; + break; + case 't': + *r = '\t'; + break; + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + { + int c = *q - '0'; + if (q[1] >= '0' && q[1] <= '7') { + c = (c * 8) + *++q - '0'; + if (q[1] >= '0' && q[1] <= '7') + c = (c * 8) + *++q - '0'; + } + *r = c; + } + break; + + default: + *r = *q; + break; + } else + *r = *q; + } + *r = '\0'; + ++q; + } else { + char *r; + + /* In this case, the string is not quoted. We will break + it using the coma (like for ints). If the user wants to + include comas in a string, he just has to quote it */ + + /* Search the next coma */ + r = strchr(q, ','); + + /* Found ? */ + if (r != (char *) NULL) { + /* Recopy the current field */ + str = alloca(r - q + 1); + memcpy(str, q, r - q); + + /* I don't know if it is useful, as the previous case + doesn't nul terminate the string ??? */ + str[r - q] = '\0'; + + /* Keep next fields */ + q = r; + } else { + /* last string */ + str = q; + q = ""; + } + } + + if (*p == 's') { + /* Normal string */ + obj_string_patch(f, sym->secidx, loc - contents, str); + loc += tgt_sizeof_char_p; + } else { + /* Array of chars (in fact, matrix !) */ + unsigned long charssize; /* size of each member */ + + /* Get the size of each member */ + /* Probably we should do that outside the loop ? */ + if (!isdigit(*(p + 1))) { + bb_error_msg("parameter type 'c' for %s must be followed by" + " the maximum size", key); + return 0; + } + charssize = strtoul(p + 1, (char **) NULL, 10); + + /* Check length */ + if (strlen(str) >= charssize) { + bb_error_msg("string too long for %s (max %ld)", key, + charssize - 1); + return 0; + } + + /* Copy to location */ + strcpy((char *) loc, str); + loc += charssize; + } + } else { + long v = strtoul(q, &q, 0); + switch (*p) { + case 'b': + *loc++ = v; + break; + case 'h': + *(short *) loc = v; + loc += tgt_sizeof_short; + break; + case 'i': + *(int *) loc = v; + loc += tgt_sizeof_int; + break; + case 'l': + *(long *) loc = v; + loc += tgt_sizeof_long; + break; + + default: + bb_error_msg("unknown parameter type '%c' for %s", *p, key); + return 0; + } + } + +retry_end_of_value: + switch (*q) { + case '\0': + goto end_of_arg; + + case ' ': + case '\t': + case '\n': + case '\r': + ++q; + goto retry_end_of_value; + + case ',': + if (++n > max) { + bb_error_msg("too many values for %s (max %d)", key, max); + return 0; + } + ++q; + break; + + default: + bb_error_msg("invalid argument syntax for %s", key); + return 0; + } + } + +end_of_arg: + if (n < min) { + bb_error_msg("too few values for %s (min %d)", key, min); + return 0; + } + + argc--, argv++; + } + + return 1; +} + +#if ENABLE_FEATURE_INSMOD_VERSION_CHECKING +static int new_is_module_checksummed(struct obj_file *f) +{ + const char *p = get_modinfo_value(f, "using_checksums"); + if (p) + return xatoi(p); + else + return 0; +} + +/* Get the module's kernel version in the canonical integer form. */ + +static int +new_get_module_version(struct obj_file *f, char str[STRVERSIONLEN]) +{ + char *p, *q; + int a, b, c; + + p = get_modinfo_value(f, "kernel_version"); + if (p == NULL) + return -1; + safe_strncpy(str, p, STRVERSIONLEN); + + a = strtoul(p, &p, 10); + if (*p != '.') + return -1; + b = strtoul(p + 1, &p, 10); + if (*p != '.') + return -1; + c = strtoul(p + 1, &q, 10); + if (p + 1 == q) + return -1; + + return a << 16 | b << 8 | c; +} + +#endif /* FEATURE_INSMOD_VERSION_CHECKING */ + + +/* Fetch the loaded modules, and all currently exported symbols. */ + +static int new_get_kernel_symbols(void) +{ + char *module_names, *mn; + struct external_module *modules, *m; + struct new_module_symbol *syms, *s; + size_t ret, bufsize, nmod, nsyms, i, j; + + /* Collect the loaded modules. */ + + module_names = xmalloc(bufsize = 256); +retry_modules_load: + if (query_module(NULL, QM_MODULES, module_names, bufsize, &ret)) { + if (errno == ENOSPC && bufsize < ret) { + module_names = xrealloc(module_names, bufsize = ret); + goto retry_modules_load; + } + bb_perror_msg("QM_MODULES"); + return 0; + } + + n_ext_modules = nmod = ret; + + /* Collect the modules' symbols. */ + + if (nmod) { + ext_modules = modules = xmalloc(nmod * sizeof(*modules)); + memset(modules, 0, nmod * sizeof(*modules)); + for (i = 0, mn = module_names, m = modules; + i < nmod; ++i, ++m, mn += strlen(mn) + 1) { + struct new_module_info info; + + if (query_module(mn, QM_INFO, &info, sizeof(info), &ret)) { + if (errno == ENOENT) { + /* The module was removed out from underneath us. */ + continue; + } + bb_perror_msg("query_module: QM_INFO: %s", mn); + return 0; + } + + syms = xmalloc(bufsize = 1024); +retry_mod_sym_load: + if (query_module(mn, QM_SYMBOLS, syms, bufsize, &ret)) { + switch (errno) { + case ENOSPC: + syms = xrealloc(syms, bufsize = ret); + goto retry_mod_sym_load; + case ENOENT: + /* The module was removed out from underneath us. */ + continue; + default: + bb_perror_msg("query_module: QM_SYMBOLS: %s", mn); + return 0; + } + } + nsyms = ret; + + m->name = mn; + m->addr = info.addr; + m->nsyms = nsyms; + m->syms = syms; + + for (j = 0, s = syms; j < nsyms; ++j, ++s) { + s->name += (unsigned long) syms; + } + } + } + + /* Collect the kernel's symbols. */ + + syms = xmalloc(bufsize = 16 * 1024); +retry_kern_sym_load: + if (query_module(NULL, QM_SYMBOLS, syms, bufsize, &ret)) { + if (errno == ENOSPC && bufsize < ret) { + syms = xrealloc(syms, bufsize = ret); + goto retry_kern_sym_load; + } + bb_perror_msg("kernel: QM_SYMBOLS"); + return 0; + } + nksyms = nsyms = ret; + ksyms = syms; + + for (j = 0, s = syms; j < nsyms; ++j, ++s) { + s->name += (unsigned long) syms; + } + return 1; +} + + +/* Return the kernel symbol checksum version, or zero if not used. */ + +static int new_is_kernel_checksummed(void) +{ + struct new_module_symbol *s; + size_t i; + + /* Using_Versions is not the first symbol, but it should be in there. */ + + for (i = 0, s = ksyms; i < nksyms; ++i, ++s) + if (strcmp((char *) s->name, "Using_Versions") == 0) + return s->value; + + return 0; +} + + +static int new_create_this_module(struct obj_file *f, const char *m_name) +{ + struct obj_section *sec; + + sec = obj_create_alloced_section_first(f, ".this", tgt_sizeof_long, + sizeof(struct new_module)); + memset(sec->contents, 0, sizeof(struct new_module)); + + obj_add_symbol(f, SPFX "__this_module", -1, + ELF_ST_INFO(STB_LOCAL, STT_OBJECT), sec->idx, 0, + sizeof(struct new_module)); + + obj_string_patch(f, sec->idx, offsetof(struct new_module, name), + m_name); + + return 1; +} + +#if ENABLE_FEATURE_INSMOD_KSYMOOPS_SYMBOLS +/* add an entry to the __ksymtab section, creating it if necessary */ +static void new_add_ksymtab(struct obj_file *f, struct obj_symbol *sym) +{ + struct obj_section *sec; + ElfW(Addr) ofs; + + /* ensure __ksymtab is allocated, EXPORT_NOSYMBOLS creates a non-alloc section. + * If __ksymtab is defined but not marked alloc, x out the first character + * (no obj_delete routine) and create a new __ksymtab with the correct + * characteristics. + */ + sec = obj_find_section(f, "__ksymtab"); + if (sec && !(sec->header.sh_flags & SHF_ALLOC)) { + *((char *)(sec->name)) = 'x'; /* override const */ + sec = NULL; + } + if (!sec) + sec = obj_create_alloced_section(f, "__ksymtab", + tgt_sizeof_void_p, 0); + if (!sec) + return; + sec->header.sh_flags |= SHF_ALLOC; + /* Empty section might be byte-aligned */ + sec->header.sh_addralign = tgt_sizeof_void_p; + ofs = sec->header.sh_size; + obj_symbol_patch(f, sec->idx, ofs, sym); + obj_string_patch(f, sec->idx, ofs + tgt_sizeof_void_p, sym->name); + obj_extend_section(sec, 2 * tgt_sizeof_char_p); +} +#endif /* FEATURE_INSMOD_KSYMOOPS_SYMBOLS */ + +static int new_create_module_ksymtab(struct obj_file *f) +{ + struct obj_section *sec; + int i; + + /* We must always add the module references. */ + + if (n_ext_modules_used) { + struct new_module_ref *dep; + struct obj_symbol *tm; + + sec = obj_create_alloced_section(f, ".kmodtab", tgt_sizeof_void_p, + (sizeof(struct new_module_ref) + * n_ext_modules_used)); + if (!sec) + return 0; + + tm = obj_find_symbol(f, SPFX "__this_module"); + dep = (struct new_module_ref *) sec->contents; + for (i = 0; i < n_ext_modules; ++i) + if (ext_modules[i].used) { + dep->dep = ext_modules[i].addr; + obj_symbol_patch(f, sec->idx, + (char *) &dep->ref - sec->contents, tm); + dep->next_ref = 0; + ++dep; + } + } + + if (!flag_noexport && !obj_find_section(f, "__ksymtab")) { + size_t nsyms; + int *loaded; + + sec = obj_create_alloced_section(f, "__ksymtab", tgt_sizeof_void_p, 0); + + /* We don't want to export symbols residing in sections that + aren't loaded. There are a number of these created so that + we make sure certain module options don't appear twice. */ + + loaded = alloca(sizeof(int) * (i = f->header.e_shnum)); + while (--i >= 0) + loaded[i] = (f->sections[i]->header.sh_flags & SHF_ALLOC) != 0; + + for (nsyms = i = 0; i < HASH_BUCKETS; ++i) { + struct obj_symbol *sym; + for (sym = f->symtab[i]; sym; sym = sym->next) + if (ELF_ST_BIND(sym->info) != STB_LOCAL + && sym->secidx <= SHN_HIRESERVE + && (sym->secidx >= SHN_LORESERVE + || loaded[sym->secidx])) { + ElfW(Addr) ofs = nsyms * 2 * tgt_sizeof_void_p; + + obj_symbol_patch(f, sec->idx, ofs, sym); + obj_string_patch(f, sec->idx, ofs + tgt_sizeof_void_p, + sym->name); + + nsyms++; + } + } + + obj_extend_section(sec, nsyms * 2 * tgt_sizeof_char_p); + } + + return 1; +} + + +static int +new_init_module(const char *m_name, struct obj_file *f, unsigned long m_size) +{ + struct new_module *module; + struct obj_section *sec; + void *image; + int ret; + tgt_long m_addr; + + sec = obj_find_section(f, ".this"); + if (!sec || !sec->contents) { + bb_perror_msg_and_die("corrupt module %s?",m_name); + } + module = (struct new_module *) sec->contents; + m_addr = sec->header.sh_addr; + + module->size_of_struct = sizeof(*module); + module->size = m_size; + module->flags = flag_autoclean ? NEW_MOD_AUTOCLEAN : 0; + + sec = obj_find_section(f, "__ksymtab"); + if (sec && sec->header.sh_size) { + module->syms = sec->header.sh_addr; + module->nsyms = sec->header.sh_size / (2 * tgt_sizeof_char_p); + } + + if (n_ext_modules_used) { + sec = obj_find_section(f, ".kmodtab"); + module->deps = sec->header.sh_addr; + module->ndeps = n_ext_modules_used; + } + + module->init = + obj_symbol_final_value(f, obj_find_symbol(f, SPFX "init_module")); + module->cleanup = + obj_symbol_final_value(f, obj_find_symbol(f, SPFX "cleanup_module")); + + sec = obj_find_section(f, "__ex_table"); + if (sec) { + module->ex_table_start = sec->header.sh_addr; + module->ex_table_end = sec->header.sh_addr + sec->header.sh_size; + } + + sec = obj_find_section(f, ".text.init"); + if (sec) { + module->runsize = sec->header.sh_addr - m_addr; + } + sec = obj_find_section(f, ".data.init"); + if (sec) { + if (!module->runsize || + module->runsize > sec->header.sh_addr - m_addr) + module->runsize = sec->header.sh_addr - m_addr; + } + sec = obj_find_section(f, ARCHDATA_SEC_NAME); + if (sec && sec->header.sh_size) { + module->archdata_start = (void*)sec->header.sh_addr; + module->archdata_end = module->archdata_start + sec->header.sh_size; + } + sec = obj_find_section(f, KALLSYMS_SEC_NAME); + if (sec && sec->header.sh_size) { + module->kallsyms_start = (void*)sec->header.sh_addr; + module->kallsyms_end = module->kallsyms_start + sec->header.sh_size; + } + + /* Whew! All of the initialization is complete. Collect the final + module image and give it to the kernel. */ + + image = xmalloc(m_size); + obj_create_image(f, image); + + ret = init_module(m_name, (struct new_module *) image); + if (ret) + bb_perror_msg("init_module: %s", m_name); + + free(image); + + return ret == 0; +} + + +/*======================================================================*/ + +static int +obj_string_patch(struct obj_file *f, int secidx, ElfW(Addr) offset, + const char *string) +{ + struct obj_string_patch *p; + struct obj_section *strsec; + size_t len = strlen(string) + 1; + char *loc; + + p = xmalloc(sizeof(*p)); + p->next = f->string_patches; + p->reloc_secidx = secidx; + p->reloc_offset = offset; + f->string_patches = p; + + strsec = obj_find_section(f, ".kstrtab"); + if (strsec == NULL) { + strsec = obj_create_alloced_section(f, ".kstrtab", 1, len); + p->string_offset = 0; + loc = strsec->contents; + } else { + p->string_offset = strsec->header.sh_size; + loc = obj_extend_section(strsec, len); + } + memcpy(loc, string, len); + + return 1; +} + +static int +obj_symbol_patch(struct obj_file *f, int secidx, ElfW(Addr) offset, + struct obj_symbol *sym) +{ + struct obj_symbol_patch *p; + + p = xmalloc(sizeof(*p)); + p->next = f->symbol_patches; + p->reloc_secidx = secidx; + p->reloc_offset = offset; + p->sym = sym; + f->symbol_patches = p; + + return 1; +} + +static int obj_check_undefineds(struct obj_file *f) +{ + unsigned long i; + int ret = 1; + + for (i = 0; i < HASH_BUCKETS; ++i) { + struct obj_symbol *sym; + for (sym = f->symtab[i]; sym; sym = sym->next) + if (sym->secidx == SHN_UNDEF) { + if (ELF_ST_BIND(sym->info) == STB_WEAK) { + sym->secidx = SHN_ABS; + sym->value = 0; + } else { + if (!flag_quiet) { + bb_error_msg("unresolved symbol %s", sym->name); + } + ret = 0; + } + } + } + + return ret; +} + +static void obj_allocate_commons(struct obj_file *f) +{ + struct common_entry { + struct common_entry *next; + struct obj_symbol *sym; + } *common_head = NULL; + + unsigned long i; + + for (i = 0; i < HASH_BUCKETS; ++i) { + struct obj_symbol *sym; + for (sym = f->symtab[i]; sym; sym = sym->next) + if (sym->secidx == SHN_COMMON) { + /* Collect all COMMON symbols and sort them by size so as to + minimize space wasted by alignment requirements. */ + { + struct common_entry **p, *n; + for (p = &common_head; *p; p = &(*p)->next) + if (sym->size <= (*p)->sym->size) + break; + + n = alloca(sizeof(*n)); + n->next = *p; + n->sym = sym; + *p = n; + } + } + } + + for (i = 1; i < f->local_symtab_size; ++i) { + struct obj_symbol *sym = f->local_symtab[i]; + if (sym && sym->secidx == SHN_COMMON) { + struct common_entry **p, *n; + for (p = &common_head; *p; p = &(*p)->next) + if (sym == (*p)->sym) + break; + else if (sym->size < (*p)->sym->size) { + n = alloca(sizeof(*n)); + n->next = *p; + n->sym = sym; + *p = n; + break; + } + } + } + + if (common_head) { + /* Find the bss section. */ + for (i = 0; i < f->header.e_shnum; ++i) + if (f->sections[i]->header.sh_type == SHT_NOBITS) + break; + + /* If for some reason there hadn't been one, create one. */ + if (i == f->header.e_shnum) { + struct obj_section *sec; + + f->sections = xrealloc(f->sections, (i + 1) * sizeof(sec)); + f->sections[i] = sec = arch_new_section(); + f->header.e_shnum = i + 1; + + memset(sec, 0, sizeof(*sec)); + sec->header.sh_type = SHT_PROGBITS; + sec->header.sh_flags = SHF_WRITE | SHF_ALLOC; + sec->name = ".bss"; + sec->idx = i; + } + + /* Allocate the COMMONS. */ + { + ElfW(Addr) bss_size = f->sections[i]->header.sh_size; + ElfW(Addr) max_align = f->sections[i]->header.sh_addralign; + struct common_entry *c; + + for (c = common_head; c; c = c->next) { + ElfW(Addr) align = c->sym->value; + + if (align > max_align) + max_align = align; + if (bss_size & (align - 1)) + bss_size = (bss_size | (align - 1)) + 1; + + c->sym->secidx = i; + c->sym->value = bss_size; + + bss_size += c->sym->size; + } + + f->sections[i]->header.sh_size = bss_size; + f->sections[i]->header.sh_addralign = max_align; + } + } + + /* For the sake of patch relocation and parameter initialization, + allocate zeroed data for NOBITS sections now. Note that after + this we cannot assume NOBITS are really empty. */ + for (i = 0; i < f->header.e_shnum; ++i) { + struct obj_section *s = f->sections[i]; + if (s->header.sh_type == SHT_NOBITS) { + if (s->header.sh_size != 0) + s->contents = memset(xmalloc(s->header.sh_size), + 0, s->header.sh_size); + else + s->contents = NULL; + + s->header.sh_type = SHT_PROGBITS; + } + } +} + +static unsigned long obj_load_size(struct obj_file *f) +{ + unsigned long dot = 0; + struct obj_section *sec; + + /* Finalize the positions of the sections relative to one another. */ + + for (sec = f->load_order; sec; sec = sec->load_next) { + ElfW(Addr) align; + + align = sec->header.sh_addralign; + if (align && (dot & (align - 1))) + dot = (dot | (align - 1)) + 1; + + sec->header.sh_addr = dot; + dot += sec->header.sh_size; + } + + return dot; +} + +static int obj_relocate(struct obj_file *f, ElfW(Addr) base) +{ + int i, n = f->header.e_shnum; + int ret = 1; + + /* Finalize the addresses of the sections. */ + + f->baseaddr = base; + for (i = 0; i < n; ++i) + f->sections[i]->header.sh_addr += base; + + /* And iterate over all of the relocations. */ + + for (i = 0; i < n; ++i) { + struct obj_section *relsec, *symsec, *targsec, *strsec; + ElfW(RelM) * rel, *relend; + ElfW(Sym) * symtab; + const char *strtab; + + relsec = f->sections[i]; + if (relsec->header.sh_type != SHT_RELM) + continue; + + symsec = f->sections[relsec->header.sh_link]; + targsec = f->sections[relsec->header.sh_info]; + strsec = f->sections[symsec->header.sh_link]; + + rel = (ElfW(RelM) *) relsec->contents; + relend = rel + (relsec->header.sh_size / sizeof(ElfW(RelM))); + symtab = (ElfW(Sym) *) symsec->contents; + strtab = (const char *) strsec->contents; + + for (; rel < relend; ++rel) { + ElfW(Addr) value = 0; + struct obj_symbol *intsym = NULL; + unsigned long symndx; + ElfW(Sym) * extsym = 0; + const char *errmsg; + + /* Attempt to find a value to use for this relocation. */ + + symndx = ELF_R_SYM(rel->r_info); + if (symndx) { + /* Note we've already checked for undefined symbols. */ + + extsym = &symtab[symndx]; + if (ELF_ST_BIND(extsym->st_info) == STB_LOCAL) { + /* Local symbols we look up in the local table to be sure + we get the one that is really intended. */ + intsym = f->local_symtab[symndx]; + } else { + /* Others we look up in the hash table. */ + const char *name; + if (extsym->st_name) + name = strtab + extsym->st_name; + else + name = f->sections[extsym->st_shndx]->name; + intsym = obj_find_symbol(f, name); + } + + value = obj_symbol_final_value(f, intsym); + intsym->referenced = 1; + } +#if SHT_RELM == SHT_RELA +#if defined(__alpha__) && defined(AXP_BROKEN_GAS) + /* Work around a nasty GAS bug, that is fixed as of 2.7.0.9. */ + if (!extsym || !extsym->st_name || + ELF_ST_BIND(extsym->st_info) != STB_LOCAL) +#endif + value += rel->r_addend; +#endif + + /* Do it! */ + switch (arch_apply_relocation + (f, targsec, symsec, intsym, rel, value) + ) { + case obj_reloc_ok: + break; + + case obj_reloc_overflow: + errmsg = "Relocation overflow"; + goto bad_reloc; + case obj_reloc_dangerous: + errmsg = "Dangerous relocation"; + goto bad_reloc; + case obj_reloc_unhandled: + errmsg = "Unhandled relocation"; +bad_reloc: + if (extsym) { + bb_error_msg("%s of type %ld for %s", errmsg, + (long) ELF_R_TYPE(rel->r_info), + strtab + extsym->st_name); + } else { + bb_error_msg("%s of type %ld", errmsg, + (long) ELF_R_TYPE(rel->r_info)); + } + ret = 0; + break; + } + } + } + + /* Finally, take care of the patches. */ + + if (f->string_patches) { + struct obj_string_patch *p; + struct obj_section *strsec; + ElfW(Addr) strsec_base; + strsec = obj_find_section(f, ".kstrtab"); + strsec_base = strsec->header.sh_addr; + + for (p = f->string_patches; p; p = p->next) { + struct obj_section *targsec = f->sections[p->reloc_secidx]; + *(ElfW(Addr) *) (targsec->contents + p->reloc_offset) + = strsec_base + p->string_offset; + } + } + + if (f->symbol_patches) { + struct obj_symbol_patch *p; + + for (p = f->symbol_patches; p; p = p->next) { + struct obj_section *targsec = f->sections[p->reloc_secidx]; + *(ElfW(Addr) *) (targsec->contents + p->reloc_offset) + = obj_symbol_final_value(f, p->sym); + } + } + + return ret; +} + +static int obj_create_image(struct obj_file *f, char *image) +{ + struct obj_section *sec; + ElfW(Addr) base = f->baseaddr; + + for (sec = f->load_order; sec; sec = sec->load_next) { + char *secimg; + + if (sec->contents == 0 || sec->header.sh_size == 0) + continue; + + secimg = image + (sec->header.sh_addr - base); + + /* Note that we allocated data for NOBITS sections earlier. */ + memcpy(secimg, sec->contents, sec->header.sh_size); + } + + return 1; +} + +/*======================================================================*/ + +static struct obj_file *obj_load(FILE * fp, int loadprogbits) +{ + struct obj_file *f; + ElfW(Shdr) * section_headers; + int shnum, i; + char *shstrtab; + + /* Read the file header. */ + + f = arch_new_file(); + memset(f, 0, sizeof(*f)); + f->symbol_cmp = strcmp; + f->symbol_hash = obj_elf_hash; + f->load_order_search_start = &f->load_order; + + fseek(fp, 0, SEEK_SET); + if (fread(&f->header, sizeof(f->header), 1, fp) != 1) { + bb_perror_msg("error reading ELF header"); + return NULL; + } + + if (f->header.e_ident[EI_MAG0] != ELFMAG0 + || f->header.e_ident[EI_MAG1] != ELFMAG1 + || f->header.e_ident[EI_MAG2] != ELFMAG2 + || f->header.e_ident[EI_MAG3] != ELFMAG3) { + bb_error_msg("not an ELF file"); + return NULL; + } + if (f->header.e_ident[EI_CLASS] != ELFCLASSM + || f->header.e_ident[EI_DATA] != (BB_BIG_ENDIAN + ? ELFDATA2MSB : ELFDATA2LSB) + || f->header.e_ident[EI_VERSION] != EV_CURRENT + || !MATCH_MACHINE(f->header.e_machine)) { + bb_error_msg("ELF file not for this architecture"); + return NULL; + } + if (f->header.e_type != ET_REL) { + bb_error_msg("ELF file not a relocatable object"); + return NULL; + } + + /* Read the section headers. */ + + if (f->header.e_shentsize != sizeof(ElfW(Shdr))) { + bb_error_msg("section header size mismatch: %lu != %lu", + (unsigned long) f->header.e_shentsize, + (unsigned long) sizeof(ElfW(Shdr))); + return NULL; + } + + shnum = f->header.e_shnum; + f->sections = xmalloc(sizeof(struct obj_section *) * shnum); + memset(f->sections, 0, sizeof(struct obj_section *) * shnum); + + section_headers = alloca(sizeof(ElfW(Shdr)) * shnum); + fseek(fp, f->header.e_shoff, SEEK_SET); + if (fread(section_headers, sizeof(ElfW(Shdr)), shnum, fp) != shnum) { + bb_perror_msg("error reading ELF section headers"); + return NULL; + } + + /* Read the section data. */ + + for (i = 0; i < shnum; ++i) { + struct obj_section *sec; + + f->sections[i] = sec = arch_new_section(); + memset(sec, 0, sizeof(*sec)); + + sec->header = section_headers[i]; + sec->idx = i; + + if(sec->header.sh_size) { + switch (sec->header.sh_type) { + case SHT_NULL: + case SHT_NOTE: + case SHT_NOBITS: + /* ignore */ + break; + + case SHT_PROGBITS: +#if LOADBITS + if (!loadprogbits) { + sec->contents = NULL; + break; + } +#endif + case SHT_SYMTAB: + case SHT_STRTAB: + case SHT_RELM: + if (sec->header.sh_size > 0) { + sec->contents = xmalloc(sec->header.sh_size); + fseek(fp, sec->header.sh_offset, SEEK_SET); + if (fread(sec->contents, sec->header.sh_size, 1, fp) != 1) { + bb_perror_msg("error reading ELF section data"); + return NULL; + } + } else { + sec->contents = NULL; + } + break; + +#if SHT_RELM == SHT_REL + case SHT_RELA: + bb_error_msg("RELA relocations not supported on this architecture"); + return NULL; +#else + case SHT_REL: + bb_error_msg("REL relocations not supported on this architecture"); + return NULL; +#endif + + default: + if (sec->header.sh_type >= SHT_LOPROC) { + /* Assume processor specific section types are debug + info and can safely be ignored. If this is ever not + the case (Hello MIPS?), don't put ifdefs here but + create an arch_load_proc_section(). */ + break; + } + + bb_error_msg("can't handle sections of type %ld", + (long) sec->header.sh_type); + return NULL; + } + } + } + + /* Do what sort of interpretation as needed by each section. */ + + shstrtab = f->sections[f->header.e_shstrndx]->contents; + + for (i = 0; i < shnum; ++i) { + struct obj_section *sec = f->sections[i]; + sec->name = shstrtab + sec->header.sh_name; + } + + for (i = 0; i < shnum; ++i) { + struct obj_section *sec = f->sections[i]; + + /* .modinfo should be contents only but gcc has no attribute for that. + * The kernel may have marked .modinfo as ALLOC, ignore this bit. + */ + if (strcmp(sec->name, ".modinfo") == 0) + sec->header.sh_flags &= ~SHF_ALLOC; + + if (sec->header.sh_flags & SHF_ALLOC) + obj_insert_section_load_order(f, sec); + + switch (sec->header.sh_type) { + case SHT_SYMTAB: + { + unsigned long nsym, j; + char *strtab; + ElfW(Sym) * sym; + + if (sec->header.sh_entsize != sizeof(ElfW(Sym))) { + bb_error_msg("symbol size mismatch: %lu != %lu", + (unsigned long) sec->header.sh_entsize, + (unsigned long) sizeof(ElfW(Sym))); + return NULL; + } + + nsym = sec->header.sh_size / sizeof(ElfW(Sym)); + strtab = f->sections[sec->header.sh_link]->contents; + sym = (ElfW(Sym) *) sec->contents; + + /* Allocate space for a table of local symbols. */ + j = f->local_symtab_size = sec->header.sh_info; + f->local_symtab = xzalloc(j * sizeof(struct obj_symbol *)); + + /* Insert all symbols into the hash table. */ + for (j = 1, ++sym; j < nsym; ++j, ++sym) { + ElfW(Addr) val = sym->st_value; + const char *name; + if (sym->st_name) + name = strtab + sym->st_name; + else if (sym->st_shndx < shnum) + name = f->sections[sym->st_shndx]->name; + else + continue; +#if defined(__SH5__) + /* + * For sh64 it is possible that the target of a branch + * requires a mode switch (32 to 16 and back again). + * + * This is implied by the lsb being set in the target + * address for SHmedia mode and clear for SHcompact. + */ + val |= sym->st_other & 4; +#endif + + obj_add_symbol(f, name, j, sym->st_info, sym->st_shndx, + val, sym->st_size); + } + } + break; + + case SHT_RELM: + if (sec->header.sh_entsize != sizeof(ElfW(RelM))) { + bb_error_msg("relocation entry size mismatch: %lu != %lu", + (unsigned long) sec->header.sh_entsize, + (unsigned long) sizeof(ElfW(RelM))); + return NULL; + } + break; + /* XXX Relocation code from modutils-2.3.19 is not here. + * Why? That's about 20 lines of code from obj/obj_load.c, + * which gets done in a second pass through the sections. + * This BusyBox insmod does similar work in obj_relocate(). */ + } + } + + return f; +} + +#if ENABLE_FEATURE_INSMOD_LOADINKMEM +/* + * load the unloaded sections directly into the memory allocated by + * kernel for the module + */ + +static int obj_load_progbits(FILE * fp, struct obj_file* f, char* imagebase) +{ + ElfW(Addr) base = f->baseaddr; + struct obj_section* sec; + + for (sec = f->load_order; sec; sec = sec->load_next) { + + /* section already loaded? */ + if (sec->contents != NULL) + continue; + + if (sec->header.sh_size == 0) + continue; + + sec->contents = imagebase + (sec->header.sh_addr - base); + fseek(fp, sec->header.sh_offset, SEEK_SET); + if (fread(sec->contents, sec->header.sh_size, 1, fp) != 1) { + bb_perror_msg("error reading ELF section data"); + return 0; + } + + } + return 1; +} +#endif + +static void hide_special_symbols(struct obj_file *f) +{ + static const char *const specials[] = { + SPFX "cleanup_module", + SPFX "init_module", + SPFX "kernel_version", + NULL + }; + + struct obj_symbol *sym; + const char *const *p; + + for (p = specials; *p; ++p) { + sym = obj_find_symbol(f, *p); + if (sym != NULL) + sym->info = ELF_ST_INFO(STB_LOCAL, ELF_ST_TYPE(sym->info)); + } +} + + +#if ENABLE_FEATURE_CHECK_TAINTED_MODULE +static int obj_gpl_license(struct obj_file *f, const char **license) +{ + struct obj_section *sec; + /* This list must match *exactly* the list of allowable licenses in + * linux/include/linux/module.h. Checking for leading "GPL" will not + * work, somebody will use "GPL sucks, this is proprietary". + */ + static const char * const gpl_licenses[] = { + "GPL", + "GPL v2", + "GPL and additional rights", + "Dual BSD/GPL", + "Dual MPL/GPL", + }; + + sec = obj_find_section(f, ".modinfo"); + if (sec) { + const char *value, *ptr, *endptr; + ptr = sec->contents; + endptr = ptr + sec->header.sh_size; + while (ptr < endptr) { + value = strchr(ptr, '='); + if (value && strncmp(ptr, "license", value-ptr) == 0) { + int i; + if (license) + *license = value+1; + for (i = 0; i < sizeof(gpl_licenses)/sizeof(gpl_licenses[0]); ++i) { + if (strcmp(value+1, gpl_licenses[i]) == 0) + return 0; + } + return 2; + } + if (strchr(ptr, '\0')) + ptr = strchr(ptr, '\0') + 1; + else + ptr = endptr; + } + } + return 1; +} + +#define TAINT_FILENAME "/proc/sys/kernel/tainted" +#define TAINT_PROPRIETORY_MODULE (1<<0) +#define TAINT_FORCED_MODULE (1<<1) +#define TAINT_UNSAFE_SMP (1<<2) +#define TAINT_URL "http://www.tux.org/lkml/#export-tainted" + +static void set_tainted(struct obj_file *f, int fd, char *m_name, + int kernel_has_tainted, int taint, const char *text1, const char *text2) +{ + char buf[80]; + int oldval; + static int first = 1; + if (fd < 0 && !kernel_has_tainted) + return; /* New modutils on old kernel */ + printf("Warning: loading %s will taint the kernel: %s%s\n", + m_name, text1, text2); + if (first) { + printf(" See %s for information about tainted modules\n", TAINT_URL); + first = 0; + } + if (fd >= 0) { + read(fd, buf, sizeof(buf)-1); + buf[sizeof(buf)-1] = '\0'; + oldval = strtoul(buf, NULL, 10); + sprintf(buf, "%d\n", oldval | taint); + write(fd, buf, strlen(buf)); + } +} + +/* Check if loading this module will taint the kernel. */ +static void check_tainted_module(struct obj_file *f, char *m_name) +{ + static const char tainted_file[] = TAINT_FILENAME; + int fd, kernel_has_tainted; + const char *ptr; + + kernel_has_tainted = 1; + fd = open(tainted_file, O_RDWR); + if (fd < 0) { + if (errno == ENOENT) + kernel_has_tainted = 0; + else if (errno == EACCES) + kernel_has_tainted = 1; + else { + perror(tainted_file); + kernel_has_tainted = 0; + } + } + + switch (obj_gpl_license(f, &ptr)) { + case 0: + break; + case 1: + set_tainted(f, fd, m_name, kernel_has_tainted, TAINT_PROPRIETORY_MODULE, "no license", ""); + break; + case 2: + /* The module has a non-GPL license so we pretend that the + * kernel always has a taint flag to get a warning even on + * kernels without the proc flag. + */ + set_tainted(f, fd, m_name, 1, TAINT_PROPRIETORY_MODULE, "non-GPL license - ", ptr); + break; + default: + set_tainted(f, fd, m_name, 1, TAINT_PROPRIETORY_MODULE, "Unexpected return from obj_gpl_license", ""); + break; + } + + if (flag_force_load) + set_tainted(f, fd, m_name, 1, TAINT_FORCED_MODULE, "forced load", ""); + + if (fd >= 0) + close(fd); +} +#else /* FEATURE_CHECK_TAINTED_MODULE */ +#define check_tainted_module(x, y) do { } while(0); +#endif /* FEATURE_CHECK_TAINTED_MODULE */ + +#if ENABLE_FEATURE_INSMOD_KSYMOOPS_SYMBOLS +/* add module source, timestamp, kernel version and a symbol for the + * start of some sections. this info is used by ksymoops to do better + * debugging. + */ +static int +get_module_version(struct obj_file *f, char str[STRVERSIONLEN]) +{ +#if ENABLE_FEATURE_INSMOD_VERSION_CHECKING + return new_get_module_version(f, str); +#else /* FEATURE_INSMOD_VERSION_CHECKING */ + strncpy(str, "???", sizeof(str)); + return -1; +#endif /* FEATURE_INSMOD_VERSION_CHECKING */ +} + +/* add module source, timestamp, kernel version and a symbol for the + * start of some sections. this info is used by ksymoops to do better + * debugging. + */ +static void +add_ksymoops_symbols(struct obj_file *f, const char *filename, + const char *m_name) +{ + static const char symprefix[] = "__insmod_"; + struct obj_section *sec; + struct obj_symbol *sym; + char *name, *absolute_filename; + char str[STRVERSIONLEN], real[PATH_MAX]; + int i, l, lm_name, lfilename, use_ksymtab, version; + struct stat statbuf; + + static const char *section_names[] = { + ".text", + ".rodata", + ".data", + ".bss", + ".sbss" + }; + + if (realpath(filename, real)) { + absolute_filename = xstrdup(real); + } + else { + int save_errno = errno; + bb_error_msg("cannot get realpath for %s", filename); + errno = save_errno; + perror(""); + absolute_filename = xstrdup(filename); + } + + lm_name = strlen(m_name); + lfilename = strlen(absolute_filename); + + /* add to ksymtab if it already exists or there is no ksymtab and other symbols + * are not to be exported. otherwise leave ksymtab alone for now, the + * "export all symbols" compatibility code will export these symbols later. + */ + use_ksymtab = obj_find_section(f, "__ksymtab") || flag_noexport; + + if ((sec = obj_find_section(f, ".this"))) { + /* tag the module header with the object name, last modified + * timestamp and module version. worst case for module version + * is 0xffffff, decimal 16777215. putting all three fields in + * one symbol is less readable but saves kernel space. + */ + l = sizeof(symprefix)+ /* "__insmod_" */ + lm_name+ /* module name */ + 2+ /* "_O" */ + lfilename+ /* object filename */ + 2+ /* "_M" */ + 2*sizeof(statbuf.st_mtime)+ /* mtime in hex */ + 2+ /* "_V" */ + 8+ /* version in dec */ + 1; /* nul */ + name = xmalloc(l); + if (stat(absolute_filename, &statbuf) != 0) + statbuf.st_mtime = 0; + version = get_module_version(f, str); /* -1 if not found */ + snprintf(name, l, "%s%s_O%s_M%0*lX_V%d", + symprefix, m_name, absolute_filename, + (int)(2*sizeof(statbuf.st_mtime)), statbuf.st_mtime, + version); + sym = obj_add_symbol(f, name, -1, + ELF_ST_INFO(STB_GLOBAL, STT_NOTYPE), + sec->idx, sec->header.sh_addr, 0); + if (use_ksymtab) + new_add_ksymtab(f, sym); + } + free(absolute_filename); +#ifdef _NOT_SUPPORTED_ + /* record where the persistent data is going, same address as previous symbol */ + + if (f->persist) { + l = sizeof(symprefix)+ /* "__insmod_" */ + lm_name+ /* module name */ + 2+ /* "_P" */ + strlen(f->persist)+ /* data store */ + 1; /* nul */ + name = xmalloc(l); + snprintf(name, l, "%s%s_P%s", + symprefix, m_name, f->persist); + sym = obj_add_symbol(f, name, -1, ELF_ST_INFO(STB_GLOBAL, STT_NOTYPE), + sec->idx, sec->header.sh_addr, 0); + if (use_ksymtab) + new_add_ksymtab(f, sym); + } +#endif /* _NOT_SUPPORTED_ */ + /* tag the desired sections if size is non-zero */ + + for (i = 0; i < sizeof(section_names)/sizeof(section_names[0]); ++i) { + if ((sec = obj_find_section(f, section_names[i])) && + sec->header.sh_size) { + l = sizeof(symprefix)+ /* "__insmod_" */ + lm_name+ /* module name */ + 2+ /* "_S" */ + strlen(sec->name)+ /* section name */ + 2+ /* "_L" */ + 8+ /* length in dec */ + 1; /* nul */ + name = xmalloc(l); + snprintf(name, l, "%s%s_S%s_L%ld", + symprefix, m_name, sec->name, + (long)sec->header.sh_size); + sym = obj_add_symbol(f, name, -1, ELF_ST_INFO(STB_GLOBAL, STT_NOTYPE), + sec->idx, sec->header.sh_addr, 0); + if (use_ksymtab) + new_add_ksymtab(f, sym); + } + } +} +#endif /* FEATURE_INSMOD_KSYMOOPS_SYMBOLS */ + +#if ENABLE_FEATURE_INSMOD_LOAD_MAP +static void print_load_map(struct obj_file *f) +{ + struct obj_symbol *sym; + struct obj_symbol **all, **p; + struct obj_section *sec; + int i, nsyms, *loaded; + + /* Report on the section layout. */ + + printf("Sections: Size %-*s Align\n", + (int) (2 * sizeof(void *)), "Address"); + + for (sec = f->load_order; sec; sec = sec->load_next) { + int a; + unsigned long tmp; + + for (a = -1, tmp = sec->header.sh_addralign; tmp; ++a) + tmp >>= 1; + if (a == -1) + a = 0; + + printf("%-15s %08lx %0*lx 2**%d\n", + sec->name, + (long)sec->header.sh_size, + (int) (2 * sizeof(void *)), + (long)sec->header.sh_addr, + a); + } +#if ENABLE_FEATURE_INSMOD_LOAD_MAP_FULL + /* Quick reference which section indicies are loaded. */ + + loaded = alloca(sizeof(int) * (i = f->header.e_shnum)); + while (--i >= 0) + loaded[i] = (f->sections[i]->header.sh_flags & SHF_ALLOC) != 0; + + /* Collect the symbols we'll be listing. */ + + for (nsyms = i = 0; i < HASH_BUCKETS; ++i) + for (sym = f->symtab[i]; sym; sym = sym->next) + if (sym->secidx <= SHN_HIRESERVE + && (sym->secidx >= SHN_LORESERVE || loaded[sym->secidx])) + ++nsyms; + + all = alloca(nsyms * sizeof(struct obj_symbol *)); + + for (i = 0, p = all; i < HASH_BUCKETS; ++i) + for (sym = f->symtab[i]; sym; sym = sym->next) + if (sym->secidx <= SHN_HIRESERVE + && (sym->secidx >= SHN_LORESERVE || loaded[sym->secidx])) + *p++ = sym; + + /* And list them. */ + printf("\nSymbols:\n"); + for (p = all; p < all + nsyms; ++p) { + char type = '?'; + unsigned long value; + + sym = *p; + if (sym->secidx == SHN_ABS) { + type = 'A'; + value = sym->value; + } else if (sym->secidx == SHN_UNDEF) { + type = 'U'; + value = 0; + } else { + sec = f->sections[sym->secidx]; + + if (sec->header.sh_type == SHT_NOBITS) + type = 'B'; + else if (sec->header.sh_flags & SHF_ALLOC) { + if (sec->header.sh_flags & SHF_EXECINSTR) + type = 'T'; + else if (sec->header.sh_flags & SHF_WRITE) + type = 'D'; + else + type = 'R'; + } + value = sym->value + sec->header.sh_addr; + } + + if (ELF_ST_BIND(sym->info) == STB_LOCAL) + type = tolower(type); + + printf("%0*lx %c %s\n", (int) (2 * sizeof(void *)), value, + type, sym->name); + } +#endif +} +#else /* !FEATURE_INSMOD_LOAD_MAP */ +void print_load_map(struct obj_file *f); +#endif + +int insmod_main( int argc, char **argv) +{ + char *opt_o, *arg1; + int len; + int k_crcs; + char *tmp, *tmp1; + unsigned long m_size; + ElfW(Addr) m_addr; + struct obj_file *f; + struct stat st; + char *m_name = 0; + int exit_status = EXIT_FAILURE; + int m_has_modinfo; +#if ENABLE_FEATURE_INSMOD_VERSION_CHECKING + struct utsname uts_info; + char m_strversion[STRVERSIONLEN]; + int m_version, m_crcs; +#endif +#if ENABLE_FEATURE_CLEAN_UP + FILE *fp = 0; +#else + FILE *fp; +#endif + int k_version = 0; + struct utsname myuname; + + /* Parse any options */ + getopt32(argc, argv, OPTION_STR, &opt_o); + arg1 = argv[optind]; + if (option_mask32 & OPT_o) { // -o /* name the output module */ + free(m_name); + m_name = xstrdup(opt_o); + } + + if (arg1 == NULL) { + bb_show_usage(); + } + + /* Grab the module name */ + tmp1 = xstrdup(arg1); + tmp = basename(tmp1); + len = strlen(tmp); + + if (uname(&myuname) == 0) { + if (myuname.release[0] == '2') { + k_version = myuname.release[2] - '0'; + } + } + +#if ENABLE_FEATURE_2_6_MODULES + if (k_version > 4 && len > 3 && tmp[len - 3] == '.' + && tmp[len - 2] == 'k' && tmp[len - 1] == 'o' + ) { + len -= 3; + tmp[len] = '\0'; + } else +#endif + if (len > 2 && tmp[len - 2] == '.' && tmp[len - 1] == 'o') { + len -= 2; + tmp[len] = '\0'; + } + + +#if ENABLE_FEATURE_2_6_MODULES + if (k_version > 4) + m_fullName = xasprintf("%s.ko", tmp); + else +#endif + m_fullName = xasprintf("%s.o", tmp); + + if (!m_name) { + m_name = tmp; + } else { + free(tmp1); + tmp1 = 0; /* flag for free(m_name) before exit() */ + } + + /* Get a filedesc for the module. Check we we have a complete path */ + if (stat(arg1, &st) < 0 || !S_ISREG(st.st_mode) + || (fp = fopen(arg1, "r")) == NULL + ) { + /* Hmm. Could not open it. First search under /lib/modules/`uname -r`, + * but do not error out yet if we fail to find it... */ + if (k_version) { /* uname succeedd */ + char *module_dir; + char *tmdn; + char real_module_dir[FILENAME_MAX]; + + tmdn = concat_path_file(_PATH_MODULES, myuname.release); + /* Jump through hoops in case /lib/modules/`uname -r` + * is a symlink. We do not want recursive_action to + * follow symlinks, but we do want to follow the + * /lib/modules/`uname -r` dir, So resolve it ourselves + * if it is a link... */ + if (realpath(tmdn, real_module_dir) == NULL) + module_dir = tmdn; + else + module_dir = real_module_dir; + recursive_action(module_dir, TRUE, FALSE, FALSE, + check_module_name_match, 0, m_fullName, 0); + free(tmdn); + } + + /* Check if we have found anything yet */ + if (m_filename == 0 || ((fp = fopen(m_filename, "r")) == NULL)) { + char module_dir[FILENAME_MAX]; + + free(m_filename); + m_filename = 0; + if (realpath (_PATH_MODULES, module_dir) == NULL) + strcpy(module_dir, _PATH_MODULES); + /* No module found under /lib/modules/`uname -r`, this + * time cast the net a bit wider. Search /lib/modules/ */ + if (!recursive_action(module_dir, TRUE, FALSE, FALSE, + check_module_name_match, 0, m_fullName, 0) + ) { + if (m_filename == 0 + || ((fp = fopen(m_filename, "r")) == NULL) + ) { + bb_error_msg("%s: no module by that name found", m_fullName); + goto out; + } + } else + bb_error_msg_and_die("%s: no module by that name found", m_fullName); + } + } else + m_filename = xstrdup(arg1); + + if (flag_verbose) + printf("Using %s\n", m_filename); + +#if ENABLE_FEATURE_2_6_MODULES + if (k_version > 4) { + argv[optind] = m_filename; + optind--; + return insmod_ng_main(argc - optind, argv + optind); + } +#endif + + f = obj_load(fp, LOADBITS); + if (f == NULL) + bb_perror_msg_and_die("cannot load the module"); + + if (get_modinfo_value(f, "kernel_version") == NULL) + m_has_modinfo = 0; + else + m_has_modinfo = 1; + +#if ENABLE_FEATURE_INSMOD_VERSION_CHECKING + /* Version correspondence? */ + if (!flag_quiet) { + if (uname(&uts_info) < 0) + uts_info.release[0] = '\0'; + if (m_has_modinfo) { + m_version = new_get_module_version(f, m_strversion); + if (m_version == -1) { + bb_error_msg("cannot find the kernel version the module was " + "compiled for"); + goto out; + } + } + + if (strncmp(uts_info.release, m_strversion, STRVERSIONLEN) != 0) { + if (flag_force_load) { + bb_error_msg("warning: kernel-module version mismatch\n" + "\t%s was compiled for kernel version %s\n" + "\twhile this kernel is version %s", + m_filename, m_strversion, uts_info.release); + } else { + bb_error_msg("kernel-module version mismatch\n" + "\t%s was compiled for kernel version %s\n" + "\twhile this kernel is version %s.", + m_filename, m_strversion, uts_info.release); + goto out; + } + } + } + k_crcs = 0; +#endif /* FEATURE_INSMOD_VERSION_CHECKING */ + + if (!query_module(NULL, 0, NULL, 0, NULL)) { + if (!new_get_kernel_symbols()) + goto out; + k_crcs = new_is_kernel_checksummed(); + } else { + bb_error_msg("not configured to support old kernels"); + goto out; + } + +#if ENABLE_FEATURE_INSMOD_VERSION_CHECKING + m_crcs = 0; + if (m_has_modinfo) + m_crcs = new_is_module_checksummed(f); + + if (m_crcs != k_crcs) + obj_set_symbol_compare(f, ncv_strcmp, ncv_symbol_hash); +#endif /* FEATURE_INSMOD_VERSION_CHECKING */ + + /* Let the module know about the kernel symbols. */ + add_kernel_symbols(f); + + /* Allocate common symbols, symbol tables, and string tables. */ + + if (!new_create_this_module(f, m_name)) { + goto out; + } + + if (!obj_check_undefineds(f)) { + goto out; + } + obj_allocate_commons(f); + check_tainted_module(f, m_name); + + /* done with the module name, on to the optional var=value arguments */ + ++optind; + if (optind < argc) { + if (!new_process_module_arguments(f, argc - optind, argv + optind)) { + goto out; + } + } + + arch_create_got(f); + hide_special_symbols(f); + +#if ENABLE_FEATURE_INSMOD_KSYMOOPS_SYMBOLS + add_ksymoops_symbols(f, m_filename, m_name); +#endif /* FEATURE_INSMOD_KSYMOOPS_SYMBOLS */ + + new_create_module_ksymtab(f); + + /* Find current size of the module */ + m_size = obj_load_size(f); + + + m_addr = create_module(m_name, m_size); + if (m_addr == -1) switch (errno) { + case EEXIST: + bb_error_msg("a module named %s already exists", m_name); + goto out; + case ENOMEM: + bb_error_msg("can't allocate kernel memory for module; needed %lu bytes", + m_size); + goto out; + default: + bb_perror_msg("create_module: %s", m_name); + goto out; + } + +#if !LOADBITS + /* + * the PROGBITS section was not loaded by the obj_load + * now we can load them directly into the kernel memory + */ + if (!obj_load_progbits(fp, f, (char*)m_addr)) { + delete_module(m_name); + goto out; + } +#endif + + if (!obj_relocate(f, m_addr)) { + delete_module(m_name); + goto out; + } + + if (!new_init_module(m_name, f, m_size)) { + delete_module(m_name); + goto out; + } + + if(flag_print_load_map) + print_load_map(f); + + exit_status = EXIT_SUCCESS; + +out: +#if ENABLE_FEATURE_CLEAN_UP + if(fp) + fclose(fp); + free(tmp1); + if(!tmp1) + free(m_name); + free(m_filename); +#endif + return exit_status; +} + + +#endif + + +#if ENABLE_FEATURE_2_6_MODULES + +#include +#include +#include + +/* We use error numbers in a loose translation... */ +static const char *moderror(int err) +{ + switch (err) { + case ENOEXEC: + return "Invalid module format"; + case ENOENT: + return "Unknown symbol in module"; + case ESRCH: + return "Module has wrong symbol version"; + case EINVAL: + return "Invalid parameters"; + default: + return strerror(err); + } +} + +int insmod_ng_main(int argc, char **argv) +{ + long ret; + size_t len; + void *map; + char *filename, *options; + + filename = *++argv; + if (!filename) + bb_show_usage(); + + /* Rest is options */ + options = xstrdup(""); + while (*++argv) { + int optlen = strlen(options); + options = xrealloc(options, optlen + 2 + strlen(*argv) + 2); + /* Spaces handled by "" pairs, but no way of escaping quotes */ + sprintf(options + optlen, (strchr(*argv,' ') ? "\"%s\" " : "%s "), *argv); + } + +#if 0 + /* Any special reason why mmap? It isn't performace critical... */ + int fd; + struct stat st; + unsigned long len; + fd = xopen(filename, O_RDONLY); + fstat(fd, &st); + len = st.st_size; + map = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0); + if (map == MAP_FAILED) { + bb_perror_msg_and_die("cannot mmap '%s'", filename); + } + + /* map == NULL on Blackfin, probably on other MMU-less systems too. Workaround. */ + if (map == NULL) { + map = xmalloc(len); + xread(fd, map, len); + } +#else + len = MAXINT(ssize_t); + map = xmalloc_open_read_close(filename, &len); +#endif + + ret = syscall(__NR_init_module, map, len, options); + if (ret != 0) { + bb_perror_msg_and_die("cannot insert '%s': %s (%li)", + filename, moderror(errno), ret); + } + + return 0; +} + +#endif diff --git a/modutils/lsmod.c b/modutils/lsmod.c new file mode 100644 index 000000000..ffde3d829 --- /dev/null +++ b/modutils/lsmod.c @@ -0,0 +1,188 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini lsmod implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Modified by Alcove, Julien Gaulmin and + * Nicolas Ferre to support pre 2.1 kernels + * (which lack the query_module() interface). + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + + +#ifndef CONFIG_FEATURE_CHECK_TAINTED_MODULE +static void check_tainted(void) { puts(""); } +#else +#define TAINT_FILENAME "/proc/sys/kernel/tainted" +#define TAINT_PROPRIETORY_MODULE (1<<0) +#define TAINT_FORCED_MODULE (1<<1) +#define TAINT_UNSAFE_SMP (1<<2) + +static void check_tainted(void) +{ + int tainted; + FILE *f; + + tainted = 0; + if ((f = fopen(TAINT_FILENAME, "r"))) { + fscanf(f, "%d", &tainted); + fclose(f); + } + if (f && tainted) { + printf(" Tainted: %c%c%c\n", + tainted & TAINT_PROPRIETORY_MODULE ? 'P' : 'G', + tainted & TAINT_FORCED_MODULE ? 'F' : ' ', + tainted & TAINT_UNSAFE_SMP ? 'S' : ' '); + } + else { + printf(" Not tainted\n"); + } +} +#endif + +#ifdef CONFIG_FEATURE_QUERY_MODULE_INTERFACE + +struct module_info +{ + unsigned long addr; + unsigned long size; + unsigned long flags; + long usecount; +}; + + +int query_module(const char *name, int which, void *buf, size_t bufsize, size_t *ret); + +enum { +/* Values for query_module's which. */ + QM_MODULES = 1, + QM_DEPS = 2, + QM_REFS = 3, + QM_SYMBOLS = 4, + QM_INFO = 5, + +/* Bits of module.flags. */ + NEW_MOD_RUNNING = 1, + NEW_MOD_DELETED = 2, + NEW_MOD_AUTOCLEAN = 4, + NEW_MOD_VISITED = 8, + NEW_MOD_USED_ONCE = 16, + NEW_MOD_INITIALIZING = 64 +}; + +int lsmod_main(int argc, char **argv) +{ + struct module_info info; + char *module_names, *mn, *deps, *dn; + size_t bufsize, depsize, nmod, count, i, j; + + module_names = deps = NULL; + bufsize = depsize = 0; + while(query_module(NULL, QM_MODULES, module_names, bufsize, &nmod)) { + if (errno != ENOSPC) bb_perror_msg_and_die("QM_MODULES"); + module_names = xmalloc(bufsize = nmod); + } + + deps = xmalloc(depsize = 256); + printf("Module\t\t\tSize Used by"); + check_tainted(); + + for (i = 0, mn = module_names; i < nmod; mn += strlen(mn) + 1, i++) { + if (query_module(mn, QM_INFO, &info, sizeof(info), &count)) { + if (errno == ENOENT) { + /* The module was removed out from underneath us. */ + continue; + } + /* else choke */ + bb_perror_msg_and_die("module %s: QM_INFO", mn); + } + while (query_module(mn, QM_REFS, deps, depsize, &count)) { + if (errno == ENOENT) { + /* The module was removed out from underneath us. */ + continue; + } else if (errno != ENOSPC) + bb_perror_msg_and_die("module %s: QM_REFS", mn); + deps = xrealloc(deps, count); + } + printf("%-20s%8lu%4ld", mn, info.size, info.usecount); + if (info.flags & NEW_MOD_DELETED) + printf(" (deleted)"); + else if (info.flags & NEW_MOD_INITIALIZING) + printf(" (initializing)"); + else if (!(info.flags & NEW_MOD_RUNNING)) + printf(" (uninitialized)"); + else { + if (info.flags & NEW_MOD_AUTOCLEAN) + printf(" (autoclean) "); + if (!(info.flags & NEW_MOD_USED_ONCE)) + printf(" (unused)"); + } + if (count) printf(" ["); + for (j = 0, dn = deps; j < count; dn += strlen(dn) + 1, j++) { + printf("%s%s", dn, (j==count-1)? "":" "); + } + if (count) printf("]"); + + puts(""); + } + +#ifdef CONFIG_FEATURE_CLEAN_UP + free(module_names); +#endif + + return 0; +} + +#else /* CONFIG_FEATURE_QUERY_MODULE_INTERFACE */ + +int lsmod_main(int argc, char **argv) +{ + FILE *file = xfopen("/proc/modules", "r"); + + printf("Module Size Used by"); + check_tainted(); +#if defined(CONFIG_FEATURE_LSMOD_PRETTY_2_6_OUTPUT) + { + char *line; + while ((line = xmalloc_fgets(file)) != NULL) { + char *tok; + + tok = strtok(line, " \t"); + printf("%-19s", tok); + tok = strtok(NULL, " \t\n"); + printf(" %8s", tok); + tok = strtok(NULL, " \t\n"); + /* Null if no module unloading support. */ + if (tok) { + printf(" %s", tok); + tok = strtok(NULL, "\n"); + if (!tok) + tok = ""; + /* New-style has commas, or -. If so, + truncate (other fields might follow). */ + else if (strchr(tok, ',')) { + tok = strtok(tok, "\t "); + /* Strip trailing comma. */ + if (tok[strlen(tok)-1] == ',') + tok[strlen(tok)-1] = '\0'; + } else if (tok[0] == '-' + && (tok[1] == '\0' || isspace(tok[1]))) + tok = ""; + printf(" %s", tok); + } + puts(""); + free(line); + } + fclose(file); + } +#else + xprint_and_close_file(file); +#endif /* CONFIG_FEATURE_2_6_MODULES */ + return EXIT_SUCCESS; +} + +#endif /* CONFIG_FEATURE_QUERY_MODULE_INTERFACE */ diff --git a/modutils/modprobe.c b/modutils/modprobe.c new file mode 100644 index 000000000..dcab493f1 --- /dev/null +++ b/modutils/modprobe.c @@ -0,0 +1,905 @@ +/* vi: set sw=4 ts=4: */ +/* + * Modprobe written from scratch for BusyBox + * + * Copyright (c) 2002 by Robert Griebl, griebl@gmx.de + * Copyright (c) 2003 by Andrew Dennison, andrew.dennison@motec.com.au + * Copyright (c) 2005 by Jim Bauer, jfbauer@nfr.com + * + * Portions Copyright (c) 2005 by Yann E. MORIN, yann.morin.1998@anciens.enib.fr + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. +*/ + +#include "busybox.h" +#include +#include + +struct mod_opt_t { /* one-way list of options to pass to a module */ + char * m_opt_val; + struct mod_opt_t * m_next; +}; + +struct dep_t { /* one-way list of dependency rules */ + /* a dependency rule */ + char * m_name; /* the module name*/ + char * m_path; /* the module file path */ + struct mod_opt_t * m_options; /* the module options */ + + int m_isalias : 1; /* the module is an alias */ + int m_reserved : 15; /* stuffin' */ + + int m_depcnt : 16; /* the number of dependable module(s) */ + char ** m_deparr; /* the list of dependable module(s) */ + + struct dep_t * m_next; /* the next dependency rule */ +}; + +struct mod_list_t { /* two-way list of modules to process */ + /* a module description */ + char * m_name; + char * m_path; + struct mod_opt_t * m_options; + + struct mod_list_t * m_prev; + struct mod_list_t * m_next; +}; + + +static struct dep_t *depend; + +#define main_options "acdklnqrst:vVC:" +#define INSERT_ALL 1 /* a */ +#define DUMP_CONF_EXIT 2 /* c */ +#define D_OPT_IGNORED 4 /* d */ +#define AUTOCLEAN_FLG 8 /* k */ +#define LIST_ALL 16 /* l */ +#define SHOW_ONLY 32 /* n */ +#define QUIET 64 /* q */ +#define REMOVE_OPT 128 /* r */ +#define DO_SYSLOG 256 /* s */ +#define RESTRICT_DIR 512 /* t */ +#define VERBOSE 1024 /* v */ +#define VERSION_ONLY 2048 /* V */ +#define CONFIG_FILE 4096 /* C */ + +#define autoclean (main_opts & AUTOCLEAN_FLG) +#define show_only (main_opts & SHOW_ONLY) +#define quiet (main_opts & QUIET) +#define remove_opt (main_opts & REMOVE_OPT) +#define do_syslog (main_opts & DO_SYSLOG) +#define verbose (main_opts & VERBOSE) + +static int main_opts; + +static int parse_tag_value(char *buffer, char **ptag, char **pvalue) +{ + char *tag, *value; + + buffer = skip_whitespace(buffer); + tag = value = buffer; + while (!isspace(*value)) + if (!*value) return 0; + else value++; + *value++ = 0; + value = skip_whitespace(value); + if (!*value) return 0; + + *ptag = tag; + *pvalue = value; + + return 1; +} + +/* + * This function appends an option to a list + */ +static struct mod_opt_t *append_option(struct mod_opt_t *opt_list, char *opt) +{ + struct mod_opt_t *ol = opt_list; + + if (ol) { + while (ol->m_next) { + ol = ol->m_next; + } + ol->m_next = xmalloc(sizeof(struct mod_opt_t)); + ol = ol->m_next; + } else { + ol = opt_list = xmalloc(sizeof(struct mod_opt_t)); + } + + ol->m_opt_val = xstrdup(opt); + ol->m_next = NULL; + + return opt_list; +} + +#if ENABLE_FEATURE_MODPROBE_MULTIPLE_OPTIONS +/* static char* parse_command_string(char* src, char **dst); + * src: pointer to string containing argument + * dst: pointer to where to store the parsed argument + * return value: the pointer to the first char after the parsed argument, + * NULL if there was no argument parsed (only trailing spaces). + * Note that memory is allocated with xstrdup when a new argument was + * parsed. Don't forget to free it! + */ +#define ARG_EMPTY 0x00 +#define ARG_IN_DQUOTES 0x01 +#define ARG_IN_SQUOTES 0x02 +static char *parse_command_string(char *src, char **dst) +{ + int opt_status = ARG_EMPTY; + char* tmp_str; + + /* Dumb you, I have nothing to do... */ + if (src == NULL) return src; + + /* Skip leading spaces */ + while (*src == ' ') { + src++; + } + /* Is the end of string reached? */ + if (*src == '\0') { + return NULL; + } + /* Reached the start of an argument + * By the way, we duplicate a little too much + * here but what is too much is freed later. */ + *dst = tmp_str = xstrdup(src); + /* Get to the end of that argument */ + while (*tmp_str != '\0' + && (*tmp_str != ' ' || (opt_status & (ARG_IN_DQUOTES | ARG_IN_SQUOTES))) + ) { + switch (*tmp_str) { + case '\'': + if (opt_status & ARG_IN_DQUOTES) { + /* Already in double quotes, keep current char as is */ + } else { + /* shift left 1 char, until end of string: get rid of the opening/closing quotes */ + memmove(tmp_str, tmp_str + 1, strlen(tmp_str)); + /* mark me: we enter or leave single quotes */ + opt_status ^= ARG_IN_SQUOTES; + /* Back one char, as we need to re-scan the new char there. */ + tmp_str--; + } + break; + case '"': + if (opt_status & ARG_IN_SQUOTES) { + /* Already in single quotes, keep current char as is */ + } else { + /* shift left 1 char, until end of string: get rid of the opening/closing quotes */ + memmove(tmp_str, tmp_str + 1, strlen(tmp_str)); + /* mark me: we enter or leave double quotes */ + opt_status ^= ARG_IN_DQUOTES; + /* Back one char, as we need to re-scan the new char there. */ + tmp_str--; + } + break; + case '\\': + if (opt_status & ARG_IN_SQUOTES) { + /* Between single quotes: keep as is. */ + } else { + switch (*(tmp_str+1)) { + case 'a': + case 'b': + case 't': + case 'n': + case 'v': + case 'f': + case 'r': + case '0': + /* We escaped a special character. For now, keep + * both the back-slash and the following char. */ + tmp_str++; src++; + break; + default: + /* We escaped a space or a single or double quote, + * or a back-slash, or a non-escapable char. Remove + * the '\' and keep the new current char as is. */ + memmove(tmp_str, tmp_str + 1, strlen(tmp_str)); + break; + } + } + break; + /* Any other char that is special shall appear here. + * Example: $ starts a variable + case '$': + do_variable_expansion(); + break; + * */ + default: + /* any other char is kept as is. */ + break; + } + tmp_str++; /* Go to next char */ + src++; /* Go to next char to find the end of the argument. */ + } + /* End of string, but still no ending quote */ + if (opt_status & (ARG_IN_DQUOTES | ARG_IN_SQUOTES)) { + bb_error_msg_and_die("unterminated (single or double) quote in options list: %s", src); + } + *tmp_str++ = '\0'; + *dst = xrealloc(*dst, (tmp_str - *dst)); + return src; +} +#else +#define parse_command_string(src, dst) (0) +#endif /* ENABLE_FEATURE_MODPROBE_MULTIPLE_OPTIONS */ + +/* + * This function reads aliases and default module options from a configuration file + * (/etc/modprobe.conf syntax). It supports includes (only files, no directories). + */ +static void include_conf(struct dep_t **first, struct dep_t **current, char *buffer, int buflen, int fd) +{ + int continuation_line = 0; + + // alias parsing is not 100% correct (no correct handling of continuation lines within an alias) ! + + while (reads(fd, buffer, buflen)) { + int l; + char *p; + + p = strchr(buffer, '#'); + if (p) + *p = 0; + + l = strlen(buffer); + + while (l && isspace(buffer[l-1])) { + buffer[l-1] = 0; + l--; + } + + if (l == 0) { + continuation_line = 0; + continue; + } + + if (!continuation_line) { + if ((strncmp(buffer, "alias", 5) == 0) && isspace(buffer[5])) { + char *alias, *mod; + + if (parse_tag_value(buffer + 6, &alias, &mod)) { + /* handle alias as a module dependent on the aliased module */ + if (!*current) { + (*first) = (*current) = xzalloc(sizeof(struct dep_t)); + } + else { + (*current)->m_next = xzalloc(sizeof(struct dep_t)); + (*current) = (*current)->m_next; + } + (*current)->m_name = xstrdup(alias); + (*current)->m_isalias = 1; + + if ((strcmp(mod, "off") == 0) || (strcmp(mod, "null") == 0)) { + (*current)->m_depcnt = 0; + (*current)->m_deparr = 0; + } + else { + (*current)->m_depcnt = 1; + (*current)->m_deparr = xmalloc(1 * sizeof(char *)); + (*current)->m_deparr[0] = xstrdup(mod); + } + (*current)->m_next = 0; + } + } + else if ((strncmp(buffer, "options", 7) == 0) && isspace(buffer[7])) { + char *mod, *opt; + + /* split the line in the module/alias name, and options */ + if (parse_tag_value(buffer + 8, &mod, &opt)) { + struct dep_t *dt; + + /* find the corresponding module */ + for (dt = *first; dt; dt = dt->m_next) { + if (strcmp(dt->m_name, mod) == 0) + break; + } + if (dt) { + if (ENABLE_FEATURE_MODPROBE_MULTIPLE_OPTIONS) { + char* new_opt = NULL; + while ((opt = parse_command_string(opt, &new_opt))) { + dt->m_options = append_option(dt->m_options, new_opt); + } + } else { + dt->m_options = append_option(dt->m_options, opt); + } + } + } + } + else if ((strncmp(buffer, "include", 7) == 0) && isspace(buffer[7])) { + int fdi; char *filename; + + filename = skip_whitespace(buffer + 8); + + if ((fdi = open(filename, O_RDONLY)) >= 0) { + include_conf(first, current, buffer, buflen, fdi); + close(fdi); + } + } + } + } +} + +/* + * This function builds a list of dependency rules from /lib/modules/`uname -r`\modules.dep. + * It then fills every modules and aliases with their default options, found by parsing + * modprobe.conf (or modules.conf, or conf.modules). + */ +static struct dep_t *build_dep(void) +{ + int fd; + struct utsname un; + struct dep_t *first = 0; + struct dep_t *current = 0; + char buffer[2048]; + char *filename; + int continuation_line = 0; + int k_version; + + if (uname(&un)) + bb_error_msg_and_die("can't determine kernel version"); + + k_version = 0; + if (un.release[0] == '2') { + k_version = un.release[2] - '0'; + } + + filename = xasprintf("/lib/modules/%s/modules.dep", un.release); + fd = open(filename, O_RDONLY); + if (ENABLE_FEATURE_CLEAN_UP) + free(filename); + if (fd < 0) { + /* Ok, that didn't work. Fall back to looking in /lib/modules */ + fd = open("/lib/modules/modules.dep", O_RDONLY); + if (fd < 0) { + return 0; + } + } + + while (reads(fd, buffer, sizeof(buffer))) { + int l = strlen(buffer); + char *p = 0; + + while (l > 0 && isspace(buffer[l-1])) { + buffer[l-1] = 0; + l--; + } + + if (l == 0) { + continuation_line = 0; + continue; + } + + /* Is this a new module dep description? */ + if (!continuation_line) { + /* find the dep beginning */ + char *col = strchr(buffer, ':'); + char *dot = col; + + if (col) { + /* This line is a dep description */ + char *mods; + char *modpath; + char *mod; + + /* Find the beginning of the module file name */ + *col = 0; + mods = strrchr(buffer, '/'); + + if (!mods) + mods = buffer; /* no path for this module */ + else + mods++; /* there was a path for this module... */ + + /* find the path of the module */ + modpath = strchr(buffer, '/'); /* ... and this is the path */ + if (!modpath) + modpath = buffer; /* module with no path */ + /* find the end of the module name in the file name */ + if (ENABLE_FEATURE_2_6_MODULES && + (k_version > 4) && (*(col-3) == '.') && + (*(col-2) == 'k') && (*(col-1) == 'o')) + dot = col - 3; + else + if ((*(col-2) == '.') && (*(col-1) == 'o')) + dot = col - 2; + + mod = xstrndup(mods, dot - mods); + + /* enqueue new module */ + if (!current) { + first = current = xmalloc(sizeof(struct dep_t)); + } + else { + current->m_next = xmalloc(sizeof(struct dep_t)); + current = current->m_next; + } + current->m_name = mod; + current->m_path = xstrdup(modpath); + current->m_options = NULL; + current->m_isalias = 0; + current->m_depcnt = 0; + current->m_deparr = 0; + current->m_next = 0; + + p = col + 1; + } + else + /* this line is not a dep description */ + p = 0; + } + else + /* It's a dep description continuation */ + p = buffer; + + while (p && *p && isblank(*p)) + p++; + + /* p points to the first dependable module; if NULL, no dependable module */ + if (p && *p) { + char *end = &buffer[l-1]; + char *deps; + char *dep; + char *next; + int ext = 0; + + while (isblank(*end) || (*end == '\\')) + end--; + + do { + /* search the end of the dependency */ + next = strchr(p, ' '); + if (next) { + *next = 0; + next--; + } + else + next = end; + + /* find the beginning of the module file name */ + deps = strrchr(p, '/'); + + if (!deps || (deps < p)) { + deps = p; + + while (isblank(*deps)) + deps++; + } else + deps++; + + /* find the end of the module name in the file name */ + if (ENABLE_FEATURE_2_6_MODULES + && (k_version > 4) && (*(next-2) == '.') + && (*(next-1) == 'k') && (*next == 'o')) + ext = 3; + else + if ((*(next-1) == '.') && (*next == 'o')) + ext = 2; + + /* Cope with blank lines */ + if ((next-deps-ext+1) <= 0) + continue; + dep = xstrndup(deps, next - deps - ext + 1); + + /* Add the new dependable module name */ + current->m_depcnt++; + current->m_deparr = xrealloc(current->m_deparr, + sizeof(char *) * current->m_depcnt); + current->m_deparr[current->m_depcnt - 1] = dep; + + p = next + 2; + } while (next < end); + } + + /* is there other dependable module(s) ? */ + if (buffer[l-1] == '\\') + continuation_line = 1; + else + continuation_line = 0; + } + close(fd); + + /* + * First parse system-specific options and aliases + * as they take precedence over the kernel ones. + */ + if (!ENABLE_FEATURE_2_6_MODULES + || (fd = open("/etc/modprobe.conf", O_RDONLY)) < 0) + if ((fd = open("/etc/modules.conf", O_RDONLY)) < 0) + fd = open("/etc/conf.modules", O_RDONLY); + + if (fd >= 0) { + include_conf(&first, ¤t, buffer, sizeof(buffer), fd); + close(fd); + } + + /* Only 2.6 has a modules.alias file */ + if (ENABLE_FEATURE_2_6_MODULES) { + /* Parse kernel-declared aliases */ + filename = xasprintf("/lib/modules/%s/modules.alias", un.release); + fd = open(filename, O_RDONLY); + if (fd < 0) { + /* Ok, that didn't work. Fall back to looking in /lib/modules */ + fd = open("/lib/modules/modules.alias", O_RDONLY); + } + if (ENABLE_FEATURE_CLEAN_UP) + free(filename); + + if (fd >= 0) { + include_conf(&first, ¤t, buffer, sizeof(buffer), fd); + close(fd); + } + } + + return first; +} + +/* return 1 = loaded, 0 = not loaded, -1 = can't tell */ +static int already_loaded(const char *name) +{ + int fd, ret = 0; + char buffer[4096]; + + fd = open("/proc/modules", O_RDONLY); + if (fd < 0) + return -1; + + while (reads(fd, buffer, sizeof(buffer))) { + char *p; + + p = strchr (buffer, ' '); + if (p) { + const char *n; + + // Truncate buffer at first space and check for matches, with + // the idiosyncrasy that _ and - are interchangeable because the + // 2.6 kernel does weird things. + + *p = 0; + for (p = buffer, n = name; ; p++, n++) { + if (*p != *n) { + if ((*p == '_' || *p == '-') && (*n == '_' || *n == '-')) + continue; + break; + } + // If we made it to the end, that's a match. + if (!*p) { + ret = 1; + goto done; + } + } + } + } +done: + close (fd); + return ret; +} + +static int mod_process(struct mod_list_t *list, int do_insert) +{ + int rc = 0; + char **argv = NULL; + struct mod_opt_t *opts; + int argc_malloc; /* never used when CONFIG_FEATURE_CLEAN_UP not defined */ + int argc; + + while (list) { + argc = 0; + if (ENABLE_FEATURE_CLEAN_UP) + argc_malloc = 0; + /* If CONFIG_FEATURE_CLEAN_UP is not defined, then we leak memory + * each time we allocate memory for argv. + * But it is (quite) small amounts of memory that leak each + * time a module is loaded, and it is reclaimed when modprobe + * exits anyway (even when standalone shell?). + * This could become a problem when loading a module with LOTS of + * dependencies, with LOTS of options for each dependencies, with + * very little memory on the target... But in that case, the module + * would not load because there is no more memory, so there's no + * problem. */ + /* enough for minimal insmod (5 args + NULL) or rmmod (3 args + NULL) */ + argv = xmalloc(6 * sizeof(char*)); + if (do_insert) { + if (already_loaded(list->m_name) != 1) { + argv[argc++] = "insmod"; + if (ENABLE_FEATURE_2_4_MODULES) { + if (do_syslog) + argv[argc++] = "-s"; + if (autoclean) + argv[argc++] = "-k"; + if (quiet) + argv[argc++] = "-q"; + else if (verbose) /* verbose and quiet are mutually exclusive */ + argv[argc++] = "-v"; + } + argv[argc++] = list->m_path; + if (ENABLE_FEATURE_CLEAN_UP) + argc_malloc = argc; + opts = list->m_options; + while (opts) { + /* Add one more option */ + argc++; + argv = xrealloc(argv,(argc + 1)* sizeof(char*)); + argv[argc-1] = opts->m_opt_val; + opts = opts->m_next; + } + } + } else { + /* modutils uses short name for removal */ + if (already_loaded(list->m_name) != 0) { + argv[argc++] = "rmmod"; + if (do_syslog) + argv[argc++] = "-s"; + argv[argc++] = list->m_name; + if (ENABLE_FEATURE_CLEAN_UP) + argc_malloc = argc; + } + } + argv[argc] = NULL; + + if (argc) { + if (verbose) { + printf("%s module %s\n", do_insert?"Loading":"Unloading", list->m_name); + } + if (!show_only) { + int rc2 = wait4pid(spawn(argv)); + + if (do_insert) { + rc = rc2; /* only last module matters */ + } + else if (!rc2) { + rc = 0; /* success if remove any mod */ + } + } + if (ENABLE_FEATURE_CLEAN_UP) { + /* the last value in the array has index == argc, but + * it is the terminating NULL, so we must not free it. */ + while (argc_malloc < argc) { + free(argv[argc_malloc++]); + } + } + } + if (ENABLE_FEATURE_CLEAN_UP) { + free(argv); + argv = NULL; + } + list = do_insert ? list->m_prev : list->m_next; + } + return (show_only) ? 0 : rc; +} + +/* + * Check the matching between a pattern and a module name. + * We need this as *_* is equivalent to *-*, even in pattern matching. + */ +static int check_pattern(const char* pat_src, const char* mod_src) { + int ret; + + if (ENABLE_FEATURE_MODPROBE_FANCY_ALIAS) { + char* pat; + char* mod; + char* p; + + pat = xstrdup (pat_src); + mod = xstrdup (mod_src); + + for (p = pat; (p = strchr(p, '-')); *p++ = '_'); + for (p = mod; (p = strchr(p, '-')); *p++ = '_'); + + ret = fnmatch(pat, mod, 0); + + if (ENABLE_FEATURE_CLEAN_UP) { + free (pat); + free (mod); + } + + return ret; + } else { + return fnmatch(pat_src, mod_src, 0); + } +} + +/* + * Builds the dependency list (aka stack) of a module. + * head: the highest module in the stack (last to insmod, first to rmmod) + * tail: the lowest module in the stack (first to insmod, last to rmmod) + */ +static void check_dep(char *mod, struct mod_list_t **head, struct mod_list_t **tail) +{ + struct mod_list_t *find; + struct dep_t *dt; + struct mod_opt_t *opt = 0; + char *path = 0; + + /* Search for the given module name amongst all dependency rules. + * The module name in a dependency rule can be a shell pattern, + * so try to match the given module name against such a pattern. + * Of course if the name in the dependency rule is a plain string, + * then we consider it a pattern, and matching will still work. */ + for (dt = depend; dt; dt = dt->m_next) { + if (check_pattern(dt->m_name, mod) == 0) { + break; + } + } + + if (!dt) { + bb_error_msg("module %s not found", mod); + return; + } + + // resolve alias names + while (dt->m_isalias) { + if (dt->m_depcnt == 1) { + struct dep_t *adt; + + for (adt = depend; adt; adt = adt->m_next) { + if (check_pattern(adt->m_name, dt->m_deparr[0]) == 0) + break; + } + if (adt) { + /* This is the module we are aliased to */ + struct mod_opt_t *opts = dt->m_options; + /* Option of the alias are appended to the options of the module */ + while (opts) { + adt->m_options = append_option(adt->m_options, opts->m_opt_val); + opts = opts->m_next; + } + dt = adt; + } + else { + bb_error_msg("module %s not found", mod); + return; + } + } + else { + bb_error_msg("bad alias %s", dt->m_name); + return; + } + } + + mod = dt->m_name; + path = dt->m_path; + opt = dt->m_options; + + // search for duplicates + for (find = *head; find; find = find->m_next) { + if (!strcmp(mod, find->m_name)) { + // found ->dequeue it + + if (find->m_prev) + find->m_prev->m_next = find->m_next; + else + *head = find->m_next; + + if (find->m_next) + find->m_next->m_prev = find->m_prev; + else + *tail = find->m_prev; + + break; // there can be only one duplicate + } + } + + if (!find) { // did not find a duplicate + find = xmalloc(sizeof(struct mod_list_t)); + find->m_name = mod; + find->m_path = path; + find->m_options = opt; + } + + // enqueue at tail + if (*tail) + (*tail)->m_next = find; + find->m_prev = *tail; + find->m_next = 0; + + if (!*head) + *head = find; + *tail = find; + + if (dt) { + int i; + + /* Add all dependable module for that new module */ + for (i = 0; i < dt->m_depcnt; i++) + check_dep(dt->m_deparr[i], head, tail); + } +} + +static int mod_insert(char *mod, int argc, char **argv) +{ + struct mod_list_t *tail = 0; + struct mod_list_t *head = 0; + int rc; + + // get dep list for module mod + check_dep(mod, &head, &tail); + + if (head && tail) { + if (argc) { + int i; + // append module args + for (i = 0; i < argc; i++) + head->m_options = append_option(head->m_options, argv[i]); + } + + // process tail ---> head + if ((rc = mod_process(tail, 1)) != 0) { + /* + * In case of using udev, multiple instances of modprobe can be + * spawned to load the same module (think of two same usb devices, + * for example; or cold-plugging at boot time). Thus we shouldn't + * fail if the module was loaded, and not by us. + */ + if (already_loaded(mod)) + rc = 0; + } + } + else + rc = 1; + + return rc; +} + +static int mod_remove(char *mod) +{ + int rc; + static struct mod_list_t rm_a_dummy = { "-a", NULL, NULL, NULL, NULL }; + + struct mod_list_t *head = 0; + struct mod_list_t *tail = 0; + + if (mod) + check_dep(mod, &head, &tail); + else // autoclean + head = tail = &rm_a_dummy; + + if (head && tail) + rc = mod_process(head, 0); // process head ---> tail + else + rc = 1; + return rc; + +} + +int modprobe_main(int argc, char** argv) +{ + int rc = EXIT_SUCCESS; + char *unused; + + opt_complementary = "?V-:q-v:v-q"; + main_opts = getopt32(argc, argv, "acdklnqrst:vVC:", + &unused, &unused); + if (main_opts & (DUMP_CONF_EXIT | LIST_ALL)) + return EXIT_SUCCESS; + if (main_opts & (RESTRICT_DIR | CONFIG_FILE)) + bb_error_msg_and_die("-t and -C not supported"); + + depend = build_dep(); + + if (!depend) + bb_error_msg_and_die("cannot parse modules.dep"); + + if (remove_opt) { + do { + if (mod_remove(optind < argc ? + argv[optind] : NULL)) { + bb_error_msg("failed to remove module %s", + argv[optind]); + rc = EXIT_FAILURE; + } + } while (++optind < argc); + } else { + if (optind >= argc) + bb_error_msg_and_die("no module or pattern provided"); + + if (mod_insert(argv[optind], argc - optind - 1, argv + optind + 1)) + bb_error_msg_and_die("failed to load module %s", argv[optind]); + } + + /* Here would be a good place to free up memory allocated during the dependencies build. */ + + return rc; +} diff --git a/modutils/rmmod.c b/modutils/rmmod.c new file mode 100644 index 000000000..22e864d57 --- /dev/null +++ b/modutils/rmmod.c @@ -0,0 +1,96 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini rmmod implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include + +#ifdef CONFIG_FEATURE_2_6_MODULES +static inline void filename2modname(char *modname, const char *afterslash) +{ + unsigned int i; + int kr_chk = 1; + + if (ENABLE_FEATURE_2_4_MODULES + && get_linux_version_code() <= KERNEL_VERSION(2,6,0)) + kr_chk = 0; + + /* Convert to underscores, stop at first . */ + for (i = 0; afterslash[i] && afterslash[i] != '.'; i++) { + if (kr_chk && (afterslash[i] == '-')) + modname[i] = '_'; + else + modname[i] = afterslash[i]; + } + modname[i] = '\0'; +} +#else +void filename2modname(char *modname, const char *afterslash); +#endif + +// There really should be a header file for this... + +int query_module(const char *name, int which, void *buf, + size_t bufsize, size_t *ret); + +int rmmod_main(int argc, char **argv) +{ + int n, ret = EXIT_SUCCESS; + unsigned int flags = O_NONBLOCK|O_EXCL; + + /* Parse command line. */ + n = getopt32(argc, argv, "wfa"); + if((n & 1)) // --wait + flags &= ~O_NONBLOCK; + if((n & 2)) // --force + flags |= O_TRUNC; + if((n & 4)) { + /* Unload _all_ unused modules via NULL delete_module() call */ + /* until the number of modules does not change */ + size_t nmod = 0; /* number of modules */ + size_t pnmod = -1; /* previous number of modules */ + + while (nmod != pnmod) { + if (syscall(__NR_delete_module, NULL, flags) != 0) { + if (errno == EFAULT) + return ret; + bb_perror_msg_and_die("rmmod"); + } + pnmod = nmod; + // the 1 here is QM_MODULES. + if (ENABLE_FEATURE_QUERY_MODULE_INTERFACE && query_module(NULL, + 1, bb_common_bufsiz1, sizeof(bb_common_bufsiz1), + &nmod)) + { + bb_perror_msg_and_die("QM_MODULES"); + } + } + return EXIT_SUCCESS; + } + + if (optind == argc) + bb_show_usage(); + + for (n = optind; n < argc; n++) { + if (ENABLE_FEATURE_2_6_MODULES) { + const char *afterslash; + + afterslash = strrchr(argv[n], '/'); + if (!afterslash) afterslash = argv[n]; + else afterslash++; + filename2modname(bb_common_bufsiz1, afterslash); + } + + if (syscall(__NR_delete_module, ENABLE_FEATURE_2_6_MODULES ? bb_common_bufsiz1 : argv[n], flags)) { + bb_perror_msg("%s", argv[n]); + ret = EXIT_FAILURE; + } + } + + return ret; +} diff --git a/networking/Config.in b/networking/Config.in new file mode 100644 index 000000000..e6711078c --- /dev/null +++ b/networking/Config.in @@ -0,0 +1,720 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Networking Utilities" + +config FEATURE_IPV6 + bool "Enable IPv6 support" + default n + help + Enable IPv6 support in busybox. + This adds IPv6 support in the networking applets. + +config ARPING + bool "arping" + default n + help + Ping hosts by ARP packets + +config DNSD + bool "dnsd" + default n + help + Small and static DNS server daemon. + +config ETHER_WAKE + bool "ether-wake" + default n + help + Send a magic packet to wake up sleeping machines. + +config FAKEIDENTD + bool "fakeidentd" + default n + select FEATURE_SYSLOG + help + fakeidentd listens on the ident port and returns a predefined + fake value on any query. + +config FTPGET + bool "ftpget" + default n + help + Retrieve a remote file via FTP. + +config FTPPUT + bool "ftpput" + default n + help + Store a remote file via FTP. + +config FEATURE_FTPGETPUT_LONG_OPTIONS + bool "Enable long options in ftpget/ftpput" + default n + depends on GETOPT_LONG && (FTPGET || FTPPUT) + help + Support long options for the ftpget/ftpput applet. + +config HOSTNAME + bool "hostname" + default n + help + Show or set the system's host name + +config HTTPD + bool "httpd" + default n + help + Serve web pages via an HTTP server. + +config FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP + bool "Support reloading the global config file using hup signal" + default n + depends on HTTPD && FEATURE_HTTPD_WITHOUT_INETD + help + This option enables processing of SIGHUP to reload cached + configuration settings. + +config FEATURE_HTTPD_SETUID + bool "Enable support -u option" + default n + depends on HTTPD && FEATURE_HTTPD_WITHOUT_INETD + help + This option allows the server to run as a specific user + rather than defaulting to the user that starts the server. + Use of this option requires special privileges to change to a + different user. + +config FEATURE_HTTPD_BASIC_AUTH + bool "Enable Basic http Authentication" + default y + depends on HTTPD + help + Utilizes password settings from /etc/httpd.conf for basic + authentication on a per url basis. + +config FEATURE_HTTPD_AUTH_MD5 + bool "Support MD5 crypted passwords for http Authentication" + default n + depends on FEATURE_HTTPD_BASIC_AUTH + help + Enables basic per URL authentication from /etc/httpd.conf + using md5 passwords. + +config FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + bool "Support loading additional MIME types at run-time" + default n + depends on HTTPD + help + This option enables support for additional MIME types at + run-time to be specified in the configuration file. + +config FEATURE_HTTPD_CGI + bool "Support Common Gateway Interface (CGI)" + default y + depends on HTTPD + help + This option allows scripts and executables to be invoked + when specific URLs are requested. + +config FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + bool "Enable support for running scripts through an interpreter" + default n + depends on FEATURE_HTTPD_CGI + help + This option enables support for running scripts through an + interpreter. Turn this on if you want PHP scripts to work + properly. You need to supply an addition line in your httpd + config file: + *.php:/path/to/your/php + +config FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV + bool "Support the REMOTE_PORT environment variable for CGI" + default n + depends on FEATURE_HTTPD_CGI + help + Use of this option can assist scripts in generating + references that contain a unique port number. + +config FEATURE_HTTPD_ENCODE_URL_STR + bool "Enable the -e option for shell script CGI simplification." + default y + depends on HTTPD + help + This option allows html encoding arbitrary + strings for display of the browser. Output goes to stdout. + For example, httpd -e "" as + "<Hello World>". + +config IFCONFIG + bool "ifconfig" + default n + help + Ifconfig is used to configure the kernel-resident network interfaces. + +config FEATURE_IFCONFIG_STATUS + bool "Enable status reporting output (+7k)" + default y + depends on IFCONFIG + help + If ifconfig is called with no arguments it will display the status + of the currently active interfaces. + +config FEATURE_IFCONFIG_SLIP + bool "Enable slip-specific options \"keepalive\" and \"outfill\"" + default n + depends on IFCONFIG + help + Allow "keepalive" and "outfill" support for SLIP. If you're not + planning on using serial lines, leave this unchecked. + +config FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ + bool "Enable options \"mem_start\", \"io_addr\", and \"irq\"" + default n + depends on IFCONFIG + help + Allow the start address for shared memory, start address for I/O, + and/or the interrupt line used by the specified device. + +config FEATURE_IFCONFIG_HW + bool "Enable option \"hw\" (ether only)" + default y + depends on IFCONFIG + help + Set the hardware address of this interface, if the device driver + supports this operation. Currently, we only support the 'ether' + class. + +config FEATURE_IFCONFIG_BROADCAST_PLUS + bool "Set the broadcast automatically" + default n + depends on IFCONFIG + help + Setting this will make ifconfig attempt to find the broadcast + automatically if the value '+' is used. + +config IFUPDOWN + bool "ifupdown" + default n + select RUN_PARTS + help + Activate or deactivate the specified interfaces. This applet makes + use of either "ifconfig" and "route" or the "ip" command to actually + configure network interfaces. Therefore, you will probably also want + to enable either IFCONFIG and ROUTE, or enable + FEATURE_IFUPDOWN_IP and the various IP options. Of + course you could use non-busybox versions of these programs, so + against my better judgement (since this will surely result in plenty + of support questions on the mailing list), I do not force you to + enable these additional options. It is up to you to supply either + "ifconfig" and "route" or the "ip" command, either via busybox or via + standalone utilities. + +config FEATURE_IFUPDOWN_IP + bool "Use ip applet" + default n + depends on IFUPDOWN + help + Use the iproute "ip" command to implement "ifup" and "ifdown", rather + than the default of using the older 'ifconfig' and 'route' utilities. + +config FEATURE_IFUPDOWN_IP_BUILTIN + bool "Use busybox ip applet" + default y + depends on FEATURE_IFUPDOWN_IP + select IP + select FEATURE_IP_ADDRESS + select FEATURE_IP_LINK + select FEATURE_IP_ROUTE + help + Use the busybox iproute "ip" applet to implement "ifupdown". + + If left disabled, you must install the full-blown iproute2 + utility or the "ifup" and "ifdown" applets will not work. + +config FEATURE_IFUPDOWN_IFCONFIG_BUILTIN + bool "Use busybox ifconfig and route applets" + default y + depends on IFUPDOWN && !FEATURE_IFUPDOWN_IP + select IFCONFIG + select ROUTE + help + Use the busybox iproute "ifconfig" and "route" applets to + implement the "ifup" and "ifdown" utilities. + + If left disabled, you must install the full-blown ifconfig + and route utilities, or the "ifup" and "ifdown" applets will not + work. + +config FEATURE_IFUPDOWN_IPV4 + bool "Enable support for IPv4" + default y + depends on IFUPDOWN + help + If you want busybox to talk IPv4, leave this on. + +config FEATURE_IFUPDOWN_IPV6 + bool "Enable support for IPv6" + default n + depends on IFUPDOWN && FEATURE_IPV6 + help + If you need support for IPv6, turn this option on. + +config FEATURE_IFUPDOWN_IPX + bool "Enable support for IPX" + default n + depends on IFUPDOWN + help + If this option is selected you can use busybox to work with IPX + networks. + +config FEATURE_IFUPDOWN_MAPPING + bool "Enable mapping support" + default n + depends on IFUPDOWN + help + This enables support for the "mapping" stanza, unless you have + a weird network setup you don't need it. + +config INETD + bool "inetd" + default n + select FEATURE_SYSLOG + help + Internet superserver daemon + +config FEATURE_INETD_SUPPORT_BUILTIN_ECHO + bool "Support echo service" + default y + depends on INETD + help + Echo received data internal inetd service + +config FEATURE_INETD_SUPPORT_BUILTIN_DISCARD + bool "Support discard service" + default y + depends on INETD + help + Internet /dev/null internal inetd service + +config FEATURE_INETD_SUPPORT_BUILTIN_TIME + bool "Support time service" + default y + depends on INETD + help + Return 32 bit time since 1900 internal inetd service + +config FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME + bool "Support daytime service" + default y + depends on INETD + help + Return human-readable time internal inetd service + +config FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN + bool "Support chargen service" + default y + depends on INETD + help + Familiar character generator internal inetd service + +config FEATURE_INETD_RPC + bool "Support RPC services" + default n + depends on INETD + depends on FEATURE_HAVE_RPC + help + Support Sun-RPC based services + +config IP + bool "ip" + default n + help + The "ip" applet is a TCP/IP interface configuration and routing + utility. You generally don't need "ip" to use busybox with + TCP/IP. + +config FEATURE_IP_ADDRESS + bool "ip address" + default y + depends on IP + help + Address manipulation support for the "ip" applet. + +config FEATURE_IP_LINK + bool "ip link" + default y + depends on IP + help + Configure network devices with "ip". + +config FEATURE_IP_ROUTE + bool "ip route" + default y + depends on IP + help + Add support for routing table management to "ip". + +config FEATURE_IP_TUNNEL + bool "ip tunnel" + default n + depends on IP + help + Add support for tunneling commands to "ip". + +config FEATURE_IP_RULE + bool "ip rule" + default n + depends on IP + help + Add support for rule commands to "ip". + +config FEATURE_IP_SHORT_FORMS + bool "Support short forms of ip commands." + default n + depends on IP + help + Also support short-form of ip commands: + ip addr -> ipaddr + ip link -> iplink + ip route -> iproute + ip tunnel -> iptunnel + + Say N unless you desparately need the short form of the ip + object commands. + +config IPADDR + bool + default y + depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_ADDRESS + +config IPLINK + bool + default y + depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_LINK + +config IPROUTE + bool + default y + depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_ROUTE + +config IPTUNNEL + bool + default y + depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_TUNNEL + +config IPRULE + bool + default y + depends on FEATURE_IP_SHORT_FORMS && FEATURE_IP_RULE + +config IPCALC + bool "ipcalc" + default n + help + ipcalc takes an IP address and netmask and calculates the + resulting broadcast, network, and host range. + +config FEATURE_IPCALC_FANCY + bool "Fancy IPCALC, more options, adds 1 kbyte" + default y + depends on IPCALC + help + Adds the options hostname, prefix and silent to the output of "ipcalc". + +config FEATURE_IPCALC_LONG_OPTIONS + bool "Enable long options" + default n + depends on IPCALC && GETOPT_LONG + help + Support long options for the ipcalc applet. + +config NAMEIF + bool "nameif" + default n + select FEATURE_SYSLOG + help + nameif is used to rename network interface by its MAC address. + Renamed interfaces MUST be in the down state. + It is possible to use a file (default: /etc/mactab) + with list of new interface names and MACs. + Maximum interface name length: IF_NAMESIZE = 16 + File fields are separated by space or tab. + File format: + # Comment + new_interface_name XX:XX:XX:XX:XX:XX + +config NC + bool "nc" + default n + help + A simple Unix utility which reads and writes data across network + connections. + +config NC_SERVER + bool "Netcat server options (-lp)" + default n + depends on NC + help + Allow netcat to act as a server. + +config NC_EXTRA + bool "Netcat extensions (-eiw and filename)" + default n + depends on NC + help + Add -e (support for executing the rest of the command line after + making or receiving a successful connection), -i (delay interval for + lines sent), -w (timeout for initial connection). + +config NETSTAT + bool "netstat" + default n + help + netstat prints information about the Linux networking subsystem. + +config NSLOOKUP + bool "nslookup" + default n + help + nslookup is a tool to query Internet name servers. + +config PING + bool "ping" + default n + help + ping uses the ICMP protocol's mandatory ECHO_REQUEST datagram to + elicit an ICMP ECHO_RESPONSE from a host or gateway. + +config FEATURE_FANCY_PING + bool "Enable fancy ping output" + default y + depends on PING + help + Make the output from the ping applet include statistics, and at the + same time provide full support for ICMP packets. + +config PING6 + bool "ping6" + default n + depends on FEATURE_IPV6 + help + This will give you a ping that can talk IPv6. + +config FEATURE_FANCY_PING6 + bool "Enable fancy ping6 output" + default y + depends on PING6 + help + Make the output from the ping6 applet include statistics, and at the + same time provide full support for ICMP packets. + +config ROUTE + bool "route" + default n + help + Route displays or manipulates the kernel's IP routing tables. + +config TELNET + bool "telnet" + default n + help + Telnet is an interface to the TELNET protocol, but is also commonly + used to test other simple protocols. + +config FEATURE_TELNET_TTYPE + bool "Pass TERM type to remote host" + default y + depends on TELNET + help + Setting this option will forward the TERM environment variable to the + remote host you are connecting to. This is useful to make sure that + things like ANSI colors and other control sequences behave. + +config FEATURE_TELNET_AUTOLOGIN + bool "Pass USER type to remote host" + default y + depends on TELNET + help + Setting this option will forward the USER environment variable to the + remote host you are connecting to. This is useful when you need to + log into a machine without telling the username (autologin). This + option enables `-a' and `-l USER' arguments. + +config TELNETD + bool "telnetd" + default n + select FEATURE_SYSLOG + help + A daemon for the TELNET protocol, allowing you to log onto the host + running the daemon. Please keep in mind that the TELNET protocol + sends passwords in plain text. If you can't afford the space for an + SSH daemon and you trust your network, you may say 'y' here. As a + more secure alternative, you should seriously consider installing the + very small Dropbear SSH daemon instead: + http://matt.ucc.asn.au/dropbear/dropbear.html + + Note that for busybox telnetd to work you need several things: + First of all, your kernel needs: + UNIX98_PTYS=y + DEVPTS_FS=y + + Next, you need a /dev/pts directory on your root filesystem: + + $ ls -ld /dev/pts + drwxr-xr-x 2 root root 0 Sep 23 13:21 /dev/pts/ + + Next you need the pseudo terminal master multiplexer /dev/ptmx: + + $ ls -la /dev/ptmx + crw-rw-rw- 1 root tty 5, 2 Sep 23 13:55 /dev/ptmx + + Any /dev/ttyp[0-9]* files you may have can be removed. + Next, you need to mount the devpts filesystem on /dev/pts using: + + mount -t devpts devpts /dev/pts + + You need to be sure that Busybox has LOGIN and + FEATURE_SUID enabled. And finally, you should make + certain that Busybox has been installed setuid root: + + chown root.root /bin/busybox + chmod 4755 /bin/busybox + + with all that done, telnetd _should_ work.... + + +config FEATURE_TELNETD_STANDALONE + bool "Support standalone telnetd (not inetd only)" + default n + depends on TELNETD + help + Selecting this will make telnetd able to run standalone. + +config TFTP + bool "tftp" + default n + help + This enables the Trivial File Transfer Protocol client program. TFTP + is usually used for simple, small transfers such as a root image + for a network-enabled bootloader. + +config FEATURE_TFTP_GET + bool "Enable \"get\" command" + default y + depends on TFTP + help + Add support for the GET command within the TFTP client. This allows + a client to retrieve a file from a TFTP server. + +config FEATURE_TFTP_PUT + bool "Enable \"put\" command" + default y + depends on TFTP + help + Add support for the PUT command within the TFTP client. This allows + a client to transfer a file to a TFTP server. + +config FEATURE_TFTP_BLOCKSIZE + bool "Enable \"blocksize\" command" + default n + depends on TFTP + help + Allow the client to specify the desired block size for transfers. + +config DEBUG_TFTP + bool "Enable debug" + default n + depends on TFTP + help + Enable debug settings for tftp. This is useful if you're running + into problems with tftp as the protocol doesn't help you much when + you run into problems. + +config TRACEROUTE + bool "traceroute" + default n + help + Utility to trace the route of IP packets + +config FEATURE_TRACEROUTE_VERBOSE + bool "Enable verbose output" + default n + depends on TRACEROUTE + help + Add some verbosity to traceroute. This includes amongst other things + hostnames and ICMP response types. + +config FEATURE_TRACEROUTE_SOURCE_ROUTE + bool "Enable loose source route" + default n + depends on TRACEROUTE + help + Add option to specify a loose source route gateway + (8 maximum). + +config FEATURE_TRACEROUTE_USE_ICMP + bool "Use ICMP instead of UDP" + default n + depends on TRACEROUTE + help + Add feature to allow for ICMP ECHO instead of UDP datagrams. + +source networking/udhcp/Config.in + +config VCONFIG + bool "vconfig" + default n + help + Creates, removes, and configures VLAN interfaces + +config WGET + bool "wget" + default n + help + wget is a utility for non-interactive download of files from HTTP, + HTTPS, and FTP servers. + +config FEATURE_WGET_STATUSBAR + bool "Enable a nifty process meter (+2k)" + default y + depends on WGET + help + Enable the transfer progress bar for wget transfers. + +config FEATURE_WGET_AUTHENTICATION + bool "Enable HTTP authentication" + default y + depends on WGET + help + Support authenticated HTTP transfers. + +config FEATURE_WGET_IP6_LITERAL + bool "Enable IPv6 literal addresses" + default y + depends on WGET && FEATURE_IPV6 + help + Support IPv6 address literal notation in URLs. + +config FEATURE_WGET_LONG_OPTIONS + bool "Enable long options" + default n + depends on WGET && GETOPT_LONG + help + Support long options for the wget applet. + +config ZCIP + bool "zcip" + default n + select FEATURE_SYSLOG + help + ZCIP provides ZeroConf IPv4 address selection, according to RFC 3927. + It's a daemon that allocates and defends a dynamically assigned + address on the 169.254/16 network, requiring no system administrator. + + See http://www.zeroconf.org for further details, and "zcip.script" + in the busybox examples. + +endmenu diff --git a/networking/Kbuild b/networking/Kbuild new file mode 100644 index 000000000..a9a51fc2e --- /dev/null +++ b/networking/Kbuild @@ -0,0 +1,39 @@ +# 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_ARPING) += arping.o +lib-$(CONFIG_DNSD) += dnsd.o +lib-$(CONFIG_ETHER_WAKE) += ether-wake.o +lib-$(CONFIG_FAKEIDENTD) += fakeidentd.o +lib-$(CONFIG_FTPGET) += ftpgetput.o +lib-$(CONFIG_FTPPUT) += ftpgetput.o +lib-$(CONFIG_HOSTNAME) += hostname.o +lib-$(CONFIG_HTTPD) += httpd.o +lib-$(CONFIG_IFCONFIG) += ifconfig.o interface.o +lib-$(CONFIG_IFUPDOWN) += ifupdown.o +lib-$(CONFIG_INETD) += inetd.o +lib-$(CONFIG_IP) += ip.o +lib-$(CONFIG_IPCALC) += ipcalc.o +lib-$(CONFIG_IPADDR) += ipaddr.o +lib-$(CONFIG_IPLINK) += iplink.o +lib-$(CONFIG_IPROUTE) += iproute.o +lib-$(CONFIG_IPRULE) += iprule.o +lib-$(CONFIG_IPTUNNEL) += iptunnel.o +lib-$(CONFIG_NAMEIF) += nameif.o +lib-$(CONFIG_NC) += nc.o +lib-$(CONFIG_NETSTAT) += netstat.o +lib-$(CONFIG_NSLOOKUP) += nslookup.o +lib-$(CONFIG_PING) += ping.o +lib-$(CONFIG_PING6) += ping6.o +lib-$(CONFIG_ROUTE) += route.o +lib-$(CONFIG_TELNET) += telnet.o +lib-$(CONFIG_TELNETD) += telnetd.o +lib-$(CONFIG_TFTP) += tftp.o +lib-$(CONFIG_TRACEROUTE) += traceroute.o +lib-$(CONFIG_VCONFIG) += vconfig.o +lib-$(CONFIG_WGET) += wget.o +lib-$(CONFIG_ZCIP) += zcip.o diff --git a/networking/arping.c b/networking/arping.c new file mode 100644 index 000000000..2d92bf4be --- /dev/null +++ b/networking/arping.c @@ -0,0 +1,446 @@ +/* vi: set sw=4 ts=4: */ +/* + * arping.c - Ping hosts by ARP requests/replies + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Author: Alexey Kuznetsov + * Busybox port: Nick Fedchik + */ + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "busybox.h" + +static struct in_addr src; +static struct in_addr dst; +static struct sockaddr_ll me; +static struct sockaddr_ll he; +static struct timeval last; + +enum cfg_e { + dad = 1, + unsolicited = 2, + advert = 4, + quiet = 8, + quit_on_reply = 16, + broadcast_only = 32, + unicasting = 64 +}; +static int cfg; + +static int s; +static unsigned count = UINT_MAX; +static unsigned timeout; +static int sent; +static int brd_sent; +static int received; +static int brd_recv; +static int req_recv; + + +#define MS_TDIFF(tv1,tv2) ( ((tv1).tv_sec-(tv2).tv_sec)*1000 + \ + ((tv1).tv_usec-(tv2).tv_usec)/1000 ) +static int send_pack(int sock, struct in_addr *src_addr, + struct in_addr *dst_addr, struct sockaddr_ll *ME, + struct sockaddr_ll *HE) +{ + int err; + struct timeval now; + RESERVE_CONFIG_UBUFFER(buf, 256); + struct arphdr *ah = (struct arphdr *) buf; + unsigned char *p = (unsigned char *) (ah + 1); + + ah->ar_hrd = htons(ME->sll_hatype); + ah->ar_hrd = htons(ARPHRD_ETHER); + ah->ar_pro = htons(ETH_P_IP); + ah->ar_hln = ME->sll_halen; + ah->ar_pln = 4; + ah->ar_op = cfg&advert ? htons(ARPOP_REPLY) : htons(ARPOP_REQUEST); + + memcpy(p, &ME->sll_addr, ah->ar_hln); + p += ME->sll_halen; + + memcpy(p, src_addr, 4); + p += 4; + + if (cfg&advert) + memcpy(p, &ME->sll_addr, ah->ar_hln); + else + memcpy(p, &HE->sll_addr, ah->ar_hln); + p += ah->ar_hln; + + memcpy(p, dst_addr, 4); + p += 4; + + gettimeofday(&now, NULL); + err = sendto(sock, buf, p - buf, 0, (struct sockaddr *) HE, sizeof(*HE)); + if (err == p - buf) { + last = now; + sent++; + if (!(cfg&unicasting)) + brd_sent++; + } + RELEASE_CONFIG_BUFFER(buf); + return err; +} + +static void finish(void) +{ + if (!(cfg&quiet)) { + printf("Sent %d probes (%d broadcast(s))\n" + "Received %d repl%s", + sent, brd_sent, + received, (received > 1) ? "ies" : "y"); + if (brd_recv || req_recv) { + printf(" ("); + if (req_recv) + printf("%d request(s)", req_recv); + if (brd_recv) + printf("%s%d broadcast(s)", req_recv ? ", " : "", brd_recv); + putchar(')'); + } + putchar('\n'); + fflush(stdout); + } + if (cfg&dad) + exit(!!received); + if (cfg&unsolicited) + exit(0); + exit(!received); +} + +static void catcher(void) +{ + struct timeval tv; + static struct timeval start; + + gettimeofday(&tv, NULL); + + if (start.tv_sec == 0) + start = tv; + + if (count-- == 0 + || (timeout && MS_TDIFF(tv, start) > timeout * 1000 + 500)) + finish(); + + if (last.tv_sec == 0 || MS_TDIFF(tv, last) > 500) { + send_pack(s, &src, &dst, &me, &he); + if (count == 0 && cfg&unsolicited) + finish(); + } + alarm(1); +} + +static int recv_pack(unsigned char *buf, int len, struct sockaddr_ll *FROM) +{ + struct arphdr *ah = (struct arphdr *) buf; + unsigned char *p = (unsigned char *) (ah + 1); + struct in_addr src_ip, dst_ip; + + /* Filter out wild packets */ + if (FROM->sll_pkttype != PACKET_HOST && + FROM->sll_pkttype != PACKET_BROADCAST && + FROM->sll_pkttype != PACKET_MULTICAST) + return 0; + + /* Only these types are recognised */ + if (ah->ar_op != htons(ARPOP_REQUEST) && ah->ar_op != htons(ARPOP_REPLY)) + return 0; + + /* ARPHRD check and this darned FDDI hack here :-( */ + if (ah->ar_hrd != htons(FROM->sll_hatype) && + (FROM->sll_hatype != ARPHRD_FDDI + || ah->ar_hrd != htons(ARPHRD_ETHER))) + return 0; + + /* Protocol must be IP. */ + if (ah->ar_pro != htons(ETH_P_IP)) + return 0; + if (ah->ar_pln != 4) + return 0; + if (ah->ar_hln != me.sll_halen) + return 0; + if (len < sizeof(*ah) + 2 * (4 + ah->ar_hln)) + return 0; + memcpy(&src_ip, p + ah->ar_hln, 4); + memcpy(&dst_ip, p + ah->ar_hln + 4 + ah->ar_hln, 4); + if (!(cfg&dad)) { + if (src_ip.s_addr != dst.s_addr) + return 0; + if (src.s_addr != dst_ip.s_addr) + return 0; + if (memcmp(p + ah->ar_hln + 4, &me.sll_addr, ah->ar_hln)) + return 0; + } else { + /* DAD packet was: + src_ip = 0 (or some src) + src_hw = ME + dst_ip = tested address + dst_hw = + + We fail, if receive request/reply with: + src_ip = tested_address + src_hw != ME + if src_ip in request was not zero, check + also that it matches to dst_ip, otherwise + dst_ip/dst_hw do not matter. + */ + if (src_ip.s_addr != dst.s_addr) + return 0; + if (memcmp(p, &me.sll_addr, me.sll_halen) == 0) + return 0; + if (src.s_addr && src.s_addr != dst_ip.s_addr) + return 0; + } + if (!(cfg&quiet)) { + int s_printed = 0; + struct timeval tv; + + gettimeofday(&tv, NULL); + + printf("%s %s from %s [%s]", + FROM->sll_pkttype == PACKET_HOST ? "Unicast" : "Broadcast", + ah->ar_op == htons(ARPOP_REPLY) ? "reply" : "request", + inet_ntoa(src_ip), + ether_ntoa((struct ether_addr *) p)); + if (dst_ip.s_addr != src.s_addr) { + printf("for %s ", inet_ntoa(dst_ip)); + s_printed = 1; + } + if (memcmp(p + ah->ar_hln + 4, me.sll_addr, ah->ar_hln)) { + if (!s_printed) + printf("for "); + printf("[%s]", + ether_ntoa((struct ether_addr *) p + ah->ar_hln + 4)); + } + + if (last.tv_sec) { + long usecs = (tv.tv_sec - last.tv_sec) * 1000000 + + tv.tv_usec - last.tv_usec; + long msecs = (usecs + 500) / 1000; + + usecs -= msecs * 1000 - 500; + printf(" %ld.%03ldms\n", msecs, usecs); + } else { + printf(" UNSOLICITED?\n"); + } + fflush(stdout); + } + received++; + if (FROM->sll_pkttype != PACKET_HOST) + brd_recv++; + if (ah->ar_op == htons(ARPOP_REQUEST)) + req_recv++; + if (cfg&quit_on_reply) + finish(); + if (!(cfg&broadcast_only)) { + memcpy(he.sll_addr, p, me.sll_halen); + cfg |= unicasting; + } + return 1; +} + +int arping_main(int argc, char **argv) +{ + char *device = "eth0"; + int ifindex; + char *source = NULL; + char *target; + + s = xsocket(PF_PACKET, SOCK_DGRAM, 0); + + // Drop suid root privileges + xsetuid(getuid()); + + { + unsigned opt; + char *_count, *_timeout; + + /* Dad also sets quit_on_reply. + * Advert also sets unsolicited. + */ + opt_complementary = "Df:AU"; + opt = getopt32(argc, argv, "DUAqfbc:w:i:s:", + &_count, &_timeout, &device, &source); + cfg |= opt & 0x3f; /* set respective flags */ + if (opt & 0x40) /* -c: count */ + count = xatou(_count); + if (opt & 0x80) /* -w: timeout */ + timeout = xatoul_range(_timeout, 0, INT_MAX/2000); + if (opt & 0x100) { /* -i: interface */ + if (strlen(device) > IF_NAMESIZE) { + bb_error_msg_and_die("interface name '%s' is too long", + device); + } + } + //if (opt & 0x200) /* -s: source */ + } + argc -= optind; + argv += optind; + + if (argc != 1) + bb_show_usage(); + + target = *argv; + + xfunc_error_retval = 2; + + { + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, device, IFNAMSIZ - 1); + if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) { + bb_error_msg_and_die("interface %s not found", device); + } + ifindex = ifr.ifr_ifindex; + + if (ioctl(s, SIOCGIFFLAGS, (char *) &ifr)) { + bb_error_msg_and_die("SIOCGIFFLAGS"); + } + if (!(ifr.ifr_flags & IFF_UP)) { + bb_error_msg_and_die("interface %s is down", device); + } + if (ifr.ifr_flags & (IFF_NOARP | IFF_LOOPBACK)) { + bb_error_msg("interface %s is not ARPable", device); + exit(cfg&dad ? 0 : 2); + } + } + + if (!inet_aton(target, &dst)) { + struct hostent *hp; + + hp = gethostbyname2(target, AF_INET); + if (!hp) { + bb_error_msg_and_die("invalid or unknown target %s", target); + } + memcpy(&dst, hp->h_addr, 4); + } + + if (source && !inet_aton(source, &src)) { + bb_error_msg_and_die("invalid source address %s", source); + } + + if (!(cfg&dad) && cfg&unsolicited && src.s_addr == 0) + src = dst; + + if (!(cfg&dad) || src.s_addr) { + struct sockaddr_in saddr; + int probe_fd = xsocket(AF_INET, SOCK_DGRAM, 0); + + if (device) { + if (setsockopt + (probe_fd, SOL_SOCKET, SO_BINDTODEVICE, device, + strlen(device) + 1) == -1) + bb_error_msg("warning: interface %s is ignored", device); + } + memset(&saddr, 0, sizeof(saddr)); + saddr.sin_family = AF_INET; + if (src.s_addr) { + saddr.sin_addr = src; + if (bind(probe_fd, (struct sockaddr *) &saddr, sizeof(saddr)) == -1) { + bb_error_msg_and_die("bind"); + } + } else if (!(cfg&dad)) { + static const int on = 1; + socklen_t alen = sizeof(saddr); + + saddr.sin_port = htons(1025); + saddr.sin_addr = dst; + + if (setsockopt + (probe_fd, SOL_SOCKET, SO_DONTROUTE, (char *) &on, + sizeof(on)) == -1) + bb_perror_msg("warning: setsockopt(SO_DONTROUTE)"); + if (connect(probe_fd, (struct sockaddr *) &saddr, sizeof(saddr)) + == -1) { + bb_error_msg_and_die("connect"); + } + if (getsockname(probe_fd, (struct sockaddr *) &saddr, &alen) == + -1) { + bb_error_msg_and_die("getsockname"); + } + src = saddr.sin_addr; + } + close(probe_fd); + }; + + me.sll_family = AF_PACKET; + me.sll_ifindex = ifindex; + me.sll_protocol = htons(ETH_P_ARP); + if (bind(s, (struct sockaddr *) &me, sizeof(me)) == -1) { + bb_error_msg_and_die("bind"); + } + + { + socklen_t alen = sizeof(me); + + if (getsockname(s, (struct sockaddr *) &me, &alen) == -1) { + bb_error_msg_and_die("getsockname"); + } + } + if (me.sll_halen == 0) { + bb_error_msg("interface \"%s\" is not ARPable (no ll address)", device); + exit(cfg&dad ? 0 : 2); + } + he = me; + memset(he.sll_addr, -1, he.sll_halen); + + if (!(cfg&quiet)) { + printf("ARPING to %s from %s via %s\n", + inet_ntoa(dst), inet_ntoa(src), + device ? device : "unknown"); + } + + if (!src.s_addr && !(cfg&dad)) { + bb_error_msg_and_die("no src address in the non-DAD mode"); + } + + { + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_RESTART; + + sa.sa_handler = (void (*)(int)) finish; + sigaction(SIGINT, &sa, NULL); + + sa.sa_handler = (void (*)(int)) catcher; + sigaction(SIGALRM, &sa, NULL); + } + + catcher(); + + while (1) { + sigset_t sset, osset; + RESERVE_CONFIG_UBUFFER(packet, 4096); + struct sockaddr_ll from; + socklen_t alen = sizeof(from); + int cc; + + if ((cc = recvfrom(s, packet, 4096, 0, + (struct sockaddr *) &from, &alen)) < 0) { + bb_perror_msg("recvfrom"); + continue; + } + sigemptyset(&sset); + sigaddset(&sset, SIGALRM); + sigaddset(&sset, SIGINT); + sigprocmask(SIG_BLOCK, &sset, &osset); + recv_pack(packet, cc, &from); + sigprocmask(SIG_SETMASK, &osset, NULL); + RELEASE_CONFIG_BUFFER(packet); + } +} diff --git a/networking/dnsd.c b/networking/dnsd.c new file mode 100644 index 000000000..5e9cf52f1 --- /dev/null +++ b/networking/dnsd.c @@ -0,0 +1,456 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini DNS server implementation for busybox + * + * Copyright (C) 2005 Roberto A. Foglietta (me@roberto.foglietta.name) + * Copyright (C) 2005 Odd Arild Olsen (oao at fibula dot no) + * Copyright (C) 2003 Paul Sheer + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Odd Arild Olsen started out with the sheerdns [1] of Paul Sheer and rewrote + * it into a shape which I believe is both easier to understand and maintain. + * I also reused the input buffer for output and removed services he did not + * need. [1] http://threading.2038bug.com/sheerdns/ + * + * Some bugfix and minor changes was applied by Roberto A. Foglietta who made + * the first porting of oao' scdns to busybox also. + */ + +#include "busybox.h" + +static char *fileconf = "/etc/dnsd.conf"; +#define LOCK_FILE "/var/run/dnsd.lock" +#define LOG_FILE "/var/log/dnsd.log" + +// Must matct getopt32 call +#define OPT_daemon (option_mask32 & 0x10) +#define OPT_verbose (option_mask32 & 0x20) + +//#define DEBUG 1 + +enum { + MAX_HOST_LEN = 16, // longest host name allowed is 15 + IP_STRING_LEN = 18, // .xxx.xxx.xxx.xxx\0 + +//must be strlen('.in-addr.arpa') larger than IP_STRING_LEN + MAX_NAME_LEN = (IP_STRING_LEN + 13), + +/* Cannot get bigger packets than 512 per RFC1035 + In practice this can be set considerably smaller: + Length of response packet is header (12B) + 2*type(4B) + 2*class(4B) + + ttl(4B) + rlen(2B) + r (MAX_NAME_LEN =21B) + + 2*querystring (2 MAX_NAME_LEN= 42B), all together 90 Byte +*/ + MAX_PACK_LEN = 512 + 1, + + DEFAULT_TTL = 30, // increase this when not testing? + + REQ_A = 1, + REQ_PTR = 12 +}; + +struct dns_repl { // resource record, add 0 or 1 to accepted dns_msg in resp + uint16_t rlen; + uint8_t *r; // resource + uint16_t flags; +}; + +struct dns_head { // the message from client and first part of response mag + uint16_t id; + uint16_t flags; + uint16_t nquer; // accepts 0 + uint16_t nansw; // 1 in response + uint16_t nauth; // 0 + uint16_t nadd; // 0 +}; +struct dns_prop { + uint16_t type; + uint16_t class; +}; +struct dns_entry { // element of known name, ip address and reversed ip address + struct dns_entry *next; + char ip[IP_STRING_LEN]; // dotted decimal IP + char rip[IP_STRING_LEN]; // length decimal reversed IP + char name[MAX_HOST_LEN]; +}; + +static struct dns_entry *dnsentry = NULL; +// FIXME! unused! :( +static int daemonmode; +static uint32_t ttl = DEFAULT_TTL; + +/* + * Convert host name from C-string to dns length/string. + */ +static void convname(char *a, uint8_t *q) +{ + int i = (q[0] == '.') ? 0 : 1; + for (; i < MAX_HOST_LEN-1 && *q; i++, q++) + a[i] = tolower(*q); + a[0] = i - 1; + a[i] = 0; +} + +/* + * Insert length of substrings instead of dots + */ +static void undot(uint8_t * rip) +{ + int i = 0, s = 0; + while (rip[i]) + i++; + for (--i; i >= 0; i--) { + if (rip[i] == '.') { + rip[i] = s; + s = 0; + } else s++; + } +} + +/* + * Append message to log file + */ +static void log_message(char *filename, char *message) +{ + FILE *logfile; + if (!daemonmode) + return; + logfile = fopen(filename, "a"); + if (!logfile) + return; + fprintf(logfile, "%s\n", message); + fclose(logfile); +} + +/* + * Read one line of hostname/IP from file + * Returns 0 for each valid entry read, -1 at EOF + * Assumes all host names are lower case only + * Hostnames with more than one label is not handled correctly. + * Presently the dot is copied into name without + * converting to a length/string substring for that label. + */ + +static int getfileentry(FILE * fp, struct dns_entry *s) +{ + unsigned int a,b,c,d; + char *r, *name; + + restart: + r = xmalloc_fgets(fp); + if (!r) + return -1; + while (*r == ' ' || *r == '\t') { + r++; + if (!*r || *r == '#' || *r == '\n') + goto restart; /* skipping empty/blank and commented lines */ + } + name = r; + while (*r != ' ' && *r != '\t') + r++; + *r++ = 0; + if (sscanf(r, "%u.%u.%u.%u", &a, &b, &c, &d) != 4) + goto restart; /* skipping wrong lines */ + + sprintf(s->ip, "%u.%u.%u.%u", a, b, c, d); + sprintf(s->rip, ".%u.%u.%u.%u", d, c, b, a); + undot((uint8_t*)s->rip); + convname(s->name,(uint8_t*)name); + + if (OPT_verbose) + fprintf(stderr, "\tname:%s, ip:%s\n", &(s->name[1]),s->ip); + + return 0; +} + +/* + * Read hostname/IP records from file + */ +static void dnsentryinit(void) +{ + FILE *fp; + struct dns_entry *m, *prev; + prev = dnsentry = NULL; + + fp = xfopen(fileconf, "r"); + + while (1) { + m = xmalloc(sizeof(struct dns_entry)); + + m->next = NULL; + if (getfileentry(fp, m)) + break; + + if (prev == NULL) + dnsentry = m; + else + prev->next = m; + prev = m; + } + fclose(fp); +} + + +/* + * Set up UDP socket + */ +static int listen_socket(char *iface_addr, int listen_port) +{ + struct sockaddr_in a; + char msg[100]; + int s; + s = xsocket(PF_INET, SOCK_DGRAM, 0); + if (setsockopt_reuseaddr(s) < 0) + bb_perror_msg_and_die("setsockopt() failed"); + memset(&a, 0, sizeof(a)); + a.sin_port = htons(listen_port); + a.sin_family = AF_INET; + if (!inet_aton(iface_addr, &a.sin_addr)) + bb_perror_msg_and_die("bad iface address"); + xbind(s, (struct sockaddr *)&a, sizeof(a)); + xlisten(s, 50); + sprintf(msg, "accepting UDP packets on addr:port %s:%d\n", + iface_addr, (int)listen_port); + log_message(LOG_FILE, msg); + return s; +} + +/* + * Look query up in dns records and return answer if found + * qs is the query string, first byte the string length + */ +static int table_lookup(uint16_t type, uint8_t * as, uint8_t * qs) +{ + int i; + struct dns_entry *d=dnsentry; + + do { +#ifdef DEBUG + char *p,*q; + q = (char *)&(qs[1]); + p = &(d->name[1]); + fprintf(stderr, "\n%s: %d/%d p:%s q:%s %d", + __FUNCTION__, strlen(p), (int)(d->name[0]), p, q, strlen(q)); +#endif + if (type == REQ_A) { /* search by host name */ + for (i = 1; i <= (int)(d->name[0]); i++) + if (tolower(qs[i]) != d->name[i]) + break; + if (i > (int)(d->name[0])) { +#ifdef DEBUG + fprintf(stderr, " OK"); +#endif + strcpy((char *)as, d->ip); +#ifdef DEBUG + fprintf(stderr, " as:%s\n", as); +#endif + return 0; + } + } else + if (type == REQ_PTR) { /* search by IP-address */ + if (!strncmp((char*)&d->rip[1], (char*)&qs[1], strlen(d->rip)-1)) { + strcpy((char *)as, d->name); + return 0; + } + } + d = d->next; + } while (d); + return -1; +} + + +/* + * Decode message and generate answer + */ +#define eret(s) do { fputs(s, stderr); return -1; } while (0) +static int process_packet(uint8_t * buf) +{ + struct dns_head *head; + struct dns_prop *qprop; + struct dns_repl outr; + void *next, *from, *answb; + + uint8_t answstr[MAX_NAME_LEN + 1]; + int lookup_result, type, len, packet_len; + uint16_t flags; + + answstr[0] = '\0'; + + head = (struct dns_head *)buf; + if (head->nquer == 0) + eret("no queries\n"); + + if (head->flags & 0x8000) + eret("ignoring response packet\n"); + + from = (void *)&head[1]; // start of query string + next = answb = from + strlen((char *)from) + 1 + sizeof(struct dns_prop); // where to append answer block + + outr.rlen = 0; // may change later + outr.r = NULL; + outr.flags = 0; + + qprop = (struct dns_prop *)(answb - 4); + type = ntohs(qprop->type); + + // only let REQ_A and REQ_PTR pass + if (!(type == REQ_A || type == REQ_PTR)) { + goto empty_packet; /* we can't handle the query type */ + } + + if (ntohs(qprop->class) != 1 /* class INET */ ) { + outr.flags = 4; /* not supported */ + goto empty_packet; + } + /* we only support standard queries */ + + if ((ntohs(head->flags) & 0x7800) != 0) + goto empty_packet; + + // We have a standard query + log_message(LOG_FILE, (char *)from); + lookup_result = table_lookup(type, answstr, (uint8_t*)from); + if (lookup_result != 0) { + outr.flags = 3 | 0x0400; //name do not exist and auth + goto empty_packet; + } + if (type == REQ_A) { // return an address + struct in_addr a; + if (!inet_aton((char*)answstr, &a)) {//dotted dec to long conv + outr.flags = 1; /* Frmt err */ + goto empty_packet; + } + memcpy(answstr, &a.s_addr, 4); // save before a disappears + outr.rlen = 4; // uint32_t IP + } + else + outr.rlen = strlen((char *)answstr) + 1; // a host name + outr.r = answstr; // 32 bit ip or a host name + outr.flags |= 0x0400; /* authority-bit */ + // we have an answer + head->nansw = htons(1); + + // copy query block to answer block + len = answb - from; + memcpy(answb, from, len); + next += len; + + // and append answer rr + *(uint32_t *) next = htonl(ttl); + next += 4; + *(uint16_t *) next = htons(outr.rlen); + next += 2; + memcpy(next, (void *)answstr, outr.rlen); + next += outr.rlen; + + empty_packet: + + flags = ntohs(head->flags); + // clear rcode and RA, set responsebit and our new flags + flags |= (outr.flags & 0xff80) | 0x8000; + head->flags = htons(flags); + head->nauth = head->nadd = htons(0); + head->nquer = htons(1); + + packet_len = next - (void *)buf; + return packet_len; +} + +/* + * Exit on signal + */ +static void interrupt(int x) +{ + unlink(LOCK_FILE); + write(2, "interrupt exiting\n", 18); + exit(2); +} + +int dnsd_main(int argc, char **argv) +{ + int udps; + uint16_t port = 53; + uint8_t buf[MAX_PACK_LEN]; + char *listen_interface = "0.0.0.0"; + char *sttl, *sport; + + getopt32(argc, argv, "i:c:t:p:dv", &listen_interface, &fileconf, &sttl, &sport); + //if (option_mask32 & 0x1) // -i + //if (option_mask32 & 0x2) // -c + if (option_mask32 & 0x4) // -t + if (!(ttl = atol(sttl))) + bb_show_usage(); + if (option_mask32 & 0x8) // -p + if (!(port = atol(sport))) + bb_show_usage(); + + if (OPT_verbose) { + bb_info_msg("listen_interface: %s", listen_interface); + bb_info_msg("ttl: %d, port: %d", ttl, port); + bb_info_msg("fileconf: %s", fileconf); + } + + if (OPT_daemon) +#ifdef BB_NOMMU + /* reexec for vfork() do continue parent */ + vfork_daemon_rexec(1, 0, argc, argv, "-d"); +#else + xdaemon(1, 0); +#endif + + dnsentryinit(); + + signal(SIGINT, interrupt); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); +#ifdef SIGTSTP + signal(SIGTSTP, SIG_IGN); +#endif +#ifdef SIGURG + signal(SIGURG, SIG_IGN); +#endif + + udps = listen_socket(listen_interface, port); + if (udps < 0) + exit(1); + + while (1) { + fd_set fdset; + int r; + + FD_ZERO(&fdset); + FD_SET(udps, &fdset); + // Block until a message arrives + r = select(udps + 1, &fdset, NULL, NULL, NULL); + if (r < 0) + bb_perror_msg_and_die("select error"); + if (r == 0) + bb_perror_msg_and_die("select spurious return"); + + /* Can this test ever be false? - yes */ + if (FD_ISSET(udps, &fdset)) { + struct sockaddr_in from; + int fromlen = sizeof(from); + r = recvfrom(udps, buf, sizeof(buf), 0, + (struct sockaddr *)&from, + (void *)&fromlen); + if (OPT_verbose) + fprintf(stderr, "\n--- Got UDP "); + log_message(LOG_FILE, "\n--- Got UDP "); + + if (r < 12 || r > 512) { + bb_error_msg("invalid packet size"); + continue; + } + if (r > 0) { + r = process_packet(buf); + if (r > 0) + sendto(udps, buf, + r, 0, (struct sockaddr *)&from, + fromlen); + } + } // end if + } // end while + return 0; +} diff --git a/networking/ether-wake.c b/networking/ether-wake.c new file mode 100644 index 000000000..e205ffc00 --- /dev/null +++ b/networking/ether-wake.c @@ -0,0 +1,284 @@ +/* vi: set sw=4 ts=4: */ +/* + * ether-wake.c - Send a magic packet to wake up sleeping machines. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Author: Donald Becker, http://www.scyld.com/"; http://www.scyld.com/wakeonlan.html + * Busybox port: Christian Volkmann + * Used version of ether-wake.c: v1.09 11/12/2003 Donald Becker, http://www.scyld.com/"; + */ + +/* full usage according Donald Becker + * usage: ether-wake [-i ] [-p aa:bb:cc:dd[:ee:ff]] 00:11:22:33:44:55\n" + * + * This program generates and transmits a Wake-On-LAN (WOL)\n" + * \"Magic Packet\", used for restarting machines that have been\n" + * soft-powered-down (ACPI D3-warm state).\n" + * It currently generates the standard AMD Magic Packet format, with\n" + * an optional password appended.\n" + * + * The single required parameter is the Ethernet MAC (station) address\n" + * of the machine to wake or a host ID with known NSS 'ethers' entry.\n" + * The MAC address may be found with the 'arp' program while the target\n" + * machine is awake.\n" + * + * Options:\n" + * -b Send wake-up packet to the broadcast address.\n" + * -D Increase the debug level.\n" + * -i ifname Use interface IFNAME instead of the default 'eth0'.\n" + * -p Append the four or six byte password PW to the packet.\n" + * A password is only required for a few adapter types.\n" + * The password may be specified in ethernet hex format\n" + * or dotted decimal (Internet address)\n" + * -p 00:22:44:66:88:aa\n" + * -p 192.168.1.1\n"; + * + * + * This program generates and transmits a Wake-On-LAN (WOL) "Magic Packet", + * used for restarting machines that have been soft-powered-down + * (ACPI D3-warm state). It currently generates the standard AMD Magic Packet + * format, with an optional password appended. + * + * This software may be used and distributed according to the terms + * of the GNU Public License, incorporated herein by reference. + * Contact the author for use under other terms. + * + * This source file was originally part of the network tricks package, and + * is now distributed to support the Scyld Beowulf system. + * Copyright 1999-2003 Donald Becker and Scyld Computing Corporation. + * + * The author may be reached as becker@scyld, or C/O + * Scyld Computing Corporation + * 914 Bay Ridge Road, Suite 220 + * Annapolis MD 21403 + * + * Notes: + * On some systems dropping root capability allows the process to be + * dumped, traced or debugged. + * If someone traces this program, they get control of a raw socket. + * Linux handles this safely, but beware when porting this program. + * + * An alternative to needing 'root' is using a UDP broadcast socket, however + * doing so only works with adapters configured for unicast+broadcast Rx + * filter. That configuration consumes more power. +*/ + + +#include +#include +#include +#include + +#include "busybox.h" + +/* Note: PF_INET, SOCK_DGRAM, IPPROTO_UDP would allow SIOCGIFHWADDR to + * work as non-root, but we need SOCK_PACKET to specify the Ethernet + * destination address. + */ +#ifdef PF_PACKET +# define whereto_t sockaddr_ll +# define make_socket() xsocket(PF_PACKET, SOCK_RAW, 0) +#else +# define whereto_t sockaddr +# define make_socket() xsocket(AF_INET, SOCK_PACKET, SOCK_PACKET) +#endif + +#ifdef DEBUG +# define bb_debug_msg(fmt, args...) fprintf(stderr, fmt, ## args) +void bb_debug_dump_packet(unsigned char *outpack, int pktsize) +{ + int i; + printf("packet dump:\n"); + for (i = 0; i < pktsize; ++i) { + printf("%2.2x ", outpack[i]); + if (i % 20 == 19) puts(""); + } + printf("\n\n"); +} +#else +# define bb_debug_msg(fmt, args...) +# define bb_debug_dump_packet(outpack, pktsize) +#endif + +static inline void get_dest_addr(const char *arg, struct ether_addr *eaddr); +static inline int get_fill(unsigned char *pkt, struct ether_addr *eaddr, int broadcast); +static inline int get_wol_pw(const char *ethoptarg, unsigned char *wol_passwd); + +int ether_wake_main(int argc, char *argv[]) +{ + char *ifname = "eth0", *pass = NULL; + unsigned long flags; + unsigned char wol_passwd[6]; + int wol_passwd_sz = 0; + + int s; /* Raw socket */ + int pktsize; + unsigned char outpack[1000]; + + struct ether_addr eaddr; + struct whereto_t whereto; /* who to wake up */ + + /* handle misc user options */ + flags = getopt32(argc, argv, "bi:p:", &ifname, &pass); + if (optind == argc) + bb_show_usage(); + if (pass) + wol_passwd_sz = get_wol_pw(pass, wol_passwd); + + /* create the raw socket */ + s = make_socket(); + + /* now that we have a raw socket we can drop root */ + xsetuid(getuid()); + + /* look up the dest mac address */ + get_dest_addr(argv[optind], &eaddr); + + /* fill out the header of the packet */ + pktsize = get_fill(outpack, &eaddr, flags /*& 1 [OPT_BROADCAST]*/); + + bb_debug_dump_packet(outpack, pktsize); + + /* Fill in the source address, if possible. */ +#ifdef __linux__ + { + struct ifreq if_hwaddr; + + strncpy(if_hwaddr.ifr_name, ifname, sizeof(if_hwaddr.ifr_name)); + if (ioctl(s, SIOCGIFHWADDR, &if_hwaddr) < 0) + bb_perror_msg_and_die("SIOCGIFHWADDR on %s failed", ifname); + + memcpy(outpack+6, if_hwaddr.ifr_hwaddr.sa_data, 6); + +# ifdef DEBUG + { + unsigned char *hwaddr = if_hwaddr.ifr_hwaddr.sa_data; + printf("The hardware address (SIOCGIFHWADDR) of %s is type %d " + "%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x\n\n", ifname, + if_hwaddr.ifr_hwaddr.sa_family, hwaddr[0], hwaddr[1], + hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5]); + } +# endif + } +#endif /* __linux__ */ + + bb_debug_dump_packet(outpack, pktsize); + + /* append the password if specified */ + if (wol_passwd_sz > 0) { + memcpy(outpack+pktsize, wol_passwd, wol_passwd_sz); + pktsize += wol_passwd_sz; + } + + bb_debug_dump_packet(outpack, pktsize); + + /* This is necessary for broadcasts to work */ + if (flags /*& 1 [OPT_BROADCAST]*/) { + if (setsockopt_broadcast(s) < 0) + bb_perror_msg("SO_BROADCAST"); + } + +#if defined(PF_PACKET) + { + struct ifreq ifr; + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(s, SIOCGIFINDEX, &ifr) == -1) + bb_perror_msg_and_die("SIOCGIFINDEX"); + memset(&whereto, 0, sizeof(whereto)); + whereto.sll_family = AF_PACKET; + whereto.sll_ifindex = ifr.ifr_ifindex; + /* The manual page incorrectly claims the address must be filled. + We do so because the code may change to match the docs. */ + whereto.sll_halen = ETH_ALEN; + memcpy(whereto.sll_addr, outpack, ETH_ALEN); + } +#else + whereto.sa_family = 0; + strcpy(whereto.sa_data, ifname); +#endif + + if (sendto(s, outpack, pktsize, 0, (struct sockaddr *)&whereto, sizeof(whereto)) < 0) + bb_perror_msg(bb_msg_write_error); + + close(s); + + return EXIT_SUCCESS; +} + +/* Convert the host ID string to a MAC address. + * The string may be a: + * Host name + * IP address string + * MAC address string +*/ +static inline void get_dest_addr(const char *hostid, struct ether_addr *eaddr) +{ + struct ether_addr *eap; + + eap = ether_aton(hostid); + if (eap) { + *eaddr = *eap; + bb_debug_msg("The target station address is %s\n\n", ether_ntoa(eaddr)); +#if !defined(__UCLIBC__) + } else if (ether_hostton(hostid, eaddr) == 0) { + bb_debug_msg("Station address for hostname %s is %s\n\n", hostid, ether_ntoa(eaddr)); +#else +# warning Need to implement ether_hostton() for uClibc +#endif + } else + bb_show_usage(); +} + +static inline int get_fill(unsigned char *pkt, struct ether_addr *eaddr, int broadcast) +{ + int offset, i; + unsigned char *station_addr = eaddr->ether_addr_octet; + + if (broadcast) + memset(pkt+0, 0xff, 6); + else + memcpy(pkt, station_addr, 6); + memcpy(pkt+6, station_addr, 6); + pkt[12] = 0x08; /* Or 0x0806 for ARP, 0x8035 for RARP */ + pkt[13] = 0x42; + offset = 14; + + memset(pkt+offset, 0xff, 6); + offset += 6; + + for (i = 0; i < 16; ++i) { + memcpy(pkt+offset, station_addr, 6); + offset += 6; + } + + return offset; +} + +static inline int get_wol_pw(const char *ethoptarg, unsigned char *wol_passwd) +{ + int passwd[6]; + int byte_cnt, i; + + /* handle MAC format */ + byte_cnt = sscanf(ethoptarg, "%2x:%2x:%2x:%2x:%2x:%2x", + &passwd[0], &passwd[1], &passwd[2], + &passwd[3], &passwd[4], &passwd[5]); + /* handle IP format */ + if (byte_cnt < 4) + byte_cnt = sscanf(ethoptarg, "%d.%d.%d.%d", + &passwd[0], &passwd[1], &passwd[2], &passwd[3]); + if (byte_cnt < 4) { + bb_error_msg("cannot read Wake-On-LAN pass"); + return 0; + } + + for (i = 0; i < byte_cnt; ++i) + wol_passwd[i] = passwd[i]; + + bb_debug_msg("password: %2.2x %2.2x %2.2x %2.2x (%d)\n\n", + wol_passwd[0], wol_passwd[1], wol_passwd[2], wol_passwd[3], + byte_cnt); + + return byte_cnt; +} diff --git a/networking/fakeidentd.c b/networking/fakeidentd.c new file mode 100644 index 000000000..04138cca3 --- /dev/null +++ b/networking/fakeidentd.c @@ -0,0 +1,387 @@ +/* vi: set sw=4 ts=4: */ +/* + * A fake identd server + * + * Adapted to busybox by Thomas Lundquist + * Original Author: Tomi Ollila + * http://www.guru-group.fi/~too/sw/ + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include +#include + + +#define IDENT_PORT 113 +#define MAXCONNS 20 +#define MAXIDLETIME 45 + +static const char ident_substr[] = " : USERID : UNIX : "; +enum { ident_substr_len = sizeof(ident_substr) - 1 }; +#define PIDFILE "/var/run/identd.pid" + +/* + * We have to track the 'first connection socket' so that we + * don't go around closing file descriptors for non-clients. + * + * descriptor setup normally + * 0 = server socket + * 1 = syslog fd (hopefully -- otherwise this won't work) + * 2 = connection socket after detached from tty. standard error before that + * 3 - 2 + MAXCONNS = rest connection sockets + * + * To try to make sure that syslog fd is what is "requested", the that fd + * is closed before openlog() call. It can only severely fail if fd 0 + * is initially closed. + */ +#define FCS 2 + +/* + * FD of the connection is always the index of the connection structure + * in `conns' array + FCS + */ +static struct { + char buf[20]; + int len; + time_t lasttime; +} conns[MAXCONNS]; + +/* When using global variables, bind those at least to a structure. */ +static struct { + const char *identuser; + fd_set readfds; + int conncnt; +} G; + +/* + * Prototypes + */ +static void reply(int s, char *buf); +static void replyError(int s, char *buf); + +static const char *nobodystr = "nobody"; /* this needs to be declared like this */ +static char *bind_ip_address = "0.0.0.0"; + +static void movefd(int from, int to) +{ + if (from != to) { + dup2(from, to); + close(from); + } +} + +static void inetbind(void) +{ + int s, port; + struct sockaddr_in addr; + int len = sizeof(addr); + struct servent *se; + + se = getservbyname("identd", "tcp"); + port = IDENT_PORT; + if (se) + port = se->s_port; + + s = xsocket(AF_INET, SOCK_STREAM, 0); + + setsockopt_reuseaddr(s); + + memset(&addr, 0, sizeof(addr)); + addr.sin_addr.s_addr = inet_addr(bind_ip_address); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + xbind(s, (struct sockaddr *)&addr, len); + xlisten(s, 5); + + movefd(s, 0); +} + +static void handlexitsigs(int signum) +{ + if (unlink(PIDFILE) < 0) + close(open(PIDFILE, O_WRONLY|O_CREAT|O_TRUNC, 0644)); + exit(0); +} + +/* May succeed. If not, won't care. */ +static void writepid(uid_t nobody, uid_t nogrp) +{ + char buf[sizeof(int)*3 + 2]; + int fd = open(PIDFILE, O_WRONLY|O_CREAT|O_TRUNC, 0664); + + if (fd < 0) + return; + + sprintf(buf, "%d\n", getpid()); + write(fd, buf, strlen(buf)); + fchown(fd, nobody, nogrp); + close(fd); + + /* should this handle ILL, ... (see signal(7)) */ + signal(SIGTERM, handlexitsigs); + signal(SIGINT, handlexitsigs); + signal(SIGQUIT, handlexitsigs); +} + +/* return 0 as parent, 1 as child */ +static int godaemon(void) +{ + uid_t nobody, nogrp; + struct passwd *pw; + + switch (fork()) { + case -1: + bb_perror_msg_and_die("fork"); + + case 0: + pw = getpwnam(nobodystr); + if (pw == NULL) + bb_error_msg_and_die("cannot find uid/gid of user '%s'", nobodystr); + nobody = pw->pw_uid; + nogrp = pw->pw_gid; + writepid(nobody, nogrp); + + close(0); + inetbind(); + xsetgid(nogrp); + xsetuid(nobody); + close(1); + close(2); + + signal(SIGHUP, SIG_IGN); + signal(SIGPIPE, SIG_IGN); /* connection closed when writing (raises ???) */ + + setsid(); + + return 1; + } + + return 0; +} + +static void deleteConn(int s) +{ + int i = s - FCS; + + close(s); + + G.conncnt--; + + /* + * Most of the time there is 0 connections. Most often that there + * is connections, there is just one connection. When this one connection + * closes, i == G.conncnt = 0 -> no copying. + * When there is more than one connection, the oldest connections closes + * earlier on average. When this happens, the code below starts copying + * the connection structure w/ highest index to the place which which is + * just deleted. This means that the connection structures are no longer + * in chronological order. I'd quess this means that when there is more + * than 1 connection, on average every other connection structure needs + * to be copied over the time all these connections are deleted. + */ + if (i != G.conncnt) { + memcpy(&conns[i], &conns[G.conncnt], sizeof(conns[0])); + movefd(G.conncnt + FCS, s); + } + + FD_CLR(G.conncnt + FCS, &G.readfds); +} + +static int closeOldest(void) +{ + time_t min = conns[0].lasttime; + int idx = 0; + int i; + + for (i = 1; i < MAXCONNS; i++) + if (conns[i].lasttime < min) + idx = i; + + replyError(idx + FCS, "X-SERVER-TOO-BUSY"); + close(idx + FCS); + + return idx; +} + +static int checkInput(char *buf, int len, int l) +{ + int i; + for (i = len; i < len + l; ++i) + if (buf[i] == '\n') + return 1; + return 0; +} + +int fakeidentd_main(int argc, char **argv) +{ + /* This applet is an inetd-style daemon */ + openlog(applet_name, 0, LOG_DAEMON); + logmode = LOGMODE_SYSLOG; + + memset(conns, 0, sizeof(conns)); + memset(&G, 0, sizeof(G)); + FD_ZERO(&G.readfds); + FD_SET(0, &G.readfds); + + /* handle -b parameter */ + getopt32(argc, argv, "b:", &bind_ip_address); + /* handle optional REPLY STRING */ + if (optind < argc) + G.identuser = argv[optind]; + else + G.identuser = nobodystr; + + /* daemonize and have the parent return */ + if (godaemon() == 0) + return 0; + + /* main loop where we process all events and never exit */ + while (1) { + fd_set rfds = G.readfds; + struct timeval tv = { 15, 0 }; + int i; + int tim = time(NULL); + + select(G.conncnt + FCS, &rfds, NULL, NULL, G.conncnt? &tv: NULL); + + for (i = G.conncnt - 1; i >= 0; i--) { + int s = i + FCS; + + if (FD_ISSET(s, &rfds)) { + char *buf = conns[i].buf; + unsigned int len = conns[i].len; + unsigned int l; + + if ((l = read(s, buf + len, sizeof(conns[0].buf) - len)) > 0) { + if (checkInput(buf, len, l)) { + reply(s, buf); + goto deleteconn; + } else if (len + l >= sizeof(conns[0].buf)) { + replyError(s, "X-INVALID-REQUEST"); + goto deleteconn; + } else { + conns[i].len += l; + } + } else { + goto deleteconn; + } + + conns[i].lasttime = tim; + continue; + +deleteconn: + deleteConn(s); + } else { + /* implement as time_after() in linux kernel sources ... */ + if (conns[i].lasttime + MAXIDLETIME <= tim) { + replyError(s, "X-TIMEOUT"); + deleteConn(s); + } + } + } + + if (FD_ISSET(0, &rfds)) { + int s = accept(0, NULL, 0); + + if (s < 0) { + if (errno != EINTR) /* EINTR */ + bb_perror_msg("accept"); + } else { + if (G.conncnt == MAXCONNS) + i = closeOldest(); + else + i = G.conncnt++; + + movefd(s, i + FCS); /* move if not already there */ + FD_SET(i + FCS, &G.readfds); + + conns[i].len = 0; + conns[i].lasttime = time(NULL); + } + } + } /* end of while(1) */ + + return 0; +} + +static int parseAddrs(char *ptr, char **myaddr, char **heraddr); +static void reply(int s, char *buf) +{ + char *myaddr, *heraddr; + + myaddr = heraddr = NULL; + + if (parseAddrs(buf, &myaddr, &heraddr)) + replyError(s, "X-INVALID-REQUEST"); + else { + struct iovec iv[6]; + iv[0].iov_base = myaddr; iv[0].iov_len = strlen(myaddr); + iv[1].iov_base = ", "; iv[1].iov_len = 2; + iv[2].iov_base = heraddr; iv[2].iov_len = strlen(heraddr); + iv[3].iov_base = (void *)ident_substr; iv[3].iov_len = ident_substr_len; + iv[4].iov_base = (void *)G.identuser; iv[4].iov_len = strlen(G.identuser); + iv[5].iov_base = "\r\n"; iv[5].iov_len = 2; + writev(s, iv, 6); + } +} + +static void replyError(int s, char *buf) +{ + struct iovec iv[3]; + iv[0].iov_base = "0, 0 : ERROR : "; iv[0].iov_len = 15; + iv[1].iov_base = buf; iv[1].iov_len = strlen(buf); + iv[2].iov_base = "\r\n"; iv[2].iov_len = 2; + writev(s, iv, 3); +} + +static int chmatch(char c, char *chars) +{ + for (; *chars; chars++) + if (c == *chars) + return 1; + return 0; +} + +static int skipchars(char **p, char *chars) +{ + while (chmatch(**p, chars)) + (*p)++; + if (**p == '\r' || **p == '\n') + return 0; + return 1; +} + +static int parseAddrs(char *ptr, char **myaddr, char **heraddr) +{ + /* parse , */ + + if (!skipchars(&ptr, " \t")) + return -1; + + *myaddr = ptr; + + if (!skipchars(&ptr, "1234567890")) + return -1; + + if (!chmatch(*ptr, " \t,")) + return -1; + + *ptr++ = '\0'; + + if (!skipchars(&ptr, " \t,") ) + return -1; + + *heraddr = ptr; + + skipchars(&ptr, "1234567890"); + + if (!chmatch(*ptr, " \n\r")) + return -1; + + *ptr = '\0'; + + return 0; +} diff --git a/networking/ftpgetput.c b/networking/ftpgetput.c new file mode 100644 index 000000000..223d2435c --- /dev/null +++ b/networking/ftpgetput.c @@ -0,0 +1,340 @@ +/* vi: set sw=4 ts=4: */ +/* + * ftpget + * + * Mini implementation of FTP to retrieve a remote file. + * + * Copyright (C) 2002 Jeff Angielski, The PTR Group + * Copyright (C) 2002 Glenn McGrath + * + * Based on wget.c by Chip Rosenthal Covad Communications + * + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include + +typedef struct ftp_host_info_s { + char *user; + char *password; + struct sockaddr_in *s_in; +} ftp_host_info_t; + +static char verbose_flag = 0; +static char do_continue = 0; + +static int ftpcmd(const char *s1, const char *s2, FILE *stream, char *buf) +{ + if (verbose_flag) { + bb_error_msg("cmd %s%s", s1, s2); + } + + if (s1) { + if (s2) { + fprintf(stream, "%s%s\r\n", s1, s2); + } else { + fprintf(stream, "%s\r\n", s1); + } + } + do { + char *buf_ptr; + + if (fgets(buf, 510, stream) == NULL) { + bb_perror_msg_and_die("fgets"); + } + buf_ptr = strstr(buf, "\r\n"); + if (buf_ptr) { + *buf_ptr = '\0'; + } + } while (!isdigit(buf[0]) || buf[3] != ' '); + + return xatou(buf); +} + +static int xconnect_ftpdata(ftp_host_info_t *server, const char *buf) +{ + char *buf_ptr; + unsigned short port_num; + + buf_ptr = strrchr(buf, ','); + *buf_ptr = '\0'; + port_num = xatoul_range(buf_ptr + 1, 0, 255); + + buf_ptr = strrchr(buf, ','); + *buf_ptr = '\0'; + port_num += xatoul_range(buf_ptr + 1, 0, 255) * 256; + + server->s_in->sin_port = htons(port_num); + return xconnect_tcp_v4(server->s_in); +} + +static FILE *ftp_login(ftp_host_info_t *server) +{ + FILE *control_stream; + char buf[512]; + + /* Connect to the command socket */ + control_stream = fdopen(xconnect_tcp_v4(server->s_in), "r+"); + if (control_stream == NULL) { + bb_perror_msg_and_die("cannot open control stream"); + } + + if (ftpcmd(NULL, NULL, control_stream, buf) != 220) { + bb_error_msg_and_die("%s", buf + 4); + } + + /* Login to the server */ + switch (ftpcmd("USER ", server->user, control_stream, buf)) { + case 230: + break; + case 331: + if (ftpcmd("PASS ", server->password, control_stream, buf) != 230) { + bb_error_msg_and_die("PASS error: %s", buf + 4); + } + break; + default: + bb_error_msg_and_die("USER error: %s", buf + 4); + } + + ftpcmd("TYPE I", NULL, control_stream, buf); + + return control_stream; +} + +#if !ENABLE_FTPGET +int ftp_receive(ftp_host_info_t *server, FILE *control_stream, + const char *local_path, char *server_path); +#else +static +int ftp_receive(ftp_host_info_t *server, FILE *control_stream, + const char *local_path, char *server_path) +{ + char buf[512]; + off_t filesize = 0; + int fd_data; + int fd_local = -1; + off_t beg_range = 0; + + /* Connect to the data socket */ + if (ftpcmd("PASV", NULL, control_stream, buf) != 227) { + bb_error_msg_and_die("PASV error: %s", buf + 4); + } + fd_data = xconnect_ftpdata(server, buf); + + if (ftpcmd("SIZE ", server_path, control_stream, buf) == 213) { + filesize = BB_STRTOOFF(buf + 4, NULL, 10); + if (errno || filesize < 0) + bb_error_msg_and_die("SIZE error: %s", buf + 4); + } else { + filesize = -1; + do_continue = 0; + } + + if ((local_path[0] == '-') && (local_path[1] == '\0')) { + fd_local = STDOUT_FILENO; + do_continue = 0; + } + + if (do_continue) { + struct stat sbuf; + if (lstat(local_path, &sbuf) < 0) { + bb_perror_msg_and_die("lstat"); + } + if (sbuf.st_size > 0) { + beg_range = sbuf.st_size; + } else { + do_continue = 0; + } + } + + if (do_continue) { + sprintf(buf, "REST %"OFF_FMT"d", beg_range); + if (ftpcmd(buf, NULL, control_stream, buf) != 350) { + do_continue = 0; + } else { + filesize -= beg_range; + } + } + + if (ftpcmd("RETR ", server_path, control_stream, buf) > 150) { + bb_error_msg_and_die("RETR error: %s", buf + 4); + } + + /* only make a local file if we know that one exists on the remote server */ + if (fd_local == -1) { + if (do_continue) { + fd_local = xopen(local_path, O_APPEND | O_WRONLY); + } else { + fd_local = xopen(local_path, O_CREAT | O_TRUNC | O_WRONLY); + } + } + + /* Copy the file */ + if (filesize != -1) { + if (-1 == bb_copyfd_size(fd_data, fd_local, filesize)) + exit(EXIT_FAILURE); + } else { + if (-1 == bb_copyfd_eof(fd_data, fd_local)) + exit(EXIT_FAILURE); + } + + /* close it all down */ + close(fd_data); + if (ftpcmd(NULL, NULL, control_stream, buf) != 226) { + bb_error_msg_and_die("ftp error: %s", buf + 4); + } + ftpcmd("QUIT", NULL, control_stream, buf); + + return EXIT_SUCCESS; +} +#endif + +#if !ENABLE_FTPPUT +int ftp_send(ftp_host_info_t *server, FILE *control_stream, + const char *server_path, char *local_path); +#else +static +int ftp_send(ftp_host_info_t *server, FILE *control_stream, + const char *server_path, char *local_path) +{ + struct stat sbuf; + char buf[512]; + int fd_data; + int fd_local; + int response; + + /* Connect to the data socket */ + if (ftpcmd("PASV", NULL, control_stream, buf) != 227) { + bb_error_msg_and_die("PASV error: %s", buf + 4); + } + fd_data = xconnect_ftpdata(server, buf); + + /* get the local file */ + if ((local_path[0] == '-') && (local_path[1] == '\0')) { + fd_local = STDIN_FILENO; + } else { + fd_local = xopen(local_path, O_RDONLY); + fstat(fd_local, &sbuf); + + sprintf(buf, "ALLO %lu", (unsigned long)sbuf.st_size); + response = ftpcmd(buf, NULL, control_stream, buf); + switch (response) { + case 200: + case 202: + break; + default: + close(fd_local); + bb_error_msg_and_die("ALLO error: %s", buf + 4); + break; + } + } + response = ftpcmd("STOR ", server_path, control_stream, buf); + switch (response) { + case 125: + case 150: + break; + default: + close(fd_local); + bb_error_msg_and_die("STOR error: %s", buf + 4); + } + + /* transfer the file */ + if (bb_copyfd_eof(fd_local, fd_data) == -1) { + exit(EXIT_FAILURE); + } + + /* close it all down */ + close(fd_data); + if (ftpcmd(NULL, NULL, control_stream, buf) != 226) { + bb_error_msg_and_die("error: %s", buf + 4); + } + ftpcmd("QUIT", NULL, control_stream, buf); + + return EXIT_SUCCESS; +} +#endif + +#define FTPGETPUT_OPT_CONTINUE 1 +#define FTPGETPUT_OPT_VERBOSE 2 +#define FTPGETPUT_OPT_USER 4 +#define FTPGETPUT_OPT_PASSWORD 8 +#define FTPGETPUT_OPT_PORT 16 + +#if ENABLE_FEATURE_FTPGETPUT_LONG_OPTIONS +static const struct option ftpgetput_long_options[] = { + { "continue", 1, NULL, 'c' }, + { "verbose", 0, NULL, 'v' }, + { "username", 1, NULL, 'u' }, + { "password", 1, NULL, 'p' }, + { "port", 1, NULL, 'P' }, + { 0, 0, 0, 0 } +}; +#endif + +int ftpgetput_main(int argc, char **argv) +{ + /* content-length of the file */ + unsigned opt; + char *port = "ftp"; + + /* socket to ftp server */ + FILE *control_stream; + struct sockaddr_in s_in; + + /* continue a prev transfer (-c) */ + ftp_host_info_t *server; + + int (*ftp_action)(ftp_host_info_t *, FILE *, const char *, char *) = NULL; + + /* Check to see if the command is ftpget or ftput */ + if (ENABLE_FTPPUT && (!ENABLE_FTPGET || applet_name[3] == 'p')) { + ftp_action = ftp_send; + } + if (ENABLE_FTPGET && (!ENABLE_FTPPUT || applet_name[3] == 'g')) { + ftp_action = ftp_receive; + } + + /* Set default values */ + server = xmalloc(sizeof(ftp_host_info_t)); + server->user = "anonymous"; + server->password = "busybox@"; + verbose_flag = 0; + + /* + * Decipher the command line + */ +#if ENABLE_FEATURE_FTPGETPUT_LONG_OPTIONS + applet_long_options = ftpgetput_long_options; +#endif + opt = getopt32(argc, argv, "cvu:p:P:", &server->user, &server->password, &port); + + /* Process the non-option command line arguments */ + if (argc - optind != 3) { + bb_show_usage(); + } + + if (opt & FTPGETPUT_OPT_CONTINUE) { + do_continue = 1; + } + if (opt & FTPGETPUT_OPT_VERBOSE) { + verbose_flag = 1; + } + + /* We want to do exactly _one_ DNS lookup, since some + * sites (i.e. ftp.us.debian.org) use round-robin DNS + * and we want to connect to only one IP... */ + server->s_in = &s_in; + bb_lookup_host(&s_in, argv[optind]); + s_in.sin_port = bb_lookup_port(port, "tcp", 21); + if (verbose_flag) { + printf("Connecting to %s[%s]:%d\n", + argv[optind], inet_ntoa(s_in.sin_addr), ntohs(s_in.sin_port)); + } + + /* Connect/Setup/Configure the FTP session */ + control_stream = ftp_login(server); + + return ftp_action(server, control_stream, argv[optind + 1], argv[optind + 2]); +} diff --git a/networking/hostname.c b/networking/hostname.c new file mode 100644 index 000000000..4fe28d740 --- /dev/null +++ b/networking/hostname.c @@ -0,0 +1,102 @@ +/* vi: set sw=4 ts=4: */ +/* + * $Id: hostname.c,v 1.36 2003/07/14 21:21:01 andersen Exp $ + * Mini hostname implementation for busybox + * + * Copyright (C) 1999 by Randolph Chung + * + * adjusted by Erik Andersen to remove + * use of long options and GNU getopt. Improved the usage info. + * + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +static void do_sethostname(char *s, int isfile) +{ + FILE *f; + char buf[256]; + + if (!s) + return; + if (!isfile) { + if (sethostname(s, strlen(s)) < 0) { + if (errno == EPERM) + bb_error_msg_and_die("you must be root to change the hostname"); + else + bb_perror_msg_and_die("sethostname"); + } + } else { + f = xfopen(s, "r"); + while (fgets(buf, sizeof(buf), f) != NULL) { + if (buf[0] =='#') { + continue; + } + chomp(buf); + do_sethostname(buf, 0); + } +#ifdef CONFIG_FEATURE_CLEAN_UP + fclose(f); +#endif + } +} + +int hostname_main(int argc, char **argv) +{ + enum { + OPT_d = 0x1, + OPT_f = 0x2, + OPT_i = 0x4, + OPT_s = 0x8, + OPT_dfis = 0xf, + }; + + char buf[256]; + unsigned opt; + char *hostname_str = NULL; + + if (argc < 1) + bb_show_usage(); + + opt = getopt32(argc, argv, "dfisF:", &hostname_str); + + /* Output in desired format */ + if (opt & OPT_dfis) { + struct hostent *hp; + char *p; + gethostname(buf, sizeof(buf)); + hp = xgethostbyname(buf); + p = strchr(hp->h_name, '.'); + if (opt & OPT_f) { + puts(hp->h_name); + } else if (opt & OPT_s) { + if (p != NULL) { + *p = 0; + } + puts(hp->h_name); + } else if (opt & OPT_d) { + if (p) puts(p + 1); + } else if (opt & OPT_i) { + while (hp->h_addr_list[0]) { + printf("%s ", inet_ntoa(*(struct in_addr *) (*hp->h_addr_list++))); + } + puts(""); + } + } + /* Set the hostname */ + else if (hostname_str != NULL) { + do_sethostname(hostname_str, 1); + } else if (optind < argc) { + do_sethostname(argv[optind], 0); + } + /* Or if all else fails, + * just print the current hostname */ + else { + gethostname(buf, sizeof(buf)); + puts(buf); + } + return 0; +} diff --git a/networking/httpd.c b/networking/httpd.c new file mode 100644 index 000000000..49c2c76be --- /dev/null +++ b/networking/httpd.c @@ -0,0 +1,1970 @@ +/* vi: set sw=4 ts=4: */ +/* + * httpd implementation for busybox + * + * Copyright (C) 2002,2003 Glenn Engel + * Copyright (C) 2003-2006 Vladimir Oleynik + * + * simplify patch stolen from libbb without using strdup + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + ***************************************************************************** + * + * Typical usage: + * for non root user + * httpd -p 8080 -h $HOME/public_html + * or for daemon start from rc script with uid=0: + * httpd -u www + * This is equivalent if www user have uid=80 to + * httpd -p 80 -u 80 -h /www -c /etc/httpd.conf -r "Web Server Authentication" + * + * + * When a url contains "cgi-bin" it is assumed to be a cgi script. The + * server changes directory to the location of the script and executes it + * after setting QUERY_STRING and other environment variables. + * + * Doc: + * "CGI Environment Variables": http://hoohoo.ncsa.uiuc.edu/cgi/env.html + * + * The server can also be invoked as a url arg decoder and html text encoder + * as follows: + * foo=`httpd -d $foo` # decode "Hello%20World" as "Hello World" + * bar=`httpd -e ""` # encode as "<Hello World>" + * Note that url encoding for arguments is not the same as html encoding for + * presentation. -d decodes a url-encoded argument while -e encodes in html + * for page display. + * + * httpd.conf has the following format: + * + * A:172.20. # Allow address from 172.20.0.0/16 + * A:10.0.0.0/25 # Allow any address from 10.0.0.0-10.0.0.127 + * A:10.0.0.0/255.255.255.128 # Allow any address that previous set + * A:127.0.0.1 # Allow local loopback connections + * D:* # Deny from other IP connections + * /cgi-bin:foo:bar # Require user foo, pwd bar on urls starting with /cgi-bin/ + * /adm:admin:setup # Require user admin, pwd setup on urls starting with /adm/ + * /adm:toor:PaSsWd # or user toor, pwd PaSsWd on urls starting with /adm/ + * .au:audio/basic # additional mime type for audio.au files + * *.php:/path/php # running cgi.php scripts through an interpreter + * + * A/D may be as a/d or allow/deny - first char case insensitive + * Deny IP rules take precedence over allow rules. + * + * + * The Deny/Allow IP logic: + * + * - Default is to allow all. No addresses are denied unless + * denied with a D: rule. + * - Order of Deny/Allow rules is significant + * - Deny rules take precedence over allow rules. + * - If a deny all rule (D:*) is used it acts as a catch-all for unmatched + * addresses. + * - Specification of Allow all (A:*) is a no-op + * + * Example: + * 1. Allow only specified addresses + * A:172.20 # Allow any address that begins with 172.20. + * A:10.10. # Allow any address that begins with 10.10. + * A:127.0.0.1 # Allow local loopback connections + * D:* # Deny from other IP connections + * + * 2. Only deny specified addresses + * D:1.2.3. # deny from 1.2.3.0 - 1.2.3.255 + * D:2.3.4. # deny from 2.3.4.0 - 2.3.4.255 + * A:* # (optional line added for clarity) + * + * If a sub directory contains a config file it is parsed and merged with + * any existing settings as if it was appended to the original configuration. + * + * subdir paths are relative to the containing subdir and thus cannot + * affect the parent rules. + * + * Note that since the sub dir is parsed in the forked thread servicing the + * subdir http request, any merge is discarded when the process exits. As a + * result, the subdir settings only have a lifetime of a single request. + * + * + * If -c is not set, an attempt will be made to open the default + * root configuration file. If -c is set and the file is not found, the + * server exits with an error. + * +*/ + + +#include "busybox.h" + + +static const char httpdVersion[] = "busybox httpd/1.35 6-Oct-2004"; +static const char default_path_httpd_conf[] = "/etc"; +static const char httpd_conf[] = "httpd.conf"; +static const char home[] = "./"; + +#define TIMEOUT 60 + +// Note: busybox xfuncs are not used because we want the server to keep running +// if something bad happens due to a malformed user request. +// As a result, all memory allocation after daemonize +// is checked rigorously + +//#define DEBUG 1 + +#ifndef DEBUG +# define DEBUG 0 +#endif + +#define MAX_MEMORY_BUFF 8192 /* IO buffer */ + +typedef struct HT_ACCESS { + char *after_colon; + struct HT_ACCESS *next; + char before_colon[1]; /* really bigger, must last */ +} Htaccess; + +typedef struct HT_ACCESS_IP { + unsigned int ip; + unsigned int mask; + int allow_deny; + struct HT_ACCESS_IP *next; +} Htaccess_IP; + +typedef struct { + char buf[MAX_MEMORY_BUFF]; + + USE_FEATURE_HTTPD_BASIC_AUTH(const char *realm;) + USE_FEATURE_HTTPD_BASIC_AUTH(char *remoteuser;) + + const char *query; + + USE_FEATURE_HTTPD_CGI(char *referer;) + + const char *configFile; + + unsigned int rmt_ip; +#if ENABLE_FEATURE_HTTPD_CGI || DEBUG + char rmt_ip_str[16]; /* for set env REMOTE_ADDR */ +#endif + unsigned port; /* server initial port and for + set env REMOTE_PORT */ + const char *found_mime_type; + const char *found_moved_temporarily; + + off_t ContentLength; /* -1 - unknown */ + time_t last_mod; + + Htaccess_IP *ip_a_d; /* config allow/deny lines */ + int flg_deny_all; +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + Htaccess *auth; /* config user:password lines */ +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + Htaccess *mime_a; /* config mime types */ +#endif + + int server_socket; + int accepted_socket; + volatile int alarm_signaled; + +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + Htaccess *script_i; /* config script interpreters */ +#endif +} HttpdConfig; + +static HttpdConfig *config; + +static const char request_GET[] = "GET"; /* size algorithmic optimize */ + +static const char* const suffixTable [] = { +/* Warning: shorted equivalent suffix in one line must be first */ + ".htm.html", "text/html", + ".jpg.jpeg", "image/jpeg", + ".gif", "image/gif", + ".png", "image/png", + ".txt.h.c.cc.cpp", "text/plain", + ".css", "text/css", + ".wav", "audio/wav", + ".avi", "video/x-msvideo", + ".qt.mov", "video/quicktime", + ".mpe.mpeg", "video/mpeg", + ".mid.midi", "audio/midi", + ".mp3", "audio/mpeg", +#if 0 /* unpopular */ + ".au", "audio/basic", + ".pac", "application/x-ns-proxy-autoconfig", + ".vrml.wrl", "model/vrml", +#endif + 0, "application/octet-stream" /* default */ +}; + +typedef enum { + HTTP_OK = 200, + HTTP_MOVED_TEMPORARILY = 302, + HTTP_BAD_REQUEST = 400, /* malformed syntax */ + HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */ + HTTP_NOT_FOUND = 404, + HTTP_FORBIDDEN = 403, + HTTP_REQUEST_TIMEOUT = 408, + HTTP_NOT_IMPLEMENTED = 501, /* used for unrecognized requests */ + HTTP_INTERNAL_SERVER_ERROR = 500, +#if 0 /* future use */ + HTTP_CONTINUE = 100, + HTTP_SWITCHING_PROTOCOLS = 101, + HTTP_CREATED = 201, + HTTP_ACCEPTED = 202, + HTTP_NON_AUTHORITATIVE_INFO = 203, + HTTP_NO_CONTENT = 204, + HTTP_MULTIPLE_CHOICES = 300, + HTTP_MOVED_PERMANENTLY = 301, + HTTP_NOT_MODIFIED = 304, + HTTP_PAYMENT_REQUIRED = 402, + HTTP_BAD_GATEWAY = 502, + HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */ + HTTP_RESPONSE_SETSIZE = 0xffffffff +#endif +} HttpResponseNum; + +typedef struct { + HttpResponseNum type; + const char *name; + const char *info; +} HttpEnumString; + +static const HttpEnumString httpResponseNames[] = { + { HTTP_OK, "OK", NULL }, + { HTTP_MOVED_TEMPORARILY, "Found", "Directories must end with a slash." }, + { HTTP_REQUEST_TIMEOUT, "Request Timeout", + "No request appeared within a reasonable time period." }, + { HTTP_NOT_IMPLEMENTED, "Not Implemented", + "The requested method is not recognized by this server." }, +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + { HTTP_UNAUTHORIZED, "Unauthorized", "" }, +#endif + { HTTP_NOT_FOUND, "Not Found", + "The requested URL was not found on this server." }, + { HTTP_BAD_REQUEST, "Bad Request", "Unsupported method." }, + { HTTP_FORBIDDEN, "Forbidden", "" }, + { HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error", + "Internal Server Error" }, +#if 0 /* not implemented */ + { HTTP_CREATED, "Created" }, + { HTTP_ACCEPTED, "Accepted" }, + { HTTP_NO_CONTENT, "No Content" }, + { HTTP_MULTIPLE_CHOICES, "Multiple Choices" }, + { HTTP_MOVED_PERMANENTLY, "Moved Permanently" }, + { HTTP_NOT_MODIFIED, "Not Modified" }, + { HTTP_BAD_GATEWAY, "Bad Gateway", "" }, + { HTTP_SERVICE_UNAVAILABLE, "Service Unavailable", "" }, +#endif +}; + + +static const char RFC1123FMT[] = "%a, %d %b %Y %H:%M:%S GMT"; + + +#define STRNCASECMP(a, str) strncasecmp((a), (str), sizeof(str)-1) + + +static int scan_ip(const char **ep, unsigned int *ip, unsigned char endc) +{ + const char *p = *ep; + int auto_mask = 8; + int j; + + *ip = 0; + for (j = 0; j < 4; j++) { + unsigned int octet; + + if ((*p < '0' || *p > '9') && (*p != '/' || j == 0) && *p != 0) + return -auto_mask; + octet = 0; + while (*p >= '0' && *p <= '9') { + octet *= 10; + octet += *p - '0'; + if (octet > 255) + return -auto_mask; + p++; + } + if (*p == '.') + p++; + if (*p != '/' && *p != 0) + auto_mask += 8; + *ip = ((*ip) << 8) | octet; + } + if (*p != 0) { + if (*p != endc) + return -auto_mask; + p++; + if (*p == 0) + return -auto_mask; + } + *ep = p; + return auto_mask; +} + +static int scan_ip_mask(const char *ipm, unsigned int *ip, unsigned int *mask) +{ + int i; + unsigned int msk; + + i = scan_ip(&ipm, ip, '/'); + if (i < 0) + return i; + if (*ipm) { + const char *p = ipm; + + i = 0; + while (*p) { + if (*p < '0' || *p > '9') { + if (*p == '.') { + i = scan_ip(&ipm, mask, 0); + return i != 32; + } + return -1; + } + i *= 10; + i += *p - '0'; + p++; + } + } + if (i > 32 || i < 0) + return -1; + msk = 0x80000000; + *mask = 0; + while (i > 0) { + *mask |= msk; + msk >>= 1; + i--; + } + return 0; +} + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES +static void free_config_lines(Htaccess **pprev) +{ + Htaccess *prev = *pprev; + + while (prev) { + Htaccess *cur = prev; + + prev = cur->next; + free(cur); + } + *pprev = NULL; +} +#endif + +/* flag */ +#define FIRST_PARSE 0 +#define SUBDIR_PARSE 1 +#define SIGNALED_PARSE 2 +#define FIND_FROM_HTTPD_ROOT 3 +/**************************************************************************** + * + > $Function: parse_conf() + * + * $Description: parse configuration file into in-memory linked list. + * + * The first non-white character is examined to determine if the config line + * is one of the following: + * .ext:mime/type # new mime type not compiled into httpd + * [adAD]:from # ip address allow/deny, * for wildcard + * /path:user:pass # username/password + * + * Any previous IP rules are discarded. + * If the flag argument is not SUBDIR_PARSE then all /path and mime rules + * are also discarded. That is, previous settings are retained if flag is + * SUBDIR_PARSE. + * + * $Parameters: + * (const char *) path . . null for ip address checks, path for password + * checks. + * (int) flag . . . . . . the source of the parse request. + * + * $Return: (None) + * + ****************************************************************************/ +static void parse_conf(const char *path, int flag) +{ + FILE *f; +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + Htaccess *prev, *cur; +#elif ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + Htaccess *cur; +#endif + + const char *cf = config->configFile; + char buf[160]; + char *p0 = NULL; + char *c, *p; + + /* free previous ip setup if present */ + Htaccess_IP *pip = config->ip_a_d; + + while (pip) { + Htaccess_IP *cur_ipl = pip; + + pip = cur_ipl->next; + free(cur_ipl); + } + config->ip_a_d = NULL; + + config->flg_deny_all = 0; + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + /* retain previous auth and mime config only for subdir parse */ + if (flag != SUBDIR_PARSE) { +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + free_config_lines(&config->auth); +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + free_config_lines(&config->mime_a); +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + free_config_lines(&config->script_i); +#endif + } +#endif + + if (flag == SUBDIR_PARSE || cf == NULL) { + cf = alloca(strlen(path) + sizeof(httpd_conf) + 2); + if (cf == NULL) { + if (flag == FIRST_PARSE) + bb_error_msg_and_die(bb_msg_memory_exhausted); + return; + } + sprintf((char *)cf, "%s/%s", path, httpd_conf); + } + + while ((f = fopen(cf, "r")) == NULL) { + if (flag == SUBDIR_PARSE || flag == FIND_FROM_HTTPD_ROOT) { + /* config file not found, no changes to config */ + return; + } + if (config->configFile && flag == FIRST_PARSE) /* if -c option given */ + bb_perror_msg_and_die("%s", cf); + flag = FIND_FROM_HTTPD_ROOT; + cf = httpd_conf; + } + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + prev = config->auth; +#endif + /* This could stand some work */ + while ((p0 = fgets(buf, sizeof(buf), f)) != NULL) { + c = NULL; + for (p = p0; *p0 != 0 && *p0 != '#'; p0++) { + if (!isspace(*p0)) { + *p++ = *p0; + if (*p0 == ':' && c == NULL) + c = p; + } + } + *p = 0; + + /* test for empty or strange line */ + if (c == NULL || *c == 0) + continue; + p0 = buf; + if (*p0 == 'd') + *p0 = 'D'; + if (*c == '*') { + if (*p0 == 'D') { + /* memorize deny all */ + config->flg_deny_all++; + } + /* skip default other "word:*" config lines */ + continue; + } + + if (*p0 == 'a') + *p0 = 'A'; + else if (*p0 != 'D' && *p0 != 'A' +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + && *p0 != '/' +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + && *p0 != '.' +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + && *p0 != '*' +#endif + ) + continue; + if (*p0 == 'A' || *p0 == 'D') { + /* storing current config IP line */ + pip = calloc(1, sizeof(Htaccess_IP)); + if (pip) { + if (scan_ip_mask(c, &(pip->ip), &(pip->mask))) { + /* syntax IP{/mask} error detected, protect all */ + *p0 = 'D'; + pip->mask = 0; + } + pip->allow_deny = *p0; + if (*p0 == 'D') { + /* Deny:form_IP move top */ + pip->next = config->ip_a_d; + config->ip_a_d = pip; + } else { + /* add to bottom A:form_IP config line */ + Htaccess_IP *prev_IP = config->ip_a_d; + + if (prev_IP == NULL) { + config->ip_a_d = pip; + } else { + while (prev_IP->next) + prev_IP = prev_IP->next; + prev_IP->next = pip; + } + } + } + continue; + } +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + if (*p0 == '/') { + /* make full path from httpd root / curent_path / config_line_path */ + cf = flag == SUBDIR_PARSE ? path : ""; + p0 = malloc(strlen(cf) + (c - buf) + 2 + strlen(c)); + if (p0 == NULL) + continue; + c[-1] = 0; + sprintf(p0, "/%s%s", cf, buf); + + /* another call bb_simplify_path */ + cf = p = p0; + + do { + if (*p == '/') { + if (*cf == '/') { /* skip duplicate (or initial) slash */ + continue; + } else if (*cf == '.') { + if (cf[1] == '/' || cf[1] == 0) { /* remove extra '.' */ + continue; + } else if ((cf[1] == '.') && (cf[2] == '/' || cf[2] == 0)) { + ++cf; + if (p > p0) { + while (*--p != '/') /* omit previous dir */; + } + continue; + } + } + } + *++p = *cf; + } while (*++cf); + + if ((p == p0) || (*p != '/')) { /* not a trailing slash */ + ++p; /* so keep last character */ + } + *p = 0; + sprintf(p0, "%s:%s", p0, c); + } +#endif + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH || ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES || ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + /* storing current config line */ + cur = calloc(1, sizeof(Htaccess) + strlen(p0)); + if (cur) { + cf = strcpy(cur->before_colon, p0); + c = strchr(cf, ':'); + *c++ = 0; + cur->after_colon = c; +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + if (*cf == '.') { + /* config .mime line move top for overwrite previous */ + cur->next = config->mime_a; + config->mime_a = cur; + continue; + } +#endif +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + if (*cf == '*' && cf[1] == '.') { + /* config script interpreter line move top for overwrite previous */ + cur->next = config->script_i; + config->script_i = cur; + continue; + } +#endif +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + free(p0); + if (prev == NULL) { + /* first line */ + config->auth = prev = cur; + } else { + /* sort path, if current lenght eq or bigger then move up */ + Htaccess *prev_hti = config->auth; + size_t l = strlen(cf); + Htaccess *hti; + + for (hti = prev_hti; hti; hti = hti->next) { + if (l >= strlen(hti->before_colon)) { + /* insert before hti */ + cur->next = hti; + if (prev_hti != hti) { + prev_hti->next = cur; + } else { + /* insert as top */ + config->auth = cur; + } + break; + } + if (prev_hti != hti) + prev_hti = prev_hti->next; + } + if (!hti) { /* not inserted, add to bottom */ + prev->next = cur; + prev = cur; + } + } +#endif + } +#endif + } + fclose(f); +} + +#if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR +/**************************************************************************** + * + > $Function: encodeString() + * + * $Description: Given a string, html encode special characters. + * This is used for the -e command line option to provide an easy way + * for scripts to encode result data without confusing browsers. The + * returned string pointer is memory allocated by malloc(). + * + * $Parameters: + * (const char *) string . . The first string to encode. + * + * $Return: (char *) . . . .. . . A pointer to the encoded string. + * + * $Errors: Returns a null string ("") if memory is not available. + * + ****************************************************************************/ +static char *encodeString(const char *string) +{ + /* take the simple route and encode everything */ + /* could possibly scan once to get length. */ + int len = strlen(string); + char *out = malloc(len * 6 + 1); + char *p = out; + char ch; + + if (!out) return ""; + while ((ch = *string++)) { + // very simple check for what to encode + if (isalnum(ch)) *p++ = ch; + else p += sprintf(p, "&#%d;", (unsigned char) ch); + } + *p = 0; + return out; +} +#endif /* FEATURE_HTTPD_ENCODE_URL_STR */ + +/**************************************************************************** + * + > $Function: decodeString() + * + * $Description: Given a URL encoded string, convert it to plain ascii. + * Since decoding always makes strings smaller, the decode is done in-place. + * Thus, callers should strdup() the argument if they do not want the + * argument modified. The return is the original pointer, allowing this + * function to be easily used as arguments to other functions. + * + * $Parameters: + * (char *) string . . . The first string to decode. + * (int) option_d . . 1 if called for httpd -d + * + * $Return: (char *) . . . . A pointer to the decoded string (same as input). + * + * $Errors: None + * + ****************************************************************************/ +static char *decodeString(char *orig, int option_d) +{ + /* note that decoded string is always shorter than original */ + char *string = orig; + char *ptr = string; + char c; + + while ((c = *ptr++) != '\0') { + unsigned value1, value2; + + if (option_d && c == '+') { + *string++ = ' '; + continue; + } + if (c != '%') { + *string++ = c; + continue; + } + if (sscanf(ptr, "%1X", &value1) != 1 + || sscanf(ptr+1, "%1X", &value2) != 1 + ) { + if (!option_d) + return NULL; + *string++ = '%'; + continue; + } + value1 = value1 * 16 + value2; + if (!option_d && (value1 == '/' || value1 == '\0')) { + /* caller takes it as indication of invalid + * (dangerous wrt exploits) chars */ + return orig + 1; + } + *string++ = value1; + ptr += 2; + } + *string = '\0'; + return orig; +} + + +#if ENABLE_FEATURE_HTTPD_CGI +/**************************************************************************** + * setenv helpers + ****************************************************************************/ +static void setenv1(const char *name, const char *value) +{ + if (!value) + value = ""; + setenv(name, value, 1); +} +static void setenv_long(const char *name, long value) +{ + char buf[sizeof(value)*3 + 1]; + sprintf(buf, "%ld", value); + setenv(name, buf, 1); +} +#endif + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH +/**************************************************************************** + * + > $Function: decodeBase64() + * + > $Description: Decode a base 64 data stream as per rfc1521. + * Note that the rfc states that none base64 chars are to be ignored. + * Since the decode always results in a shorter size than the input, it is + * OK to pass the input arg as an output arg. + * + * $Parameter: + * (char *) Data . . . . A pointer to a base64 encoded string. + * Where to place the decoded data. + * + * $Return: void + * + * $Errors: None + * + ****************************************************************************/ +static void decodeBase64(char *Data) +{ + + const unsigned char *in = (const unsigned char *)Data; + // The decoded size will be at most 3/4 the size of the encoded + unsigned long ch = 0; + int i = 0; + + while (*in) { + int t = *in++; + + if (t >= '0' && t <= '9') + t = t - '0' + 52; + else if (t >= 'A' && t <= 'Z') + t = t - 'A'; + else if (t >= 'a' && t <= 'z') + t = t - 'a' + 26; + else if (t == '+') + t = 62; + else if (t == '/') + t = 63; + else if (t == '=') + t = 0; + else + continue; + + ch = (ch << 6) | t; + i++; + if (i == 4) { + *Data++ = (char) (ch >> 16); + *Data++ = (char) (ch >> 8); + *Data++ = (char) ch; + i = 0; + } + } + *Data = 0; +} +#endif + + +/**************************************************************************** + * + > $Function: openServer() + * + * $Description: create a listen server socket on the designated port. + * + * $Return: (int) . . . A connection socket. -1 for errors. + * + * $Errors: None + * + ****************************************************************************/ +static int openServer(void) +{ + struct sockaddr_in lsocket; + int fd; + + /* create the socket right now */ + /* inet_addr() returns a value that is already in network order */ + memset(&lsocket, 0, sizeof(lsocket)); + lsocket.sin_family = AF_INET; + lsocket.sin_addr.s_addr = INADDR_ANY; + lsocket.sin_port = htons(config->port); + fd = xsocket(AF_INET, SOCK_STREAM, 0); + /* tell the OS it's OK to reuse a previous address even though */ + /* it may still be in a close down state. Allows bind to succeed. */ +#ifdef SO_REUSEPORT + { + static const int on = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, + (void *)&on, sizeof(on)); + } +#else + setsockopt_reuseaddr(fd); +#endif + xbind(fd, (struct sockaddr *)&lsocket, sizeof(lsocket)); + xlisten(fd, 9); + signal(SIGCHLD, SIG_IGN); /* prevent zombie (defunct) processes */ + return fd; +} + +/**************************************************************************** + * + > $Function: sendHeaders() + * + * $Description: Create and send HTTP response headers. + * The arguments are combined and sent as one write operation. Note that + * IE will puke big-time if the headers are not sent in one packet and the + * second packet is delayed for any reason. + * + * $Parameter: + * (HttpResponseNum) responseNum . . . The result code to send. + * + * $Return: (int) . . . . writing errors + * + ****************************************************************************/ +static int sendHeaders(HttpResponseNum responseNum) +{ + char *buf = config->buf; + const char *responseString = ""; + const char *infoString = 0; + const char *mime_type; + unsigned int i; + time_t timer = time(0); + char timeStr[80]; + int len; + enum { + numNames = sizeof(httpResponseNames) / sizeof(httpResponseNames[0]) + }; + + for (i = 0; i < numNames; i++) { + if (httpResponseNames[i].type == responseNum) { + responseString = httpResponseNames[i].name; + infoString = httpResponseNames[i].info; + break; + } + } + /* error message is HTML */ + mime_type = responseNum == HTTP_OK ? + config->found_mime_type : "text/html"; + + /* emit the current date */ + strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&timer)); + len = sprintf(buf, + "HTTP/1.0 %d %s\r\nContent-type: %s\r\n" + "Date: %s\r\nConnection: close\r\n", + responseNum, responseString, mime_type, timeStr); + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + if (responseNum == HTTP_UNAUTHORIZED) { + len += sprintf(buf+len, "WWW-Authenticate: Basic realm=\"%s\"\r\n", + config->realm); + } +#endif + if (responseNum == HTTP_MOVED_TEMPORARILY) { + len += sprintf(buf+len, "Location: %s/%s%s\r\n", + config->found_moved_temporarily, + (config->query ? "?" : ""), + (config->query ? config->query : "")); + } + + if (config->ContentLength != -1) { /* file */ + strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&config->last_mod)); + len += sprintf(buf+len, "Last-Modified: %s\r\n%s %"OFF_FMT"d\r\n", + timeStr, "Content-length:", config->ContentLength); + } + strcat(buf, "\r\n"); + len += 2; + if (infoString) { + len += sprintf(buf+len, + "%d %s\n" + "

    %d %s

    \n%s\n\n", + responseNum, responseString, + responseNum, responseString, infoString); + } + if (DEBUG) + fprintf(stderr, "headers: '%s'\n", buf); + return full_write(config->accepted_socket, buf, len); +} + +/**************************************************************************** + * + > $Function: getLine() + * + * $Description: Read from the socket until an end of line char found. + * + * Characters are read one at a time until an eol sequence is found. + * + * $Return: (int) . . . . number of characters read. -1 if error. + * + ****************************************************************************/ +static int getLine(void) +{ + int count = 0; + char *buf = config->buf; + + while (read(config->accepted_socket, buf + count, 1) == 1) { + if (buf[count] == '\r') continue; + if (buf[count] == '\n') { + buf[count] = 0; + return count; + } + if (count < (MAX_MEMORY_BUFF-1)) /* check overflow */ + count++; + } + if (count) return count; + else return -1; +} + +#if ENABLE_FEATURE_HTTPD_CGI +/**************************************************************************** + * + > $Function: sendCgi() + * + * $Description: Execute a CGI script and send it's stdout back + * + * Environment variables are set up and the script is invoked with pipes + * for stdin/stdout. If a post is being done the script is fed the POST + * data in addition to setting the QUERY_STRING variable (for GETs or POSTs). + * + * $Parameters: + * (const char *) url . . . . . . The requested URL (with leading /). + * (int bodyLen) . . . . . . . . Length of the post body. + * (const char *cookie) . . . . . For set HTTP_COOKIE. + * (const char *content_type) . . For set CONTENT_TYPE. + + * + * $Return: (char *) . . . . A pointer to the decoded string (same as input). + * + * $Errors: None + * + ****************************************************************************/ +static int sendCgi(const char *url, + const char *request, int bodyLen, const char *cookie, + const char *content_type) +{ + int fromCgi[2]; /* pipe for reading data from CGI */ + int toCgi[2]; /* pipe for sending data to CGI */ + + static char * argp[] = { 0, 0 }; + int pid = 0; + int inFd; + int outFd; + int firstLine = 1; + int status; + size_t post_readed_size, post_readed_idx; + + if (pipe(fromCgi) != 0) + return 0; + if (pipe(toCgi) != 0) + return 0; + + pid = fork(); + if (pid < 0) + return 0; + + if (!pid) { + /* child process */ + char *script; + char *purl = strdup(url); + char realpath_buff[MAXPATHLEN]; + + if (purl == NULL) + _exit(242); + + inFd = toCgi[0]; + outFd = fromCgi[1]; + + dup2(inFd, 0); // replace stdin with the pipe + dup2(outFd, 1); // replace stdout with the pipe + if (!DEBUG) + dup2(outFd, 2); // replace stderr with the pipe + + close(toCgi[0]); + close(toCgi[1]); + close(fromCgi[0]); + close(fromCgi[1]); + + close(config->accepted_socket); + close(config->server_socket); + + /* + * Find PATH_INFO. + */ + script = purl; + while ((script = strchr(script + 1, '/')) != NULL) { + /* have script.cgi/PATH_INFO or dirs/script.cgi[/PATH_INFO] */ + struct stat sb; + + *script = '\0'; + if (is_directory(purl + 1, 1, &sb) == 0) { + /* not directory, found script.cgi/PATH_INFO */ + *script = '/'; + break; + } + *script = '/'; /* is directory, find next '/' */ + } + setenv1("PATH_INFO", script); /* set /PATH_INFO or "" */ + /* setenv1("PATH", getenv("PATH")); redundant */ + setenv1("REQUEST_METHOD", request); + if (config->query) { + char *uri = alloca(strlen(purl) + 2 + strlen(config->query)); + if (uri) + sprintf(uri, "%s?%s", purl, config->query); + setenv1("REQUEST_URI", uri); + } else { + setenv1("REQUEST_URI", purl); + } + if (script != NULL) + *script = '\0'; /* cut off /PATH_INFO */ + /* SCRIPT_FILENAME required by PHP in CGI mode */ + if (!realpath(purl + 1, realpath_buff)) + goto error_execing_cgi; + setenv1("SCRIPT_FILENAME", realpath_buff); + /* set SCRIPT_NAME as full path: /cgi-bin/dirs/script.cgi */ + setenv1("SCRIPT_NAME", purl); + /* http://hoohoo.ncsa.uiuc.edu/cgi/env.html: + * QUERY_STRING: The information which follows the ? in the URL + * which referenced this script. This is the query information. + * It should not be decoded in any fashion. This variable + * should always be set when there is query information, + * regardless of command line decoding. */ + /* (Older versions of bbox seemed to do some decoding) */ + setenv1("QUERY_STRING", config->query); + setenv1("SERVER_SOFTWARE", httpdVersion); + putenv("SERVER_PROTOCOL=HTTP/1.0"); + putenv("GATEWAY_INTERFACE=CGI/1.1"); + setenv1("REMOTE_ADDR", config->rmt_ip_str); +#if ENABLE_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV + setenv_long("REMOTE_PORT", config->port); +#endif + if (bodyLen) + setenv_long("CONTENT_LENGTH", bodyLen); + if (cookie) + setenv1("HTTP_COOKIE", cookie); + if (content_type) + setenv1("CONTENT_TYPE", content_type); +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + if (config->remoteuser) { + setenv1("REMOTE_USER", config->remoteuser); + putenv("AUTH_TYPE=Basic"); + } +#endif + if (config->referer) + setenv1("HTTP_REFERER", config->referer); + + /* set execve argp[0] without path */ + argp[0] = strrchr(purl, '/') + 1; + /* but script argp[0] must have absolute path and chdiring to this */ + script = strrchr(realpath_buff, '/'); + if (!script) + goto error_execing_cgi; + *script = '\0'; + if (chdir(realpath_buff) == 0) { + // now run the program. If it fails, + // use _exit() so no destructors + // get called and make a mess. +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + char *interpr = NULL; + char *suffix = strrchr(purl, '.'); + + if (suffix) { + Htaccess *cur; + for (cur = config->script_i; cur; cur = cur->next) { + if (strcmp(cur->before_colon + 1, suffix) == 0) { + interpr = cur->after_colon; + break; + } + } + } +#endif + *script = '/'; +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR + if (interpr) + execv(interpr, argp); + else +#endif + execv(realpath_buff, argp); + } + error_execing_cgi: + /* send to stdout (even if we are not from inetd) */ + config->accepted_socket = 1; + sendHeaders(HTTP_NOT_FOUND); + _exit(242); + } /* end child */ + + /* parent process */ + + post_readed_size = 0; + post_readed_idx = 0; + inFd = fromCgi[0]; + outFd = toCgi[1]; + close(fromCgi[1]); + close(toCgi[0]); + signal(SIGPIPE, SIG_IGN); + + while (1) { + fd_set readSet; + fd_set writeSet; + char wbuf[128]; + int nfound; + int count; + + FD_ZERO(&readSet); + FD_ZERO(&writeSet); + FD_SET(inFd, &readSet); + if (bodyLen > 0 || post_readed_size > 0) { + FD_SET(outFd, &writeSet); + nfound = outFd > inFd ? outFd : inFd; + if (post_readed_size == 0) { + FD_SET(config->accepted_socket, &readSet); + if (nfound < config->accepted_socket) + nfound = config->accepted_socket; + } + /* Now wait on the set of sockets! */ + nfound = select(nfound + 1, &readSet, &writeSet, 0, NULL); + } else { + if (!bodyLen) { + close(outFd); + bodyLen = -1; + } + nfound = select(inFd + 1, &readSet, 0, 0, NULL); + } + + if (nfound <= 0) { + if (waitpid(pid, &status, WNOHANG) > 0) { + close(inFd); + if (DEBUG && WIFEXITED(status)) + bb_error_msg("piped has exited with status=%d", WEXITSTATUS(status)); + if (DEBUG && WIFSIGNALED(status)) + bb_error_msg("piped has exited with signal=%d", WTERMSIG(status)); + break; + } + } else if (post_readed_size > 0 && FD_ISSET(outFd, &writeSet)) { + count = full_write(outFd, wbuf + post_readed_idx, post_readed_size); + if (count > 0) { + post_readed_size -= count; + post_readed_idx += count; + if (post_readed_size == 0) + post_readed_idx = 0; + } else { + post_readed_size = post_readed_idx = bodyLen = 0; /* broken pipe to CGI */ + } + } else if (bodyLen > 0 && post_readed_size == 0 && FD_ISSET(config->accepted_socket, &readSet)) { + count = bodyLen > (int)sizeof(wbuf) ? (int)sizeof(wbuf) : bodyLen; + count = safe_read(config->accepted_socket, wbuf, count); + if (count > 0) { + post_readed_size += count; + bodyLen -= count; + } else { + bodyLen = 0; /* closed */ + } + } + if (FD_ISSET(inFd, &readSet)) { + int s = config->accepted_socket; + char *rbuf = config->buf; + +#ifndef PIPE_BUF +# define PIPESIZE 4096 /* amount of buffering in a pipe */ +#else +# define PIPESIZE PIPE_BUF +#endif +#if PIPESIZE >= MAX_MEMORY_BUFF +# error "PIPESIZE >= MAX_MEMORY_BUFF" +#endif + + /* There is something to read */ + count = safe_read(inFd, rbuf, PIPESIZE); + if (count == 0) + break; /* closed */ + if (count > 0) { + if (firstLine) { + rbuf[count] = 0; + /* check to see if the user script added headers */ + if (strncmp(rbuf, "HTTP/1.0 200 OK\r\n", 4) != 0) { + full_write(s, "HTTP/1.0 200 OK\r\n", 17); + } + /* Sometimes CGI is writing to pipe in small chunks + * and we don't see Content-type (because the read + * is too short) and we emit bogus "text/plain"! + * Is it a bug or CGI *has to* write it in one piece? */ + if (strstr(rbuf, "ontent-") == 0) { + full_write(s, "Content-type: text/plain\r\n\r\n", 28); + } + firstLine = 0; + } + if (full_write(s, rbuf, count) != count) + break; + + if (DEBUG) + fprintf(stderr, "cgi read %d bytes: '%.*s'\n", count, count, rbuf); + } + } + } + return 0; +} +#endif /* FEATURE_HTTPD_CGI */ + +/**************************************************************************** + * + > $Function: sendFile() + * + * $Description: Send a file response to a HTTP request + * + * $Parameter: + * (const char *) url . . The URL requested. + * + * $Return: (int) . . . . . . Always 0. + * + ****************************************************************************/ +static int sendFile(const char *url) +{ + char * suffix; + int f; + const char * const * table; + const char * try_suffix; + + suffix = strrchr(url, '.'); + + for (table = suffixTable; *table; table += 2) + if (suffix != NULL && (try_suffix = strstr(*table, suffix)) != 0) { + try_suffix += strlen(suffix); + if (*try_suffix == 0 || *try_suffix == '.') + break; + } + /* also, if not found, set default as "application/octet-stream"; */ + config->found_mime_type = table[1]; +#if ENABLE_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES + if (suffix) { + Htaccess * cur; + + for (cur = config->mime_a; cur; cur = cur->next) { + if (strcmp(cur->before_colon, suffix) == 0) { + config->found_mime_type = cur->after_colon; + break; + } + } + } +#endif /* FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES */ + + if (DEBUG) + fprintf(stderr, "sending file '%s' content-type: %s\n", + url, config->found_mime_type); + + f = open(url, O_RDONLY); + if (f >= 0) { + int count; + char *buf = config->buf; + + sendHeaders(HTTP_OK); + /* TODO: sendfile() */ + while ((count = full_read(f, buf, MAX_MEMORY_BUFF)) > 0) { + if (full_write(config->accepted_socket, buf, count) != count) + break; + } + close(f); + } else { + if (DEBUG) + bb_perror_msg("cannot open '%s'", url); + sendHeaders(HTTP_NOT_FOUND); + } + + return 0; +} + +static int checkPermIP(void) +{ + Htaccess_IP * cur; + + /* This could stand some work */ + for (cur = config->ip_a_d; cur; cur = cur->next) { + if (DEBUG) + fprintf(stderr, "checkPermIP: '%s' ? ", config->rmt_ip_str); + if (DEBUG) + fprintf(stderr, "'%u.%u.%u.%u/%u.%u.%u.%u'\n", + (unsigned char)(cur->ip >> 24), + (unsigned char)(cur->ip >> 16), + (unsigned char)(cur->ip >> 8), + cur->ip & 0xff, + (unsigned char)(cur->mask >> 24), + (unsigned char)(cur->mask >> 16), + (unsigned char)(cur->mask >> 8), + cur->mask & 0xff); + if ((config->rmt_ip & cur->mask) == cur->ip) + return cur->allow_deny == 'A'; /* Allow/Deny */ + } + + /* if unconfigured, return 1 - access from all */ + return !config->flg_deny_all; +} + +/**************************************************************************** + * + > $Function: checkPerm() + * + * $Description: Check the permission file for access password protected. + * + * If config file isn't present, everything is allowed. + * Entries are of the form you can see example from header source + * + * $Parameters: + * (const char *) path . . . . The file path. + * (const char *) request . . . User information to validate. + * + * $Return: (int) . . . . . . . . . 1 if request OK, 0 otherwise. + * + ****************************************************************************/ + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH +static int checkPerm(const char *path, const char *request) +{ + Htaccess * cur; + const char *p; + const char *p0; + + const char *prev = NULL; + + /* This could stand some work */ + for (cur = config->auth; cur; cur = cur->next) { + size_t l; + + p0 = cur->before_colon; + if (prev != NULL && strcmp(prev, p0) != 0) + continue; /* find next identical */ + p = cur->after_colon; + if (DEBUG) + fprintf(stderr, "checkPerm: '%s' ? '%s'\n", p0, request); + + l = strlen(p0); + if (strncmp(p0, path, l) == 0 + && (l == 1 || path[l] == '/' || path[l] == '\0') + ) { + char *u; + /* path match found. Check request */ + /* for check next /path:user:password */ + prev = p0; + u = strchr(request, ':'); + if (u == NULL) { + /* bad request, ':' required */ + break; + } + + if (ENABLE_FEATURE_HTTPD_AUTH_MD5) { + char *cipher; + char *pp; + + if (strncmp(p, request, u-request) != 0) { + /* user uncompared */ + continue; + } + pp = strchr(p, ':'); + if (pp && pp[1] == '$' && pp[2] == '1' && + pp[3] == '$' && pp[4]) { + pp++; + cipher = pw_encrypt(u+1, pp); + if (strcmp(cipher, pp) == 0) + goto set_remoteuser_var; /* Ok */ + /* unauthorized */ + continue; + } + } + + if (strcmp(p, request) == 0) { +set_remoteuser_var: + config->remoteuser = strdup(request); + if (config->remoteuser) + config->remoteuser[(u - request)] = 0; + return 1; /* Ok */ + } + /* unauthorized */ + } + } /* for */ + + return prev == NULL; +} + +#endif /* FEATURE_HTTPD_BASIC_AUTH */ + +/**************************************************************************** + * + > $Function: handle_sigalrm() + * + * $Description: Handle timeouts + * + ****************************************************************************/ + +static void handle_sigalrm(int sig) +{ + sendHeaders(HTTP_REQUEST_TIMEOUT); + config->alarm_signaled = sig; +} + +/**************************************************************************** + * + > $Function: handleIncoming() + * + * $Description: Handle an incoming http request. + * + ****************************************************************************/ +static void handleIncoming(void) +{ + char *buf = config->buf; + char *url; + char *purl; + int blank = -1; + char *test; + struct stat sb; + int ip_allowed; +#if ENABLE_FEATURE_HTTPD_CGI + const char *prequest = request_GET; + unsigned long length = 0; + char *cookie = 0; + char *content_type = 0; +#endif + fd_set s_fd; + struct timeval tv; + int retval; + struct sigaction sa; + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + int credentials = -1; /* if not required this is Ok */ +#endif + + sa.sa_handler = handle_sigalrm; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; /* no SA_RESTART */ + sigaction(SIGALRM, &sa, NULL); + + do { + int count; + + (void) alarm(TIMEOUT); + if (getLine() <= 0) + break; /* closed */ + + purl = strpbrk(buf, " \t"); + if (purl == NULL) { + BAD_REQUEST: + sendHeaders(HTTP_BAD_REQUEST); + break; + } + *purl = '\0'; +#if ENABLE_FEATURE_HTTPD_CGI + if (strcasecmp(buf, prequest) != 0) { + prequest = "POST"; + if (strcasecmp(buf, prequest) != 0) { + sendHeaders(HTTP_NOT_IMPLEMENTED); + break; + } + } +#else + if (strcasecmp(buf, request_GET) != 0) { + sendHeaders(HTTP_NOT_IMPLEMENTED); + break; + } +#endif + *purl = ' '; + count = sscanf(purl, " %[^ ] HTTP/%d.%*d", buf, &blank); + + if (count < 1 || buf[0] != '/') { + /* Garbled request/URL */ + goto BAD_REQUEST; + } + url = alloca(strlen(buf) + sizeof("/index.html")); + if (url == NULL) { + sendHeaders(HTTP_INTERNAL_SERVER_ERROR); + break; + } + strcpy(url, buf); + /* extract url args if present */ + test = strchr(url, '?'); + config->query = NULL; + if (test) { + *test++ = '\0'; + config->query = test; + } + + test = decodeString(url, 0); + if (test == NULL) + goto BAD_REQUEST; + if (test == url+1) { + /* '/' or NUL is encoded */ + sendHeaders(HTTP_NOT_FOUND); + break; + } + + /* algorithm stolen from libbb bb_simplify_path(), + but don't strdup and reducing trailing slash and protect out root */ + purl = test = url; + do { + if (*purl == '/') { + /* skip duplicate (or initial) slash */ + if (*test == '/') { + continue; + } + if (*test == '.') { + /* skip extra '.' */ + if (test[1] == '/' || test[1] == 0) { + continue; + } else + /* '..': be careful */ + if (test[1] == '.' && (test[2] == '/' || test[2] == 0)) { + ++test; + if (purl == url) { + /* protect out root */ + goto BAD_REQUEST; + } + while (*--purl != '/') /* omit previous dir */; + continue; + } + } + } + *++purl = *test; + } while (*++test); + *++purl = '\0'; /* so keep last character */ + test = purl; /* end ptr */ + + /* If URL is directory, adding '/' */ + if (test[-1] != '/') { + if (is_directory(url + 1, 1, &sb)) { + config->found_moved_temporarily = url; + } + } + if (DEBUG) + fprintf(stderr, "url='%s', args=%s\n", url, config->query); + + test = url; + ip_allowed = checkPermIP(); + while (ip_allowed && (test = strchr(test + 1, '/')) != NULL) { + /* have path1/path2 */ + *test = '\0'; + if (is_directory(url + 1, 1, &sb)) { + /* may be having subdir config */ + parse_conf(url + 1, SUBDIR_PARSE); + ip_allowed = checkPermIP(); + } + *test = '/'; + } + if (blank >= 0) { + /* read until blank line for HTTP version specified, else parse immediate */ + while (1) { + alarm(TIMEOUT); + count = getLine(); + if (count <= 0) + break; + + if (DEBUG) + fprintf(stderr, "header: '%s'\n", buf); + +#if ENABLE_FEATURE_HTTPD_CGI + /* try and do our best to parse more lines */ + if ((STRNCASECMP(buf, "Content-length:") == 0)) { + /* extra read only for POST */ + if (prequest != request_GET) { + test = buf + sizeof("Content-length:")-1; + if (!test[0]) goto bail_out; + errno = 0; + /* not using strtoul: it ignores leading munis! */ + length = strtol(test, &test, 10); + /* length is "ulong", but we need to pass it to int later */ + /* so we check for negative or too large values in one go: */ + /* (long -> ulong conv caused negatives to be seen as > INT_MAX) */ + if (test[0] || errno || length > INT_MAX) + goto bail_out; + } + } else if ((STRNCASECMP(buf, "Cookie:") == 0)) { + cookie = strdup(skip_whitespace(buf + sizeof("Cookie:")-1)); + } else if ((STRNCASECMP(buf, "Content-Type:") == 0)) { + content_type = strdup(skip_whitespace(buf + sizeof("Content-Type:")-1)); + } else if ((STRNCASECMP(buf, "Referer:") == 0)) { + config->referer = strdup(skip_whitespace(buf + sizeof("Referer:")-1)); + } +#endif + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + if (STRNCASECMP(buf, "Authorization:") == 0) { + /* We only allow Basic credentials. + * It shows up as "Authorization: Basic " where + * the userid:password is base64 encoded. + */ + test = skip_whitespace(buf + sizeof("Authorization:")-1); + if (STRNCASECMP(test, "Basic") != 0) + continue; + test += sizeof("Basic")-1; + /* decodeBase64() skips whitespace itself */ + decodeBase64(test); + credentials = checkPerm(url, test); + } +#endif /* FEATURE_HTTPD_BASIC_AUTH */ + + } /* while extra header reading */ + } + alarm(0); + if (config->alarm_signaled) + break; + + if (strcmp(strrchr(url, '/') + 1, httpd_conf) == 0 || ip_allowed == 0) { + /* protect listing [/path]/httpd_conf or IP deny */ +#if ENABLE_FEATURE_HTTPD_CGI + FORBIDDEN: /* protect listing /cgi-bin */ +#endif + sendHeaders(HTTP_FORBIDDEN); + break; + } + +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + if (credentials <= 0 && checkPerm(url, ":") == 0) { + sendHeaders(HTTP_UNAUTHORIZED); + break; + } +#endif + + if (config->found_moved_temporarily) { + sendHeaders(HTTP_MOVED_TEMPORARILY); + /* clear unforked memory flag */ + config->found_moved_temporarily = NULL; + break; + } + + test = url + 1; /* skip first '/' */ + +#if ENABLE_FEATURE_HTTPD_CGI + if (strncmp(test, "cgi-bin", 7) == 0) { + if (test[7] == '/' && test[8] == 0) + goto FORBIDDEN; /* protect listing cgi-bin/ */ + sendCgi(url, prequest, length, cookie, content_type); + break; + } + if (prequest != request_GET) { + sendHeaders(HTTP_NOT_IMPLEMENTED); + break; + } +#endif /* FEATURE_HTTPD_CGI */ + if (purl[-1] == '/') + strcpy(purl, "index.html"); + if (stat(test, &sb) == 0) { + /* It's a dir URL and there is index.html */ + config->ContentLength = sb.st_size; + config->last_mod = sb.st_mtime; + } +#if ENABLE_FEATURE_HTTPD_CGI + else if (purl[-1] == '/') { + /* It's a dir URL and there is no index.html + * Try cgi-bin/index.cgi */ + if (access("/cgi-bin/index.cgi"+1, X_OK) == 0) { + purl[0] = '\0'; + config->query = url; + sendCgi("/cgi-bin/index.cgi", prequest, length, cookie, content_type); + break; + } + } +#endif /* FEATURE_HTTPD_CGI */ + sendFile(test); + config->ContentLength = -1; + } while (0); + + bail_out: + + if (DEBUG) + fprintf(stderr, "closing socket\n\n"); +#if ENABLE_FEATURE_HTTPD_CGI + free(cookie); + free(content_type); + free(config->referer); + config->referer = NULL; +# if ENABLE_FEATURE_HTTPD_BASIC_AUTH + free(config->remoteuser); + config->remoteuser = NULL; +# endif +#endif + shutdown(config->accepted_socket, SHUT_WR); + + /* Properly wait for remote to closed */ + FD_ZERO(&s_fd); + FD_SET(config->accepted_socket, &s_fd); + + do { + tv.tv_sec = 2; + tv.tv_usec = 0; + retval = select(config->accepted_socket + 1, &s_fd, NULL, NULL, &tv); + } while (retval > 0 && read(config->accepted_socket, buf, sizeof(config->buf) > 0)); + + shutdown(config->accepted_socket, SHUT_RD); + /* In inetd case, we close fd 1 (stdout) here. We will exit soon anyway */ + close(config->accepted_socket); +} + +/**************************************************************************** + * + > $Function: miniHttpd() + * + * $Description: The main http server function. + * + * Given an open socket fildes, listen for new connections and farm out + * the processing as a forked process. + * + * $Parameters: + * (int) server. . . The server socket fildes. + * + * $Return: (int) . . . . Always 0. + * + ****************************************************************************/ +static int miniHttpd(int server) +{ + fd_set readfd, portfd; + + FD_ZERO(&portfd); + FD_SET(server, &portfd); + + /* copy the ports we are watching to the readfd set */ + while (1) { + int on, s; + socklen_t fromAddrLen; + struct sockaddr_in fromAddr; + + /* Now wait INDEFINITELY on the set of sockets! */ + readfd = portfd; + if (select(server + 1, &readfd, 0, 0, 0) <= 0) + continue; + if (!FD_ISSET(server, &readfd)) + continue; + fromAddrLen = sizeof(fromAddr); + s = accept(server, (struct sockaddr *)&fromAddr, &fromAddrLen); + if (s < 0) + continue; + config->accepted_socket = s; + config->rmt_ip = ntohl(fromAddr.sin_addr.s_addr); +#if ENABLE_FEATURE_HTTPD_CGI || DEBUG + sprintf(config->rmt_ip_str, "%u.%u.%u.%u", + (unsigned char)(config->rmt_ip >> 24), + (unsigned char)(config->rmt_ip >> 16), + (unsigned char)(config->rmt_ip >> 8), + config->rmt_ip & 0xff); + config->port = ntohs(fromAddr.sin_port); +#if DEBUG + bb_error_msg("connection from IP=%s, port %u", + config->rmt_ip_str, config->port); +#endif +#endif /* FEATURE_HTTPD_CGI */ + + /* set the KEEPALIVE option to cull dead connections */ + on = 1; + setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on)); + + if (DEBUG || fork() == 0) { + /* child */ +#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP + /* protect reload config, may be confuse checking */ + signal(SIGHUP, SIG_IGN); +#endif + handleIncoming(); + if (!DEBUG) + exit(0); + } + close(s); + } /* while (1) */ + return 0; +} + +/* from inetd */ +static int miniHttpd_inetd(void) +{ + struct sockaddr_in fromAddrLen; + socklen_t sinlen = sizeof(struct sockaddr_in); + + getpeername(0, (struct sockaddr *)&fromAddrLen, &sinlen); + config->rmt_ip = ntohl(fromAddrLen.sin_addr.s_addr); +#if ENABLE_FEATURE_HTTPD_CGI + sprintf(config->rmt_ip_str, "%u.%u.%u.%u", + (unsigned char)(config->rmt_ip >> 24), + (unsigned char)(config->rmt_ip >> 16), + (unsigned char)(config->rmt_ip >> 8), + config->rmt_ip & 0xff); +#endif + config->port = ntohs(fromAddrLen.sin_port); + handleIncoming(); + return 0; +} + +#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP +static void sighup_handler(int sig) +{ + /* set and reset */ + struct sigaction sa; + + parse_conf(default_path_httpd_conf, sig == SIGHUP ? SIGNALED_PARSE : FIRST_PARSE); + sa.sa_handler = sighup_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGHUP, &sa, NULL); +} +#endif + +enum { + c_opt_config_file = 0, + d_opt_decode_url, + h_opt_home_httpd, + USE_FEATURE_HTTPD_ENCODE_URL_STR(e_opt_encode_url,) + USE_FEATURE_HTTPD_BASIC_AUTH( r_opt_realm ,) + USE_FEATURE_HTTPD_AUTH_MD5( m_opt_md5 ,) + USE_FEATURE_HTTPD_SETUID( u_opt_setuid ,) + p_opt_port , + p_opt_inetd , + p_opt_foreground, + OPT_CONFIG_FILE = 1 << c_opt_config_file, + OPT_DECODE_URL = 1 << d_opt_decode_url, + OPT_HOME_HTTPD = 1 << h_opt_home_httpd, + OPT_ENCODE_URL = USE_FEATURE_HTTPD_ENCODE_URL_STR((1 << e_opt_encode_url)) + 0, + OPT_REALM = USE_FEATURE_HTTPD_BASIC_AUTH( (1 << r_opt_realm )) + 0, + OPT_MD5 = USE_FEATURE_HTTPD_AUTH_MD5( (1 << m_opt_md5 )) + 0, + OPT_SETUID = USE_FEATURE_HTTPD_SETUID( (1 << u_opt_setuid )) + 0, + OPT_PORT = 1 << p_opt_port, + OPT_INETD = 1 << p_opt_inetd, + OPT_FOREGROUND = 1 << p_opt_foreground, +}; + +static const char httpd_opts[] = "c:d:h:" + USE_FEATURE_HTTPD_ENCODE_URL_STR("e:") + USE_FEATURE_HTTPD_BASIC_AUTH("r:") + USE_FEATURE_HTTPD_AUTH_MD5("m:") + USE_FEATURE_HTTPD_SETUID("u:") + "p:if"; + + +int httpd_main(int argc, char *argv[]) +{ + unsigned opt; + const char *home_httpd = home; + char *url_for_decode; + USE_FEATURE_HTTPD_ENCODE_URL_STR(const char *url_for_encode;) + const char *s_port; + USE_FEATURE_HTTPD_SETUID(const char *s_ugid = NULL;) + USE_FEATURE_HTTPD_SETUID(struct bb_uidgid_t ugid;) + USE_FEATURE_HTTPD_AUTH_MD5(const char *pass;) + +#if ENABLE_LOCALE_SUPPORT + /* Undo busybox.c: we want to speak English in http (dates etc) */ + setlocale(LC_TIME, "C"); +#endif + + config = xzalloc(sizeof(*config)); +#if ENABLE_FEATURE_HTTPD_BASIC_AUTH + config->realm = "Web Server Authentication"; +#endif + config->port = 80; + config->ContentLength = -1; + + opt = getopt32(argc, argv, httpd_opts, + &(config->configFile), &url_for_decode, &home_httpd + USE_FEATURE_HTTPD_ENCODE_URL_STR(, &url_for_encode) + USE_FEATURE_HTTPD_BASIC_AUTH(, &(config->realm)) + USE_FEATURE_HTTPD_AUTH_MD5(, &pass) + USE_FEATURE_HTTPD_SETUID(, &s_ugid) + , &s_port + ); + if (opt & OPT_DECODE_URL) { + printf("%s", decodeString(url_for_decode, 1)); + return 0; + } +#if ENABLE_FEATURE_HTTPD_ENCODE_URL_STR + if (opt & OPT_ENCODE_URL) { + printf("%s", encodeString(url_for_encode)); + return 0; + } +#endif +#if ENABLE_FEATURE_HTTPD_AUTH_MD5 + if (opt & OPT_MD5) { + puts(pw_encrypt(pass, "$1$")); + return 0; + } +#endif + if (opt & OPT_PORT) + config->port = xatou16(s_port); + +#if ENABLE_FEATURE_HTTPD_SETUID + if (opt & OPT_SETUID) { + char *e; + // FIXME: what the default group should be? + ugid.gid = -1; + ugid.uid = bb_strtoul(s_ugid, &e, 0); + if (*e == ':') { + e++; + ugid.gid = bb_strtoul(e, NULL, 0); + } + if (errno) { + /* not integer */ + if (!uidgid_get(&ugid, s_ugid)) + bb_error_msg_and_die("unrecognized user[:group] " + "name '%s'", s_ugid); + } + } +#endif + + xchdir(home_httpd); + if (!(opt & OPT_INETD)) { + config->server_socket = openServer(); +#if ENABLE_FEATURE_HTTPD_SETUID + /* drop privileges */ + if (opt & OPT_SETUID) { + if (ugid.gid != (gid_t)-1) { + if (setgroups(1, &ugid.gid) == -1) + bb_perror_msg_and_die("setgroups"); + xsetgid(ugid.gid); + } + xsetuid(ugid.uid); + } +#endif + } + +#if ENABLE_FEATURE_HTTPD_CGI + { + char *p = getenv("PATH"); + p = xstrdup(p); /* if gets NULL, returns NULL */ + clearenv(); + if (p) + setenv1("PATH", p); + if (!(opt & OPT_INETD)) + setenv_long("SERVER_PORT", config->port); + } +#endif + +#if ENABLE_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP + sighup_handler(0); +#else + parse_conf(default_path_httpd_conf, FIRST_PARSE); +#endif + + if (opt & OPT_INETD) + return miniHttpd_inetd(); + + if (!(opt & OPT_FOREGROUND)) + xdaemon(1, 0); /* don't change current directory */ + return miniHttpd(config->server_socket); +} diff --git a/networking/httpd_index_cgi_example b/networking/httpd_index_cgi_example new file mode 100644 index 000000000..31e768c70 --- /dev/null +++ b/networking/httpd_index_cgi_example @@ -0,0 +1,55 @@ +#!/bin/sh +# This CGI creates directory index. +# Put it into cgi-bin/index.cgi and chmod 0755. +# +# Problems: +# * Unsafe wrt weird filenames with <>"'& etc... +# * Not efficient: calls stat (program, not syscall) for each file +# * Probably requires bash +# +# If you want speed and safety, you need to code it in C + +# Must start with '/' +test "${QUERY_STRING:0:1}" = "/" || exit 1 +# /../ is not allowed +test "${QUERY_STRING%/../*}" = "$QUERY_STRING" || exit 1 +test "${QUERY_STRING%/..}" = "$QUERY_STRING" || exit 1 + +# Outta cgi-bin... +cd .. 2>/dev/null || exit 1 +# Strip leading '/', go to target dir +cd "${QUERY_STRING:1}" 2>/dev/null || exit 1 + +f=`dirname "$QUERY_STRING"` +test "$f" = "/" && f="" + +# Pipe thru dd (need to write header as single write(), +# or else httpd doesn't see "Content-type: text/html" +# in first read() and decides that it is not html) +{ +printf "%s" \ +$'HTTP/1.0 200 OK\r\n'\ +$'Content-type: text/html\r\n\r\n'\ +"Index of $QUERY_STRING"$'\r\n'\ +"

    Index of $QUERY_STRING

    "$'\r\n'\
    +$'\r\n'\
    +$'\r\n'\
    +$'
    NameLast modifiedSize\r\n'\ +\ +"
    .."$'\r\n' + +IFS='#' +for f in *; do + # Guard against empty dirs... + test -e "$f" && \ + stat -c "%F#%s#%z" "$f" | { + read type size cdt junk + dir='' + test "$type" = "directory" && dir='/' + cdt="${cdt//.*}" # no fractional seconds + cdt="${cdt// / }" # prevent wrapping around space + printf "%s" "
    $f$cdt$size"$'\r\n' + } +done +printf "

    "$'\r\n' +} | dd bs=4k diff --git a/networking/ifconfig.c b/networking/ifconfig.c new file mode 100644 index 000000000..ae5b468ce --- /dev/null +++ b/networking/ifconfig.c @@ -0,0 +1,548 @@ +/* vi: set sw=4 ts=4: */ +/* ifconfig + * + * Similar to the standard Unix ifconfig, but with only the necessary + * parts for AF_INET, and without any printing of if info (for now). + * + * Bjorn Wesen, Axis Communications AB + * + * + * Authors of the original ifconfig was: + * Fred N. van Kempen, + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* + * Heavily modified by Manuel Novoa III Mar 6, 2001 + * + * From initial port to busybox, removed most of the redundancy by + * converting to a table-driven approach. Added several (optional) + * args missing from initial port. + * + * Still missing: media, tunnel. + * + * 2002-04-20 + * IPV6 support added by Bart Visscher + */ + +#include +#include +#include +#if __GLIBC__ >=2 && __GLIBC_MINOR__ >= 1 +#include +#include +#else +#include +#include +#endif +#include "inet_common.h" +#include "busybox.h" + +#if ENABLE_FEATURE_IFCONFIG_SLIP +# include +#endif + +/* I don't know if this is needed for busybox or not. Anyone? */ +#define QUESTIONABLE_ALIAS_CASE + + +/* Defines for glibc2.0 users. */ +#ifndef SIOCSIFTXQLEN +# define SIOCSIFTXQLEN 0x8943 +# define SIOCGIFTXQLEN 0x8942 +#endif + +/* ifr_qlen is ifru_ivalue, but it isn't present in 2.0 kernel headers */ +#ifndef ifr_qlen +# define ifr_qlen ifr_ifru.ifru_mtu +#endif + +#ifndef IFF_DYNAMIC +# define IFF_DYNAMIC 0x8000 /* dialup device with changing addresses */ +#endif + +#if ENABLE_FEATURE_IPV6 +struct in6_ifreq { + struct in6_addr ifr6_addr; + uint32_t ifr6_prefixlen; + int ifr6_ifindex; +}; +#endif + +/* + * Here are the bit masks for the "flags" member of struct options below. + * N_ signifies no arg prefix; M_ signifies arg prefixed by '-'. + * CLR clears the flag; SET sets the flag; ARG signifies (optional) arg. + */ +#define N_CLR 0x01 +#define M_CLR 0x02 +#define N_SET 0x04 +#define M_SET 0x08 +#define N_ARG 0x10 +#define M_ARG 0x20 + +#define M_MASK (M_CLR | M_SET | M_ARG) +#define N_MASK (N_CLR | N_SET | N_ARG) +#define SET_MASK (N_SET | M_SET) +#define CLR_MASK (N_CLR | M_CLR) +#define SET_CLR_MASK (SET_MASK | CLR_MASK) +#define ARG_MASK (M_ARG | N_ARG) + +/* + * Here are the bit masks for the "arg_flags" member of struct options below. + */ + +/* + * cast type: + * 00 int + * 01 char * + * 02 HOST_COPY in_ether + * 03 HOST_COPY INET_resolve + */ +#define A_CAST_TYPE 0x03 +/* + * map type: + * 00 not a map type (mem_start, io_addr, irq) + * 04 memstart (unsigned long) + * 08 io_addr (unsigned short) + * 0C irq (unsigned char) + */ +#define A_MAP_TYPE 0x0C +#define A_ARG_REQ 0x10 /* Set if an arg is required. */ +#define A_NETMASK 0x20 /* Set if netmask (check for multiple sets). */ +#define A_SET_AFTER 0x40 /* Set a flag at the end. */ +#define A_COLON_CHK 0x80 /* Is this needed? See below. */ +#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS +#define A_HOSTNAME 0x100 /* Set if it is ip addr. */ +#define A_BROADCAST 0x200 /* Set if it is broadcast addr. */ +#else +#define A_HOSTNAME 0 +#define A_BROADCAST 0 +#endif + +/* + * These defines are for dealing with the A_CAST_TYPE field. + */ +#define A_CAST_CHAR_PTR 0x01 +#define A_CAST_RESOLVE 0x01 +#define A_CAST_HOST_COPY 0x02 +#define A_CAST_HOST_COPY_IN_ETHER A_CAST_HOST_COPY +#define A_CAST_HOST_COPY_RESOLVE (A_CAST_HOST_COPY | A_CAST_RESOLVE) + +/* + * These defines are for dealing with the A_MAP_TYPE field. + */ +#define A_MAP_ULONG 0x04 /* memstart */ +#define A_MAP_USHORT 0x08 /* io_addr */ +#define A_MAP_UCHAR 0x0C /* irq */ + +/* + * Define the bit masks signifying which operations to perform for each arg. + */ + +#define ARG_METRIC (A_ARG_REQ /*| A_CAST_INT*/) +#define ARG_MTU (A_ARG_REQ /*| A_CAST_INT*/) +#define ARG_TXQUEUELEN (A_ARG_REQ /*| A_CAST_INT*/) +#define ARG_MEM_START (A_ARG_REQ | A_MAP_ULONG) +#define ARG_IO_ADDR (A_ARG_REQ | A_MAP_ULONG) +#define ARG_IRQ (A_ARG_REQ | A_MAP_UCHAR) +#define ARG_DSTADDR (A_ARG_REQ | A_CAST_HOST_COPY_RESOLVE) +#define ARG_NETMASK (A_ARG_REQ | A_CAST_HOST_COPY_RESOLVE | A_NETMASK) +#define ARG_BROADCAST (A_ARG_REQ | A_CAST_HOST_COPY_RESOLVE | A_SET_AFTER | A_BROADCAST) +#define ARG_HW (A_ARG_REQ | A_CAST_HOST_COPY_IN_ETHER) +#define ARG_POINTOPOINT (A_ARG_REQ | A_CAST_HOST_COPY_RESOLVE | A_SET_AFTER) +#define ARG_KEEPALIVE (A_ARG_REQ | A_CAST_CHAR_PTR) +#define ARG_OUTFILL (A_ARG_REQ | A_CAST_CHAR_PTR) +#define ARG_HOSTNAME (A_CAST_HOST_COPY_RESOLVE | A_SET_AFTER | A_COLON_CHK | A_HOSTNAME) +#define ARG_ADD_DEL (A_CAST_HOST_COPY_RESOLVE | A_SET_AFTER) + + +/* + * Set up the tables. Warning! They must have corresponding order! + */ + +struct arg1opt { + const char *name; + int selector; + unsigned short ifr_offset; +}; + +struct options { + const char *name; +#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS + const unsigned int flags:6; + const unsigned int arg_flags:10; +#else + const unsigned char flags; + const unsigned char arg_flags; +#endif + const unsigned short selector; +}; + +#define ifreq_offsetof(x) offsetof(struct ifreq, x) + +static const struct arg1opt Arg1Opt[] = { + {"SIOCSIFMETRIC", SIOCSIFMETRIC, ifreq_offsetof(ifr_metric)}, + {"SIOCSIFMTU", SIOCSIFMTU, ifreq_offsetof(ifr_mtu)}, + {"SIOCSIFTXQLEN", SIOCSIFTXQLEN, ifreq_offsetof(ifr_qlen)}, + {"SIOCSIFDSTADDR", SIOCSIFDSTADDR, ifreq_offsetof(ifr_dstaddr)}, + {"SIOCSIFNETMASK", SIOCSIFNETMASK, ifreq_offsetof(ifr_netmask)}, + {"SIOCSIFBRDADDR", SIOCSIFBRDADDR, ifreq_offsetof(ifr_broadaddr)}, +#if ENABLE_FEATURE_IFCONFIG_HW + {"SIOCSIFHWADDR", SIOCSIFHWADDR, ifreq_offsetof(ifr_hwaddr)}, +#endif + {"SIOCSIFDSTADDR", SIOCSIFDSTADDR, ifreq_offsetof(ifr_dstaddr)}, +#ifdef SIOCSKEEPALIVE + {"SIOCSKEEPALIVE", SIOCSKEEPALIVE, ifreq_offsetof(ifr_data)}, +#endif +#ifdef SIOCSOUTFILL + {"SIOCSOUTFILL", SIOCSOUTFILL, ifreq_offsetof(ifr_data)}, +#endif +#if ENABLE_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ + {"SIOCSIFMAP", SIOCSIFMAP, ifreq_offsetof(ifr_map.mem_start)}, + {"SIOCSIFMAP", SIOCSIFMAP, ifreq_offsetof(ifr_map.base_addr)}, + {"SIOCSIFMAP", SIOCSIFMAP, ifreq_offsetof(ifr_map.irq)}, +#endif + /* Last entry if for unmatched (possibly hostname) arg. */ +#if ENABLE_FEATURE_IPV6 + {"SIOCSIFADDR", SIOCSIFADDR, ifreq_offsetof(ifr_addr)}, /* IPv6 version ignores the offset */ + {"SIOCDIFADDR", SIOCDIFADDR, ifreq_offsetof(ifr_addr)}, /* IPv6 version ignores the offset */ +#endif + {"SIOCSIFADDR", SIOCSIFADDR, ifreq_offsetof(ifr_addr)}, +}; + +static const struct options OptArray[] = { + {"metric", N_ARG, ARG_METRIC, 0}, + {"mtu", N_ARG, ARG_MTU, 0}, + {"txqueuelen", N_ARG, ARG_TXQUEUELEN, 0}, + {"dstaddr", N_ARG, ARG_DSTADDR, 0}, + {"netmask", N_ARG, ARG_NETMASK, 0}, + {"broadcast", N_ARG | M_CLR, ARG_BROADCAST, IFF_BROADCAST}, +#if ENABLE_FEATURE_IFCONFIG_HW + {"hw", N_ARG, ARG_HW, 0}, +#endif + {"pointopoint", N_ARG | M_CLR, ARG_POINTOPOINT, IFF_POINTOPOINT}, +#ifdef SIOCSKEEPALIVE + {"keepalive", N_ARG, ARG_KEEPALIVE, 0}, +#endif +#ifdef SIOCSOUTFILL + {"outfill", N_ARG, ARG_OUTFILL, 0}, +#endif +#if ENABLE_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ + {"mem_start", N_ARG, ARG_MEM_START, 0}, + {"io_addr", N_ARG, ARG_IO_ADDR, 0}, + {"irq", N_ARG, ARG_IRQ, 0}, +#endif +#if ENABLE_FEATURE_IPV6 + {"add", N_ARG, ARG_ADD_DEL, 0}, + {"del", N_ARG, ARG_ADD_DEL, 0}, +#endif + {"arp", N_CLR | M_SET, 0, IFF_NOARP}, + {"trailers", N_CLR | M_SET, 0, IFF_NOTRAILERS}, + {"promisc", N_SET | M_CLR, 0, IFF_PROMISC}, + {"multicast", N_SET | M_CLR, 0, IFF_MULTICAST}, + {"allmulti", N_SET | M_CLR, 0, IFF_ALLMULTI}, + {"dynamic", N_SET | M_CLR, 0, IFF_DYNAMIC}, + {"up", N_SET, 0, (IFF_UP | IFF_RUNNING)}, + {"down", N_CLR, 0, IFF_UP}, + {NULL, 0, ARG_HOSTNAME, (IFF_UP | IFF_RUNNING)} +}; + +/* + * A couple of prototypes. + */ + +#if ENABLE_FEATURE_IFCONFIG_HW +static int in_ether(const char *bufp, struct sockaddr *sap); +#endif + +#if ENABLE_FEATURE_IFCONFIG_STATUS +extern int interface_opt_a; +extern int display_interfaces(char *ifname); +#endif + +/* + * Our main function. + */ + +int ifconfig_main(int argc, char **argv) +{ + struct ifreq ifr; + struct sockaddr_in sai; +#if ENABLE_FEATURE_IPV6 + struct sockaddr_in6 sai6; +#endif +#if ENABLE_FEATURE_IFCONFIG_HW + struct sockaddr sa; +#endif + const struct arg1opt *a1op; + const struct options *op; + int sockfd; /* socket fd we use to manipulate stuff with */ + int selector; +#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS + unsigned int mask; + unsigned int did_flags; + unsigned int sai_hostname, sai_netmask; +#else + unsigned char mask; + unsigned char did_flags; +#endif + char *p; + /*char host[128];*/ + const char *host = NULL; /* make gcc happy */ + + did_flags = 0; +#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS + sai_hostname = 0; + sai_netmask = 0; +#endif + + /* skip argv[0] */ + ++argv; + --argc; + +#if ENABLE_FEATURE_IFCONFIG_STATUS + if (argc > 0 && (argv[0][0] == '-' && argv[0][1] == 'a' && !argv[0][2])) { + interface_opt_a = 1; + --argc; + ++argv; + } +#endif + + if (argc <= 1) { +#if ENABLE_FEATURE_IFCONFIG_STATUS + return display_interfaces(argc ? *argv : NULL); +#else + bb_error_msg_and_die("no support for status display"); +#endif + } + + /* Create a channel to the NET kernel. */ + sockfd = xsocket(AF_INET, SOCK_DGRAM, 0); + + /* get interface name */ + safe_strncpy(ifr.ifr_name, *argv, IFNAMSIZ); + + /* Process the remaining arguments. */ + while (*++argv != (char *) NULL) { + p = *argv; + mask = N_MASK; + if (*p == '-') { /* If the arg starts with '-'... */ + ++p; /* advance past it and */ + mask = M_MASK; /* set the appropriate mask. */ + } + for (op = OptArray; op->name; op++) { /* Find table entry. */ + if (strcmp(p, op->name) == 0) { /* If name matches... */ + mask &= op->flags; + if (mask) /* set the mask and go. */ + goto FOUND_ARG; + /* If we get here, there was a valid arg with an */ + /* invalid '-' prefix. */ + bb_error_msg_and_die("bad: '%s'", p-1); + } + } + + /* We fell through, so treat as possible hostname. */ + a1op = Arg1Opt + (sizeof(Arg1Opt) / sizeof(Arg1Opt[0])) - 1; + mask = op->arg_flags; + goto HOSTNAME; + + FOUND_ARG: + if (mask & ARG_MASK) { + mask = op->arg_flags; + a1op = Arg1Opt + (op - OptArray); + if (mask & A_NETMASK & did_flags) + bb_show_usage(); + if (*++argv == NULL) { + if (mask & A_ARG_REQ) + bb_show_usage(); + --argv; + mask &= A_SET_AFTER; /* just for broadcast */ + } else { /* got an arg so process it */ + HOSTNAME: + did_flags |= (mask & (A_NETMASK|A_HOSTNAME)); + if (mask & A_CAST_HOST_COPY) { +#if ENABLE_FEATURE_IFCONFIG_HW + if (mask & A_CAST_RESOLVE) { +#endif +#if ENABLE_FEATURE_IPV6 + char *prefix; + int prefix_len = 0; +#endif + /*safe_strncpy(host, *argv, (sizeof host));*/ + host = *argv; +#if ENABLE_FEATURE_IPV6 + prefix = strchr(host, '/'); + if (prefix) { + prefix_len = xatou_range(prefix + 1, 0, 128); + *prefix = '\0'; + } +#endif + sai.sin_family = AF_INET; + sai.sin_port = 0; + if (!strcmp(host, bb_str_default)) { + /* Default is special, meaning 0.0.0.0. */ + sai.sin_addr.s_addr = INADDR_ANY; + } +#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS + else if ((host[0] == '+' && !host[1]) && (mask & A_BROADCAST) + && (did_flags & (A_NETMASK|A_HOSTNAME)) == (A_NETMASK|A_HOSTNAME) + ) { + /* + is special, meaning broadcast is derived. */ + sai.sin_addr.s_addr = (~sai_netmask) | (sai_hostname & sai_netmask); + } +#endif +#if ENABLE_FEATURE_IPV6 + else if (inet_pton(AF_INET6, host, &sai6.sin6_addr) > 0) { + int sockfd6; + struct in6_ifreq ifr6; + + memcpy((char *) &ifr6.ifr6_addr, + (char *) &sai6.sin6_addr, + sizeof(struct in6_addr)); + + /* Create a channel to the NET kernel. */ + sockfd6 = xsocket(AF_INET6, SOCK_DGRAM, 0); + if (ioctl(sockfd6, SIOGIFINDEX, &ifr) < 0) + bb_perror_msg_and_die("SIOGIFINDEX"); + ifr6.ifr6_ifindex = ifr.ifr_ifindex; + ifr6.ifr6_prefixlen = prefix_len; + if (ioctl(sockfd6, a1op->selector, &ifr6) < 0) + bb_perror_msg_and_die(a1op->name); + continue; + } +#endif + else if (inet_aton(host, &sai.sin_addr) == 0) { + /* It's not a dotted quad. */ + struct hostent *hp = xgethostbyname(host); + memcpy((char *) &sai.sin_addr, (char *) hp->h_addr_list[0], + sizeof(struct in_addr)); + } +#if ENABLE_FEATURE_IFCONFIG_BROADCAST_PLUS + if (mask & A_HOSTNAME) + sai_hostname = sai.sin_addr.s_addr; + if (mask & A_NETMASK) + sai_netmask = sai.sin_addr.s_addr; +#endif + p = (char *) &sai; +#if ENABLE_FEATURE_IFCONFIG_HW + } else { /* A_CAST_HOST_COPY_IN_ETHER */ + /* This is the "hw" arg case. */ + if (strcmp("ether", *argv) || !*++argv) + bb_show_usage(); + /*safe_strncpy(host, *argv, sizeof(host));*/ + host = *argv; + if (in_ether(host, &sa)) + bb_error_msg_and_die("invalid hw-addr %s", host); + p = (char *) &sa; + } +#endif + memcpy( (((char *)&ifr) + a1op->ifr_offset), + p, sizeof(struct sockaddr)); + } else { + /* FIXME: error check?? */ + unsigned long i = strtoul(*argv, NULL, 0); + p = ((char *)&ifr) + a1op->ifr_offset; +#if ENABLE_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ + if (mask & A_MAP_TYPE) { + if (ioctl(sockfd, SIOCGIFMAP, &ifr) < 0) + bb_perror_msg_and_die("SIOCGIFMAP"); + if ((mask & A_MAP_UCHAR) == A_MAP_UCHAR) + *((unsigned char *) p) = i; + else if (mask & A_MAP_USHORT) + *((unsigned short *) p) = i; + else + *((unsigned long *) p) = i; + } else +#endif + if (mask & A_CAST_CHAR_PTR) + *((caddr_t *) p) = (caddr_t) i; + else /* A_CAST_INT */ + *((int *) p) = i; + } + + if (ioctl(sockfd, a1op->selector, &ifr) < 0) + bb_perror_msg_and_die(a1op->name); +#ifdef QUESTIONABLE_ALIAS_CASE + if (mask & A_COLON_CHK) { + /* + * Don't do the set_flag() if the address is an alias with + * a '-' at the end, since it's deleted already! - Roman + * + * Should really use regex.h here, not sure though how well + * it'll go with the cross-platform support etc. + */ + char *ptr; + short int found_colon = 0; + for (ptr = ifr.ifr_name; *ptr; ptr++) + if (*ptr == ':') + found_colon++; + if (found_colon && ptr[-1] == '-') + continue; + } +#endif + } + if (!(mask & A_SET_AFTER)) + continue; + mask = N_SET; + } + + if (ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0) + bb_perror_msg_and_die("SIOCGIFFLAGS"); + selector = op->selector; + if (mask & SET_MASK) + ifr.ifr_flags |= selector; + else + ifr.ifr_flags &= ~selector; + if (ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0) + bb_perror_msg_and_die("SIOCSIFFLAGS"); + } /* while() */ + + if (ENABLE_FEATURE_CLEAN_UP) + close(sockfd); + return 0; +} + +#if ENABLE_FEATURE_IFCONFIG_HW +/* Input an Ethernet address and convert to binary. */ +static int in_ether(const char *bufp, struct sockaddr *sap) +{ + char *ptr; + int i, j; + unsigned char val; + unsigned char c; + + sap->sa_family = ARPHRD_ETHER; + ptr = sap->sa_data; + + i = 0; + do { + j = val = 0; + + /* We might get a semicolon here - not required. */ + if (i && (*bufp == ':')) { + bufp++; + } + + do { + c = *bufp; + if (((unsigned char)(c - '0')) <= 9) { + c -= '0'; + } else if (((unsigned char)((c|0x20) - 'a')) <= 5) { + c = (c|0x20) - ('a'-10); + } else if (j && (c == ':' || c == 0)) { + break; + } else { + return -1; + } + ++bufp; + val <<= 4; + val += c; + } while (++j < 2); + *ptr++ = val; + } while (++i < ETH_ALEN); + + return *bufp; /* Error if we don't end at end of string. */ +} +#endif diff --git a/networking/ifupdown.c b/networking/ifupdown.c new file mode 100644 index 000000000..76ff2a830 --- /dev/null +++ b/networking/ifupdown.c @@ -0,0 +1,1270 @@ +/* vi: set sw=4 ts=4: */ +/* + * ifupdown for busybox + * Copyright (c) 2002 Glenn McGrath + * Copyright (c) 2003-2004 Erik Andersen + * + * Based on ifupdown v 0.6.4 by Anthony Towns + * Copyright (c) 1999 Anthony Towns + * + * Changes to upstream version + * Remove checks for kernel version, assume kernel version 2.2.0 or better. + * Lines in the interfaces file cannot wrap. + * To adhere to the FHS, the default state file is /var/run/ifstate. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include +#include +#include + +#define MAX_OPT_DEPTH 10 +#define EUNBALBRACK 10001 +#define EUNDEFVAR 10002 +#define EUNBALPER 10000 + +#ifdef CONFIG_FEATURE_IFUPDOWN_MAPPING +#define MAX_INTERFACE_LENGTH 10 +#endif + +#define debug_noise(fmt, args...) + +/* Forward declaration */ +struct interface_defn_t; + +typedef int execfn(char *command); + +struct method_t +{ + char *name; + int (*up)(struct interface_defn_t *ifd, execfn *e); + int (*down)(struct interface_defn_t *ifd, execfn *e); +}; + +struct address_family_t +{ + char *name; + int n_methods; + const struct method_t *method; +}; + +struct mapping_defn_t +{ + struct mapping_defn_t *next; + + int max_matches; + int n_matches; + char **match; + + char *script; + + int max_mappings; + int n_mappings; + char **mapping; +}; + +struct variable_t +{ + char *name; + char *value; +}; + +struct interface_defn_t +{ + const struct address_family_t *address_family; + const struct method_t *method; + + char *iface; + int max_options; + int n_options; + struct variable_t *option; +}; + +struct interfaces_file_t +{ + llist_t *autointerfaces; + llist_t *ifaces; + struct mapping_defn_t *mappings; +}; + +#define OPTION_STR "anvf" USE_FEATURE_IFUPDOWN_MAPPING("m") "i:" +enum { + OPT_do_all = 0x1, + OPT_no_act = 0x2, + OPT_verbose = 0x4, + OPT_force = 0x8, + OPT_no_mappings = 0x10, +}; +#define DO_ALL (option_mask32 & OPT_do_all) +#define NO_ACT (option_mask32 & OPT_no_act) +#define VERBOSE (option_mask32 & OPT_verbose) +#define FORCE (option_mask32 & OPT_force) +#define NO_MAPPINGS (option_mask32 & OPT_no_mappings) + +static char **__myenviron; + +static char *startup_PATH; + +#if ENABLE_FEATURE_IFUPDOWN_IPV4 || ENABLE_FEATURE_IFUPDOWN_IPV6 + +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + +static unsigned count_bits(unsigned a) +{ + unsigned result; + result = (a & 0x55) + ((a >> 1) & 0x55); + result = (result & 0x33) + ((result >> 2) & 0x33); + return (result & 0x0F) + ((result >> 4) & 0x0F); +} + +static int count_netmask_bits(char *dotted_quad) +{ + unsigned result, a, b, c, d; + /* Found a netmask... Check if it is dotted quad */ + if (sscanf(dotted_quad, "%u.%u.%u.%u", &a, &b, &c, &d) != 4) + return -1; + // FIXME: will be confused by e.g. 255.0.255.0 + result = count_bits(a); + result += count_bits(b); + result += count_bits(c); + result += count_bits(d); + return (int)result; +} +#endif + +static void addstr(char **bufp, const char *str, size_t str_length) +{ + /* xasprintf trick will be smaller, but we are often + * called with str_length == 1 - don't want to have + * THAT much of malloc/freeing! */ + char *buf = *bufp; + int len = (buf ? strlen(buf) : 0); + str_length++; + buf = xrealloc(buf, len + str_length); + /* copies at most str_length-1 chars! */ + safe_strncpy(buf + len, str, str_length); + *bufp = buf; +} + +static int strncmpz(const char *l, const char *r, size_t llen) +{ + int i = strncmp(l, r, llen); + + if (i == 0) + return -r[llen]; + return i; +} + +static char *get_var(const char *id, size_t idlen, struct interface_defn_t *ifd) +{ + int i; + + if (strncmpz(id, "iface", idlen) == 0) { + char *result; + static char label_buf[20]; + safe_strncpy(label_buf, ifd->iface, sizeof(label_buf)); + result = strchr(label_buf, ':'); + if (result) { + *result = '\0'; + } + return label_buf; + } + if (strncmpz(id, "label", idlen) == 0) { + return ifd->iface; + } + for (i = 0; i < ifd->n_options; i++) { + if (strncmpz(id, ifd->option[i].name, idlen) == 0) { + return ifd->option[i].value; + } + } + return NULL; +} + +static char *parse(const char *command, struct interface_defn_t *ifd) +{ + char *result = NULL; + size_t old_pos[MAX_OPT_DEPTH] = { 0 }; + int okay[MAX_OPT_DEPTH] = { 1 }; + int opt_depth = 1; + + while (*command) { + switch (*command) { + default: + addstr(&result, command, 1); + command++; + break; + case '\\': + if (command[1]) { + addstr(&result, command + 1, 1); + command += 2; + } else { + addstr(&result, command, 1); + command++; + } + break; + case '[': + if (command[1] == '[' && opt_depth < MAX_OPT_DEPTH) { + old_pos[opt_depth] = strlen(result); + okay[opt_depth] = 1; + opt_depth++; + command += 2; + } else { + addstr(&result, "[", 1); + command++; + } + break; + case ']': + if (command[1] == ']' && opt_depth > 1) { + opt_depth--; + if (!okay[opt_depth]) { + result[old_pos[opt_depth]] = '\0'; + } + command += 2; + } else { + addstr(&result, "]", 1); + command++; + } + break; + case '%': + { + char *nextpercent; + char *varvalue; + + command++; + nextpercent = strchr(command, '%'); + if (!nextpercent) { + errno = EUNBALPER; + free(result); + return NULL; + } + + varvalue = get_var(command, nextpercent - command, ifd); + + if (varvalue) { + addstr(&result, varvalue, strlen(varvalue)); + } else { +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + /* Sigh... Add a special case for 'ip' to convert from + * dotted quad to bit count style netmasks. */ + if (strncmp(command, "bnmask", 6)==0) { + unsigned res; + varvalue = get_var("netmask", 7, ifd); + if (varvalue && (res = count_netmask_bits(varvalue)) > 0) { + const char *argument = utoa(res); + addstr(&result, argument, strlen(argument)); + command = nextpercent + 1; + break; + } + } +#endif + okay[opt_depth - 1] = 0; + } + + command = nextpercent + 1; + } + break; + } + } + + if (opt_depth > 1) { + errno = EUNBALBRACK; + free(result); + return NULL; + } + + if (!okay[0]) { + errno = EUNDEFVAR; + free(result); + return NULL; + } + + return result; +} + +/* execute() returns 1 for success and 0 for failure */ +static int execute(const char *command, struct interface_defn_t *ifd, execfn *exec) +{ + char *out; + int ret; + + out = parse(command, ifd); + if (!out) { + return 0; + } + ret = (*exec)(out); + + free(out); + if (ret != 1) { + return 0; + } + return 1; +} +#endif + +#ifdef CONFIG_FEATURE_IFUPDOWN_IPV6 +static int loopback_up6(struct interface_defn_t *ifd, execfn *exec) +{ +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + int result; + result = execute("ip addr add ::1 dev %iface%", ifd, exec); + result += execute("ip link set %iface% up", ifd, exec); + return ((result == 2) ? 2 : 0); +#else + return execute("ifconfig %iface% add ::1", ifd, exec); +#endif +} + +static int loopback_down6(struct interface_defn_t *ifd, execfn *exec) +{ +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + return execute("ip link set %iface% down", ifd, exec); +#else + return execute("ifconfig %iface% del ::1", ifd, exec); +#endif +} + +static int static_up6(struct interface_defn_t *ifd, execfn *exec) +{ + int result; +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + result = execute("ip addr add %address%/%netmask% dev %iface% [[label %label%]]", ifd, exec); + result += execute("ip link set [[mtu %mtu%]] [[address %hwaddress%]] %iface% up", ifd, exec); + result += execute("[[ ip route add ::/0 via %gateway% ]]", ifd, exec); +#else + result = execute("ifconfig %iface% [[media %media%]] [[hw %hwaddress%]] [[mtu %mtu%]] up", ifd, exec); + result += execute("ifconfig %iface% add %address%/%netmask%", ifd, exec); + result += execute("[[ route -A inet6 add ::/0 gw %gateway% ]]", ifd, exec); +#endif + return ((result == 3) ? 3 : 0); +} + +static int static_down6(struct interface_defn_t *ifd, execfn *exec) +{ +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + return execute("ip link set %iface% down", ifd, exec); +#else + return execute("ifconfig %iface% down", ifd, exec); +#endif +} + +#ifdef CONFIG_FEATURE_IFUPDOWN_IP +static int v4tunnel_up(struct interface_defn_t *ifd, execfn *exec) +{ + int result; + result = execute("ip tunnel add %iface% mode sit remote " + "%endpoint% [[local %local%]] [[ttl %ttl%]]", ifd, exec); + result += execute("ip link set %iface% up", ifd, exec); + result += execute("ip addr add %address%/%netmask% dev %iface%", ifd, exec); + result += execute("[[ ip route add ::/0 via %gateway% ]]", ifd, exec); + return ((result == 4) ? 4 : 0); +} + +static int v4tunnel_down(struct interface_defn_t * ifd, execfn * exec) +{ + return execute("ip tunnel del %iface%", ifd, exec); +} +#endif + +static const struct method_t methods6[] = { +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + { "v4tunnel", v4tunnel_up, v4tunnel_down, }, +#endif + { "static", static_up6, static_down6, }, + { "loopback", loopback_up6, loopback_down6, }, +}; + +static const struct address_family_t addr_inet6 = { + "inet6", + sizeof(methods6) / sizeof(struct method_t), + methods6 +}; +#endif /* CONFIG_FEATURE_IFUPDOWN_IPV6 */ + +#ifdef CONFIG_FEATURE_IFUPDOWN_IPV4 +static int loopback_up(struct interface_defn_t *ifd, execfn *exec) +{ +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + int result; + result = execute("ip addr add 127.0.0.1/8 dev %iface%", ifd, exec); + result += execute("ip link set %iface% up", ifd, exec); + return ((result == 2) ? 2 : 0); +#else + return execute("ifconfig %iface% 127.0.0.1 up", ifd, exec); +#endif +} + +static int loopback_down(struct interface_defn_t *ifd, execfn *exec) +{ +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + int result; + result = execute("ip addr flush dev %iface%", ifd, exec); + result += execute("ip link set %iface% down", ifd, exec); + return ((result == 2) ? 2 : 0); +#else + return execute("ifconfig %iface% 127.0.0.1 down", ifd, exec); +#endif +} + +static int static_up(struct interface_defn_t *ifd, execfn *exec) +{ + int result; +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + result = execute("ip addr add %address%/%bnmask% [[broadcast %broadcast%]] " + "dev %iface% [[peer %pointopoint%]] [[label %label%]]", ifd, exec); + result += execute("ip link set [[mtu %mtu%]] [[address %hwaddress%]] %iface% up", ifd, exec); + result += execute("[[ ip route add default via %gateway% dev %iface% ]]", ifd, exec); + return ((result == 3) ? 3 : 0); +#else + /* ifconfig said to set iface up before it processes hw %hwaddress%, + * which then of course fails. Thus we run two separate ifconfig */ + result = execute("ifconfig %iface% [[hw %hwaddress%]] [[media %media%]] [[mtu %mtu%]] up", + ifd, exec); + result += execute("ifconfig %iface% %address% netmask %netmask% " + "[[broadcast %broadcast%]] [[pointopoint %pointopoint%]] ", + ifd, exec); + result += execute("[[ route add default gw %gateway% %iface% ]]", ifd, exec); + return ((result == 3) ? 3 : 0); +#endif +} + +static int static_down(struct interface_defn_t *ifd, execfn *exec) +{ + int result; +#ifdef CONFIG_FEATURE_IFUPDOWN_IP + result = execute("ip addr flush dev %iface%", ifd, exec); + result += execute("ip link set %iface% down", ifd, exec); +#else + result = execute("[[ route del default gw %gateway% %iface% ]]", ifd, exec); + result += execute("ifconfig %iface% down", ifd, exec); +#endif + return ((result == 2) ? 2 : 0); +} + +#ifndef CONFIG_APP_UDHCPC +struct dhcp_client_t +{ + const char *name; + const char *startcmd; + const char *stopcmd; +}; + +static const struct dhcp_client_t ext_dhcp_clients[] = { + { "udhcpc", + "udhcpc -R -n -p /var/run/udhcpc.%iface%.pid -i %iface% [[-H %hostname%]] [[-c %clientid%]] [[-s %script%]]", + "kill -TERM `cat /var/run/udhcpc.%iface%.pid` 2>/dev/null", + }, + { "pump", + "pump -i %iface% [[-h %hostname%]] [[-l %leasehours%]]", + "pump -i %iface% -k", + }, + { "dhclient", + "dhclient -pf /var/run/dhclient.%iface%.pid %iface%", + "kill -9 `cat /var/run/dhclient.%iface%.pid` 2>/dev/null", + }, + { "dhcpcd", + "dhcpcd [[-h %hostname%]] [[-i %vendor%]] [[-I %clientid%]] [[-l %leasetime%]] %iface%", + "dhcpcd -k %iface%", + }, +}; +#endif + +static int dhcp_up(struct interface_defn_t *ifd, execfn *exec) +{ +#ifdef CONFIG_APP_UDHCPC + return execute("udhcpc -R -n -p /var/run/udhcpc.%iface%.pid " + "-i %iface% [[-H %hostname%]] [[-c %clientid%]] [[-s %script%]]", + ifd, exec); +#else + int i, nclients = sizeof(ext_dhcp_clients) / sizeof(ext_dhcp_clients[0]); + for (i = 0; i < nclients; i++) { + if (exists_execable(ext_dhcp_clients[i].name)) + return execute(ext_dhcp_clients[i].startcmd, ifd, exec); + } + bb_error_msg("no dhcp clients found"); + return 0; +#endif +} + +static int dhcp_down(struct interface_defn_t *ifd, execfn *exec) +{ +#ifdef CONFIG_APP_UDHCPC + return execute("kill -TERM " + "`cat /var/run/udhcpc.%iface%.pid` 2>/dev/null", ifd, exec); +#else + int i, nclients = sizeof(ext_dhcp_clients) / sizeof(ext_dhcp_clients[0]); + for (i = 0; i < nclients; i++) { + if (exists_execable(ext_dhcp_clients[i].name)) + return execute(ext_dhcp_clients[i].stopcmd, ifd, exec); + } + bb_error_msg("no dhcp clients found, using static interface shutdown"); + return static_down(ifd, exec); +#endif +} + +static int manual_up_down(struct interface_defn_t *ifd, execfn *exec) +{ + return 1; +} + +static int bootp_up(struct interface_defn_t *ifd, execfn *exec) +{ + return execute("bootpc [[--bootfile %bootfile%]] --dev %iface% " + "[[--server %server%]] [[--hwaddr %hwaddr%]] " + "--returniffail --serverbcast", ifd, exec); +} + +static int ppp_up(struct interface_defn_t *ifd, execfn *exec) +{ + return execute("pon [[%provider%]]", ifd, exec); +} + +static int ppp_down(struct interface_defn_t *ifd, execfn *exec) +{ + return execute("poff [[%provider%]]", ifd, exec); +} + +static int wvdial_up(struct interface_defn_t *ifd, execfn *exec) +{ + return execute("/sbin/start-stop-daemon --start -x /usr/bin/wvdial " + "-p /var/run/wvdial.%iface% -b -m -- [[ %provider% ]]", ifd, exec); +} + +static int wvdial_down(struct interface_defn_t *ifd, execfn *exec) +{ + return execute("/sbin/start-stop-daemon --stop -x /usr/bin/wvdial " + "-p /var/run/wvdial.%iface% -s 2", ifd, exec); +} + +static const struct method_t methods[] = { + { "manual", manual_up_down, manual_up_down, }, + { "wvdial", wvdial_up, wvdial_down, }, + { "ppp", ppp_up, ppp_down, }, + { "static", static_up, static_down, }, + { "bootp", bootp_up, static_down, }, + { "dhcp", dhcp_up, dhcp_down, }, + { "loopback", loopback_up, loopback_down, }, +}; + +static const struct address_family_t addr_inet = { + "inet", + sizeof(methods) / sizeof(struct method_t), + methods +}; + +#endif /* ifdef CONFIG_FEATURE_IFUPDOWN_IPV4 */ + +static char *next_word(char **buf) +{ + unsigned short length; + char *word; + + if (!buf || !*buf || !**buf) { + return NULL; + } + + /* Skip over leading whitespace */ + word = skip_whitespace(*buf); + + /* Skip over comments */ + if (*word == '#') { + return NULL; + } + + /* Find the length of this word */ + length = strcspn(word, " \t\n"); + if (length == 0) { + return NULL; + } + *buf = word + length; + /*DBU:[dave@cray.com] if we are already at EOL dont't increment beyond it */ + if (**buf) { + **buf = '\0'; + (*buf)++; + } + + return word; +} + +static const struct address_family_t *get_address_family(const struct address_family_t *const af[], char *name) +{ + int i; + + if (!name) + return NULL; + + for (i = 0; af[i]; i++) { + if (strcmp(af[i]->name, name) == 0) { + return af[i]; + } + } + return NULL; +} + +static const struct method_t *get_method(const struct address_family_t *af, char *name) +{ + int i; + + if (!name) + return NULL; + + for (i = 0; i < af->n_methods; i++) { + if (strcmp(af->method[i].name, name) == 0) { + return &af->method[i]; + } + } + return NULL; +} + +static const llist_t *find_list_string(const llist_t *list, const char *string) +{ + if (string == NULL) + return NULL; + + while (list) { + if (strcmp(list->data, string) == 0) { + return list; + } + list = list->link; + } + return NULL; +} + +static struct interfaces_file_t *read_interfaces(const char *filename) +{ +#ifdef CONFIG_FEATURE_IFUPDOWN_MAPPING + struct mapping_defn_t *currmap = NULL; +#endif + struct interface_defn_t *currif = NULL; + struct interfaces_file_t *defn; + FILE *f; + char *firstword; + char *buf; + + enum { NONE, IFACE, MAPPING } currently_processing = NONE; + + defn = xzalloc(sizeof(struct interfaces_file_t)); + + f = xfopen(filename, "r"); + + while ((buf = xmalloc_getline(f)) != NULL) { + char *buf_ptr = buf; + + firstword = next_word(&buf_ptr); + if (firstword == NULL) { + free(buf); + continue; /* blank line */ + } + + if (strcmp(firstword, "mapping") == 0) { +#ifdef CONFIG_FEATURE_IFUPDOWN_MAPPING + currmap = xzalloc(sizeof(struct mapping_defn_t)); + + while ((firstword = next_word(&buf_ptr)) != NULL) { + if (currmap->max_matches == currmap->n_matches) { + currmap->max_matches = currmap->max_matches * 2 + 1; + currmap->match = xrealloc(currmap->match, sizeof(currmap->match) * currmap->max_matches); + } + + currmap->match[currmap->n_matches++] = xstrdup(firstword); + } + currmap->max_mappings = 0; + currmap->n_mappings = 0; + currmap->mapping = NULL; + currmap->script = NULL; + { + struct mapping_defn_t **where = &defn->mappings; + while (*where != NULL) { + where = &(*where)->next; + } + *where = currmap; + currmap->next = NULL; + } + debug_noise("Added mapping\n"); +#endif + currently_processing = MAPPING; + } else if (strcmp(firstword, "iface") == 0) { + static const struct address_family_t *const addr_fams[] = { +#ifdef CONFIG_FEATURE_IFUPDOWN_IPV4 + &addr_inet, +#endif +#ifdef CONFIG_FEATURE_IFUPDOWN_IPV6 + &addr_inet6, +#endif + NULL + }; + + char *iface_name; + char *address_family_name; + char *method_name; + llist_t *iface_list; + + currif = xzalloc(sizeof(struct interface_defn_t)); + iface_name = next_word(&buf_ptr); + address_family_name = next_word(&buf_ptr); + method_name = next_word(&buf_ptr); + + if (buf_ptr == NULL) { + bb_error_msg("too few parameters for line \"%s\"", buf); + return NULL; + } + + /* ship any trailing whitespace */ + buf_ptr = skip_whitespace(buf_ptr); + + if (buf_ptr[0] != '\0') { + bb_error_msg("too many parameters \"%s\"", buf); + return NULL; + } + + currif->iface = xstrdup(iface_name); + + currif->address_family = get_address_family(addr_fams, address_family_name); + if (!currif->address_family) { + bb_error_msg("unknown address type \"%s\"", address_family_name); + return NULL; + } + + currif->method = get_method(currif->address_family, method_name); + if (!currif->method) { + bb_error_msg("unknown method \"%s\"", method_name); + return NULL; + } + + for (iface_list = defn->ifaces; iface_list; iface_list = iface_list->link) { + struct interface_defn_t *tmp = (struct interface_defn_t *) iface_list->data; + if ((strcmp(tmp->iface, currif->iface) == 0) && + (tmp->address_family == currif->address_family)) { + bb_error_msg("duplicate interface \"%s\"", tmp->iface); + return NULL; + } + } + llist_add_to_end(&(defn->ifaces), (char*)currif); + + debug_noise("iface %s %s %s\n", currif->iface, address_family_name, method_name); + currently_processing = IFACE; + } else if (strcmp(firstword, "auto") == 0) { + while ((firstword = next_word(&buf_ptr)) != NULL) { + + /* Check the interface isnt already listed */ + if (find_list_string(defn->autointerfaces, firstword)) { + bb_perror_msg_and_die("interface declared auto twice \"%s\"", buf); + } + + /* Add the interface to the list */ + llist_add_to_end(&(defn->autointerfaces), xstrdup(firstword)); + debug_noise("\nauto %s\n", firstword); + } + currently_processing = NONE; + } else { + switch (currently_processing) { + case IFACE: + { + int i; + + if (strlen(buf_ptr) == 0) { + bb_error_msg("option with empty value \"%s\"", buf); + return NULL; + } + + if (strcmp(firstword, "up") != 0 + && strcmp(firstword, "down") != 0 + && strcmp(firstword, "pre-up") != 0 + && strcmp(firstword, "post-down") != 0) { + for (i = 0; i < currif->n_options; i++) { + if (strcmp(currif->option[i].name, firstword) == 0) { + bb_error_msg("duplicate option \"%s\"", buf); + return NULL; + } + } + } + } + if (currif->n_options >= currif->max_options) { + struct variable_t *opt; + + currif->max_options = currif->max_options + 10; + opt = xrealloc(currif->option, sizeof(*opt) * currif->max_options); + currif->option = opt; + } + currif->option[currif->n_options].name = xstrdup(firstword); + currif->option[currif->n_options].value = xstrdup(buf_ptr); + if (!currif->option[currif->n_options].name) { + perror(filename); + return NULL; + } + if (!currif->option[currif->n_options].value) { + perror(filename); + return NULL; + } + debug_noise("\t%s=%s\n", currif->option[currif->n_options].name, + currif->option[currif->n_options].value); + currif->n_options++; + break; + case MAPPING: +#ifdef CONFIG_FEATURE_IFUPDOWN_MAPPING + if (strcmp(firstword, "script") == 0) { + if (currmap->script != NULL) { + bb_error_msg("duplicate script in mapping \"%s\"", buf); + return NULL; + } else { + currmap->script = xstrdup(next_word(&buf_ptr)); + } + } else if (strcmp(firstword, "map") == 0) { + if (currmap->max_mappings == currmap->n_mappings) { + currmap->max_mappings = currmap->max_mappings * 2 + 1; + currmap->mapping = xrealloc(currmap->mapping, sizeof(char *) * currmap->max_mappings); + } + currmap->mapping[currmap->n_mappings] = xstrdup(next_word(&buf_ptr)); + currmap->n_mappings++; + } else { + bb_error_msg("misplaced option \"%s\"", buf); + return NULL; + } +#endif + break; + case NONE: + default: + bb_error_msg("misplaced option \"%s\"", buf); + return NULL; + } + } + free(buf); + } + if (ferror(f) != 0) { + bb_perror_msg_and_die("%s", filename); + } + fclose(f); + + return defn; +} + +static char *setlocalenv(char *format, const char *name, const char *value) +{ + char *result; + char *here; + char *there; + + result = xasprintf(format, name, value); + + for (here = there = result; *there != '=' && *there; there++) { + if (*there == '-') + *there = '_'; + if (isalpha(*there)) + *there = toupper(*there); + + if (isalnum(*there) || *there == '_') { + *here = *there; + here++; + } + } + memmove(here, there, strlen(there) + 1); + + return result; +} + +static void set_environ(struct interface_defn_t *iface, const char *mode) +{ + char **environend; + int i; + const int n_env_entries = iface->n_options + 5; + char **ppch; + + if (__myenviron != NULL) { + for (ppch = __myenviron; *ppch; ppch++) { + free(*ppch); + *ppch = NULL; + } + free(__myenviron); + } + __myenviron = xzalloc(sizeof(char *) * (n_env_entries + 1 /* for final NULL */ )); + environend = __myenviron; + + for (i = 0; i < iface->n_options; i++) { + if (strcmp(iface->option[i].name, "up") == 0 + || strcmp(iface->option[i].name, "down") == 0 + || strcmp(iface->option[i].name, "pre-up") == 0 + || strcmp(iface->option[i].name, "post-down") == 0) { + continue; + } + *(environend++) = setlocalenv("IF_%s=%s", iface->option[i].name, iface->option[i].value); + } + + *(environend++) = setlocalenv("%s=%s", "IFACE", iface->iface); + *(environend++) = setlocalenv("%s=%s", "ADDRFAM", iface->address_family->name); + *(environend++) = setlocalenv("%s=%s", "METHOD", iface->method->name); + *(environend++) = setlocalenv("%s=%s", "MODE", mode); + *(environend++) = setlocalenv("%s=%s", "PATH", startup_PATH); +} + +static int doit(char *str) +{ + if (option_mask32 & (OPT_no_act|OPT_verbose)) { + puts(str); + } + if (!(option_mask32 & OPT_no_act)) { + pid_t child; + int status; + + fflush(NULL); + switch (child = fork()) { + case -1: /* failure */ + return 0; + case 0: /* child */ + execle(DEFAULT_SHELL, DEFAULT_SHELL, "-c", str, NULL, __myenviron); + exit(127); + } + waitpid(child, &status, 0); + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + return 0; + } + } + return 1; +} + +static int execute_all(struct interface_defn_t *ifd, const char *opt) +{ + int i; + char *buf; + for (i = 0; i < ifd->n_options; i++) { + if (strcmp(ifd->option[i].name, opt) == 0) { + if (!doit(ifd->option[i].value)) { + return 0; + } + } + } + + buf = xasprintf("run-parts /etc/network/if-%s.d", opt); + if (doit(buf) != 1) { + return 0; + } + return 1; +} + +static int check(char *str) { + return str != NULL; +} + +static int iface_up(struct interface_defn_t *iface) +{ + if (!iface->method->up(iface,check)) return -1; + set_environ(iface, "start"); + if (!execute_all(iface, "pre-up")) return 0; + if (!iface->method->up(iface, doit)) return 0; + if (!execute_all(iface, "up")) return 0; + return 1; +} + +static int iface_down(struct interface_defn_t *iface) +{ + if (!iface->method->down(iface,check)) return -1; + set_environ(iface, "stop"); + if (!execute_all(iface, "down")) return 0; + if (!iface->method->down(iface, doit)) return 0; + if (!execute_all(iface, "post-down")) return 0; + return 1; +} + +#ifdef CONFIG_FEATURE_IFUPDOWN_MAPPING +static int popen2(FILE **in, FILE **out, char *command, ...) +{ + va_list ap; + char *argv[11] = { command }; + int argc; + int infd[2], outfd[2]; + pid_t pid; + + argc = 1; + va_start(ap, command); + while ((argc < 10) && (argv[argc] = va_arg(ap, char *))) { + argc++; + } + argv[argc] = NULL; /* make sure */ + va_end(ap); + + if (pipe(infd) != 0) { + return 0; + } + + if (pipe(outfd) != 0) { + close(infd[0]); + close(infd[1]); + return 0; + } + + fflush(NULL); + switch (pid = fork()) { + case -1: /* failure */ + close(infd[0]); + close(infd[1]); + close(outfd[0]); + close(outfd[1]); + return 0; + case 0: /* child */ + dup2(infd[0], 0); + dup2(outfd[1], 1); + close(infd[0]); + close(infd[1]); + close(outfd[0]); + close(outfd[1]); + execvp(command, argv); + exit(127); + default: /* parent */ + *in = fdopen(infd[1], "w"); + *out = fdopen(outfd[0], "r"); + close(infd[0]); + close(outfd[1]); + return pid; + } + /* unreached */ +} + +static char *run_mapping(char *physical, struct mapping_defn_t * map) +{ + FILE *in, *out; + int i, status; + pid_t pid; + + char *logical = xstrdup(physical); + + /* Run the mapping script. */ + pid = popen2(&in, &out, map->script, physical, NULL); + + /* popen2() returns 0 on failure. */ + if (pid == 0) + return logical; + + /* Write mappings to stdin of mapping script. */ + for (i = 0; i < map->n_mappings; i++) { + fprintf(in, "%s\n", map->mapping[i]); + } + fclose(in); + waitpid(pid, &status, 0); + + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { + /* If the mapping script exited successfully, try to + * grab a line of output and use that as the name of the + * logical interface. */ + char *new_logical = (char *)xmalloc(MAX_INTERFACE_LENGTH); + + if (fgets(new_logical, MAX_INTERFACE_LENGTH, out)) { + /* If we are able to read a line of output from the script, + * remove any trailing whitespace and use this value + * as the name of the logical interface. */ + char *pch = new_logical + strlen(new_logical) - 1; + + while (pch >= new_logical && isspace(*pch)) + *(pch--) = '\0'; + + free(logical); + logical = new_logical; + } else { + /* If we are UNABLE to read a line of output, discard our + * freshly allocated memory. */ + free(new_logical); + } + } + + fclose(out); + + return logical; +} +#endif /* CONFIG_FEATURE_IFUPDOWN_MAPPING */ + +static llist_t *find_iface_state(llist_t *state_list, const char *iface) +{ + unsigned short iface_len = strlen(iface); + llist_t *search = state_list; + + while (search) { + if ((strncmp(search->data, iface, iface_len) == 0) && + (search->data[iface_len] == '=')) { + return search; + } + search = search->link; + } + return NULL; +} + +int ifupdown_main(int argc, char **argv) +{ + static const char statefile[] = "/var/run/ifstate"; + + int (*cmds)(struct interface_defn_t *) = NULL; + struct interfaces_file_t *defn; + llist_t *state_list = NULL; + llist_t *target_list = NULL; + const char *interfaces = "/etc/network/interfaces"; + int any_failures = 0; + + if (applet_name[2] == 'u') { + /* ifup command */ + cmds = iface_up; + } else { + /* ifdown command */ + cmds = iface_down; + } + + getopt32(argc, argv, OPTION_STR, &interfaces); + if (argc - optind > 0) { + if (DO_ALL) bb_show_usage(); + } else + if (!DO_ALL) bb_show_usage(); + + debug_noise("reading %s file:\n", interfaces); + defn = read_interfaces(interfaces); + debug_noise("\ndone reading %s\n\n", interfaces); + + if (!defn) { + exit(EXIT_FAILURE); + } + + startup_PATH = getenv("PATH"); + if (!startup_PATH) startup_PATH = ""; + + /* Create a list of interfaces to work on */ + if (DO_ALL) { + if (cmds == iface_up) { + target_list = defn->autointerfaces; + } else { + /* iface_down */ + const llist_t *list = state_list; + while (list) { + llist_add_to_end(&target_list, xstrdup(list->data)); + list = list->link; + } + target_list = defn->autointerfaces; + } + } else { + llist_add_to_end(&target_list, argv[optind]); + } + + + /* Update the interfaces */ + while (target_list) { + llist_t *iface_list; + struct interface_defn_t *currif; + char *iface; + char *liface; + char *pch; + int okay = 0; + int cmds_ret; + + iface = xstrdup(target_list->data); + target_list = target_list->link; + + pch = strchr(iface, '='); + if (pch) { + *pch = '\0'; + liface = xstrdup(pch + 1); + } else { + liface = xstrdup(iface); + } + + if (!FORCE) { + const llist_t *iface_state = find_iface_state(state_list, iface); + + if (cmds == iface_up) { + /* ifup */ + if (iface_state) { + bb_error_msg("interface %s already configured", iface); + continue; + } + } else { + /* ifdown */ + if (iface_state) { + bb_error_msg("interface %s not configured", iface); + continue; + } + } + } + +#ifdef CONFIG_FEATURE_IFUPDOWN_MAPPING + if ((cmds == iface_up) && !NO_MAPPINGS) { + struct mapping_defn_t *currmap; + + for (currmap = defn->mappings; currmap; currmap = currmap->next) { + int i; + for (i = 0; i < currmap->n_matches; i++) { + if (fnmatch(currmap->match[i], liface, 0) != 0) + continue; + if (VERBOSE) { + printf("Running mapping script %s on %s\n", currmap->script, liface); + } + liface = run_mapping(iface, currmap); + break; + } + } + } +#endif + + + iface_list = defn->ifaces; + while (iface_list) { + currif = (struct interface_defn_t *) iface_list->data; + if (strcmp(liface, currif->iface) == 0) { + char *oldiface = currif->iface; + + okay = 1; + currif->iface = iface; + + debug_noise("\nConfiguring interface %s (%s)\n", liface, currif->address_family->name); + + /* Call the cmds function pointer, does either iface_up() or iface_down() */ + cmds_ret = cmds(currif); + if (cmds_ret == -1) { + bb_error_msg("don't seem to have all the variables for %s/%s", + liface, currif->address_family->name); + any_failures += 1; + } else if (cmds_ret == 0) { + any_failures += 1; + } + + currif->iface = oldiface; + } + iface_list = iface_list->link; + } + if (VERBOSE) { + puts(""); + } + + if (!okay && !FORCE) { + bb_error_msg("ignoring unknown interface %s", liface); + any_failures += 1; + } else { + llist_t *iface_state = find_iface_state(state_list, iface); + + if (cmds == iface_up) { + char *newiface = xasprintf("%s=%s", iface, liface); + if (iface_state == NULL) { + llist_add_to_end(&state_list, newiface); + } else { + free(iface_state->data); + iface_state->data = newiface; + } + } else { + /* Remove an interface from the linked list */ + free(llist_pop(&iface_state)); + } + } + } + + /* Actually write the new state */ + if (!NO_ACT) { + FILE *state_fp = NULL; + + state_fp = xfopen(statefile, "w"); + while (state_list) { + if (state_list->data) { + fputs(state_list->data, state_fp); + fputc('\n', state_fp); + } + state_list = state_list->link; + } + fclose(state_fp); + } + + if (any_failures) + return 1; + return 0; +} diff --git a/networking/inetd.c b/networking/inetd.c new file mode 100644 index 000000000..ec7b2e8f7 --- /dev/null +++ b/networking/inetd.c @@ -0,0 +1,1767 @@ +/* vi: set sw=4 ts=4: */ +/* $Slackware: inetd.c 1.79s 2001/02/06 13:18:00 volkerdi Exp $ */ +/* $OpenBSD: inetd.c,v 1.79 2001/01/30 08:30:57 deraadt Exp $ */ +/* $NetBSD: inetd.c,v 1.11 1996/02/22 11:14:41 mycroft Exp $ */ +/* Busybox port by Vladimir Oleynik (C) 2001-2005 */ +/* + * Copyright (c) 1983,1991 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Inetd - Internet super-server + * + * This program invokes all internet services as needed. + * connection-oriented services are invoked each time a + * connection is made, by creating a process. This process + * is passed the connection as file descriptor 0 and is + * expected to do a getpeername to find out the source host + * and port. + * + * Datagram oriented services are invoked when a datagram + * arrives; a process is created and passed a pending message + * on file descriptor 0. Datagram servers may either connect + * to their peer, freeing up the original socket for inetd + * to receive further messages on, or ``take over the socket'', + * processing all arriving datagrams and, eventually, timing + * out. The first type of server is said to be ``multi-threaded''; + * the second type of server ``single-threaded''. + * + * Inetd uses a configuration file which is read at startup + * and, possibly, at some later time in response to a hangup signal. + * The configuration file is ``free format'' with fields given in the + * order shown below. Continuation lines for an entry must begin with + * a space or tab. All fields must be present in each entry. + * + * service name must be in /etc/services + * socket type stream/dgram/raw/rdm/seqpacket + * protocol must be in /etc/protocols + * wait/nowait[.max] single-threaded/multi-threaded, max # + * user[.group] or user[:group] user/group to run daemon as + * server program full path name + * server program arguments maximum of MAXARGS (20) + * + * For RPC services + * service name/version must be in /etc/rpc + * socket type stream/dgram/raw/rdm/seqpacket + * protocol must be in /etc/protocols + * wait/nowait[.max] single-threaded/multi-threaded + * user[.group] or user[:group] user to run daemon as + * server program full path name + * server program arguments maximum of MAXARGS (20) + * + * For non-RPC services, the "service name" can be of the form + * hostaddress:servicename, in which case the hostaddress is used + * as the host portion of the address to listen on. If hostaddress + * consists of a single `*' character, INADDR_ANY is used. + * + * A line can also consist of just + * hostaddress: + * where hostaddress is as in the preceding paragraph. Such a line must + * have no further fields; the specified hostaddress is remembered and + * used for all further lines that have no hostaddress specified, + * until the next such line (or EOF). (This is why * is provided to + * allow explicit specification of INADDR_ANY.) A line + * *: + * is implicitly in effect at the beginning of the file. + * + * The hostaddress specifier may (and often will) contain dots; + * the service name must not. + * + * For RPC services, host-address specifiers are accepted and will + * work to some extent; however, because of limitations in the + * portmapper interface, it will not work to try to give more than + * one line for any given RPC service, even if the host-address + * specifiers are different. + * + * Comment lines are indicated by a `#' in column 1. + */ + +/* + * Here's the scoop concerning the user[.:]group feature: + * + * 1) set-group-option off. + * + * a) user = root: NO setuid() or setgid() is done + * + * b) other: setgid(primary group as found in passwd) + * initgroups(name, primary group) + * setuid() + * + * 2) set-group-option on. + * + * a) user = root: setgid(specified group) + * NO initgroups() + * NO setuid() + * + * b) other: setgid(specified group) + * initgroups(name, specified group) + * setuid() + * + */ + +#include "busybox.h" +#include +#include + +//#define CONFIG_FEATURE_INETD_RPC +//#define CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO +//#define CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD +//#define CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_TIME +//#define CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME +//#define CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN +//#define CONFIG_FEATURE_IPV6 + +#ifdef CONFIG_FEATURE_INETD_RPC +#include +#include +#endif + +#define _PATH_INETDCONF "/etc/inetd.conf" +#define _PATH_INETDPID "/var/run/inetd.pid" + + +#define CNT_INTVL 60 /* servers in CNT_INTVL sec. */ +#define RETRYTIME (60*10) /* retry after bind or server fail */ + +#ifndef RLIMIT_NOFILE +#define RLIMIT_NOFILE RLIMIT_OFILE +#endif + +#ifndef OPEN_MAX +#define OPEN_MAX 64 +#endif + +/* Reserve some descriptors, 3 stdio + at least: 1 log, 1 conf. file */ +#define FD_MARGIN (8) +static rlim_t rlim_ofile_cur = OPEN_MAX; +static struct rlimit rlim_ofile; + + +/* Check unsupporting builtin */ +#if defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO || \ + defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD || \ + defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_TIME || \ + defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME || \ + defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN +# define INETD_FEATURE_ENABLED +#endif + +#if defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO || \ + defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD || \ + defined CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN +# define INETD_SETPROCTITLE +#endif + +typedef struct servtab { + char *se_hostaddr; /* host address to listen on */ + char *se_service; /* name of service */ + int se_socktype; /* type of socket to use */ + int se_family; /* address family */ + char *se_proto; /* protocol used */ +#ifdef CONFIG_FEATURE_INETD_RPC + int se_rpcprog; /* rpc program number */ + int se_rpcversl; /* rpc program lowest version */ + int se_rpcversh; /* rpc program highest version */ +#define isrpcservice(sep) ((sep)->se_rpcversl != 0) +#else +#define isrpcservice(sep) 0 +#endif + pid_t se_wait; /* single threaded server */ + short se_checked; /* looked at during merge */ + char *se_user; /* user name to run as */ + char *se_group; /* group name to run as */ +#ifdef INETD_FEATURE_ENABLED + const struct builtin *se_bi; /* if built-in, description */ +#endif + char *se_server; /* server program */ +#define MAXARGV 20 + char *se_argv[MAXARGV + 1]; /* program arguments */ + int se_fd; /* open descriptor */ + union { + struct sockaddr se_un_ctrladdr; + struct sockaddr_in se_un_ctrladdr_in; +#ifdef CONFIG_FEATURE_IPV6 + struct sockaddr_in6 se_un_ctrladdr_in6; +#endif + struct sockaddr_un se_un_ctrladdr_un; + } se_un; /* bound address */ +#define se_ctrladdr se_un.se_un_ctrladdr +#define se_ctrladdr_in se_un.se_un_ctrladdr_in +#define se_ctrladdr_in6 se_un.se_un_ctrladdr_in6 +#define se_ctrladdr_un se_un.se_un_ctrladdr_un + int se_ctrladdr_size; + int se_max; /* max # of instances of this service */ + int se_count; /* number started since se_time */ + struct timeval se_time; /* start of se_count */ + struct servtab *se_next; +} servtab_t; + +static servtab_t *servtab; + +#ifdef INETD_FEATURE_ENABLED +struct builtin { + const char *bi_service; /* internally provided service name */ + int bi_socktype; /* type of socket supported */ + short bi_fork; /* 1 if should fork before call */ + short bi_wait; /* 1 if should wait for child */ + void (*bi_fn) (int, servtab_t *); +}; + + /* Echo received data */ +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO +static void echo_stream(int, servtab_t *); +static void echo_dg(int, servtab_t *); +#endif + /* Internet /dev/null */ +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD +static void discard_stream(int, servtab_t *); +static void discard_dg(int, servtab_t *); +#endif + /* Return 32 bit time since 1900 */ +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_TIME +static void machtime_stream(int, servtab_t *); +static void machtime_dg(int, servtab_t *); +#endif + /* Return human-readable time */ +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME +static void daytime_stream(int, servtab_t *); +static void daytime_dg(int, servtab_t *); +#endif + /* Familiar character generator */ +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN +static void chargen_stream(int, servtab_t *); +static void chargen_dg(int, servtab_t *); +#endif + +static const struct builtin builtins[] = { +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO + /* Echo received data */ + {"echo", SOCK_STREAM, 1, 0, echo_stream,}, + {"echo", SOCK_DGRAM, 0, 0, echo_dg,}, +#endif +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD + /* Internet /dev/null */ + {"discard", SOCK_STREAM, 1, 0, discard_stream,}, + {"discard", SOCK_DGRAM, 0, 0, discard_dg,}, +#endif +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_TIME + /* Return 32 bit time since 1900 */ + {"time", SOCK_STREAM, 0, 0, machtime_stream,}, + {"time", SOCK_DGRAM, 0, 0, machtime_dg,}, +#endif +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME + /* Return human-readable time */ + {"daytime", SOCK_STREAM, 0, 0, daytime_stream,}, + {"daytime", SOCK_DGRAM, 0, 0, daytime_dg,}, +#endif +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN + /* Familiar character generator */ + {"chargen", SOCK_STREAM, 1, 0, chargen_stream,}, + {"chargen", SOCK_DGRAM, 0, 0, chargen_dg,}, +#endif + {NULL, 0, 0, 0, NULL} +}; +#endif /* INETD_FEATURE_ENABLED */ + +static int global_queuelen = 128; +static int nsock, maxsock; +static fd_set allsock; +static int toomany; +static int timingout; +static struct servent *sp; +static uid_t uid; + +static char *CONFIG = _PATH_INETDCONF; + +static FILE *fconfig; +static char line[1024]; +static char *defhost; + +/* xstrdup(NULL) returns NULL, but this one + * will return newly-allocated "" if called with NULL arg + * TODO: audit whether this makes any real difference + */ +static char *xxstrdup(char *cp) +{ + return xstrdup(cp ? cp : ""); +} + +static int setconfig(void) +{ + free(defhost); + defhost = xstrdup("*"); + if (fconfig != NULL) { + fseek(fconfig, 0L, SEEK_SET); + return 1; + } + fconfig = fopen(CONFIG, "r"); + return (fconfig != NULL); +} + +static void endconfig(void) +{ + if (fconfig) { + (void) fclose(fconfig); + fconfig = NULL; + } + free(defhost); + defhost = 0; +} + +#ifdef CONFIG_FEATURE_INETD_RPC +static void register_rpc(servtab_t *sep) +{ + int n; + struct sockaddr_in ir_sin; + struct protoent *pp; + socklen_t size; + + if ((pp = getprotobyname(sep->se_proto + 4)) == NULL) { + bb_perror_msg("%s: getproto", sep->se_proto); + return; + } + size = sizeof ir_sin; + if (getsockname(sep->se_fd, (struct sockaddr *) &ir_sin, &size) < 0) { + bb_perror_msg("%s/%s: getsockname", + sep->se_service, sep->se_proto); + return; + } + + for (n = sep->se_rpcversl; n <= sep->se_rpcversh; n++) { + (void) pmap_unset(sep->se_rpcprog, n); + if (!pmap_set(sep->se_rpcprog, n, pp->p_proto, ntohs(ir_sin.sin_port))) + bb_perror_msg("%s %s: pmap_set: %u %u %u %u", + sep->se_service, sep->se_proto, + sep->se_rpcprog, n, pp->p_proto, ntohs(ir_sin.sin_port)); + } +} + +static void unregister_rpc(servtab_t *sep) +{ + int n; + + for (n = sep->se_rpcversl; n <= sep->se_rpcversh; n++) { + if (!pmap_unset(sep->se_rpcprog, n)) + bb_error_msg("pmap_unset(%u, %u)", sep->se_rpcprog, n); + } +} +#endif /* CONFIG_FEATURE_INETD_RPC */ + +static void freeconfig(servtab_t *cp) +{ + int i; + + free(cp->se_hostaddr); + free(cp->se_service); + free(cp->se_proto); + free(cp->se_user); + free(cp->se_group); + free(cp->se_server); + for (i = 0; i < MAXARGV; i++) + free(cp->se_argv[i]); +} + +static int bump_nofile (void) +{ +#define FD_CHUNK 32 + + struct rlimit rl; + + if (getrlimit (RLIMIT_NOFILE, &rl) < 0) { + bb_perror_msg("getrlimit"); + return -1; + } + rl.rlim_cur = MIN(rl.rlim_max, rl.rlim_cur + FD_CHUNK); + rl.rlim_cur = MIN(FD_SETSIZE, rl.rlim_cur + FD_CHUNK); + if (rl.rlim_cur <= rlim_ofile_cur) { + bb_error_msg("bump_nofile: cannot extend file limit, max = %d", + (int) rl.rlim_cur); + return -1; + } + + if (setrlimit(RLIMIT_NOFILE, &rl) < 0) { + bb_perror_msg("setrlimit"); + return -1; + } + + rlim_ofile_cur = rl.rlim_cur; + return 0; +} + +static void setup(servtab_t *sep) +{ + int r; + + sep->se_fd = socket(sep->se_family, sep->se_socktype, 0); + if (sep->se_fd < 0) { + bb_perror_msg("%s/%s: socket", sep->se_service, sep->se_proto); + return; + } + if (setsockopt_reuseaddr(sep->se_fd) < 0) + bb_perror_msg("setsockopt(SO_REUSEADDR)"); + +#ifdef CONFIG_FEATURE_INETD_RPC + if (isrpcservice(sep)) { + struct passwd *pwd; + + /* + * for RPC services, attempt to use a reserved port + * if they are going to be running as root. + * + * Also, zero out the port for all RPC services; let bind() + * find one. + */ + sep->se_ctrladdr_in.sin_port = 0; + if (sep->se_user && (pwd = getpwnam(sep->se_user)) && + pwd->pw_uid == 0 && uid == 0) + r = bindresvport(sep->se_fd, &sep->se_ctrladdr_in); + else { + r = bind(sep->se_fd, &sep->se_ctrladdr, sep->se_ctrladdr_size); + if (r == 0) { + socklen_t len = sep->se_ctrladdr_size; + int saveerrno = errno; + + /* update se_ctrladdr_in.sin_port */ + r = getsockname(sep->se_fd, &sep->se_ctrladdr, &len); + if (r <= 0) + errno = saveerrno; + } + } + } else +#endif + r = bind(sep->se_fd, &sep->se_ctrladdr, sep->se_ctrladdr_size); + if (r < 0) { + bb_perror_msg("%s/%s (%d): bind", + sep->se_service, sep->se_proto, sep->se_ctrladdr.sa_family); + close(sep->se_fd); + sep->se_fd = -1; + if (!timingout) { + timingout = 1; + alarm(RETRYTIME); + } + return; + } + if (sep->se_socktype == SOCK_STREAM) + listen(sep->se_fd, global_queuelen); + + FD_SET(sep->se_fd, &allsock); + nsock++; + if (sep->se_fd > maxsock) { + maxsock = sep->se_fd; + if ((rlim_t)maxsock > rlim_ofile_cur - FD_MARGIN) + bump_nofile(); + } +} + +static char *nextline(void) +{ + char *cp; + FILE *fd = fconfig; + + if (fgets(line, sizeof(line), fd) == NULL) + return NULL; + cp = strchr(line, '\n'); + if (cp) + *cp = '\0'; + return line; +} + +static char *skip(char **cpp) /* int report; */ +{ + char *cp = *cpp; + char *start; + +/* erp: */ + if (*cpp == NULL) { + /* if (report) */ + /* bb_error_msg("syntax error in inetd config file"); */ + return NULL; + } + +again: + while (*cp == ' ' || *cp == '\t') + cp++; + if (*cp == '\0') { + int c; + + c = getc(fconfig); + (void) ungetc(c, fconfig); + if (c == ' ' || c == '\t') + if ((cp = nextline())) + goto again; + *cpp = NULL; + /* goto erp; */ + return NULL; + } + start = cp; + while (*cp && *cp != ' ' && *cp != '\t') + cp++; + if (*cp != '\0') + *cp++ = '\0'; + /* if ((*cpp = cp) == NULL) */ + /* goto erp; */ + + *cpp = cp; + return start; +} + +static servtab_t *new_servtab(void) +{ + return xmalloc(sizeof(servtab_t)); +} + +static servtab_t *dupconfig(servtab_t *sep) +{ + servtab_t *newtab; + int argc; + + newtab = new_servtab(); + memset(newtab, 0, sizeof(servtab_t)); + newtab->se_service = xstrdup(sep->se_service); + newtab->se_socktype = sep->se_socktype; + newtab->se_family = sep->se_family; + newtab->se_proto = xstrdup(sep->se_proto); +#ifdef CONFIG_FEATURE_INETD_RPC + newtab->se_rpcprog = sep->se_rpcprog; + newtab->se_rpcversl = sep->se_rpcversl; + newtab->se_rpcversh = sep->se_rpcversh; +#endif + newtab->se_wait = sep->se_wait; + newtab->se_user = xstrdup(sep->se_user); + newtab->se_group = xstrdup(sep->se_group); +#ifdef INETD_FEATURE_ENABLED + newtab->se_bi = sep->se_bi; +#endif + newtab->se_server = xstrdup(sep->se_server); + + for (argc = 0; argc <= MAXARGV; argc++) + newtab->se_argv[argc] = xstrdup(sep->se_argv[argc]); + newtab->se_max = sep->se_max; + + return newtab; +} + +static servtab_t *getconfigent(void) +{ + servtab_t *sep; + int argc; + char *cp, *arg; + char *hostdelim; + servtab_t *nsep; + servtab_t *psep; + + sep = new_servtab(); + + /* memset(sep, 0, sizeof *sep); */ + more: + /* freeconfig(sep); */ + + while ((cp = nextline()) && *cp == '#') /* skip comment line */; + if (cp == NULL) { + /* free(sep); */ + return NULL; + } + + memset((char *) sep, 0, sizeof *sep); + arg = skip(&cp); + if (arg == NULL) { + /* A blank line. */ + goto more; + } + + /* Check for a host name. */ + hostdelim = strrchr(arg, ':'); + if (hostdelim) { + *hostdelim = '\0'; + sep->se_hostaddr = xstrdup(arg); + arg = hostdelim + 1; + /* + * If the line is of the form `host:', then just change the + * default host for the following lines. + */ + if (*arg == '\0') { + arg = skip(&cp); + if (cp == NULL) { + free(defhost); + defhost = sep->se_hostaddr; + goto more; + } + } + } else + sep->se_hostaddr = xxstrdup(defhost); + + sep->se_service = xxstrdup(arg); + arg = skip(&cp); + + if (strcmp(arg, "stream") == 0) + sep->se_socktype = SOCK_STREAM; + else if (strcmp(arg, "dgram") == 0) + sep->se_socktype = SOCK_DGRAM; + else if (strcmp(arg, "rdm") == 0) + sep->se_socktype = SOCK_RDM; + else if (strcmp(arg, "seqpacket") == 0) + sep->se_socktype = SOCK_SEQPACKET; + else if (strcmp(arg, "raw") == 0) + sep->se_socktype = SOCK_RAW; + else + sep->se_socktype = -1; + + sep->se_proto = xxstrdup(skip(&cp)); + + if (strcmp(sep->se_proto, "unix") == 0) { + sep->se_family = AF_UNIX; + } else { + sep->se_family = AF_INET; + if (sep->se_proto[strlen(sep->se_proto) - 1] == '6') +#ifdef CONFIG_FEATURE_IPV6 + sep->se_family = AF_INET6; +#else + bb_error_msg("%s: IPV6 not supported", sep->se_proto); +#endif + if (strncmp(sep->se_proto, "rpc/", 4) == 0) { +#ifdef CONFIG_FEATURE_INETD_RPC + char *p, *ccp; + long l; + + p = strchr(sep->se_service, '/'); + if (p == 0) { + bb_error_msg("%s: no rpc version", sep->se_service); + goto more; + } + *p++ = '\0'; + l = strtol(p, &ccp, 0); + if (ccp == p || l < 0 || l > INT_MAX) { + badafterall: + bb_error_msg("%s/%s: bad rpc version", sep->se_service, p); + goto more; + } + sep->se_rpcversl = sep->se_rpcversh = l; + if (*ccp == '-') { + p = ccp + 1; + l = strtol(p, &ccp, 0); + if (ccp == p || l < 0 || l > INT_MAX || l < sep->se_rpcversl || *ccp) + goto badafterall; + sep->se_rpcversh = l; + } else if (*ccp != '\0') + goto badafterall; +#else + bb_error_msg("%s: rpc services not supported", sep->se_service); +#endif + } + } + arg = skip(&cp); + if (arg == NULL) + goto more; + + { + char *s = strchr(arg, '.'); + if (s) { + *s++ = '\0'; + sep->se_max = xatoi(s); + } else + sep->se_max = toomany; + } + sep->se_wait = strcmp(arg, "wait") == 0; + /* if ((arg = skip(&cp, 1)) == NULL) */ + /* goto more; */ + sep->se_user = xxstrdup(skip(&cp)); + arg = strchr(sep->se_user, '.'); + if (arg == NULL) + arg = strchr(sep->se_user, ':'); + if (arg) { + *arg++ = '\0'; + sep->se_group = xstrdup(arg); + } + /* if ((arg = skip(&cp, 1)) == NULL) */ + /* goto more; */ + + sep->se_server = xxstrdup(skip(&cp)); + if (strcmp(sep->se_server, "internal") == 0) { +#ifdef INETD_FEATURE_ENABLED + const struct builtin *bi; + + for (bi = builtins; bi->bi_service; bi++) + if (bi->bi_socktype == sep->se_socktype && + strcmp(bi->bi_service, sep->se_service) == 0) + break; + if (bi->bi_service == 0) { + bb_error_msg("internal service %s unknown", sep->se_service); + goto more; + } + sep->se_bi = bi; + sep->se_wait = bi->bi_wait; +#else + bb_perror_msg("internal service %s unknown", sep->se_service); + goto more; +#endif + } +#ifdef INETD_FEATURE_ENABLED + else + sep->se_bi = NULL; +#endif + argc = 0; + for (arg = skip(&cp); cp; arg = skip(&cp)) { + if (argc < MAXARGV) + sep->se_argv[argc++] = xxstrdup(arg); + } + while (argc <= MAXARGV) + sep->se_argv[argc++] = NULL; + + /* + * Now that we've processed the entire line, check if the hostname + * specifier was a comma separated list of hostnames. If so + * we'll make new entries for each address. + */ + while ((hostdelim = strrchr(sep->se_hostaddr, ',')) != NULL) { + nsep = dupconfig(sep); + + /* + * NULL terminate the hostname field of the existing entry, + * and make a dup for the new entry. + */ + *hostdelim++ = '\0'; + nsep->se_hostaddr = xstrdup(hostdelim); + + nsep->se_next = sep->se_next; + sep->se_next = nsep; + } + + nsep = sep; + while (nsep != NULL) { + nsep->se_checked = 1; + if (nsep->se_family == AF_INET) { + if (!strcmp(nsep->se_hostaddr, "*")) + nsep->se_ctrladdr_in.sin_addr.s_addr = INADDR_ANY; + else if (!inet_aton(nsep->se_hostaddr, &nsep->se_ctrladdr_in.sin_addr)) { + struct hostent *hp; + + hp = gethostbyname(nsep->se_hostaddr); + if (hp == 0) { + bb_error_msg("%s: unknown host", nsep->se_hostaddr); + nsep->se_checked = 0; + goto skip; + } else if (hp->h_addrtype != AF_INET) { + bb_error_msg("%s: address isn't an Internet " + "address", nsep->se_hostaddr); + nsep->se_checked = 0; + goto skip; + } else { + int i = 1; + + memmove(&nsep->se_ctrladdr_in.sin_addr, + hp->h_addr_list[0], sizeof(struct in_addr)); + while (hp->h_addr_list[i] != NULL) { + psep = dupconfig(nsep); + psep->se_hostaddr = xxstrdup(nsep->se_hostaddr); + psep->se_checked = 1; + memmove(&psep->se_ctrladdr_in.sin_addr, + hp->h_addr_list[i], sizeof(struct in_addr)); + psep->se_ctrladdr_size = sizeof(psep->se_ctrladdr_in); + i++; + /* Prepend to list, don't want to look up */ + /* its hostname again. */ + psep->se_next = sep; + sep = psep; + } + } + } + } +/* XXX BUG?: is this skip: label supposed to remain? */ + skip: + nsep = nsep->se_next; + } + + /* + * Finally, free any entries which failed the gethostbyname + * check. + */ + psep = NULL; + nsep = sep; + while (nsep != NULL) { + servtab_t *tsep; + + if (nsep->se_checked == 0) { + tsep = nsep; + if (psep == NULL) { + sep = nsep->se_next; + nsep = sep; + } else { + nsep = nsep->se_next; + psep->se_next = nsep; + } + freeconfig(tsep); + } else { + nsep->se_checked = 0; + psep = nsep; + nsep = nsep->se_next; + } + } + + return sep; +} + +#define Block_Using_Signals(m) do { \ + sigemptyset(&m); \ + sigaddset(&m, SIGCHLD); \ + sigaddset(&m, SIGHUP); \ + sigaddset(&m, SIGALRM); \ + sigprocmask(SIG_BLOCK, &m, NULL); \ +} while(0) + +static servtab_t *enter(servtab_t *cp) +{ + servtab_t *sep; + sigset_t omask; + + sep = new_servtab(); + *sep = *cp; + sep->se_fd = -1; +#ifdef CONFIG_FEATURE_INETD_RPC + sep->se_rpcprog = -1; +#endif + Block_Using_Signals(omask); + sep->se_next = servtab; + servtab = sep; + sigprocmask(SIG_UNBLOCK, &omask, NULL); + return sep; +} + +static int matchconf(servtab_t *old, servtab_t *new) +{ + if (strcmp(old->se_service, new->se_service) != 0) + return 0; + + if (strcmp(old->se_hostaddr, new->se_hostaddr) != 0) + return 0; + + if (strcmp(old->se_proto, new->se_proto) != 0) + return 0; + + /* + * If the new servtab is bound to a specific address, check that the + * old servtab is bound to the same entry. If the new service is not + * bound to a specific address then the check of se_hostaddr above + * is sufficient. + */ + + if (old->se_family == AF_INET && new->se_family == AF_INET && + memcmp(&old->se_ctrladdr_in.sin_addr, + &new->se_ctrladdr_in.sin_addr, + sizeof(new->se_ctrladdr_in.sin_addr)) != 0) + return 0; + +#ifdef CONFIG_FEATURE_IPV6 + if (old->se_family == AF_INET6 && new->se_family == AF_INET6 && + memcmp(&old->se_ctrladdr_in6.sin6_addr, + &new->se_ctrladdr_in6.sin6_addr, + sizeof(new->se_ctrladdr_in6.sin6_addr)) != 0) + return 0; +#endif + return 1; +} + +static void config(int sig ATTRIBUTE_UNUSED) +{ + servtab_t *sep, *cp, **sepp; + sigset_t omask; + size_t n; + char protoname[10]; + + if (!setconfig()) { + bb_perror_msg("%s", CONFIG); + return; + } + for (sep = servtab; sep; sep = sep->se_next) + sep->se_checked = 0; + cp = getconfigent(); + while (cp != NULL) { + for (sep = servtab; sep; sep = sep->se_next) + if (matchconf(sep, cp)) + break; + + if (sep != 0) { + int i; + +#define SWAP(type, a, b) do {type c=(type)a; a=(type)b; b=(type)c;} while (0) + + Block_Using_Signals(omask); + /* + * sep->se_wait may be holding the pid of a daemon + * that we're waiting for. If so, don't overwrite + * it unless the config file explicitly says don't + * wait. + */ + if ( +#ifdef INETD_FEATURE_ENABLED + cp->se_bi == 0 && +#endif + (sep->se_wait == 1 || cp->se_wait == 0)) + sep->se_wait = cp->se_wait; + SWAP(int, cp->se_max, sep->se_max); + SWAP(char *, sep->se_user, cp->se_user); + SWAP(char *, sep->se_group, cp->se_group); + SWAP(char *, sep->se_server, cp->se_server); + for (i = 0; i < MAXARGV; i++) + SWAP(char *, sep->se_argv[i], cp->se_argv[i]); +#undef SWAP + +#ifdef CONFIG_FEATURE_INETD_RPC + if (isrpcservice(sep)) + unregister_rpc(sep); + sep->se_rpcversl = cp->se_rpcversl; + sep->se_rpcversh = cp->se_rpcversh; +#endif + sigprocmask(SIG_UNBLOCK, &omask, NULL); + freeconfig(cp); + } else { + sep = enter(cp); + } + sep->se_checked = 1; + + switch (sep->se_family) { + case AF_UNIX: + if (sep->se_fd != -1) + break; + (void) unlink(sep->se_service); + n = strlen(sep->se_service); + if (n > sizeof sep->se_ctrladdr_un.sun_path - 1) + n = sizeof sep->se_ctrladdr_un.sun_path - 1; + safe_strncpy(sep->se_ctrladdr_un.sun_path, sep->se_service, n + 1); + sep->se_ctrladdr_un.sun_family = AF_UNIX; + sep->se_ctrladdr_size = n + sizeof sep->se_ctrladdr_un.sun_family; + setup(sep); + break; + case AF_INET: + sep->se_ctrladdr_in.sin_family = AF_INET; + /* se_ctrladdr_in was set in getconfigent */ + sep->se_ctrladdr_size = sizeof sep->se_ctrladdr_in; + +#ifdef CONFIG_FEATURE_INETD_RPC + if (isrpcservice(sep)) { + struct rpcent *rp; + // FIXME: atoi_or_else(str, 0) would be handy here + sep->se_rpcprog = atoi(sep->se_service); + if (sep->se_rpcprog == 0) { + rp = getrpcbyname(sep->se_service); + if (rp == 0) { + bb_error_msg("%s: unknown rpc service", sep->se_service); + goto serv_unknown; + } + sep->se_rpcprog = rp->r_number; + } + if (sep->se_fd == -1) + setup(sep); + if (sep->se_fd != -1) + register_rpc(sep); + } else +#endif + { + u_short port = htons(atoi(sep->se_service)); + // FIXME: atoi_or_else(str, 0) would be handy here + if (!port) { + /*XXX*/ strncpy(protoname, sep->se_proto, sizeof(protoname)); + if (isdigit(protoname[strlen(protoname) - 1])) + protoname[strlen(protoname) - 1] = '\0'; + sp = getservbyname(sep->se_service, protoname); + if (sp == 0) { + bb_error_msg("%s/%s: unknown service", + sep->se_service, sep->se_proto); + goto serv_unknown; + } + port = sp->s_port; + } + if (port != sep->se_ctrladdr_in.sin_port) { + sep->se_ctrladdr_in.sin_port = port; + if (sep->se_fd != -1) { + FD_CLR(sep->se_fd, &allsock); + nsock--; + (void) close(sep->se_fd); + } + sep->se_fd = -1; + } + if (sep->se_fd == -1) + setup(sep); + } + break; +#ifdef CONFIG_FEATURE_IPV6 + case AF_INET6: + sep->se_ctrladdr_in6.sin6_family = AF_INET6; + /* se_ctrladdr_in was set in getconfigent */ + sep->se_ctrladdr_size = sizeof sep->se_ctrladdr_in6; + +#ifdef CONFIG_FEATURE_INETD_RPC + if (isrpcservice(sep)) { + struct rpcent *rp; + + sep->se_rpcprog = atoi(sep->se_service); + if (sep->se_rpcprog == 0) { + rp = getrpcbyname(sep->se_service); + if (rp == 0) { + bb_error_msg("%s: unknown rpc service", sep->se_service); + goto serv_unknown; + } + sep->se_rpcprog = rp->r_number; + } + if (sep->se_fd == -1) + setup(sep); + if (sep->se_fd != -1) + register_rpc(sep); + } else +#endif + { + u_short port = htons(atoi(sep->se_service)); + + if (!port) { + /*XXX*/ strncpy(protoname, sep->se_proto, sizeof(protoname)); + if (isdigit(protoname[strlen(protoname) - 1])) + protoname[strlen(protoname) - 1] = '\0'; + sp = getservbyname(sep->se_service, protoname); + if (sp == 0) { + bb_error_msg("%s/%s: unknown service", + sep->se_service, sep->se_proto); + goto serv_unknown; + } + port = sp->s_port; + } + if (port != sep->se_ctrladdr_in6.sin6_port) { + sep->se_ctrladdr_in6.sin6_port = port; + if (sep->se_fd != -1) { + FD_CLR(sep->se_fd, &allsock); + nsock--; + (void) close(sep->se_fd); + } + sep->se_fd = -1; + } + if (sep->se_fd == -1) + setup(sep); + } + break; +#endif /* CONFIG_FEATURE_IPV6 */ + } + serv_unknown: + if (cp->se_next != NULL) { + servtab_t *tmp = cp; + + cp = cp->se_next; + free(tmp); + } else { + free(cp); + cp = getconfigent(); + } + } + endconfig(); + /* + * Purge anything not looked at above. + */ + Block_Using_Signals(omask); + sepp = &servtab; + while ((sep = *sepp)) { + if (sep->se_checked) { + sepp = &sep->se_next; + continue; + } + *sepp = sep->se_next; + if (sep->se_fd != -1) { + FD_CLR(sep->se_fd, &allsock); + nsock--; + (void) close(sep->se_fd); + } +#ifdef CONFIG_FEATURE_INETD_RPC + if (isrpcservice(sep)) + unregister_rpc(sep); +#endif + if (sep->se_family == AF_UNIX) + (void) unlink(sep->se_service); + freeconfig(sep); + free(sep); + } + sigprocmask(SIG_UNBLOCK, &omask, NULL); +} + + +static void reapchild(int sig ATTRIBUTE_UNUSED) +{ + pid_t pid; + int save_errno = errno, status; + servtab_t *sep; + + for (;;) { + pid = wait3(&status, WNOHANG, NULL); + if (pid <= 0) + break; + for (sep = servtab; sep; sep = sep->se_next) + if (sep->se_wait == pid) { + if (WIFEXITED(status) && WEXITSTATUS(status)) + bb_error_msg("%s: exit status 0x%x", + sep->se_server, WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + bb_error_msg("%s: exit signal 0x%x", + sep->se_server, WTERMSIG(status)); + sep->se_wait = 1; + FD_SET(sep->se_fd, &allsock); + nsock++; + } + } + errno = save_errno; +} + +static void retry(int sig ATTRIBUTE_UNUSED) +{ + servtab_t *sep; + + timingout = 0; + for (sep = servtab; sep; sep = sep->se_next) { + if (sep->se_fd == -1) { + switch (sep->se_family) { + case AF_UNIX: + case AF_INET: +#ifdef CONFIG_FEATURE_IPV6 + case AF_INET6: +#endif + setup(sep); +#ifdef CONFIG_FEATURE_INETD_RPC + if (sep->se_fd != -1 && isrpcservice(sep)) + register_rpc(sep); +#endif + break; + } + } + } +} + +static void goaway(int sig ATTRIBUTE_UNUSED) +{ + servtab_t *sep; + + /* XXX signal race walking sep list */ + for (sep = servtab; sep; sep = sep->se_next) { + if (sep->se_fd == -1) + continue; + + switch (sep->se_family) { + case AF_UNIX: + (void) unlink(sep->se_service); + break; + case AF_INET: +#ifdef CONFIG_FEATURE_IPV6 + case AF_INET6: +#endif +#ifdef CONFIG_FEATURE_INETD_RPC + if (sep->se_wait == 1 && isrpcservice(sep)) + unregister_rpc(sep); /* XXX signal race */ +#endif + break; + } + (void) close(sep->se_fd); + } + (void) unlink(_PATH_INETDPID); + exit(0); +} + + +#ifdef INETD_SETPROCTITLE +static char **Argv; +static char *LastArg; + +static void +inetd_setproctitle(char *a, int s) +{ + socklen_t size; + char *cp; + struct sockaddr_in prt_sin; + char buf[80]; + + cp = Argv[0]; + size = sizeof(prt_sin); + (void) snprintf(buf, sizeof buf, "-%s", a); + if (getpeername(s, (struct sockaddr *) &prt_sin, &size) == 0) { + char *sa = inet_ntoa(prt_sin.sin_addr); + + buf[sizeof(buf) - 1 - strlen(sa) - 3] = '\0'; + strcat(buf, " ["); + strcat(buf, sa); + strcat(buf, "]"); + } + strncpy(cp, buf, LastArg - cp); + cp += strlen(cp); + while (cp < LastArg) + *cp++ = ' '; +} +#endif + + +int +inetd_main(int argc, char *argv[]) +{ + servtab_t *sep; + struct passwd *pwd; + struct group *grp = NULL; + int tmpint; + struct sigaction sa, sapipe; + int opt; + pid_t pid; + char buf[50]; + char *stoomany; + sigset_t omask, wait_mask; + +#ifdef INETD_SETPROCTITLE + extern char **environ; + char **envp = environ; + + Argv = argv; + if (envp == 0 || *envp == 0) + envp = argv; + while (*envp) + envp++; + LastArg = envp[-1] + strlen(envp[-1]); +#endif + + openlog(applet_name, LOG_PID | LOG_NOWAIT, LOG_DAEMON); + + opt = getopt32(argc, argv, "R:f", &stoomany); + if(opt & 1) { + toomany = xatoi_u(stoomany); + } + argc -= optind; + argv += optind; + + uid = getuid(); + if (uid != 0) + CONFIG = NULL; + if (argc > 0) + CONFIG = argv[0]; + if (CONFIG == NULL) + bb_error_msg_and_die("non-root must specify a config file"); + + if (!(opt & 2)) { +#ifdef BB_NOMMU + /* reexec for vfork() do continue parent */ + vfork_daemon_rexec(0, 0, argc, argv, "-f"); +#else + xdaemon(0, 0); +#endif + } else { + setsid(); + } + logmode = LOGMODE_SYSLOG; + + if (uid == 0) { + gid_t gid = getgid(); + + /* If run by hand, ensure groups vector gets trashed */ + setgroups(1, &gid); + } + + { + FILE *fp = fopen(_PATH_INETDPID, "w"); + + if (fp != NULL) { + fprintf(fp, "%u\n", getpid()); + (void) fclose(fp); + } + } + + if (getrlimit(RLIMIT_NOFILE, &rlim_ofile) < 0) { + bb_perror_msg("getrlimit"); + } else { + rlim_ofile_cur = rlim_ofile.rlim_cur; + if (rlim_ofile_cur == RLIM_INFINITY) /* ! */ + rlim_ofile_cur = OPEN_MAX; + } + + memset((char *) &sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sigaddset(&sa.sa_mask, SIGALRM); + sigaddset(&sa.sa_mask, SIGCHLD); + sigaddset(&sa.sa_mask, SIGHUP); + sa.sa_handler = retry; + sigaction(SIGALRM, &sa, NULL); + config(SIGHUP); + sa.sa_handler = config; + sigaction(SIGHUP, &sa, NULL); + sa.sa_handler = reapchild; + sigaction(SIGCHLD, &sa, NULL); + sa.sa_handler = goaway; + sigaction(SIGTERM, &sa, NULL); + sa.sa_handler = goaway; + sigaction(SIGINT, &sa, NULL); + sa.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &sa, &sapipe); + memset(&wait_mask, 0, sizeof(wait_mask)); + { + /* space for daemons to overwrite environment for ps */ +#define DUMMYSIZE 100 + char dummy[DUMMYSIZE]; + + (void) memset(dummy, 'x', DUMMYSIZE - 1); + dummy[DUMMYSIZE - 1] = '\0'; + + (void) setenv("inetd_dummy", dummy, 1); + } + + for (;;) { + int n, ctrl = -1; + fd_set readable; + + if (nsock == 0) { + Block_Using_Signals(omask); + while (nsock == 0) + sigsuspend(&wait_mask); + sigprocmask(SIG_UNBLOCK, &omask, NULL); + } + + readable = allsock; + n = select(maxsock + 1, &readable, NULL, NULL, NULL); + if (n <= 0) { + if (n < 0 && errno != EINTR) { + bb_perror_msg("select"); + sleep(1); + } + continue; + } + + for (sep = servtab; n && sep; sep = sep->se_next) { + if (sep->se_fd == -1 || !FD_ISSET(sep->se_fd, &readable)) + continue; + + n--; + if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) { + ctrl = accept(sep->se_fd, NULL, NULL); + if (ctrl < 0) { + if (errno == EINTR) + continue; + bb_perror_msg("accept (for %s)", sep->se_service); + continue; + } + if (sep->se_family == AF_INET && sep->se_socktype == SOCK_STREAM) { + struct sockaddr_in peer; + socklen_t plen = sizeof(peer); + + if (getpeername(ctrl, (struct sockaddr *) &peer, &plen) < 0) { + bb_error_msg("cannot getpeername"); + close(ctrl); + continue; + } + if (ntohs(peer.sin_port) == 20) { + /* XXX ftp bounce */ + close(ctrl); + continue; + } + } + } else + ctrl = sep->se_fd; + + Block_Using_Signals(omask); + pid = 0; +#ifdef INETD_FEATURE_ENABLED + if (sep->se_bi == 0 || sep->se_bi->bi_fork) +#endif + { + if (sep->se_count++ == 0) + (void) gettimeofday(&sep->se_time, NULL); + else if (toomany > 0 && sep->se_count >= sep->se_max) { + struct timeval now; + + (void) gettimeofday(&now, NULL); + if (now.tv_sec - sep->se_time.tv_sec > CNT_INTVL) { + sep->se_time = now; + sep->se_count = 1; + } else { + if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) + close(ctrl); + if (sep->se_family == AF_INET && + ntohs(sep->se_ctrladdr_in.sin_port) >= IPPORT_RESERVED) { + /* + * Cannot close it -- there are + * thieves on the system. + * Simply ignore the connection. + */ + --sep->se_count; + continue; + } + bb_error_msg("%s/%s server failing (looping), service terminated", + sep->se_service, sep->se_proto); + if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) + close(ctrl); + FD_CLR(sep->se_fd, &allsock); + (void) close(sep->se_fd); + sep->se_fd = -1; + sep->se_count = 0; + nsock--; + sigprocmask(SIG_UNBLOCK, &omask, NULL); + if (!timingout) { + timingout = 1; + alarm(RETRYTIME); + } + continue; + } + } + pid = fork(); + } + if (pid < 0) { + bb_perror_msg("fork"); + if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) + close(ctrl); + sigprocmask(SIG_UNBLOCK, &omask, NULL); + sleep(1); + continue; + } + if (pid && sep->se_wait) { + sep->se_wait = pid; + FD_CLR(sep->se_fd, &allsock); + nsock--; + } + sigprocmask(SIG_UNBLOCK, &omask, NULL); + if (pid == 0) { +#ifdef INETD_FEATURE_ENABLED + if (sep->se_bi) { + (*sep->se_bi->bi_fn)(ctrl, sep); + } else +#endif + { + pwd = getpwnam(sep->se_user); + if (pwd == NULL) { + bb_error_msg("getpwnam: %s: no such user", sep->se_user); + goto do_exit1; + } + if (setsid() < 0) + bb_perror_msg("%s: setsid", sep->se_service); + if (sep->se_group && (grp = getgrnam(sep->se_group)) == NULL) { + bb_error_msg("getgrnam: %s: no such group", sep->se_group); + goto do_exit1; + } + if (uid != 0) { + /* a user running private inetd */ + if (uid != pwd->pw_uid) + _exit(1); + } else if (pwd->pw_uid) { + if (sep->se_group) + pwd->pw_gid = grp->gr_gid; + xsetgid((gid_t) pwd->pw_gid); + initgroups(pwd->pw_name, pwd->pw_gid); + xsetuid((uid_t) pwd->pw_uid); + } else if (sep->se_group) { + xsetgid(grp->gr_gid); + setgroups(1, &grp->gr_gid); + } + dup2(ctrl, 0); + if (ctrl) close(ctrl); + dup2(0, 1); + dup2(0, 2); + if (rlim_ofile.rlim_cur != rlim_ofile_cur) + if (setrlimit(RLIMIT_NOFILE, &rlim_ofile) < 0) + bb_perror_msg("setrlimit"); + closelog(); + for (tmpint = rlim_ofile_cur - 1; --tmpint > 2;) + (void) close(tmpint); + sigaction(SIGPIPE, &sapipe, NULL); + execv(sep->se_server, sep->se_argv); + bb_perror_msg("execv %s", sep->se_server); +do_exit1: + if (sep->se_socktype != SOCK_STREAM) + recv(0, buf, sizeof(buf), 0); + _exit(1); + } + } + if (!sep->se_wait && sep->se_socktype == SOCK_STREAM) + close(ctrl); + } /* for (sep = servtab...) */ + } /* for(;;) */ +} + +/* + * Internet services provided internally by inetd: + */ +#define BUFSIZE 4096 + +#if defined(CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO) || \ + defined(CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN) || \ + defined(CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME) +static int dg_badinput(struct sockaddr_in *dg_sin) +{ + if (ntohs(dg_sin->sin_port) < IPPORT_RESERVED) + return 1; + if (dg_sin->sin_addr.s_addr == htonl(INADDR_BROADCAST)) + return 1; + /* XXX compare against broadcast addresses in SIOCGIFCONF list? */ + return 0; +} +#endif + +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO +/* Echo service -- echo data back */ +/* ARGSUSED */ +static void +echo_stream(int s, servtab_t *sep) +{ + char buffer[BUFSIZE]; + int i; + + inetd_setproctitle(sep->se_service, s); + while (1) { + i = read(s, buffer, sizeof(buffer)); + if (i <= 0) break; + /* FIXME: this isnt correct - safe_write()? */ + if (write(s, buffer, i) <= 0) break; + } + exit(0); +} + +/* Echo service -- echo data back */ +/* ARGSUSED */ +static void +echo_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) +{ + char buffer[BUFSIZE]; + int i; + socklen_t size; + /* struct sockaddr_storage ss; */ + struct sockaddr sa; + + size = sizeof(sa); + i = recvfrom(s, buffer, sizeof(buffer), 0, &sa, &size); + if (i < 0) + return; + if (dg_badinput((struct sockaddr_in *) &sa)) + return; + (void) sendto(s, buffer, i, 0, &sa, sizeof(sa)); +} +#endif /* CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO */ + +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD +/* Discard service -- ignore data */ +/* ARGSUSED */ +static void +discard_stream(int s, servtab_t *sep) +{ + char buffer[BUFSIZE]; + + inetd_setproctitle(sep->se_service, s); + while (1) { + errno = 0; + if (read(s, buffer, sizeof(buffer)) <= 0 && errno != EINTR) + exit(0); + } +} + +/* Discard service -- ignore data */ +/* ARGSUSED */ +static void +discard_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) +{ + char buffer[BUFSIZE]; + + (void) read(s, buffer, sizeof(buffer)); +} +#endif /* CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD */ + + +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN +#define LINESIZ 72 +static char ring[128]; +static char *endring; + +static void +initring(void) +{ + int i; + + endring = ring; + + for (i = 0; i <= 128; ++i) + if (isprint(i)) + *endring++ = i; +} + +/* Character generator */ +/* ARGSUSED */ +static void +chargen_stream(int s, servtab_t *sep) +{ + char *rs; + int len; + char text[LINESIZ + 2]; + + inetd_setproctitle(sep->se_service, s); + + if (!endring) { + initring(); + rs = ring; + } + + text[LINESIZ] = '\r'; + text[LINESIZ + 1] = '\n'; + rs = ring; + for (;;) { + len = endring - rs; + if (len >= LINESIZ) + memmove(text, rs, LINESIZ); + else { + memmove(text, rs, len); + memmove(text + len, ring, LINESIZ - len); + } + if (++rs == endring) + rs = ring; + if (write(s, text, sizeof(text)) != sizeof(text)) + break; + } + exit(0); +} + +/* Character generator */ +/* ARGSUSED */ +static void +chargen_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) +{ + /* struct sockaddr_storage ss; */ + struct sockaddr sa; + static char *rs; + int len; + char text[LINESIZ + 2]; + socklen_t size; + + if (endring == 0) { + initring(); + rs = ring; + } + + size = sizeof(sa); + if (recvfrom(s, text, sizeof(text), 0, &sa, &size) < 0) + return; + if (dg_badinput((struct sockaddr_in *) &sa)) + return; + + if ((len = endring - rs) >= LINESIZ) + memmove(text, rs, LINESIZ); + else { + memmove(text, rs, len); + memmove(text + len, ring, LINESIZ - len); + } + if (++rs == endring) + rs = ring; + text[LINESIZ] = '\r'; + text[LINESIZ + 1] = '\n'; + (void) sendto(s, text, sizeof(text), 0, &sa, sizeof(sa)); +} +#endif /* CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN */ + + +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_TIME +/* + * Return a machine readable date and time, in the form of the + * number of seconds since midnight, Jan 1, 1900. Since gettimeofday + * returns the number of seconds since midnight, Jan 1, 1970, + * we must add 2208988800 seconds to this figure to make up for + * some seventy years Bell Labs was asleep. + */ + +static u_int machtime(void) +{ + struct timeval tv; + + if (gettimeofday(&tv, NULL) < 0) { + fprintf(stderr, "Unable to get time of day\n"); + return 0L; + } + return htonl((u_int) tv.tv_sec + 2208988800UL); +} + +/* ARGSUSED */ +static void +machtime_stream(int s, servtab_t *sep ATTRIBUTE_UNUSED) +{ + u_int result; + + result = machtime(); + (void) write(s, (char *) &result, sizeof(result)); +} + +/* ARGSUSED */ +static void +machtime_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) +{ + u_int result; + /* struct sockaddr_storage ss; */ + struct sockaddr sa; + struct sockaddr_in *dg_sin; + socklen_t size; + + size = sizeof(sa); + if (recvfrom(s, (char *) &result, sizeof(result), 0, &sa, &size) < 0) + return; + /* if (dg_badinput((struct sockaddr *)&ss)) */ + dg_sin = (struct sockaddr_in *) &sa; + if (dg_sin->sin_addr.s_addr == htonl(INADDR_BROADCAST) || + ntohs(dg_sin->sin_port) < IPPORT_RESERVED / 2) + return; + result = machtime(); + (void) sendto(s, (char *) &result, sizeof(result), 0, &sa, sizeof(sa)); +} +#endif /* CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_TIME */ + + +#ifdef CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME +/* Return human-readable time of day */ +/* ARGSUSED */ +static void daytime_stream(int s, servtab_t *sep ATTRIBUTE_UNUSED) +{ + char buffer[256]; + time_t t; + + t = time(NULL); + + (void) sprintf(buffer, "%.24s\r\n", ctime(&t)); + (void) write(s, buffer, strlen(buffer)); +} + +/* Return human-readable time of day */ +/* ARGSUSED */ +void +daytime_dg(int s, servtab_t *sep ATTRIBUTE_UNUSED) +{ + char buffer[256]; + time_t t; + /* struct sockaddr_storage ss; */ + struct sockaddr sa; + socklen_t size; + + t = time(NULL); + + size = sizeof(sa); + if (recvfrom(s, buffer, sizeof(buffer), 0, &sa, &size) < 0) + return; + if (dg_badinput((struct sockaddr_in *) &sa)) + return; + (void) sprintf(buffer, "%.24s\r\n", ctime(&t)); + (void) sendto(s, buffer, strlen(buffer), 0, &sa, sizeof(sa)); +} +#endif /* CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME */ diff --git a/networking/interface.c b/networking/interface.c new file mode 100644 index 000000000..dd455823b --- /dev/null +++ b/networking/interface.c @@ -0,0 +1,1152 @@ +/* vi: set sw=4 ts=4: */ +/* + * stolen from net-tools-1.59 and stripped down for busybox by + * Erik Andersen + * + * Heavily modified by Manuel Novoa III Mar 12, 2001 + * + * Added print_bytes_scaled function to reduce code size. + * Added some (potentially) missing defines. + * Improved display support for -a and for a named interface. + * + * ----------------------------------------------------------- + * + * ifconfig This file contains an implementation of the command + * that either displays or sets the characteristics of + * one or more of the system's networking interfaces. + * + * Version: $Id: interface.c,v 1.25 2004/08/26 21:45:21 andersen Exp $ + * + * Author: Fred N. van Kempen, + * and others. Copyright 1993 MicroWalt Corporation + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Patched to support 'add' and 'del' keywords for INET(4) addresses + * by Mrs. Brisby + * + * {1.34} - 19980630 - Arnaldo Carvalho de Melo + * - gettext instead of catgets for i18n + * 10/1998 - Andi Kleen. Use interface list primitives. + * 20001008 - Bernd Eckenfels, Patch from RH for setting mtu + * (default AF was wrong) + */ + +#include "inet_common.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "busybox.h" + +#ifdef CONFIG_FEATURE_IPV6 +# define HAVE_AFINET6 1 +#else +# undef HAVE_AFINET6 +#endif + +#define _PATH_PROCNET_DEV "/proc/net/dev" +#define _PATH_PROCNET_IFINET6 "/proc/net/if_inet6" + +#ifdef HAVE_AFINET6 + +#ifndef _LINUX_IN6_H +/* + * This is in linux/include/net/ipv6.h. + */ + +struct in6_ifreq { + struct in6_addr ifr6_addr; + uint32_t ifr6_prefixlen; + unsigned int ifr6_ifindex; +}; + +#endif + +#endif /* HAVE_AFINET6 */ + +/* Defines for glibc2.0 users. */ +#ifndef SIOCSIFTXQLEN +#define SIOCSIFTXQLEN 0x8943 +#define SIOCGIFTXQLEN 0x8942 +#endif + +/* ifr_qlen is ifru_ivalue, but it isn't present in 2.0 kernel headers */ +#ifndef ifr_qlen +#define ifr_qlen ifr_ifru.ifru_mtu +#endif + +#ifndef HAVE_TXQUEUELEN +#define HAVE_TXQUEUELEN 1 +#endif + +#ifndef IFF_DYNAMIC +#define IFF_DYNAMIC 0x8000 /* dialup device with changing addresses */ +#endif + +/* This structure defines protocol families and their handlers. */ +struct aftype { + const char *name; + const char *title; + int af; + int alen; + char *(*print) (unsigned char *); + char *(*sprint) (struct sockaddr *, int numeric); + int (*input) (int type, char *bufp, struct sockaddr *); + void (*herror) (char *text); + int (*rprint) (int options); + int (*rinput) (int typ, int ext, char **argv); + + /* may modify src */ + int (*getmask) (char *src, struct sockaddr * mask, char *name); + + int fd; + char *flag_file; +}; + +/* Display an Internet socket address. */ +static char *INET_sprint(struct sockaddr *sap, int numeric) +{ + static char buff[128]; + + if (sap->sa_family == 0xFFFF || sap->sa_family == 0) + return safe_strncpy(buff, "[NONE SET]", sizeof(buff)); + + if (INET_rresolve(buff, sizeof(buff), (struct sockaddr_in *) sap, + numeric, 0xffffff00) != 0) + return NULL; + + return buff; +} + +static struct aftype inet_aftype = { + .name = "inet", + .title = "DARPA Internet", + .af = AF_INET, + .alen = 4, + .sprint = INET_sprint, + .fd = -1 +}; + +#ifdef HAVE_AFINET6 + +/* Display an Internet socket address. */ +/* dirty! struct sockaddr usually doesn't suffer for inet6 addresses, fst. */ +static char *INET6_sprint(struct sockaddr *sap, int numeric) +{ + static char buff[128]; + + if (sap->sa_family == 0xFFFF || sap->sa_family == 0) + return safe_strncpy(buff, "[NONE SET]", sizeof(buff)); + if (INET6_rresolve + (buff, sizeof(buff), (struct sockaddr_in6 *) sap, numeric) != 0) + return safe_strncpy(buff, "[UNKNOWN]", sizeof(buff)); + return buff; +} + +static struct aftype inet6_aftype = { + .name = "inet6", + .title = "IPv6", + .af = AF_INET6, + .alen = sizeof(struct in6_addr), + .sprint = INET6_sprint, + .fd = -1 +}; + +#endif /* HAVE_AFINET6 */ + +/* Display an UNSPEC address. */ +static char *UNSPEC_print(unsigned char *ptr) +{ + static char buff[sizeof(struct sockaddr) * 3 + 1]; + char *pos; + unsigned int i; + + pos = buff; + for (i = 0; i < sizeof(struct sockaddr); i++) { + /* careful -- not every libc's sprintf returns # bytes written */ + sprintf(pos, "%02X-", (*ptr++ & 0377)); + pos += 3; + } + /* Erase trailing "-". Works as long as sizeof(struct sockaddr) != 0 */ + *--pos = '\0'; + return buff; +} + +/* Display an UNSPEC socket address. */ +static char *UNSPEC_sprint(struct sockaddr *sap, int numeric) +{ + static char buf[64]; + + if (sap->sa_family == 0xFFFF || sap->sa_family == 0) + return safe_strncpy(buf, "[NONE SET]", sizeof(buf)); + return UNSPEC_print((unsigned char *)sap->sa_data); +} + +static struct aftype unspec_aftype = { + "unspec", "UNSPEC", AF_UNSPEC, 0, + UNSPEC_print, UNSPEC_sprint, NULL, NULL, + NULL, +}; + +static struct aftype * const aftypes[] = { + &inet_aftype, +#ifdef HAVE_AFINET6 + &inet6_aftype, +#endif + &unspec_aftype, + NULL +}; + +/* Check our protocol family table for this family. */ +static struct aftype *get_afntype(int af) +{ + struct aftype * const *afp; + + afp = aftypes; + while (*afp != NULL) { + if ((*afp)->af == af) + return *afp; + afp++; + } + return NULL; +} + +/* Check our protocol family table for this family and return its socket */ +static int get_socket_for_af(int af) +{ + struct aftype * const *afp; + + afp = aftypes; + while (*afp != NULL) { + if ((*afp)->af == af) + return (*afp)->fd; + afp++; + } + return -1; +} + +struct user_net_device_stats { + unsigned long long rx_packets; /* total packets received */ + unsigned long long tx_packets; /* total packets transmitted */ + unsigned long long rx_bytes; /* total bytes received */ + unsigned long long tx_bytes; /* total bytes transmitted */ + unsigned long rx_errors; /* bad packets received */ + unsigned long tx_errors; /* packet transmit problems */ + unsigned long rx_dropped; /* no space in linux buffers */ + unsigned long tx_dropped; /* no space available in linux */ + unsigned long rx_multicast; /* multicast packets received */ + unsigned long rx_compressed; + unsigned long tx_compressed; + unsigned long collisions; + + /* detailed rx_errors: */ + unsigned long rx_length_errors; + unsigned long rx_over_errors; /* receiver ring buff overflow */ + unsigned long rx_crc_errors; /* recved pkt with crc error */ + unsigned long rx_frame_errors; /* recv'd frame alignment error */ + unsigned long rx_fifo_errors; /* recv'r fifo overrun */ + unsigned long rx_missed_errors; /* receiver missed packet */ + /* detailed tx_errors */ + unsigned long tx_aborted_errors; + unsigned long tx_carrier_errors; + unsigned long tx_fifo_errors; + unsigned long tx_heartbeat_errors; + unsigned long tx_window_errors; +}; + +struct interface { + struct interface *next, *prev; + char name[IFNAMSIZ]; /* interface name */ + short type; /* if type */ + short flags; /* various flags */ + int metric; /* routing metric */ + int mtu; /* MTU value */ + int tx_queue_len; /* transmit queue length */ + struct ifmap map; /* hardware setup */ + struct sockaddr addr; /* IP address */ + struct sockaddr dstaddr; /* P-P IP address */ + struct sockaddr broadaddr; /* IP broadcast address */ + struct sockaddr netmask; /* IP network mask */ + int has_ip; + char hwaddr[32]; /* HW address */ + int statistics_valid; + struct user_net_device_stats stats; /* statistics */ + int keepalive; /* keepalive value for SLIP */ + int outfill; /* outfill value for SLIP */ +}; + + +int interface_opt_a; /* show all interfaces */ + +static struct interface *int_list, *int_last; +static int skfd = -1; /* generic raw socket desc. */ + + +static int sockets_open(int family) +{ + struct aftype * const *aft; + int sfd = -1; + static int force = -1; + + if (force < 0) { + force = 0; + if (get_linux_version_code() < KERNEL_VERSION(2,1,0)) + force = 1; + if (access("/proc/net", R_OK)) + force = 1; + } + for (aft = aftypes; *aft; aft++) { + struct aftype *af = *aft; + int type = SOCK_DGRAM; + + if (af->af == AF_UNSPEC) + continue; + if (family && family != af->af) + continue; + if (af->fd != -1) { + sfd = af->fd; + continue; + } + /* Check some /proc file first to not stress kmod */ + if (!family && !force && af->flag_file) { + if (access(af->flag_file, R_OK)) + continue; + } + af->fd = socket(af->af, type, 0); + if (af->fd >= 0) + sfd = af->fd; + } + if (sfd < 0) { + bb_error_msg("no usable address families found"); + } + return sfd; +} + +#ifdef CONFIG_FEATURE_CLEAN_UP +static void sockets_close(void) +{ + struct aftype * const *aft; + for (aft = aftypes; *aft != NULL; aft++) { + struct aftype *af = *aft; + if( af->fd != -1 ) { + close(af->fd); + af->fd = -1; + } + } +} +#endif +#if 0 +/* like strcmp(), but knows about numbers */ +except that the freshly added calls to xatoul() brf on ethernet aliases with +uClibc with e.g.: ife->name='lo' name='eth0:1' +static int nstrcmp(const char *a, const char *b) +{ + const char *a_ptr = a; + const char *b_ptr = b; + + while (*a == *b) { + if (*a == '\0') { + return 0; + } + if (!isdigit(*a) && isdigit(*(a+1))) { + a_ptr = a+1; + b_ptr = b+1; + } + a++; + b++; + } + + if (isdigit(*a) && isdigit(*b)) { + return xatoul(a_ptr) > xatoul(b_ptr) ? 1 : -1; + } + return *a - *b; +} +#endif + +static struct interface *add_interface(char *name) +{ + struct interface *ife, **nextp, *new; + + for (ife = int_last; ife; ife = ife->prev) { + int n = /*n*/strcmp(ife->name, name); + + if (n == 0) + return ife; + if (n < 0) + break; + } + + new = xzalloc(sizeof(*new)); + safe_strncpy(new->name, name, IFNAMSIZ); + nextp = ife ? &ife->next : &int_list; + new->prev = ife; + new->next = *nextp; + if (new->next) + new->next->prev = new; + else + int_last = new; + *nextp = new; + return new; +} + + +static int if_readconf(void) +{ + int numreqs = 30; + struct ifconf ifc; + struct ifreq *ifr; + int n, err = -1; + int skfd2; + + /* SIOCGIFCONF currently seems to only work properly on AF_INET sockets + (as of 2.1.128) */ + skfd2 = get_socket_for_af(AF_INET); + if (skfd2 < 0) { + bb_perror_msg(("warning: no inet socket available")); + /* Try to soldier on with whatever socket we can get hold of. */ + skfd2 = sockets_open(0); + if (skfd2 < 0) + return -1; + } + + ifc.ifc_buf = NULL; + for (;;) { + ifc.ifc_len = sizeof(struct ifreq) * numreqs; + ifc.ifc_buf = xrealloc(ifc.ifc_buf, ifc.ifc_len); + + if (ioctl(skfd2, SIOCGIFCONF, &ifc) < 0) { + perror("SIOCGIFCONF"); + goto out; + } + if (ifc.ifc_len == sizeof(struct ifreq) * numreqs) { + /* assume it overflowed and try again */ + numreqs += 10; + continue; + } + break; + } + + ifr = ifc.ifc_req; + for (n = 0; n < ifc.ifc_len; n += sizeof(struct ifreq)) { + add_interface(ifr->ifr_name); + ifr++; + } + err = 0; + + out: + free(ifc.ifc_buf); + return err; +} + +static char *get_name(char *name, char *p) +{ + /* Extract from nul-terminated p where p matches + : after leading whitespace. + If match is not made, set name empty and return unchanged p */ + int namestart=0, nameend=0; + while (isspace(p[namestart])) + namestart++; + nameend=namestart; + while (p[nameend] && p[nameend]!=':' && !isspace(p[nameend])) + nameend++; + if (p[nameend]==':') { + if ((nameend-namestart)stats, 0, sizeof(struct user_net_device_stats)); + + sscanf(bp, ss_fmt[procnetdev_vsn], + &ife->stats.rx_bytes, /* missing for 0 */ + &ife->stats.rx_packets, + &ife->stats.rx_errors, + &ife->stats.rx_dropped, + &ife->stats.rx_fifo_errors, + &ife->stats.rx_frame_errors, + &ife->stats.rx_compressed, /* missing for <= 1 */ + &ife->stats.rx_multicast, /* missing for <= 1 */ + &ife->stats.tx_bytes, /* missing for 0 */ + &ife->stats.tx_packets, + &ife->stats.tx_errors, + &ife->stats.tx_dropped, + &ife->stats.tx_fifo_errors, + &ife->stats.collisions, + &ife->stats.tx_carrier_errors, + &ife->stats.tx_compressed /* missing for <= 1 */ + ); + + if (procnetdev_vsn <= 1) { + if (procnetdev_vsn == 0) { + ife->stats.rx_bytes = 0; + ife->stats.tx_bytes = 0; + } + ife->stats.rx_multicast = 0; + ife->stats.rx_compressed = 0; + ife->stats.tx_compressed = 0; + } +} + +static inline int procnetdev_version(char *buf) +{ + if (strstr(buf, "compressed")) + return 2; + if (strstr(buf, "bytes")) + return 1; + return 0; +} + +static int if_readlist_proc(char *target) +{ + static int proc_read; + FILE *fh; + char buf[512]; + struct interface *ife; + int err, procnetdev_vsn; + + if (proc_read) + return 0; + if (!target) + proc_read = 1; + + fh = fopen(_PATH_PROCNET_DEV, "r"); + if (!fh) { + bb_perror_msg("warning: cannot open %s, limiting output", _PATH_PROCNET_DEV); + return if_readconf(); + } + fgets(buf, sizeof buf, fh); /* eat line */ + fgets(buf, sizeof buf, fh); + + procnetdev_vsn = procnetdev_version(buf); + + err = 0; + while (fgets(buf, sizeof buf, fh)) { + char *s, name[128]; + + s = get_name(name, buf); + ife = add_interface(name); + get_dev_fields(s, ife, procnetdev_vsn); + ife->statistics_valid = 1; + if (target && !strcmp(target, name)) + break; + } + if (ferror(fh)) { + perror(_PATH_PROCNET_DEV); + err = -1; + proc_read = 0; + } + fclose(fh); + return err; +} + +static int if_readlist(void) +{ + int err = if_readlist_proc(NULL); + + if (!err) + err = if_readconf(); + return err; +} + +static int for_all_interfaces(int (*doit) (struct interface *, void *), + void *cookie) +{ + struct interface *ife; + + if (!int_list && (if_readlist() < 0)) + return -1; + for (ife = int_list; ife; ife = ife->next) { + int err = doit(ife, cookie); + + if (err) + return err; + } + return 0; +} + +/* Fetch the interface configuration from the kernel. */ +static int if_fetch(struct interface *ife) +{ + struct ifreq ifr; + int fd; + char *ifname = ife->name; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(skfd, SIOCGIFFLAGS, &ifr) < 0) + return -1; + ife->flags = ifr.ifr_flags; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(skfd, SIOCGIFHWADDR, &ifr) < 0) + memset(ife->hwaddr, 0, 32); + else + memcpy(ife->hwaddr, ifr.ifr_hwaddr.sa_data, 8); + + ife->type = ifr.ifr_hwaddr.sa_family; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(skfd, SIOCGIFMETRIC, &ifr) < 0) + ife->metric = 0; + else + ife->metric = ifr.ifr_metric; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(skfd, SIOCGIFMTU, &ifr) < 0) + ife->mtu = 0; + else + ife->mtu = ifr.ifr_mtu; + +#ifdef SIOCGIFMAP + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(skfd, SIOCGIFMAP, &ifr) == 0) + ife->map = ifr.ifr_map; + else +#endif + memset(&ife->map, 0, sizeof(struct ifmap)); + +#ifdef HAVE_TXQUEUELEN + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(skfd, SIOCGIFTXQLEN, &ifr) < 0) + ife->tx_queue_len = -1; /* unknown value */ + else + ife->tx_queue_len = ifr.ifr_qlen; +#else + ife->tx_queue_len = -1; /* unknown value */ +#endif + + /* IPv4 address? */ + fd = get_socket_for_af(AF_INET); + if (fd >= 0) { + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + ifr.ifr_addr.sa_family = AF_INET; + if (ioctl(fd, SIOCGIFADDR, &ifr) == 0) { + ife->has_ip = 1; + ife->addr = ifr.ifr_addr; + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(fd, SIOCGIFDSTADDR, &ifr) < 0) + memset(&ife->dstaddr, 0, sizeof(struct sockaddr)); + else + ife->dstaddr = ifr.ifr_dstaddr; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(fd, SIOCGIFBRDADDR, &ifr) < 0) + memset(&ife->broadaddr, 0, sizeof(struct sockaddr)); + else + ife->broadaddr = ifr.ifr_broadaddr; + + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + if (ioctl(fd, SIOCGIFNETMASK, &ifr) < 0) + memset(&ife->netmask, 0, sizeof(struct sockaddr)); + else + ife->netmask = ifr.ifr_netmask; + } else + memset(&ife->addr, 0, sizeof(struct sockaddr)); + } + + return 0; +} + + +static int do_if_fetch(struct interface *ife) +{ + if (if_fetch(ife) < 0) { + char *errmsg; + + if (errno == ENODEV) { + /* Give better error message for this case. */ + errmsg = "Device not found"; + } else { + errmsg = strerror(errno); + } + bb_error_msg("%s: error fetching interface information: %s", + ife->name, errmsg); + return -1; + } + return 0; +} + +/* This structure defines hardware protocols and their handlers. */ +struct hwtype { + const char * const name; + const char *title; + int type; + int alen; + char *(*print) (unsigned char *); + int (*input) (char *, struct sockaddr *); + int (*activate) (int fd); + int suppress_null_addr; +}; + +static const struct hwtype unspec_hwtype = { + .name = "unspec", + .title = "UNSPEC", + .type = -1, + .print = UNSPEC_print +}; + +static const struct hwtype loop_hwtype = { + .name = "loop", + .title = "Local Loopback", + .type = ARPHRD_LOOPBACK +}; + +#include + +#if (__GLIBC__ >=2 && __GLIBC_MINOR__ >= 1) || defined(_NEWLIB_VERSION) +#include +#else +#include +#endif + +/* Display an Ethernet address in readable format. */ +static char *pr_ether(unsigned char *ptr) +{ + static char buff[64]; + + snprintf(buff, sizeof(buff), "%02X:%02X:%02X:%02X:%02X:%02X", + (ptr[0] & 0377), (ptr[1] & 0377), (ptr[2] & 0377), + (ptr[3] & 0377), (ptr[4] & 0377), (ptr[5] & 0377) + ); + return buff; +} + +static const struct hwtype ether_hwtype = { + .name = "ether", + .title = "Ethernet", + .type = ARPHRD_ETHER, + .alen = ETH_ALEN, + .print = pr_ether +}; + +#include + +static const struct hwtype ppp_hwtype = { + .name = "ppp", + .title = "Point-to-Point Protocol", + .type = ARPHRD_PPP +}; + +#ifdef CONFIG_FEATURE_IPV6 +static const struct hwtype sit_hwtype = { + .name = "sit", + .title = "IPv6-in-IPv4", + .type = ARPHRD_SIT, + .print = UNSPEC_print, + .suppress_null_addr = 1 +} ; +#endif + +static const struct hwtype * const hwtypes[] = { + &loop_hwtype, + ðer_hwtype, + &ppp_hwtype, + &unspec_hwtype, +#ifdef CONFIG_FEATURE_IPV6 + &sit_hwtype, +#endif + NULL +}; + +#ifdef IFF_PORTSEL +static const char * const if_port_text[] = { + /* Keep in step with */ + "unknown", + "10base2", + "10baseT", + "AUI", + "100baseT", + "100baseTX", + "100baseFX", + NULL +}; +#endif + +/* Check our hardware type table for this type. */ +static const struct hwtype *get_hwntype(int type) +{ + const struct hwtype * const *hwp; + + hwp = hwtypes; + while (*hwp != NULL) { + if ((*hwp)->type == type) + return *hwp; + hwp++; + } + return NULL; +} + +/* return 1 if address is all zeros */ +static int hw_null_address(const struct hwtype *hw, void *ap) +{ + unsigned int i; + unsigned char *address = (unsigned char *) ap; + + for (i = 0; i < hw->alen; i++) + if (address[i]) + return 0; + return 1; +} + +static const char TRext[] = "\0\0\0Ki\0Mi\0Gi\0Ti"; + +static void print_bytes_scaled(unsigned long long ull, const char *end) +{ + unsigned long long int_part; + const char *ext; + unsigned int frac_part; + int i; + + frac_part = 0; + ext = TRext; + int_part = ull; + i = 4; + do { + if (int_part >= 1024) { + frac_part = ((((unsigned int) int_part) & (1024-1)) * 10) / 1024; + int_part /= 1024; + ext += 3; /* KiB, MiB, GiB, TiB */ + } + --i; + } while (i); + + printf("X bytes:%llu (%llu.%u %sB)%s", ull, int_part, frac_part, ext, end); +} + +static const char * const ife_print_flags_strs[] = { + "UP ", + "BROADCAST ", + "DEBUG ", + "LOOPBACK ", + "POINTOPOINT ", + "NOTRAILERS ", + "RUNNING ", + "NOARP ", + "PROMISC ", + "ALLMULTI ", + "SLAVE ", + "MASTER ", + "MULTICAST ", +#ifdef HAVE_DYNAMIC + "DYNAMIC " +#endif +}; + +static const unsigned short ife_print_flags_mask[] = { + IFF_UP, + IFF_BROADCAST, + IFF_DEBUG, + IFF_LOOPBACK, + IFF_POINTOPOINT, + IFF_NOTRAILERS, + IFF_RUNNING, + IFF_NOARP, + IFF_PROMISC, + IFF_ALLMULTI, + IFF_SLAVE, + IFF_MASTER, + IFF_MULTICAST, +#ifdef HAVE_DYNAMIC + IFF_DYNAMIC +#endif + 0 +}; + +static void ife_print(struct interface *ptr) +{ + struct aftype *ap; + const struct hwtype *hw; + int hf; + int can_compress = 0; + +#ifdef HAVE_AFINET6 + FILE *f; + char addr6[40], devname[20]; + struct sockaddr_in6 sap; + int plen, scope, dad_status, if_idx; + char addr6p[8][5]; +#endif + + ap = get_afntype(ptr->addr.sa_family); + if (ap == NULL) + ap = get_afntype(0); + + hf = ptr->type; + + if (hf == ARPHRD_CSLIP || hf == ARPHRD_CSLIP6) + can_compress = 1; + + hw = get_hwntype(hf); + if (hw == NULL) + hw = get_hwntype(-1); + + printf("%-9.9s Link encap:%s ", ptr->name, hw->title); + /* For some hardware types (eg Ash, ATM) we don't print the + hardware address if it's null. */ + if (hw->print != NULL && (!(hw_null_address(hw, ptr->hwaddr) && + hw->suppress_null_addr))) + printf("HWaddr %s ", hw->print((unsigned char *)ptr->hwaddr)); +#ifdef IFF_PORTSEL + if (ptr->flags & IFF_PORTSEL) { + printf("Media:%s", if_port_text[ptr->map.port] /* [0] */); + if (ptr->flags & IFF_AUTOMEDIA) + printf("(auto)"); + } +#endif + puts(""); + + if (ptr->has_ip) { + printf(" %s addr:%s ", ap->name, + ap->sprint(&ptr->addr, 1)); + if (ptr->flags & IFF_POINTOPOINT) { + printf(" P-t-P:%s ", ap->sprint(&ptr->dstaddr, 1)); + } + if (ptr->flags & IFF_BROADCAST) { + printf(" Bcast:%s ", ap->sprint(&ptr->broadaddr, 1)); + } + printf(" Mask:%s\n", ap->sprint(&ptr->netmask, 1)); + } + +#ifdef HAVE_AFINET6 + +#define IPV6_ADDR_ANY 0x0000U + +#define IPV6_ADDR_UNICAST 0x0001U +#define IPV6_ADDR_MULTICAST 0x0002U +#define IPV6_ADDR_ANYCAST 0x0004U + +#define IPV6_ADDR_LOOPBACK 0x0010U +#define IPV6_ADDR_LINKLOCAL 0x0020U +#define IPV6_ADDR_SITELOCAL 0x0040U + +#define IPV6_ADDR_COMPATv4 0x0080U + +#define IPV6_ADDR_SCOPE_MASK 0x00f0U + +#define IPV6_ADDR_MAPPED 0x1000U +#define IPV6_ADDR_RESERVED 0x2000U /* reserved address space */ + + if ((f = fopen(_PATH_PROCNET_IFINET6, "r")) != NULL) { + while (fscanf + (f, "%4s%4s%4s%4s%4s%4s%4s%4s %02x %02x %02x %02x %20s\n", + addr6p[0], addr6p[1], addr6p[2], addr6p[3], addr6p[4], + addr6p[5], addr6p[6], addr6p[7], &if_idx, &plen, &scope, + &dad_status, devname) != EOF) { + if (!strcmp(devname, ptr->name)) { + sprintf(addr6, "%s:%s:%s:%s:%s:%s:%s:%s", + addr6p[0], addr6p[1], addr6p[2], addr6p[3], + addr6p[4], addr6p[5], addr6p[6], addr6p[7]); + inet_pton(AF_INET6, addr6, + (struct sockaddr *) &sap.sin6_addr); + sap.sin6_family = AF_INET6; + printf(" inet6 addr: %s/%d", + inet6_aftype.sprint((struct sockaddr *) &sap, 1), + plen); + printf(" Scope:"); + switch (scope & IPV6_ADDR_SCOPE_MASK) { + case 0: + printf("Global"); + break; + case IPV6_ADDR_LINKLOCAL: + printf("Link"); + break; + case IPV6_ADDR_SITELOCAL: + printf("Site"); + break; + case IPV6_ADDR_COMPATv4: + printf("Compat"); + break; + case IPV6_ADDR_LOOPBACK: + printf("Host"); + break; + default: + printf("Unknown"); + } + puts(""); + } + } + fclose(f); + } +#endif + + printf(" "); + /* DONT FORGET TO ADD THE FLAGS IN ife_print_short, too */ + + if (ptr->flags == 0) { + printf("[NO FLAGS] "); + } else { + int i = 0; + do { + if (ptr->flags & ife_print_flags_mask[i]) { + printf(ife_print_flags_strs[i]); + } + } while (ife_print_flags_mask[++i]); + } + + /* DONT FORGET TO ADD THE FLAGS IN ife_print_short */ + printf(" MTU:%d Metric:%d", ptr->mtu, ptr->metric ? ptr->metric : 1); +#ifdef SIOCSKEEPALIVE + if (ptr->outfill || ptr->keepalive) + printf(" Outfill:%d Keepalive:%d", ptr->outfill, ptr->keepalive); +#endif + puts(""); + + /* If needed, display the interface statistics. */ + + if (ptr->statistics_valid) { + /* XXX: statistics are currently only printed for the primary address, + * not for the aliases, although strictly speaking they're shared + * by all addresses. + */ + printf(" "); + + printf("RX packets:%llu errors:%lu dropped:%lu overruns:%lu frame:%lu\n", + ptr->stats.rx_packets, ptr->stats.rx_errors, + ptr->stats.rx_dropped, ptr->stats.rx_fifo_errors, + ptr->stats.rx_frame_errors); + if (can_compress) + printf(" compressed:%lu\n", + ptr->stats.rx_compressed); + printf(" "); + printf("TX packets:%llu errors:%lu dropped:%lu overruns:%lu carrier:%lu\n", + ptr->stats.tx_packets, ptr->stats.tx_errors, + ptr->stats.tx_dropped, ptr->stats.tx_fifo_errors, + ptr->stats.tx_carrier_errors); + printf(" collisions:%lu ", ptr->stats.collisions); + if (can_compress) + printf("compressed:%lu ", ptr->stats.tx_compressed); + if (ptr->tx_queue_len != -1) + printf("txqueuelen:%d ", ptr->tx_queue_len); + printf("\n R"); + print_bytes_scaled(ptr->stats.rx_bytes, " T"); + print_bytes_scaled(ptr->stats.tx_bytes, "\n"); + + } + + if ((ptr->map.irq || ptr->map.mem_start || ptr->map.dma || + ptr->map.base_addr)) { + printf(" "); + if (ptr->map.irq) + printf("Interrupt:%d ", ptr->map.irq); + if (ptr->map.base_addr >= 0x100) /* Only print devices using it for + I/O maps */ + printf("Base address:0x%lx ", + (unsigned long) ptr->map.base_addr); + if (ptr->map.mem_start) { + printf("Memory:%lx-%lx ", ptr->map.mem_start, + ptr->map.mem_end); + } + if (ptr->map.dma) + printf("DMA chan:%x ", ptr->map.dma); + puts(""); + } + puts(""); +} + + +static int do_if_print(struct interface *ife, void *cookie) +{ + int *opt_a = (int *) cookie; + int res; + + res = do_if_fetch(ife); + if (res >= 0) { + if ((ife->flags & IFF_UP) || *opt_a) + ife_print(ife); + } + return res; +} + +static struct interface *lookup_interface(char *name) +{ + struct interface *ife = NULL; + + if (if_readlist_proc(name) < 0) + return NULL; + ife = add_interface(name); + return ife; +} + +/* for ipv4 add/del modes */ +static int if_print(char *ifname) +{ + int res; + + if (!ifname) { + res = for_all_interfaces(do_if_print, &interface_opt_a); + } else { + struct interface *ife; + + ife = lookup_interface(ifname); + res = do_if_fetch(ife); + if (res >= 0) + ife_print(ife); + } + return res; +} + +int display_interfaces(char *ifname); +int display_interfaces(char *ifname) +{ + int status; + + /* Create a channel to the NET kernel. */ + if ((skfd = sockets_open(0)) < 0) { + bb_perror_msg_and_die("socket"); + } + + /* Do we have to show the current setup? */ + status = if_print(ifname); +#ifdef CONFIG_FEATURE_CLEAN_UP + sockets_close(); +#endif + exit(status < 0); +} diff --git a/networking/ip.c b/networking/ip.c new file mode 100644 index 000000000..4c8b89e2f --- /dev/null +++ b/networking/ip.c @@ -0,0 +1,48 @@ +/* vi: set sw=4 ts=4: */ +/* + * ip.c "ip" utility frontend. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, + * + * + * Changes: + * + * Rani Assaf 980929: resolve addresses + */ + +#include "busybox.h" + +#include "libiproute/utils.h" +#include "libiproute/ip_common.h" + +int ip_main(int argc, char **argv) +{ + int ret = EXIT_FAILURE; + + ip_parse_common_args(&argc, &argv); + + if (argc > 1) { + if (ENABLE_FEATURE_IP_ADDRESS && matches(argv[1], "address") == 0) { + ret = do_ipaddr(argc-2, argv+2); + } + if (ENABLE_FEATURE_IP_ROUTE && matches(argv[1], "route") == 0) { + ret = do_iproute(argc-2, argv+2); + } + if (ENABLE_FEATURE_IP_LINK && matches(argv[1], "link") == 0) { + ret = do_iplink(argc-2, argv+2); + } + if (ENABLE_FEATURE_IP_TUNNEL && + (matches(argv[1], "tunnel") == 0 || strcmp(argv[1], "tunl") == 0)) { + ret = do_iptunnel(argc-2, argv+2); + } + if (ENABLE_FEATURE_IP_RULE && matches(argv[1], "rule") == 0) { + ret = do_iprule(argc-2, argv+2); + } + } + if (ret) { + bb_show_usage(); + } + return EXIT_SUCCESS; +} diff --git a/networking/ipaddr.c b/networking/ipaddr.c new file mode 100644 index 000000000..c90780879 --- /dev/null +++ b/networking/ipaddr.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * ip.c "ip" utility frontend. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, + * + * + * Changes: + * + * Rani Assaf 980929: resolve addresses + */ + +#include "libiproute/utils.h" +#include "libiproute/ip_common.h" + +#include "busybox.h" + +int ipaddr_main(int argc, char **argv) +{ + ip_parse_common_args(&argc, &argv); + + return do_ipaddr(argc-1, argv+1); +} diff --git a/networking/ipcalc.c b/networking/ipcalc.c new file mode 100644 index 000000000..0ee9646c2 --- /dev/null +++ b/networking/ipcalc.c @@ -0,0 +1,194 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini ipcalc implementation for busybox + * + * By Jordan Crouse + * Stephan Linz + * + * This is a complete reimplementation of the ipcalc program + * from Red Hat. I didn't look at their source code, but there + * is no denying that this is a loving reimplementation + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include +#include +#include +#include + +#define CLASS_A_NETMASK ntohl(0xFF000000) +#define CLASS_B_NETMASK ntohl(0xFFFF0000) +#define CLASS_C_NETMASK ntohl(0xFFFFFF00) + +static unsigned long get_netmask(unsigned long ipaddr) +{ + ipaddr = htonl(ipaddr); + + if ((ipaddr & 0xC0000000) == 0xC0000000) + return CLASS_C_NETMASK; + else if ((ipaddr & 0x80000000) == 0x80000000) + return CLASS_B_NETMASK; + else if ((ipaddr & 0x80000000) == 0) + return CLASS_A_NETMASK; + else + return 0; +} + +#ifdef CONFIG_FEATURE_IPCALC_FANCY +static int get_prefix(unsigned long netmask) +{ + unsigned long msk = 0x80000000; + int ret = 0; + + netmask = htonl(netmask); + while(msk) { + if (netmask & msk) + ret++; + msk >>= 1; + } + return ret; +} +#else +int get_prefix(unsigned long netmask); +#endif + + +#define NETMASK 0x01 +#define BROADCAST 0x02 +#define NETWORK 0x04 +#define NETPREFIX 0x08 +#define HOSTNAME 0x10 +#define SILENT 0x20 + +#if ENABLE_FEATURE_IPCALC_LONG_OPTIONS + static const struct option long_options[] = { + { "netmask", no_argument, NULL, 'm' }, + { "broadcast", no_argument, NULL, 'b' }, + { "network", no_argument, NULL, 'n' }, +# if ENABLE_FEATURE_IPCALC_FANCY + { "prefix", no_argument, NULL, 'p' }, + { "hostname", no_argument, NULL, 'h' }, + { "silent", no_argument, NULL, 's' }, +# endif + { NULL, 0, NULL, 0 } + }; +#endif + +int ipcalc_main(int argc, char **argv) +{ + unsigned opt; + int have_netmask = 0; + in_addr_t netmask, broadcast, network, ipaddr; + struct in_addr a; + char *ipstr; + +#if ENABLE_FEATURE_IPCALC_LONG_OPTIONS + applet_long_options = long_options; +#endif + opt = getopt32(argc, argv, "mbn" USE_FEATURE_IPCALC_FANCY("phs")); + argc -= optind; + argv += optind; + if (opt & (BROADCAST | NETWORK | NETPREFIX)) { + if (argc > 2 || argc <= 0) + bb_show_usage(); + } else { + if (argc != 1) + bb_show_usage(); + } + if (opt & SILENT) + logmode = LOGMODE_NONE; /* Suppress error_msg() output */ + + ipstr = argv[0]; + if (ENABLE_FEATURE_IPCALC_FANCY) { + unsigned long netprefix = 0; + char *prefixstr; + + prefixstr = ipstr; + + while (*prefixstr) { + if (*prefixstr == '/') { + *prefixstr = (char)0; + prefixstr++; + if (*prefixstr) { + unsigned msk; + netprefix = xatoul_range(prefixstr, 0, 32); + netmask = 0; + msk = 0x80000000; + while (netprefix > 0) { + netmask |= msk; + msk >>= 1; + netprefix--; + } + netmask = htonl(netmask); + /* Even if it was 0, we will signify that we have a netmask. This allows */ + /* for specification of default routes, etc which have a 0 netmask/prefix */ + have_netmask = 1; + } + break; + } + prefixstr++; + } + } + ipaddr = inet_aton(ipstr, &a); + + if (ipaddr == 0) { + bb_error_msg_and_die("bad IP address: %s", argv[0]); + } + ipaddr = a.s_addr; + + if (argc == 2) { + if (ENABLE_FEATURE_IPCALC_FANCY && have_netmask) { + bb_error_msg_and_die("use prefix or netmask, not both"); + } + + netmask = inet_aton(argv[1], &a); + if (netmask == 0) { + bb_error_msg_and_die("bad netmask: %s", argv[1]); + } + netmask = a.s_addr; + } else { + + /* JHC - If the netmask wasn't provided then calculate it */ + if (!ENABLE_FEATURE_IPCALC_FANCY || !have_netmask) + netmask = get_netmask(ipaddr); + } + + if (opt & NETMASK) { + printf("NETMASK=%s\n", inet_ntoa((*(struct in_addr *) &netmask))); + } + + if (opt & BROADCAST) { + broadcast = (ipaddr & netmask) | ~netmask; + printf("BROADCAST=%s\n", inet_ntoa((*(struct in_addr *) &broadcast))); + } + + if (opt & NETWORK) { + network = ipaddr & netmask; + printf("NETWORK=%s\n", inet_ntoa((*(struct in_addr *) &network))); + } + + if (ENABLE_FEATURE_IPCALC_FANCY) { + if (opt & NETPREFIX) { + printf("PREFIX=%i\n", get_prefix(netmask)); + } + + if (opt & HOSTNAME) { + struct hostent *hostinfo; + int x; + + hostinfo = gethostbyaddr((char *) &ipaddr, sizeof(ipaddr), AF_INET); + if (!hostinfo) { + bb_herror_msg_and_die("cannot find hostname for %s", argv[0]); + } + for (x = 0; hostinfo->h_name[x]; x++) { + hostinfo->h_name[x] = tolower(hostinfo->h_name[x]); + } + + printf("HOSTNAME=%s\n", hostinfo->h_name); + } + } + + return EXIT_SUCCESS; +} diff --git a/networking/iplink.c b/networking/iplink.c new file mode 100644 index 000000000..4a28c74a9 --- /dev/null +++ b/networking/iplink.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * ip.c "ip" utility frontend. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, + * + * + * Changes: + * + * Rani Assaf 980929: resolve addresses + */ + +#include "libiproute/utils.h" +#include "libiproute/ip_common.h" + +#include "busybox.h" + +int iplink_main(int argc, char **argv) +{ + ip_parse_common_args(&argc, &argv); + + return do_iplink(argc-1, argv+1); +} diff --git a/networking/iproute.c b/networking/iproute.c new file mode 100644 index 000000000..62335a8b3 --- /dev/null +++ b/networking/iproute.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * ip.c "ip" utility frontend. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, + * + * + * Changes: + * + * Rani Assaf 980929: resolve addresses + */ + +#include "libiproute/utils.h" +#include "libiproute/ip_common.h" + +#include "busybox.h" + +int iproute_main(int argc, char **argv) +{ + ip_parse_common_args(&argc, &argv); + + return do_iproute(argc-1, argv+1); +} diff --git a/networking/iprule.c b/networking/iprule.c new file mode 100644 index 000000000..43eaea633 --- /dev/null +++ b/networking/iprule.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * ip.c "ip" utility frontend. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, + * + * + * Changes: + * + * Rani Assaf 980929: resolve addresses + */ + +#include "libiproute/utils.h" +#include "libiproute/ip_common.h" + +#include "busybox.h" + +int iprule_main(int argc, char **argv) +{ + ip_parse_common_args(&argc, &argv); + + return do_iprule(argc-1, argv+1); +} diff --git a/networking/iptunnel.c b/networking/iptunnel.c new file mode 100644 index 000000000..9ae734a21 --- /dev/null +++ b/networking/iptunnel.c @@ -0,0 +1,25 @@ +/* vi: set sw=4 ts=4: */ +/* + * ip.c "ip" utility frontend. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, + * + * + * Changes: + * + * Rani Assaf 980929: resolve addresses + */ + +#include "libiproute/utils.h" +#include "libiproute/ip_common.h" + +#include "busybox.h" + +int iptunnel_main(int argc, char **argv) +{ + ip_parse_common_args(&argc, &argv); + + return do_iptunnel(argc-1, argv+1); +} diff --git a/networking/libiproute/Kbuild b/networking/libiproute/Kbuild new file mode 100644 index 000000000..8383630f2 --- /dev/null +++ b/networking/libiproute/Kbuild @@ -0,0 +1,66 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2004 by Erik Andersen +# +# Licensed under the GPL v2 or later, see the file LICENSE in this tarball. +# + +lib-y:= +lib-$(CONFIG_IP) += \ + ip_parse_common_args.o \ + ipaddress.o \ + iplink.o \ + iproute.o \ + iptunnel.o \ + iprule.o \ + libnetlink.o \ + ll_addr.o \ + ll_map.o \ + ll_proto.o \ + ll_types.o \ + rt_names.o \ + rtm_map.o \ + utils.o + +lib-$(CONFIG_IPADDR) += \ + ip_parse_common_args.o \ + ipaddress.o \ + libnetlink.o \ + ll_addr.o \ + ll_map.o \ + ll_types.o \ + rt_names.o \ + utils.o + +lib-$(CONFIG_IPLINK) += \ + ip_parse_common_args.o \ + ipaddress.o \ + iplink.o \ + libnetlink.o \ + ll_addr.o \ + ll_map.o \ + ll_types.o \ + rt_names.o \ + utils.o + +lib-$(CONFIG_IPROUTE) += \ + ip_parse_common_args.o \ + iproute.o \ + libnetlink.o \ + ll_map.o \ + rt_names.o \ + rtm_map.o \ + utils.o + +lib-$(CONFIG_IPTUNNEL) += \ + ip_parse_common_args.o \ + iptunnel.o \ + rt_names.o \ + utils.o + +lib-$(CONFIG_IPRULE) += \ + ip_parse_common_args.o \ + iprule.o \ + rt_names.o \ + utils.o + diff --git a/networking/libiproute/ip_common.h b/networking/libiproute/ip_common.h new file mode 100644 index 000000000..15291780e --- /dev/null +++ b/networking/libiproute/ip_common.h @@ -0,0 +1,34 @@ +/* vi: set sw=4 ts=4: */ +#ifndef _IP_COMMON_H +#define _IP_COMMON_H 1 + +#include "busybox.h" +#include +#include +#include +#if !defined IFA_RTA +#include +#endif +#if !defined IFLA_RTA +#include +#endif + +extern int preferred_family; +extern char * _SL_; + +extern void ip_parse_common_args(int *argcp, char ***argvp); +extern int print_neigh(struct sockaddr_nl *who, struct nlmsghdr *n, void *arg); +extern int ipaddr_list_or_flush(int argc, char **argv, int flush); +extern int iproute_monitor(int argc, char **argv); +extern void iplink_usage(void) ATTRIBUTE_NORETURN; +extern void ipneigh_reset_filter(void); +extern int do_ipaddr(int argc, char **argv); +extern int do_iproute(int argc, char **argv); +extern int do_iprule(int argc, char **argv); +extern int do_ipneigh(int argc, char **argv); +extern int do_iptunnel(int argc, char **argv); +extern int do_iplink(int argc, char **argv); +extern int do_ipmonitor(int argc, char **argv); +extern int do_multiaddr(int argc, char **argv); +extern int do_multiroute(int argc, char **argv); +#endif /* ip_common.h */ diff --git a/networking/libiproute/ip_parse_common_args.c b/networking/libiproute/ip_parse_common_args.c new file mode 100644 index 000000000..fee6e5e95 --- /dev/null +++ b/networking/libiproute/ip_parse_common_args.c @@ -0,0 +1,77 @@ +/* vi: set sw=4 ts=4: */ +/* + * ip.c "ip" utility frontend. + * + * 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. + * + * Authors: Alexey Kuznetsov, + * + * + * Changes: + * + * Rani Assaf 980929: resolve addresses + */ + +#include + +#include "libbb.h" +#include "utils.h" +#include "ip_common.h" + + +int preferred_family = AF_UNSPEC; +int oneline = 0; +char * _SL_ = NULL; + +void ip_parse_common_args(int *argcp, char ***argvp) +{ + int argc = *argcp; + char **argv = *argvp; + + while (argc > 1) { + char *opt = argv[1]; + + if (strcmp(opt,"--") == 0) { + argc--; argv++; + break; + } + + if (opt[0] != '-') + break; + + if (opt[1] == '-') + opt++; + + if (matches(opt, "-family") == 0) { + argc--; + argv++; + if (!argv[1]) + bb_show_usage(); + if (strcmp(argv[1], "inet") == 0) + preferred_family = AF_INET; + else if (strcmp(argv[1], "inet6") == 0) + preferred_family = AF_INET6; + else if (strcmp(argv[1], "link") == 0) + preferred_family = AF_PACKET; + else + invarg(argv[1], "protocol family"); + } else if (strcmp(opt, "-4") == 0) { + preferred_family = AF_INET; + } else if (strcmp(opt, "-6") == 0) { + preferred_family = AF_INET6; + } else if (strcmp(opt, "-0") == 0) { + preferred_family = AF_PACKET; + } else if (matches(opt, "-oneline") == 0) { + ++oneline; + } else { + bb_show_usage(); + } + argc--; argv++; + } + _SL_ = oneline ? "\\" : "\n" ; + *argcp = argc; + *argvp = argv; +} diff --git a/networking/libiproute/ipaddress.c b/networking/libiproute/ipaddress.c new file mode 100644 index 000000000..2a267fef6 --- /dev/null +++ b/networking/libiproute/ipaddress.c @@ -0,0 +1,824 @@ +/* vi: set sw=4 ts=4: */ +/* + * ipaddress.c "ip address". + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Authors: Alexey Kuznetsov, + * + * Changes: + * Laszlo Valko 990223: address label must be zero terminated + */ + +#include "libbb.h" +#include +#include + +#include +#include +#include + +#include +#include + +#include "rt_names.h" +#include "utils.h" +#include "ip_common.h" + + +static struct +{ + int ifindex; + int family; + int oneline; + int showqueue; + inet_prefix pfx; + int scope, scopemask; + int flags, flagmask; + int up; + char *label; + int flushed; + char *flushb; + int flushp; + int flushe; + struct rtnl_handle *rth; +} filter; + +static void print_link_flags(FILE *fp, unsigned flags, unsigned mdown) +{ + fprintf(fp, "<"); + flags &= ~IFF_RUNNING; +#define _PF(f) if (flags&IFF_##f) { \ + flags &= ~IFF_##f ; \ + fprintf(fp, #f "%s", flags ? "," : ""); } + _PF(LOOPBACK); + _PF(BROADCAST); + _PF(POINTOPOINT); + _PF(MULTICAST); + _PF(NOARP); +#if 0 + _PF(ALLMULTI); + _PF(PROMISC); + _PF(MASTER); + _PF(SLAVE); + _PF(DEBUG); + _PF(DYNAMIC); + _PF(AUTOMEDIA); + _PF(PORTSEL); + _PF(NOTRAILERS); +#endif + _PF(UP); +#undef _PF + if (flags) + fprintf(fp, "%x", flags); + if (mdown) + fprintf(fp, ",M-DOWN"); + fprintf(fp, "> "); +} + +static void print_queuelen(char *name) +{ + struct ifreq ifr; + int s; + + s = socket(AF_INET, SOCK_STREAM, 0); + if (s < 0) + return; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); + if (ioctl(s, SIOCGIFTXQLEN, &ifr) < 0) { + perror("SIOCGIFXQLEN"); + close(s); + return; + } + close(s); + + if (ifr.ifr_qlen) + printf("qlen %d", ifr.ifr_qlen); +} + +static int print_linkinfo(struct sockaddr_nl ATTRIBUTE_UNUSED *who, + const struct nlmsghdr *n, void ATTRIBUTE_UNUSED *arg) +{ + FILE *fp = (FILE*)arg; + struct ifinfomsg *ifi = NLMSG_DATA(n); + struct rtattr * tb[IFLA_MAX+1]; + int len = n->nlmsg_len; + unsigned m_flag = 0; + + if (n->nlmsg_type != RTM_NEWLINK && n->nlmsg_type != RTM_DELLINK) + return 0; + + len -= NLMSG_LENGTH(sizeof(*ifi)); + if (len < 0) + return -1; + + if (filter.ifindex && ifi->ifi_index != filter.ifindex) + return 0; + if (filter.up && !(ifi->ifi_flags&IFF_UP)) + return 0; + + memset(tb, 0, sizeof(tb)); + parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len); + if (tb[IFLA_IFNAME] == NULL) { + bb_error_msg("nil ifname"); + return -1; + } + if (filter.label && + (!filter.family || filter.family == AF_PACKET) && + fnmatch(filter.label, RTA_DATA(tb[IFLA_IFNAME]), 0)) + return 0; + + if (n->nlmsg_type == RTM_DELLINK) + fprintf(fp, "Deleted "); + + fprintf(fp, "%d: %s", ifi->ifi_index, + tb[IFLA_IFNAME] ? (char*)RTA_DATA(tb[IFLA_IFNAME]) : ""); + + if (tb[IFLA_LINK]) { + SPRINT_BUF(b1); + int iflink = *(int*)RTA_DATA(tb[IFLA_LINK]); + if (iflink == 0) + fprintf(fp, "@NONE: "); + else { + fprintf(fp, "@%s: ", ll_idx_n2a(iflink, b1)); + m_flag = ll_index_to_flags(iflink); + m_flag = !(m_flag & IFF_UP); + } + } else { + fprintf(fp, ": "); + } + print_link_flags(fp, ifi->ifi_flags, m_flag); + + if (tb[IFLA_MTU]) + fprintf(fp, "mtu %u ", *(int*)RTA_DATA(tb[IFLA_MTU])); + if (tb[IFLA_QDISC]) + fprintf(fp, "qdisc %s ", (char*)RTA_DATA(tb[IFLA_QDISC])); +#ifdef IFLA_MASTER + if (tb[IFLA_MASTER]) { + SPRINT_BUF(b1); + fprintf(fp, "master %s ", ll_idx_n2a(*(int*)RTA_DATA(tb[IFLA_MASTER]), b1)); + } +#endif + if (filter.showqueue) + print_queuelen((char*)RTA_DATA(tb[IFLA_IFNAME])); + + if (!filter.family || filter.family == AF_PACKET) { + SPRINT_BUF(b1); + fprintf(fp, "%s", _SL_); + fprintf(fp, " link/%s ", ll_type_n2a(ifi->ifi_type, b1, sizeof(b1))); + + if (tb[IFLA_ADDRESS]) { + fprintf(fp, "%s", ll_addr_n2a(RTA_DATA(tb[IFLA_ADDRESS]), + RTA_PAYLOAD(tb[IFLA_ADDRESS]), + ifi->ifi_type, + b1, sizeof(b1))); + } + if (tb[IFLA_BROADCAST]) { + if (ifi->ifi_flags&IFF_POINTOPOINT) + fprintf(fp, " peer "); + else + fprintf(fp, " brd "); + fprintf(fp, "%s", ll_addr_n2a(RTA_DATA(tb[IFLA_BROADCAST]), + RTA_PAYLOAD(tb[IFLA_BROADCAST]), + ifi->ifi_type, + b1, sizeof(b1))); + } + } + fprintf(fp, "\n"); + fflush(fp); + return 0; +} + +static int flush_update(void) +{ + if (rtnl_send(filter.rth, filter.flushb, filter.flushp) < 0) { + perror("Failed to send flush request\n"); + return -1; + } + filter.flushp = 0; + return 0; +} + +static int print_addrinfo(struct sockaddr_nl ATTRIBUTE_UNUSED *who, + struct nlmsghdr *n, void ATTRIBUTE_UNUSED *arg) +{ + FILE *fp = (FILE*)arg; + struct ifaddrmsg *ifa = NLMSG_DATA(n); + int len = n->nlmsg_len; + struct rtattr * rta_tb[IFA_MAX+1]; + char abuf[256]; + SPRINT_BUF(b1); + + if (n->nlmsg_type != RTM_NEWADDR && n->nlmsg_type != RTM_DELADDR) + return 0; + len -= NLMSG_LENGTH(sizeof(*ifa)); + if (len < 0) { + bb_error_msg("wrong nlmsg len %d", len); + return -1; + } + + if (filter.flushb && n->nlmsg_type != RTM_NEWADDR) + return 0; + + memset(rta_tb, 0, sizeof(rta_tb)); + parse_rtattr(rta_tb, IFA_MAX, IFA_RTA(ifa), n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa))); + + if (!rta_tb[IFA_LOCAL]) + rta_tb[IFA_LOCAL] = rta_tb[IFA_ADDRESS]; + if (!rta_tb[IFA_ADDRESS]) + rta_tb[IFA_ADDRESS] = rta_tb[IFA_LOCAL]; + + if (filter.ifindex && filter.ifindex != ifa->ifa_index) + return 0; + if ((filter.scope^ifa->ifa_scope)&filter.scopemask) + return 0; + if ((filter.flags^ifa->ifa_flags)&filter.flagmask) + return 0; + if (filter.label) { + const char *label; + if (rta_tb[IFA_LABEL]) + label = RTA_DATA(rta_tb[IFA_LABEL]); + else + label = ll_idx_n2a(ifa->ifa_index, b1); + if (fnmatch(filter.label, label, 0) != 0) + return 0; + } + if (filter.pfx.family) { + if (rta_tb[IFA_LOCAL]) { + inet_prefix dst; + memset(&dst, 0, sizeof(dst)); + dst.family = ifa->ifa_family; + memcpy(&dst.data, RTA_DATA(rta_tb[IFA_LOCAL]), RTA_PAYLOAD(rta_tb[IFA_LOCAL])); + if (inet_addr_match(&dst, &filter.pfx, filter.pfx.bitlen)) + return 0; + } + } + + if (filter.flushb) { + struct nlmsghdr *fn; + if (NLMSG_ALIGN(filter.flushp) + n->nlmsg_len > filter.flushe) { + if (flush_update()) + return -1; + } + fn = (struct nlmsghdr*)(filter.flushb + NLMSG_ALIGN(filter.flushp)); + memcpy(fn, n, n->nlmsg_len); + fn->nlmsg_type = RTM_DELADDR; + fn->nlmsg_flags = NLM_F_REQUEST; + fn->nlmsg_seq = ++filter.rth->seq; + filter.flushp = (((char*)fn) + n->nlmsg_len) - filter.flushb; + filter.flushed++; + return 0; + } + + if (n->nlmsg_type == RTM_DELADDR) + fprintf(fp, "Deleted "); + + if (filter.oneline) + fprintf(fp, "%u: %s", ifa->ifa_index, ll_index_to_name(ifa->ifa_index)); + if (ifa->ifa_family == AF_INET) + fprintf(fp, " inet "); + else if (ifa->ifa_family == AF_INET6) + fprintf(fp, " inet6 "); + else + fprintf(fp, " family %d ", ifa->ifa_family); + + if (rta_tb[IFA_LOCAL]) { + fprintf(fp, "%s", rt_addr_n2a(ifa->ifa_family, + RTA_PAYLOAD(rta_tb[IFA_LOCAL]), + RTA_DATA(rta_tb[IFA_LOCAL]), + abuf, sizeof(abuf))); + + if (rta_tb[IFA_ADDRESS] == NULL || + memcmp(RTA_DATA(rta_tb[IFA_ADDRESS]), RTA_DATA(rta_tb[IFA_LOCAL]), 4) == 0) { + fprintf(fp, "/%d ", ifa->ifa_prefixlen); + } else { + fprintf(fp, " peer %s/%d ", + rt_addr_n2a(ifa->ifa_family, + RTA_PAYLOAD(rta_tb[IFA_ADDRESS]), + RTA_DATA(rta_tb[IFA_ADDRESS]), + abuf, sizeof(abuf)), + ifa->ifa_prefixlen); + } + } + + if (rta_tb[IFA_BROADCAST]) { + fprintf(fp, "brd %s ", + rt_addr_n2a(ifa->ifa_family, + RTA_PAYLOAD(rta_tb[IFA_BROADCAST]), + RTA_DATA(rta_tb[IFA_BROADCAST]), + abuf, sizeof(abuf))); + } + if (rta_tb[IFA_ANYCAST]) { + fprintf(fp, "any %s ", + rt_addr_n2a(ifa->ifa_family, + RTA_PAYLOAD(rta_tb[IFA_ANYCAST]), + RTA_DATA(rta_tb[IFA_ANYCAST]), + abuf, sizeof(abuf))); + } + fprintf(fp, "scope %s ", rtnl_rtscope_n2a(ifa->ifa_scope, b1, sizeof(b1))); + if (ifa->ifa_flags&IFA_F_SECONDARY) { + ifa->ifa_flags &= ~IFA_F_SECONDARY; + fprintf(fp, "secondary "); + } + if (ifa->ifa_flags&IFA_F_TENTATIVE) { + ifa->ifa_flags &= ~IFA_F_TENTATIVE; + fprintf(fp, "tentative "); + } + if (ifa->ifa_flags&IFA_F_DEPRECATED) { + ifa->ifa_flags &= ~IFA_F_DEPRECATED; + fprintf(fp, "deprecated "); + } + if (!(ifa->ifa_flags&IFA_F_PERMANENT)) { + fprintf(fp, "dynamic "); + } else + ifa->ifa_flags &= ~IFA_F_PERMANENT; + if (ifa->ifa_flags) + fprintf(fp, "flags %02x ", ifa->ifa_flags); + if (rta_tb[IFA_LABEL]) + fprintf(fp, "%s", (char*)RTA_DATA(rta_tb[IFA_LABEL])); + if (rta_tb[IFA_CACHEINFO]) { + struct ifa_cacheinfo *ci = RTA_DATA(rta_tb[IFA_CACHEINFO]); + char buf[128]; + fprintf(fp, "%s", _SL_); + if (ci->ifa_valid == 0xFFFFFFFFU) + sprintf(buf, "valid_lft forever"); + else + sprintf(buf, "valid_lft %dsec", ci->ifa_valid); + if (ci->ifa_prefered == 0xFFFFFFFFU) + sprintf(buf+strlen(buf), " preferred_lft forever"); + else + sprintf(buf+strlen(buf), " preferred_lft %dsec", ci->ifa_prefered); + fprintf(fp, " %s", buf); + } + fprintf(fp, "\n"); + fflush(fp); + return 0; +} + + +struct nlmsg_list +{ + struct nlmsg_list *next; + struct nlmsghdr h; +}; + +static int print_selected_addrinfo(int ifindex, struct nlmsg_list *ainfo, FILE *fp) +{ + for ( ;ainfo ; ainfo = ainfo->next) { + struct nlmsghdr *n = &ainfo->h; + struct ifaddrmsg *ifa = NLMSG_DATA(n); + + if (n->nlmsg_type != RTM_NEWADDR) + continue; + + if (n->nlmsg_len < NLMSG_LENGTH(sizeof(ifa))) + return -1; + + if (ifa->ifa_index != ifindex || + (filter.family && filter.family != ifa->ifa_family)) + continue; + + print_addrinfo(NULL, n, fp); + } + return 0; +} + + +static int store_nlmsg(struct sockaddr_nl *who, struct nlmsghdr *n, void *arg) +{ + struct nlmsg_list **linfo = (struct nlmsg_list**)arg; + struct nlmsg_list *h; + struct nlmsg_list **lp; + + h = malloc(n->nlmsg_len+sizeof(void*)); + if (h == NULL) + return -1; + + memcpy(&h->h, n, n->nlmsg_len); + h->next = NULL; + + for (lp = linfo; *lp; lp = &(*lp)->next) /* NOTHING */; + *lp = h; + + ll_remember_index(who, n, NULL); + return 0; +} + +static void ipaddr_reset_filter(int _oneline) +{ + memset(&filter, 0, sizeof(filter)); + filter.oneline = _oneline; +} + +int ipaddr_list_or_flush(int argc, char **argv, int flush) +{ + static const char *const option[] = { "to", "scope", "up", "label", "dev", 0 }; + + struct nlmsg_list *linfo = NULL; + struct nlmsg_list *ainfo = NULL; + struct nlmsg_list *l; + struct rtnl_handle rth; + char *filter_dev = NULL; + int no_link = 0; + + ipaddr_reset_filter(oneline); + filter.showqueue = 1; + + if (filter.family == AF_UNSPEC) + filter.family = preferred_family; + + if (flush) { + if (argc <= 0) { + bb_error_msg(bb_msg_requires_arg, "flush"); + return -1; + } + if (filter.family == AF_PACKET) { + bb_error_msg("cannot flush link addresses"); + return -1; + } + } + + while (argc > 0) { + const int option_num = index_in_str_array(option, *argv); + switch (option_num) { + case 0: /* to */ + NEXT_ARG(); + get_prefix(&filter.pfx, *argv, filter.family); + if (filter.family == AF_UNSPEC) { + filter.family = filter.pfx.family; + } + break; + case 1: /* scope */ + { + uint32_t scope = 0; + NEXT_ARG(); + filter.scopemask = -1; + if (rtnl_rtscope_a2n(&scope, *argv)) { + if (strcmp(*argv, "all") != 0) { + invarg(*argv, "scope"); + } + scope = RT_SCOPE_NOWHERE; + filter.scopemask = 0; + } + filter.scope = scope; + break; + } + case 2: /* up */ + filter.up = 1; + break; + case 3: /* label */ + NEXT_ARG(); + filter.label = *argv; + break; + case 4: /* dev */ + NEXT_ARG(); + default: + if (filter_dev) { + duparg2("dev", *argv); + } + filter_dev = *argv; + } + argv++; + argc--; + } + + if (rtnl_open(&rth, 0) < 0) + exit(1); + + if (rtnl_wilddump_request(&rth, preferred_family, RTM_GETLINK) < 0) { + bb_perror_msg_and_die("cannot send dump request"); + } + + if (rtnl_dump_filter(&rth, store_nlmsg, &linfo, NULL, NULL) < 0) { + bb_error_msg_and_die("dump terminated"); + } + + if (filter_dev) { + filter.ifindex = ll_name_to_index(filter_dev); + if (filter.ifindex <= 0) { + bb_error_msg("device \"%s\" does not exist", filter_dev); + return -1; + } + } + + if (flush) { + char flushb[4096-512]; + + filter.flushb = flushb; + filter.flushp = 0; + filter.flushe = sizeof(flushb); + filter.rth = &rth; + + for (;;) { + if (rtnl_wilddump_request(&rth, filter.family, RTM_GETADDR) < 0) { + perror("Cannot send dump request"); + exit(1); + } + filter.flushed = 0; + if (rtnl_dump_filter(&rth, print_addrinfo, stdout, NULL, NULL) < 0) { + fprintf(stderr, "Flush terminated\n"); + exit(1); + } + if (filter.flushed == 0) { + fflush(stdout); + return 0; + } + if (flush_update() < 0) + exit(1); + } + } + + if (filter.family != AF_PACKET) { + if (rtnl_wilddump_request(&rth, filter.family, RTM_GETADDR) < 0) { + bb_perror_msg_and_die("cannot send dump request"); + } + + if (rtnl_dump_filter(&rth, store_nlmsg, &ainfo, NULL, NULL) < 0) { + bb_error_msg_and_die("dump terminated"); + } + } + + + if (filter.family && filter.family != AF_PACKET) { + struct nlmsg_list **lp; + lp=&linfo; + + if (filter.oneline) + no_link = 1; + + while ((l=*lp)!=NULL) { + int ok = 0; + struct ifinfomsg *ifi = NLMSG_DATA(&l->h); + struct nlmsg_list *a; + + for (a=ainfo; a; a=a->next) { + struct nlmsghdr *n = &a->h; + struct ifaddrmsg *ifa = NLMSG_DATA(n); + + if (ifa->ifa_index != ifi->ifi_index || + (filter.family && filter.family != ifa->ifa_family)) + continue; + if ((filter.scope^ifa->ifa_scope)&filter.scopemask) + continue; + if ((filter.flags^ifa->ifa_flags)&filter.flagmask) + continue; + if (filter.pfx.family || filter.label) { + struct rtattr *tb[IFA_MAX+1]; + memset(tb, 0, sizeof(tb)); + parse_rtattr(tb, IFA_MAX, IFA_RTA(ifa), IFA_PAYLOAD(n)); + if (!tb[IFA_LOCAL]) + tb[IFA_LOCAL] = tb[IFA_ADDRESS]; + + if (filter.pfx.family && tb[IFA_LOCAL]) { + inet_prefix dst; + memset(&dst, 0, sizeof(dst)); + dst.family = ifa->ifa_family; + memcpy(&dst.data, RTA_DATA(tb[IFA_LOCAL]), RTA_PAYLOAD(tb[IFA_LOCAL])); + if (inet_addr_match(&dst, &filter.pfx, filter.pfx.bitlen)) + continue; + } + if (filter.label) { + SPRINT_BUF(b1); + const char *label; + if (tb[IFA_LABEL]) + label = RTA_DATA(tb[IFA_LABEL]); + else + label = ll_idx_n2a(ifa->ifa_index, b1); + if (fnmatch(filter.label, label, 0) != 0) + continue; + } + } + + ok = 1; + break; + } + if (!ok) + *lp = l->next; + else + lp = &l->next; + } + } + + for (l=linfo; l; l = l->next) { + if (no_link || print_linkinfo(NULL, &l->h, stdout) == 0) { + struct ifinfomsg *ifi = NLMSG_DATA(&l->h); + if (filter.family != AF_PACKET) + print_selected_addrinfo(ifi->ifi_index, ainfo, stdout); + } + fflush(stdout); + } + + exit(0); +} + +static int default_scope(inet_prefix *lcl) +{ + if (lcl->family == AF_INET) { + if (lcl->bytelen >= 1 && *(__u8*)&lcl->data == 127) + return RT_SCOPE_HOST; + } + return 0; +} + +static int ipaddr_modify(int cmd, int argc, char **argv) +{ + static const char *const option[] = { + "peer", "remote", "broadcast", "brd", + "anycast", "scope", "dev", "label", "local", 0 + }; + + struct rtnl_handle rth; + struct { + struct nlmsghdr n; + struct ifaddrmsg ifa; + char buf[256]; + } req; + char *d = NULL; + char *l = NULL; + inet_prefix lcl; + inet_prefix peer; + int local_len = 0; + int peer_len = 0; + int brd_len = 0; + int any_len = 0; + int scoped = 0; + + memset(&req, 0, sizeof(req)); + + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST; + req.n.nlmsg_type = cmd; + req.ifa.ifa_family = preferred_family; + + while (argc > 0) { + const int option_num = index_in_str_array(option, *argv); + switch (option_num) { + case 0: /* peer */ + case 1: /* remote */ + NEXT_ARG(); + + if (peer_len) { + duparg("peer", *argv); + } + get_prefix(&peer, *argv, req.ifa.ifa_family); + peer_len = peer.bytelen; + if (req.ifa.ifa_family == AF_UNSPEC) { + req.ifa.ifa_family = peer.family; + } + addattr_l(&req.n, sizeof(req), IFA_ADDRESS, &peer.data, peer.bytelen); + req.ifa.ifa_prefixlen = peer.bitlen; + break; + case 2: /* broadcast */ + case 3: /* brd */ + { + inet_prefix addr; + NEXT_ARG(); + if (brd_len) { + duparg("broadcast", *argv); + } + if (strcmp(*argv, "+") == 0) { + brd_len = -1; + } + else if (strcmp(*argv, "-") == 0) { + brd_len = -2; + } else { + get_addr(&addr, *argv, req.ifa.ifa_family); + if (req.ifa.ifa_family == AF_UNSPEC) + req.ifa.ifa_family = addr.family; + addattr_l(&req.n, sizeof(req), IFA_BROADCAST, &addr.data, addr.bytelen); + brd_len = addr.bytelen; + } + break; + } + case 4: /* anycast */ + { + inet_prefix addr; + NEXT_ARG(); + if (any_len) { + duparg("anycast", *argv); + } + get_addr(&addr, *argv, req.ifa.ifa_family); + if (req.ifa.ifa_family == AF_UNSPEC) { + req.ifa.ifa_family = addr.family; + } + addattr_l(&req.n, sizeof(req), IFA_ANYCAST, &addr.data, addr.bytelen); + any_len = addr.bytelen; + break; + } + case 5: /* scope */ + { + uint32_t scope = 0; + NEXT_ARG(); + if (rtnl_rtscope_a2n(&scope, *argv)) { + invarg(*argv, "scope"); + } + req.ifa.ifa_scope = scope; + scoped = 1; + break; + } + case 6: /* dev */ + NEXT_ARG(); + d = *argv; + break; + case 7: /* label */ + NEXT_ARG(); + l = *argv; + addattr_l(&req.n, sizeof(req), IFA_LABEL, l, strlen(l)+1); + break; + case 8: /* local */ + NEXT_ARG(); + default: + if (local_len) { + duparg2("local", *argv); + } + get_prefix(&lcl, *argv, req.ifa.ifa_family); + if (req.ifa.ifa_family == AF_UNSPEC) { + req.ifa.ifa_family = lcl.family; + } + addattr_l(&req.n, sizeof(req), IFA_LOCAL, &lcl.data, lcl.bytelen); + local_len = lcl.bytelen; + } + argc--; + argv++; + } + + if (d == NULL) { + bb_error_msg(bb_msg_requires_arg,"\"dev\""); + return -1; + } + if (l && matches(d, l) != 0) { + bb_error_msg_and_die("\"dev\" (%s) must match \"label\" (%s)", d, l); + } + + if (peer_len == 0 && local_len && cmd != RTM_DELADDR) { + peer = lcl; + addattr_l(&req.n, sizeof(req), IFA_ADDRESS, &lcl.data, lcl.bytelen); + } + if (req.ifa.ifa_prefixlen == 0) + req.ifa.ifa_prefixlen = lcl.bitlen; + + if (brd_len < 0 && cmd != RTM_DELADDR) { + inet_prefix brd; + int i; + if (req.ifa.ifa_family != AF_INET) { + bb_error_msg("broadcast can be set only for IPv4 addresses"); + return -1; + } + brd = peer; + if (brd.bitlen <= 30) { + for (i=31; i>=brd.bitlen; i--) { + if (brd_len == -1) + brd.data[0] |= htonl(1<<(31-i)); + else + brd.data[0] &= ~htonl(1<<(31-i)); + } + addattr_l(&req.n, sizeof(req), IFA_BROADCAST, &brd.data, brd.bytelen); + brd_len = brd.bytelen; + } + } + if (!scoped && cmd != RTM_DELADDR) + req.ifa.ifa_scope = default_scope(&lcl); + + if (rtnl_open(&rth, 0) < 0) + exit(1); + + ll_init_map(&rth); + + if ((req.ifa.ifa_index = ll_name_to_index(d)) == 0) { + bb_error_msg("cannot find device \"%s\"", d); + return -1; + } + + if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0) + exit(2); + + exit(0); +} + +int do_ipaddr(int argc, char **argv) +{ + static const char *const commands[] = { + "add", "delete", "list", "show", "lst", "flush", 0 + }; + + int command_num = 2; + + if (*argv) { + command_num = index_in_substr_array(commands, *argv); + } + switch (command_num) { + case 0: /* add */ + return ipaddr_modify(RTM_NEWADDR, argc-1, argv+1); + case 1: /* delete */ + return ipaddr_modify(RTM_DELADDR, argc-1, argv+1); + case 2: /* list */ + case 3: /* show */ + case 4: /* lst */ + return ipaddr_list_or_flush(argc-1, argv+1, 0); + case 5: /* flush */ + return ipaddr_list_or_flush(argc-1, argv+1, 1); + } + bb_error_msg_and_die("unknown command %s", *argv); +} diff --git a/networking/libiproute/iplink.c b/networking/libiproute/iplink.c new file mode 100644 index 000000000..1ea11f60b --- /dev/null +++ b/networking/libiproute/iplink.c @@ -0,0 +1,354 @@ +/* vi: set sw=4 ts=4: */ +/* + * iplink.c "ip link". + * + * Authors: Alexey Kuznetsov, + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "libbb.h" + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include "rt_names.h" +#include "utils.h" +#include "ip_common.h" + +/* take from linux/sockios.h */ +#define SIOCSIFNAME 0x8923 /* set interface name */ + +static int on_off(char *msg) +{ + bb_error_msg("error: argument of \"%s\" must be \"on\" or \"off\"", msg); + return -1; +} + +static int get_ctl_fd(void) +{ + int s_errno; + int fd; + + fd = socket(PF_INET, SOCK_DGRAM, 0); + if (fd >= 0) + return fd; + s_errno = errno; + fd = socket(PF_PACKET, SOCK_DGRAM, 0); + if (fd >= 0) + return fd; + fd = socket(PF_INET6, SOCK_DGRAM, 0); + if (fd >= 0) + return fd; + errno = s_errno; + perror("Cannot create control socket"); + return -1; +} + +static int do_chflags(char *dev, __u32 flags, __u32 mask) +{ + struct ifreq ifr; + int fd; + int err; + + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + fd = get_ctl_fd(); + if (fd < 0) + return -1; + err = ioctl(fd, SIOCGIFFLAGS, &ifr); + if (err) { + perror("SIOCGIFFLAGS"); + close(fd); + return -1; + } + if ((ifr.ifr_flags^flags)&mask) { + ifr.ifr_flags &= ~mask; + ifr.ifr_flags |= mask&flags; + err = ioctl(fd, SIOCSIFFLAGS, &ifr); + if (err) + perror("SIOCSIFFLAGS"); + } + close(fd); + return err; +} + +static int do_changename(char *dev, char *newdev) +{ + struct ifreq ifr; + int fd; + int err; + + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + strncpy(ifr.ifr_newname, newdev, sizeof(ifr.ifr_newname)); + fd = get_ctl_fd(); + if (fd < 0) + return -1; + err = ioctl(fd, SIOCSIFNAME, &ifr); + if (err) { + perror("SIOCSIFNAME"); + close(fd); + return -1; + } + close(fd); + return err; +} + +static int set_qlen(char *dev, int qlen) +{ + struct ifreq ifr; + int s; + + s = get_ctl_fd(); + if (s < 0) + return -1; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + ifr.ifr_qlen = qlen; + if (ioctl(s, SIOCSIFTXQLEN, &ifr) < 0) { + perror("SIOCSIFXQLEN"); + close(s); + return -1; + } + close(s); + + return 0; +} + +static int set_mtu(char *dev, int mtu) +{ + struct ifreq ifr; + int s; + + s = get_ctl_fd(); + if (s < 0) + return -1; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + ifr.ifr_mtu = mtu; + if (ioctl(s, SIOCSIFMTU, &ifr) < 0) { + perror("SIOCSIFMTU"); + close(s); + return -1; + } + close(s); + + return 0; +} + +static int get_address(char *dev, int *htype) +{ + struct ifreq ifr; + struct sockaddr_ll me; + socklen_t alen; + int s; + + s = socket(PF_PACKET, SOCK_DGRAM, 0); + if (s < 0) { + perror("socket(PF_PACKET)"); + return -1; + } + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) { + perror("SIOCGIFINDEX"); + close(s); + return -1; + } + + memset(&me, 0, sizeof(me)); + me.sll_family = AF_PACKET; + me.sll_ifindex = ifr.ifr_ifindex; + me.sll_protocol = htons(ETH_P_LOOP); + if (bind(s, (struct sockaddr*)&me, sizeof(me)) == -1) { + perror("bind"); + close(s); + return -1; + } + + alen = sizeof(me); + if (getsockname(s, (struct sockaddr*)&me, &alen) == -1) { + perror("getsockname"); + close(s); + return -1; + } + close(s); + *htype = me.sll_hatype; + return me.sll_halen; +} + +static int parse_address(char *dev, int hatype, int halen, char *lla, struct ifreq *ifr) +{ + int alen; + + memset(ifr, 0, sizeof(*ifr)); + strncpy(ifr->ifr_name, dev, sizeof(ifr->ifr_name)); + ifr->ifr_hwaddr.sa_family = hatype; + alen = ll_addr_a2n((unsigned char *)(ifr->ifr_hwaddr.sa_data), 14, lla); + if (alen < 0) + return -1; + if (alen != halen) { + bb_error_msg("wrong address (%s) length: expected %d bytes", lla, halen); + return -1; + } + return 0; +} + +static int set_address(struct ifreq *ifr, int brd) +{ + int s; + + s = get_ctl_fd(); + if (s < 0) + return -1; + if (ioctl(s, brd?SIOCSIFHWBROADCAST:SIOCSIFHWADDR, ifr) < 0) { + perror(brd?"SIOCSIFHWBROADCAST":"SIOCSIFHWADDR"); + close(s); + return -1; + } + close(s); + return 0; +} + + +static int do_set(int argc, char **argv) +{ + char *dev = NULL; + __u32 mask = 0; + __u32 flags = 0; + int qlen = -1; + int mtu = -1; + char *newaddr = NULL; + char *newbrd = NULL; + struct ifreq ifr0, ifr1; + char *newname = NULL; + int htype, halen; + + while (argc > 0) { + if (strcmp(*argv, "up") == 0) { + mask |= IFF_UP; + flags |= IFF_UP; + } else if (strcmp(*argv, "down") == 0) { + mask |= IFF_UP; + flags &= ~IFF_UP; + } else if (strcmp(*argv, "name") == 0) { + NEXT_ARG(); + newname = *argv; + } else if (strcmp(*argv, "mtu") == 0) { + NEXT_ARG(); + if (mtu != -1) + duparg("mtu", *argv); + if (get_integer(&mtu, *argv, 0)) + invarg(*argv, "mtu"); + } else if (strcmp(*argv, "multicast") == 0) { + NEXT_ARG(); + mask |= IFF_MULTICAST; + if (strcmp(*argv, "on") == 0) { + flags |= IFF_MULTICAST; + } else if (strcmp(*argv, "off") == 0) { + flags &= ~IFF_MULTICAST; + } else + return on_off("multicast"); + } else if (strcmp(*argv, "arp") == 0) { + NEXT_ARG(); + mask |= IFF_NOARP; + if (strcmp(*argv, "on") == 0) { + flags &= ~IFF_NOARP; + } else if (strcmp(*argv, "off") == 0) { + flags |= IFF_NOARP; + } else + return on_off("noarp"); + } else if (strcmp(*argv, "addr") == 0) { + NEXT_ARG(); + newaddr = *argv; + } else { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + } + if (dev) + duparg2("dev", *argv); + dev = *argv; + } + argc--; argv++; + } + + if (!dev) { + bb_error_msg(bb_msg_requires_arg, "\"dev\""); + exit(-1); + } + + if (newaddr || newbrd) { + halen = get_address(dev, &htype); + if (halen < 0) + return -1; + if (newaddr) { + if (parse_address(dev, htype, halen, newaddr, &ifr0) < 0) + return -1; + } + if (newbrd) { + if (parse_address(dev, htype, halen, newbrd, &ifr1) < 0) + return -1; + } + } + + if (newname && strcmp(dev, newname)) { + if (do_changename(dev, newname) < 0) + return -1; + dev = newname; + } + if (qlen != -1) { + if (set_qlen(dev, qlen) < 0) + return -1; + } + if (mtu != -1) { + if (set_mtu(dev, mtu) < 0) + return -1; + } + if (newaddr || newbrd) { + if (newbrd) { + if (set_address(&ifr1, 1) < 0) + return -1; + } + if (newaddr) { + if (set_address(&ifr0, 0) < 0) + return -1; + } + } + if (mask) + return do_chflags(dev, flags, mask); + return 0; +} + +static int ipaddr_list_link(int argc, char **argv) +{ + preferred_family = AF_PACKET; + return ipaddr_list_or_flush(argc, argv, 0); +} + +int do_iplink(int argc, char **argv) +{ + if (argc > 0) { + if (matches(*argv, "set") == 0) + return do_set(argc-1, argv+1); + if (matches(*argv, "show") == 0 || + matches(*argv, "lst") == 0 || + matches(*argv, "list") == 0) + return ipaddr_list_link(argc-1, argv+1); + } else + return ipaddr_list_link(0, NULL); + + bb_error_msg("command \"%s\" is unknown", *argv); + exit(-1); +} diff --git a/networking/libiproute/iproute.c b/networking/libiproute/iproute.c new file mode 100644 index 000000000..9c3b87040 --- /dev/null +++ b/networking/libiproute/iproute.c @@ -0,0 +1,858 @@ +/* vi: set sw=4 ts=4: */ +/* + * iproute.c "ip route". + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, + * + * + * Changes: + * + * Rani Assaf 980929: resolve addresses + * Kunihiro Ishiguro 001102: rtnh_ifindex was not initialized + */ + +#include "libbb.h" + +#include + +#include +#include +#include + +#include "rt_names.h" +#include "utils.h" +#include "ip_common.h" + +#ifndef RTAX_RTTVAR +#define RTAX_RTTVAR RTAX_HOPS +#endif + + +static struct +{ + int tb; + int flushed; + char *flushb; + int flushp; + int flushe; + struct rtnl_handle *rth; + int protocol, protocolmask; + int scope, scopemask; + int type, typemask; + int tos, tosmask; + int iif, iifmask; + int oif, oifmask; + int realm, realmmask; + inet_prefix rprefsrc; + inet_prefix rvia; + inet_prefix rdst; + inet_prefix mdst; + inet_prefix rsrc; + inet_prefix msrc; +} filter; + +static int flush_update(void) +{ + if (rtnl_send(filter.rth, filter.flushb, filter.flushp) < 0) { + perror("Failed to send flush request\n"); + return -1; + } + filter.flushp = 0; + return 0; +} + +static int print_route(struct sockaddr_nl *who ATTRIBUTE_UNUSED, + struct nlmsghdr *n, void *arg) +{ + FILE *fp = (FILE*)arg; + struct rtmsg *r = NLMSG_DATA(n); + int len = n->nlmsg_len; + struct rtattr * tb[RTA_MAX+1]; + char abuf[256]; + inet_prefix dst; + inet_prefix src; + int host_len = -1; + SPRINT_BUF(b1); + + + if (n->nlmsg_type != RTM_NEWROUTE && n->nlmsg_type != RTM_DELROUTE) { + fprintf(stderr, "Not a route: %08x %08x %08x\n", + n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags); + return 0; + } + if (filter.flushb && n->nlmsg_type != RTM_NEWROUTE) + return 0; + len -= NLMSG_LENGTH(sizeof(*r)); + if (len < 0) { + bb_error_msg("wrong nlmsg len %d", len); + return -1; + } + + if (r->rtm_family == AF_INET6) + host_len = 128; + else if (r->rtm_family == AF_INET) + host_len = 32; + + if (r->rtm_family == AF_INET6) { + if (filter.tb) { + if (filter.tb < 0) { + if (!(r->rtm_flags&RTM_F_CLONED)) { + return 0; + } + } else { + if (r->rtm_flags&RTM_F_CLONED) { + return 0; + } + if (filter.tb == RT_TABLE_LOCAL) { + if (r->rtm_type != RTN_LOCAL) { + return 0; + } + } else if (filter.tb == RT_TABLE_MAIN) { + if (r->rtm_type == RTN_LOCAL) { + return 0; + } + } else { + return 0; + } + } + } + } else { + if (filter.tb > 0 && filter.tb != r->rtm_table) { + return 0; + } + } + if (filter.rdst.family && + (r->rtm_family != filter.rdst.family || filter.rdst.bitlen > r->rtm_dst_len)) { + return 0; + } + if (filter.mdst.family && + (r->rtm_family != filter.mdst.family || + (filter.mdst.bitlen >= 0 && filter.mdst.bitlen < r->rtm_dst_len))) { + return 0; + } + if (filter.rsrc.family && + (r->rtm_family != filter.rsrc.family || filter.rsrc.bitlen > r->rtm_src_len)) { + return 0; + } + if (filter.msrc.family && + (r->rtm_family != filter.msrc.family || + (filter.msrc.bitlen >= 0 && filter.msrc.bitlen < r->rtm_src_len))) { + return 0; + } + + memset(tb, 0, sizeof(tb)); + parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len); + + if (filter.rdst.family && inet_addr_match(&dst, &filter.rdst, filter.rdst.bitlen)) + return 0; + if (filter.mdst.family && filter.mdst.bitlen >= 0 && + inet_addr_match(&dst, &filter.mdst, r->rtm_dst_len)) + return 0; + + if (filter.rsrc.family && inet_addr_match(&src, &filter.rsrc, filter.rsrc.bitlen)) + return 0; + if (filter.msrc.family && filter.msrc.bitlen >= 0 && + inet_addr_match(&src, &filter.msrc, r->rtm_src_len)) + return 0; + + if (filter.flushb && + r->rtm_family == AF_INET6 && + r->rtm_dst_len == 0 && + r->rtm_type == RTN_UNREACHABLE && + tb[RTA_PRIORITY] && + *(int*)RTA_DATA(tb[RTA_PRIORITY]) == -1) + return 0; + + if (filter.flushb) { + struct nlmsghdr *fn; + if (NLMSG_ALIGN(filter.flushp) + n->nlmsg_len > filter.flushe) { + if (flush_update()) + return -1; + } + fn = (struct nlmsghdr*)(filter.flushb + NLMSG_ALIGN(filter.flushp)); + memcpy(fn, n, n->nlmsg_len); + fn->nlmsg_type = RTM_DELROUTE; + fn->nlmsg_flags = NLM_F_REQUEST; + fn->nlmsg_seq = ++filter.rth->seq; + filter.flushp = (((char*)fn) + n->nlmsg_len) - filter.flushb; + filter.flushed++; + return 0; + } + + if (n->nlmsg_type == RTM_DELROUTE) { + fprintf(fp, "Deleted "); + } + if (r->rtm_type != RTN_UNICAST && !filter.type) { + fprintf(fp, "%s ", rtnl_rtntype_n2a(r->rtm_type, b1, sizeof(b1))); + } + + if (tb[RTA_DST]) { + if (r->rtm_dst_len != host_len) { + fprintf(fp, "%s/%u ", rt_addr_n2a(r->rtm_family, + RTA_PAYLOAD(tb[RTA_DST]), + RTA_DATA(tb[RTA_DST]), + abuf, sizeof(abuf)), + r->rtm_dst_len + ); + } else { + fprintf(fp, "%s ", format_host(r->rtm_family, + RTA_PAYLOAD(tb[RTA_DST]), + RTA_DATA(tb[RTA_DST]), + abuf, sizeof(abuf)) + ); + } + } else if (r->rtm_dst_len) { + fprintf(fp, "0/%d ", r->rtm_dst_len); + } else { + fprintf(fp, "default "); + } + if (tb[RTA_SRC]) { + if (r->rtm_src_len != host_len) { + fprintf(fp, "from %s/%u ", rt_addr_n2a(r->rtm_family, + RTA_PAYLOAD(tb[RTA_SRC]), + RTA_DATA(tb[RTA_SRC]), + abuf, sizeof(abuf)), + r->rtm_src_len + ); + } else { + fprintf(fp, "from %s ", format_host(r->rtm_family, + RTA_PAYLOAD(tb[RTA_SRC]), + RTA_DATA(tb[RTA_SRC]), + abuf, sizeof(abuf)) + ); + } + } else if (r->rtm_src_len) { + fprintf(fp, "from 0/%u ", r->rtm_src_len); + } + if (tb[RTA_GATEWAY] && filter.rvia.bitlen != host_len) { + fprintf(fp, "via %s ", + format_host(r->rtm_family, + RTA_PAYLOAD(tb[RTA_GATEWAY]), + RTA_DATA(tb[RTA_GATEWAY]), + abuf, sizeof(abuf))); + } + if (tb[RTA_OIF] && filter.oifmask != -1) { + fprintf(fp, "dev %s ", ll_index_to_name(*(int*)RTA_DATA(tb[RTA_OIF]))); + } + + if (tb[RTA_PREFSRC] && filter.rprefsrc.bitlen != host_len) { + /* Do not use format_host(). It is our local addr + and symbolic name will not be useful. + */ + fprintf(fp, " src %s ", + rt_addr_n2a(r->rtm_family, + RTA_PAYLOAD(tb[RTA_PREFSRC]), + RTA_DATA(tb[RTA_PREFSRC]), + abuf, sizeof(abuf))); + } + if (tb[RTA_PRIORITY]) { + fprintf(fp, " metric %d ", *(__u32*)RTA_DATA(tb[RTA_PRIORITY])); + } + if (r->rtm_family == AF_INET6) { + struct rta_cacheinfo *ci = NULL; + if (tb[RTA_CACHEINFO]) { + ci = RTA_DATA(tb[RTA_CACHEINFO]); + } + if ((r->rtm_flags & RTM_F_CLONED) || (ci && ci->rta_expires)) { + static int hz; + if (!hz) { + hz = get_hz(); + } + if (r->rtm_flags & RTM_F_CLONED) { + fprintf(fp, "%s cache ", _SL_); + } + if (ci->rta_expires) { + fprintf(fp, " expires %dsec", ci->rta_expires/hz); + } + if (ci->rta_error != 0) { + fprintf(fp, " error %d", ci->rta_error); + } + } else if (ci) { + if (ci->rta_error != 0) + fprintf(fp, " error %d", ci->rta_error); + } + } + if (tb[RTA_IIF] && filter.iifmask != -1) { + fprintf(fp, " iif %s", ll_index_to_name(*(int*)RTA_DATA(tb[RTA_IIF]))); + } + fprintf(fp, "\n"); + fflush(fp); + return 0; +} + +static int iproute_modify(int cmd, unsigned flags, int argc, char **argv) +{ + struct rtnl_handle rth; + struct { + struct nlmsghdr n; + struct rtmsg r; + char buf[1024]; + } req; + char mxbuf[256]; + struct rtattr * mxrta = (void*)mxbuf; + unsigned mxlock = 0; + char *d = NULL; + int gw_ok = 0; + int dst_ok = 0; + int proto_ok = 0; + int type_ok = 0; + + memset(&req, 0, sizeof(req)); + + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST|flags; + req.n.nlmsg_type = cmd; + req.r.rtm_family = preferred_family; + req.r.rtm_table = RT_TABLE_MAIN; + req.r.rtm_scope = RT_SCOPE_NOWHERE; + + if (cmd != RTM_DELROUTE) { + req.r.rtm_protocol = RTPROT_BOOT; + req.r.rtm_scope = RT_SCOPE_UNIVERSE; + req.r.rtm_type = RTN_UNICAST; + } + + mxrta->rta_type = RTA_METRICS; + mxrta->rta_len = RTA_LENGTH(0); + + while (argc > 0) { + if (strcmp(*argv, "src") == 0) { + inet_prefix addr; + NEXT_ARG(); + get_addr(&addr, *argv, req.r.rtm_family); + if (req.r.rtm_family == AF_UNSPEC) { + req.r.rtm_family = addr.family; + } + addattr_l(&req.n, sizeof(req), RTA_PREFSRC, &addr.data, addr.bytelen); + } else if (strcmp(*argv, "via") == 0) { + inet_prefix addr; + gw_ok = 1; + NEXT_ARG(); + get_addr(&addr, *argv, req.r.rtm_family); + if (req.r.rtm_family == AF_UNSPEC) { + req.r.rtm_family = addr.family; + } + addattr_l(&req.n, sizeof(req), RTA_GATEWAY, &addr.data, addr.bytelen); + } else if (strcmp(*argv, "mtu") == 0) { + unsigned mtu; + NEXT_ARG(); + if (strcmp(*argv, "lock") == 0) { + mxlock |= (1< '9') && + rtnl_rtntype_a2n(&type, *argv) == 0) { + NEXT_ARG(); + req.r.rtm_type = type; + type_ok = 1; + } + + if (dst_ok) { + duparg2("to", *argv); + } + get_prefix(&dst, *argv, req.r.rtm_family); + if (req.r.rtm_family == AF_UNSPEC) { + req.r.rtm_family = dst.family; + } + req.r.rtm_dst_len = dst.bitlen; + dst_ok = 1; + if (dst.bytelen) { + addattr_l(&req.n, sizeof(req), RTA_DST, &dst.data, dst.bytelen); + } + } + argc--; argv++; + } + + if (rtnl_open(&rth, 0) < 0) { + exit(1); + } + + if (d) { + int idx; + + ll_init_map(&rth); + + if (d) { + if ((idx = ll_name_to_index(d)) == 0) { + bb_error_msg("cannot find device \"%s\"", d); + return -1; + } + addattr32(&req.n, sizeof(req), RTA_OIF, idx); + } + } + + if (mxrta->rta_len > RTA_LENGTH(0)) { + if (mxlock) { + rta_addattr32(mxrta, sizeof(mxbuf), RTAX_LOCK, mxlock); + } + addattr_l(&req.n, sizeof(req), RTA_METRICS, RTA_DATA(mxrta), RTA_PAYLOAD(mxrta)); + } + + if (req.r.rtm_family == AF_UNSPEC) { + req.r.rtm_family = AF_INET; + } + + if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0) { + exit(2); + } + + return 0; +} + +static int rtnl_rtcache_request(struct rtnl_handle *rth, int family) +{ + struct { + struct nlmsghdr nlh; + struct rtmsg rtm; + } req; + struct sockaddr_nl nladdr; + + memset(&nladdr, 0, sizeof(nladdr)); + memset(&req, 0, sizeof(req)); + nladdr.nl_family = AF_NETLINK; + + req.nlh.nlmsg_len = sizeof(req); + req.nlh.nlmsg_type = RTM_GETROUTE; + req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_REQUEST; + req.nlh.nlmsg_pid = 0; + req.nlh.nlmsg_seq = rth->dump = ++rth->seq; + req.rtm.rtm_family = family; + req.rtm.rtm_flags |= RTM_F_CLONED; + + return sendto(rth->fd, (void*)&req, sizeof(req), 0, (struct sockaddr*)&nladdr, sizeof(nladdr)); +} + +static int iproute_flush_cache(void) +{ +#define ROUTE_FLUSH_PATH "/proc/sys/net/ipv4/route/flush" + + int len; + int flush_fd = open (ROUTE_FLUSH_PATH, O_WRONLY); + char *buffer = "-1"; + + if (flush_fd < 0) { + fprintf(stderr, "Cannot open \"%s\"\n", ROUTE_FLUSH_PATH); + return -1; + } + + len = strlen (buffer); + + if ((write (flush_fd, (void *)buffer, len)) < len) { + fprintf(stderr, "Cannot flush routing cache\n"); + return -1; + } + close(flush_fd); + return 0; +} + +static void iproute_reset_filter(void) +{ + memset(&filter, 0, sizeof(filter)); + filter.mdst.bitlen = -1; + filter.msrc.bitlen = -1; +} + +static int iproute_list_or_flush(int argc, char **argv, int flush) +{ + int do_ipv6 = preferred_family; + struct rtnl_handle rth; + char *id = NULL; + char *od = NULL; + + iproute_reset_filter(); + filter.tb = RT_TABLE_MAIN; + + if (flush && argc <= 0) { + bb_error_msg(bb_msg_requires_arg, "\"ip route flush\""); + return -1; + } + + while (argc > 0) { + if (matches(*argv, "protocol") == 0) { + uint32_t prot = 0; + NEXT_ARG(); + filter.protocolmask = -1; + if (rtnl_rtprot_a2n(&prot, *argv)) { + if (strcmp(*argv, "all") != 0) { + invarg(*argv, "protocol"); + } + prot = 0; + filter.protocolmask = 0; + } + filter.protocol = prot; + } else if (strcmp(*argv, "dev") == 0 || + strcmp(*argv, "oif") == 0) { + NEXT_ARG(); + od = *argv; + } else if (strcmp(*argv, "iif") == 0) { + NEXT_ARG(); + id = *argv; + } else if (matches(*argv, "from") == 0) { + NEXT_ARG(); + if (matches(*argv, "root") == 0) { + NEXT_ARG(); + get_prefix(&filter.rsrc, *argv, do_ipv6); + } else if (matches(*argv, "match") == 0) { + NEXT_ARG(); + get_prefix(&filter.msrc, *argv, do_ipv6); + } else { + if (matches(*argv, "exact") == 0) { + NEXT_ARG(); + } + get_prefix(&filter.msrc, *argv, do_ipv6); + filter.rsrc = filter.msrc; + } + } else { + if (matches(*argv, "to") == 0) { + NEXT_ARG(); + } + if (matches(*argv, "root") == 0) { + NEXT_ARG(); + get_prefix(&filter.rdst, *argv, do_ipv6); + } else if (matches(*argv, "match") == 0) { + NEXT_ARG(); + get_prefix(&filter.mdst, *argv, do_ipv6); + } else if (matches(*argv, "table") == 0) { + NEXT_ARG(); + if (matches(*argv, "cache") == 0) { + filter.tb = -1; + } else if (matches(*argv, "main") != 0) { + invarg(*argv, "table"); + } + } else if (matches(*argv, "cache") == 0) { + filter.tb = -1; + } else { + if (matches(*argv, "exact") == 0) { + NEXT_ARG(); + } + get_prefix(&filter.mdst, *argv, do_ipv6); + filter.rdst = filter.mdst; + } + } + argc--; argv++; + } + + if (do_ipv6 == AF_UNSPEC && filter.tb) { + do_ipv6 = AF_INET; + } + + if (rtnl_open(&rth, 0) < 0) { + exit(1); + } + + ll_init_map(&rth); + + if (id || od) { + int idx; + + if (id) { + if ((idx = ll_name_to_index(id)) == 0) { + bb_error_msg("cannot find device \"%s\"", id); + return -1; + } + filter.iif = idx; + filter.iifmask = -1; + } + if (od) { + if ((idx = ll_name_to_index(od)) == 0) { + bb_error_msg("cannot find device \"%s\"", od); + } + filter.oif = idx; + filter.oifmask = -1; + } + } + + if (flush) { + char flushb[4096-512]; + + if (filter.tb == -1) { + if (do_ipv6 != AF_INET6) + iproute_flush_cache(); + if (do_ipv6 == AF_INET) + return 0; + } + + filter.flushb = flushb; + filter.flushp = 0; + filter.flushe = sizeof(flushb); + filter.rth = &rth; + + for (;;) { + if (rtnl_wilddump_request(&rth, do_ipv6, RTM_GETROUTE) < 0) { + perror("Cannot send dump request"); + return -1; + } + filter.flushed = 0; + if (rtnl_dump_filter(&rth, print_route, stdout, NULL, NULL) < 0) { + bb_error_msg("flush terminated"); + return -1; + } + if (filter.flushed == 0) { + fflush(stdout); + return 0; + } + if (flush_update() < 0) + exit(1); + } + } + + if (filter.tb != -1) { + if (rtnl_wilddump_request(&rth, do_ipv6, RTM_GETROUTE) < 0) { + bb_perror_msg_and_die("cannot send dump request"); + } + } else { + if (rtnl_rtcache_request(&rth, do_ipv6) < 0) { + bb_perror_msg_and_die("cannot send dump request"); + } + } + + if (rtnl_dump_filter(&rth, print_route, stdout, NULL, NULL) < 0) { + bb_error_msg_and_die("dump terminated"); + } + + exit(0); +} + + +static int iproute_get(int argc, char **argv) +{ + struct rtnl_handle rth; + struct { + struct nlmsghdr n; + struct rtmsg r; + char buf[1024]; + } req; + char *idev = NULL; + char *odev = NULL; + int connected = 0; + int from_ok = 0; + static const char * const options[] = + { "from", "iif", "oif", "dev", "notify", "connected", "to", 0 }; + + memset(&req, 0, sizeof(req)); + + iproute_reset_filter(); + + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST; + req.n.nlmsg_type = RTM_GETROUTE; + req.r.rtm_family = preferred_family; + req.r.rtm_table = 0; + req.r.rtm_protocol = 0; + req.r.rtm_scope = 0; + req.r.rtm_type = 0; + req.r.rtm_src_len = 0; + req.r.rtm_dst_len = 0; + req.r.rtm_tos = 0; + + while (argc > 0) { + switch (index_in_str_array(options, *argv)) { + case 0: /* from */ + { + inet_prefix addr; + NEXT_ARG(); + from_ok = 1; + get_prefix(&addr, *argv, req.r.rtm_family); + if (req.r.rtm_family == AF_UNSPEC) { + req.r.rtm_family = addr.family; + } + if (addr.bytelen) { + addattr_l(&req.n, sizeof(req), RTA_SRC, &addr.data, addr.bytelen); + } + req.r.rtm_src_len = addr.bitlen; + break; + } + case 1: /* iif */ + NEXT_ARG(); + idev = *argv; + break; + case 2: /* oif */ + case 3: /* dev */ + NEXT_ARG(); + odev = *argv; + break; + case 4: /* notify */ + req.r.rtm_flags |= RTM_F_NOTIFY; + break; + case 5: /* connected */ + connected = 1; + break; + case 6: /* to */ + NEXT_ARG(); + default: + { + inet_prefix addr; + get_prefix(&addr, *argv, req.r.rtm_family); + if (req.r.rtm_family == AF_UNSPEC) { + req.r.rtm_family = addr.family; + } + if (addr.bytelen) { + addattr_l(&req.n, sizeof(req), RTA_DST, &addr.data, addr.bytelen); + } + req.r.rtm_dst_len = addr.bitlen; + } + argc--; argv++; + } + } + + if (req.r.rtm_dst_len == 0) { + bb_error_msg_and_die("need at least destination address"); + } + + if (rtnl_open(&rth, 0) < 0) + exit(1); + + ll_init_map(&rth); + + if (idev || odev) { + int idx; + + if (idev) { + if ((idx = ll_name_to_index(idev)) == 0) { + bb_error_msg("cannot find device \"%s\"", idev); + return -1; + } + addattr32(&req.n, sizeof(req), RTA_IIF, idx); + } + if (odev) { + if ((idx = ll_name_to_index(odev)) == 0) { + bb_error_msg("cannot find device \"%s\"", odev); + return -1; + } + addattr32(&req.n, sizeof(req), RTA_OIF, idx); + } + } + + if (req.r.rtm_family == AF_UNSPEC) { + req.r.rtm_family = AF_INET; + } + + if (rtnl_talk(&rth, &req.n, 0, 0, &req.n, NULL, NULL) < 0) { + exit(2); + } + + if (connected && !from_ok) { + struct rtmsg *r = NLMSG_DATA(&req.n); + int len = req.n.nlmsg_len; + struct rtattr * tb[RTA_MAX+1]; + + if (print_route(NULL, &req.n, (void*)stdout) < 0) { + bb_error_msg_and_die("an error :-)"); + } + + if (req.n.nlmsg_type != RTM_NEWROUTE) { + bb_error_msg("not a route?"); + return -1; + } + len -= NLMSG_LENGTH(sizeof(*r)); + if (len < 0) { + bb_error_msg("wrong len %d", len); + return -1; + } + + memset(tb, 0, sizeof(tb)); + parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len); + + if (tb[RTA_PREFSRC]) { + tb[RTA_PREFSRC]->rta_type = RTA_SRC; + r->rtm_src_len = 8*RTA_PAYLOAD(tb[RTA_PREFSRC]); + } else if (!tb[RTA_SRC]) { + bb_error_msg("failed to connect the route"); + return -1; + } + if (!odev && tb[RTA_OIF]) { + tb[RTA_OIF]->rta_type = 0; + } + if (tb[RTA_GATEWAY]) { + tb[RTA_GATEWAY]->rta_type = 0; + } + if (!idev && tb[RTA_IIF]) { + tb[RTA_IIF]->rta_type = 0; + } + req.n.nlmsg_flags = NLM_F_REQUEST; + req.n.nlmsg_type = RTM_GETROUTE; + + if (rtnl_talk(&rth, &req.n, 0, 0, &req.n, NULL, NULL) < 0) { + exit(2); + } + } + + if (print_route(NULL, &req.n, (void*)stdout) < 0) { + bb_error_msg_and_die("an error :-)"); + } + + exit(0); +} + +int do_iproute(int argc, char **argv) +{ + static const char * const ip_route_commands[] = + { "add", "append", "change", "chg", "delete", "get", + "list", "show", "prepend", "replace", "test", "flush", 0 }; + int command_num = 6; + unsigned int flags = 0; + int cmd = RTM_NEWROUTE; + + /* "Standard" 'ip r a' treats 'a' as 'add', not 'append' */ + /* It probably means that it is using "first match" rule */ + if (*argv) { + command_num = index_in_substr_array(ip_route_commands, *argv); + } + switch (command_num) { + case 0: /* add*/ + flags = NLM_F_CREATE|NLM_F_EXCL; + break; + case 1: /* append */ + flags = NLM_F_CREATE|NLM_F_APPEND; + break; + case 2: /* change */ + case 3: /* chg */ + flags = NLM_F_REPLACE; + break; + case 4: /* delete */ + case 5: /* del */ + cmd = RTM_DELROUTE; + break; + case 6: /* get */ + return iproute_get(argc-1, argv+1); + case 7: /* list */ + case 8: /* show */ + return iproute_list_or_flush(argc-1, argv+1, 0); + case 9: /* prepend */ + flags = NLM_F_CREATE; + case 10: /* replace */ + flags = NLM_F_CREATE|NLM_F_REPLACE; + case 11: /* test */ + flags = NLM_F_EXCL; + case 12: /* flush */ + return iproute_list_or_flush(argc-1, argv+1, 1); + default: + bb_error_msg_and_die("unknown command %s", *argv); + } + + return iproute_modify(cmd, flags, argc-1, argv+1); +} diff --git a/networking/libiproute/iprule.c b/networking/libiproute/iprule.c new file mode 100644 index 000000000..19338770f --- /dev/null +++ b/networking/libiproute/iprule.c @@ -0,0 +1,331 @@ +/* vi: set sw=4 ts=4: */ +/* + * iprule.c "ip rule". + * + * 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. + * + * Authors: Alexey Kuznetsov, + * + * + * Changes: + * + * Rani Assaf 980929: resolve addresses + */ + +#include "libbb.h" +#include +#include +#include +#include +#include + +#include "rt_names.h" +#include "utils.h" +/* +static void usage(void) __attribute__((noreturn)); + +static void usage(void) +{ + fprintf(stderr, "Usage: ip rule [ list | add | del ] SELECTOR ACTION\n"); + fprintf(stderr, "SELECTOR := [ from PREFIX ] [ to PREFIX ] [ tos TOS ] [ fwmark FWMARK ]\n"); + fprintf(stderr, " [ dev STRING ] [ pref NUMBER ]\n"); + fprintf(stderr, "ACTION := [ table TABLE_ID ] [ nat ADDRESS ]\n"); + fprintf(stderr, " [ prohibit | reject | unreachable ]\n"); + fprintf(stderr, " [ realms [SRCREALM/]DSTREALM ]\n"); + fprintf(stderr, "TABLE_ID := [ local | main | default | NUMBER ]\n"); + exit(-1); +} +*/ +static int print_rule(struct sockaddr_nl *who ATTRIBUTE_UNUSED, + struct nlmsghdr *n, void *arg) +{ + FILE *fp = (FILE*)arg; + struct rtmsg *r = NLMSG_DATA(n); + int len = n->nlmsg_len; + int host_len = -1; + struct rtattr * tb[RTA_MAX+1]; + char abuf[256]; + SPRINT_BUF(b1); + + if (n->nlmsg_type != RTM_NEWRULE) + return 0; + + len -= NLMSG_LENGTH(sizeof(*r)); + if (len < 0) + return -1; + + memset(tb, 0, sizeof(tb)); + parse_rtattr(tb, RTA_MAX, RTM_RTA(r), len); + + if (r->rtm_family == AF_INET) + host_len = 32; + else if (r->rtm_family == AF_INET6) + host_len = 128; +/* else if (r->rtm_family == AF_DECnet) + host_len = 16; + else if (r->rtm_family == AF_IPX) + host_len = 80; +*/ + if (tb[RTA_PRIORITY]) + fprintf(fp, "%u:\t", *(unsigned*)RTA_DATA(tb[RTA_PRIORITY])); + else + fprintf(fp, "0:\t"); + + fprintf(fp, "from "); + if (tb[RTA_SRC]) { + if (r->rtm_src_len != host_len) { + fprintf(fp, "%s/%u", rt_addr_n2a(r->rtm_family, + RTA_PAYLOAD(tb[RTA_SRC]), + RTA_DATA(tb[RTA_SRC]), + abuf, sizeof(abuf)), + r->rtm_src_len + ); + } else { + fprintf(fp, "%s", format_host(r->rtm_family, + RTA_PAYLOAD(tb[RTA_SRC]), + RTA_DATA(tb[RTA_SRC]), + abuf, sizeof(abuf)) + ); + } + } else if (r->rtm_src_len) { + fprintf(fp, "0/%d", r->rtm_src_len); + } else { + fprintf(fp, "all"); + } + fprintf(fp, " "); + + if (tb[RTA_DST]) { + if (r->rtm_dst_len != host_len) { + fprintf(fp, "to %s/%u ", rt_addr_n2a(r->rtm_family, + RTA_PAYLOAD(tb[RTA_DST]), + RTA_DATA(tb[RTA_DST]), + abuf, sizeof(abuf)), + r->rtm_dst_len + ); + } else { + fprintf(fp, "to %s ", format_host(r->rtm_family, + RTA_PAYLOAD(tb[RTA_DST]), + RTA_DATA(tb[RTA_DST]), + abuf, sizeof(abuf))); + } + } else if (r->rtm_dst_len) { + fprintf(fp, "to 0/%d ", r->rtm_dst_len); + } + + if (r->rtm_tos) { + fprintf(fp, "tos %s ", rtnl_dsfield_n2a(r->rtm_tos, b1, sizeof(b1))); + } + if (tb[RTA_PROTOINFO]) { + fprintf(fp, "fwmark %#x ", *(__u32*)RTA_DATA(tb[RTA_PROTOINFO])); + } + + if (tb[RTA_IIF]) { + fprintf(fp, "iif %s ", (char*)RTA_DATA(tb[RTA_IIF])); + } + + if (r->rtm_table) + fprintf(fp, "lookup %s ", rtnl_rttable_n2a(r->rtm_table, b1, sizeof(b1))); + + if (tb[RTA_FLOW]) { + __u32 to = *(__u32*)RTA_DATA(tb[RTA_FLOW]); + __u32 from = to>>16; + to &= 0xFFFF; + if (from) { + fprintf(fp, "realms %s/", + rtnl_rtrealm_n2a(from, b1, sizeof(b1))); + } + fprintf(fp, "%s ", + rtnl_rtrealm_n2a(to, b1, sizeof(b1))); + } + + if (r->rtm_type == RTN_NAT) { + if (tb[RTA_GATEWAY]) { + fprintf(fp, "map-to %s ", + format_host(r->rtm_family, + RTA_PAYLOAD(tb[RTA_GATEWAY]), + RTA_DATA(tb[RTA_GATEWAY]), + abuf, sizeof(abuf))); + } else + fprintf(fp, "masquerade"); + } else if (r->rtm_type != RTN_UNICAST) + fprintf(fp, "%s", rtnl_rtntype_n2a(r->rtm_type, b1, sizeof(b1))); + + fprintf(fp, "\n"); + fflush(fp); + return 0; +} + +int iprule_list(int argc, char **argv) +{ + struct rtnl_handle rth; + int af = preferred_family; + + if (af == AF_UNSPEC) + af = AF_INET; + + if (argc > 0) { + bb_error_msg("\"rule show\" needs no arguments"); + return -1; + } + + if (rtnl_open(&rth, 0) < 0) + return 1; + + if (rtnl_wilddump_request(&rth, af, RTM_GETRULE) < 0) { + bb_perror_msg("Cannot send dump request"); + return 1; + } + + if (rtnl_dump_filter(&rth, print_rule, stdout, NULL, NULL) < 0) { + bb_error_msg("Dump terminated"); + return 1; + } + + return 0; +} + + +int iprule_modify(int cmd, int argc, char **argv) +{ + int table_ok = 0; + struct rtnl_handle rth; + struct { + struct nlmsghdr n; + struct rtmsg r; + char buf[1024]; + } req; + + memset(&req, 0, sizeof(req)); + + req.n.nlmsg_type = cmd; + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); + req.n.nlmsg_flags = NLM_F_REQUEST; + req.r.rtm_family = preferred_family; + req.r.rtm_protocol = RTPROT_BOOT; + req.r.rtm_scope = RT_SCOPE_UNIVERSE; + req.r.rtm_table = 0; + req.r.rtm_type = RTN_UNSPEC; + + if (cmd == RTM_NEWRULE) { + req.n.nlmsg_flags |= NLM_F_CREATE|NLM_F_EXCL; + req.r.rtm_type = RTN_UNICAST; + } + + while (argc > 0) { + if (strcmp(*argv, "from") == 0) { + inet_prefix dst; + NEXT_ARG(); + get_prefix(&dst, *argv, req.r.rtm_family); + req.r.rtm_src_len = dst.bitlen; + addattr_l(&req.n, sizeof(req), RTA_SRC, &dst.data, dst.bytelen); + } else if (strcmp(*argv, "to") == 0) { + inet_prefix dst; + NEXT_ARG(); + get_prefix(&dst, *argv, req.r.rtm_family); + req.r.rtm_dst_len = dst.bitlen; + addattr_l(&req.n, sizeof(req), RTA_DST, &dst.data, dst.bytelen); + } else if (matches(*argv, "preference") == 0 || + matches(*argv, "order") == 0 || + matches(*argv, "priority") == 0) { + __u32 pref; + NEXT_ARG(); + if (get_u32(&pref, *argv, 0)) + invarg("preference value", *argv); + addattr32(&req.n, sizeof(req), RTA_PRIORITY, pref); + } else if (strcmp(*argv, "tos") == 0) { + __u32 tos; + NEXT_ARG(); + if (rtnl_dsfield_a2n(&tos, *argv)) + invarg("TOS value", *argv); + req.r.rtm_tos = tos; + } else if (strcmp(*argv, "fwmark") == 0) { + __u32 fwmark; + NEXT_ARG(); + if (get_u32(&fwmark, *argv, 0)) + invarg("fwmark value", *argv); + addattr32(&req.n, sizeof(req), RTA_PROTOINFO, fwmark); + } else if (matches(*argv, "realms") == 0) { + __u32 realm; + NEXT_ARG(); + if (get_rt_realms(&realm, *argv)) + invarg("realms", *argv); + addattr32(&req.n, sizeof(req), RTA_FLOW, realm); + } else if (matches(*argv, "table") == 0 || + strcmp(*argv, "lookup") == 0) { + int tid; + NEXT_ARG(); + if (rtnl_rttable_a2n(&tid, *argv)) + invarg("table ID", *argv); + req.r.rtm_table = tid; + table_ok = 1; + } else if (strcmp(*argv, "dev") == 0 || + strcmp(*argv, "iif") == 0) { + NEXT_ARG(); + addattr_l(&req.n, sizeof(req), RTA_IIF, *argv, strlen(*argv)+1); + } else if (strcmp(*argv, "nat") == 0 || + matches(*argv, "map-to") == 0) { + NEXT_ARG(); + addattr32(&req.n, sizeof(req), RTA_GATEWAY, get_addr32(*argv)); + req.r.rtm_type = RTN_NAT; + } else { + int type; + + if (strcmp(*argv, "type") == 0) { + NEXT_ARG(); + } + if (matches(*argv, "help") == 0) + bb_show_usage(); + if (rtnl_rtntype_a2n(&type, *argv)) + invarg("Failed to parse rule type", *argv); + req.r.rtm_type = type; + } + argc--; + argv++; + } + + if (req.r.rtm_family == AF_UNSPEC) + req.r.rtm_family = AF_INET; + + if (!table_ok && cmd == RTM_NEWRULE) + req.r.rtm_table = RT_TABLE_MAIN; + + if (rtnl_open(&rth, 0) < 0) + return 1; + + if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0) + return 2; + + return 0; +} + +int do_iprule(int argc, char **argv) +{ + static const char * const ip_rule_commands[] = + {"add", "delete", "list", "show", 0}; + int command_num = 2; + int cmd; + + if (argc < 1) + return iprule_list(0, NULL); + if (*argv) + command_num = index_in_substr_array(ip_rule_commands, *argv); + switch (command_num) { + case 0: /* add */ + cmd = RTM_NEWRULE; + break; + case 1: /* delete */ + cmd = RTM_DELRULE; + break; + case 2: /* list */ + case 3: /* show */ + return iprule_list(argc-1, argv+1); + break; + default: + bb_error_msg_and_die("unknown command %s", *argv); + } + return iprule_modify(cmd, argc-1, argv+1); +} + diff --git a/networking/libiproute/iptunnel.c b/networking/libiproute/iptunnel.c new file mode 100644 index 000000000..2080324ac --- /dev/null +++ b/networking/libiproute/iptunnel.c @@ -0,0 +1,546 @@ +/* vi: set sw=4 ts=4: */ +/* + * iptunnel.c "ip tunnel" + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Authors: Alexey Kuznetsov, + * + * + * Changes: + * + * Rani Assaf 980929: resolve addresses + * Rani Assaf 980930: do not allow key for ipip/sit + * Phil Karn 990408: "pmtudisc" flag + */ + +#include "libbb.h" +#include +#include + +#include +#include + +#include + +#include +#include + +#include +#ifndef __constant_htons +#define __constant_htons htons +#endif +#include + +#include "rt_names.h" +#include "utils.h" +#include "ip_common.h" + + +static int do_ioctl_get_ifindex(char *dev) +{ + struct ifreq ifr; + int fd; + + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + if (ioctl(fd, SIOCGIFINDEX, &ifr)) { + bb_perror_msg("ioctl"); + return 0; + } + close(fd); + return ifr.ifr_ifindex; +} + +static int do_ioctl_get_iftype(char *dev) +{ + struct ifreq ifr; + int fd; + + strncpy(ifr.ifr_name, dev, sizeof(ifr.ifr_name)); + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + if (ioctl(fd, SIOCGIFHWADDR, &ifr)) { + bb_perror_msg("ioctl"); + return -1; + } + close(fd); + return ifr.ifr_addr.sa_family; +} + + +static char *do_ioctl_get_ifname(int idx) +{ + static struct ifreq ifr; + int fd; + + ifr.ifr_ifindex = idx; + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + if (ioctl(fd, SIOCGIFNAME, &ifr)) { + bb_perror_msg("ioctl"); + return NULL; + } + close(fd); + return ifr.ifr_name; +} + + + +static int do_get_ioctl(char *basedev, struct ip_tunnel_parm *p) +{ + struct ifreq ifr; + int fd; + int err; + + strncpy(ifr.ifr_name, basedev, sizeof(ifr.ifr_name)); + ifr.ifr_ifru.ifru_data = (void*)p; + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + err = ioctl(fd, SIOCGETTUNNEL, &ifr); + if (err) { + bb_perror_msg("ioctl"); + } + close(fd); + return err; +} + +static int do_add_ioctl(int cmd, char *basedev, struct ip_tunnel_parm *p) +{ + struct ifreq ifr; + int fd; + int err; + + if (cmd == SIOCCHGTUNNEL && p->name[0]) { + strncpy(ifr.ifr_name, p->name, sizeof(ifr.ifr_name)); + } else { + strncpy(ifr.ifr_name, basedev, sizeof(ifr.ifr_name)); + } + ifr.ifr_ifru.ifru_data = (void*)p; + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + err = ioctl(fd, cmd, &ifr); + if (err) { + bb_perror_msg("ioctl"); + } + close(fd); + return err; +} + +static int do_del_ioctl(char *basedev, struct ip_tunnel_parm *p) +{ + struct ifreq ifr; + int fd; + int err; + + if (p->name[0]) { + strncpy(ifr.ifr_name, p->name, sizeof(ifr.ifr_name)); + } else { + strncpy(ifr.ifr_name, basedev, sizeof(ifr.ifr_name)); + } + ifr.ifr_ifru.ifru_data = (void*)p; + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + err = ioctl(fd, SIOCDELTUNNEL, &ifr); + if (err) { + bb_perror_msg("ioctl"); + } + close(fd); + return err; +} + +static int parse_args(int argc, char **argv, int cmd, struct ip_tunnel_parm *p) +{ + int count = 0; + char medium[IFNAMSIZ]; + memset(p, 0, sizeof(*p)); + memset(&medium, 0, sizeof(medium)); + + p->iph.version = 4; + p->iph.ihl = 5; +#ifndef IP_DF +#define IP_DF 0x4000 /* Flag: "Don't Fragment" */ +#endif + p->iph.frag_off = htons(IP_DF); + + while (argc > 0) { + if (strcmp(*argv, "mode") == 0) { + NEXT_ARG(); + if (strcmp(*argv, "ipip") == 0 || + strcmp(*argv, "ip/ip") == 0) { + if (p->iph.protocol && p->iph.protocol != IPPROTO_IPIP) { + bb_error_msg("you managed to ask for more than one tunnel mode"); + exit(-1); + } + p->iph.protocol = IPPROTO_IPIP; + } else if (strcmp(*argv, "gre") == 0 || + strcmp(*argv, "gre/ip") == 0) { + if (p->iph.protocol && p->iph.protocol != IPPROTO_GRE) { + bb_error_msg("you managed to ask for more than one tunnel mode"); + exit(-1); + } + p->iph.protocol = IPPROTO_GRE; + } else if (strcmp(*argv, "sit") == 0 || + strcmp(*argv, "ipv6/ip") == 0) { + if (p->iph.protocol && p->iph.protocol != IPPROTO_IPV6) { + bb_error_msg("you managed to ask for more than one tunnel mode"); + exit(-1); + } + p->iph.protocol = IPPROTO_IPV6; + } else { + bb_error_msg("cannot guess tunnel mode"); + exit(-1); + } + } else if (strcmp(*argv, "key") == 0) { + unsigned uval; + NEXT_ARG(); + p->i_flags |= GRE_KEY; + p->o_flags |= GRE_KEY; + if (strchr(*argv, '.')) + p->i_key = p->o_key = get_addr32(*argv); + else { + if (get_unsigned(&uval, *argv, 0)<0) { + bb_error_msg("invalid value of \"key\""); + exit(-1); + } + p->i_key = p->o_key = htonl(uval); + } + } else if (strcmp(*argv, "ikey") == 0) { + unsigned uval; + NEXT_ARG(); + p->i_flags |= GRE_KEY; + if (strchr(*argv, '.')) + p->o_key = get_addr32(*argv); + else { + if (get_unsigned(&uval, *argv, 0)<0) { + bb_error_msg("invalid value of \"ikey\""); + exit(-1); + } + p->i_key = htonl(uval); + } + } else if (strcmp(*argv, "okey") == 0) { + unsigned uval; + NEXT_ARG(); + p->o_flags |= GRE_KEY; + if (strchr(*argv, '.')) + p->o_key = get_addr32(*argv); + else { + if (get_unsigned(&uval, *argv, 0)<0) { + bb_error_msg("invalid value of \"okey\""); + exit(-1); + } + p->o_key = htonl(uval); + } + } else if (strcmp(*argv, "seq") == 0) { + p->i_flags |= GRE_SEQ; + p->o_flags |= GRE_SEQ; + } else if (strcmp(*argv, "iseq") == 0) { + p->i_flags |= GRE_SEQ; + } else if (strcmp(*argv, "oseq") == 0) { + p->o_flags |= GRE_SEQ; + } else if (strcmp(*argv, "csum") == 0) { + p->i_flags |= GRE_CSUM; + p->o_flags |= GRE_CSUM; + } else if (strcmp(*argv, "icsum") == 0) { + p->i_flags |= GRE_CSUM; + } else if (strcmp(*argv, "ocsum") == 0) { + p->o_flags |= GRE_CSUM; + } else if (strcmp(*argv, "nopmtudisc") == 0) { + p->iph.frag_off = 0; + } else if (strcmp(*argv, "pmtudisc") == 0) { + p->iph.frag_off = htons(IP_DF); + } else if (strcmp(*argv, "remote") == 0) { + NEXT_ARG(); + if (strcmp(*argv, "any")) + p->iph.daddr = get_addr32(*argv); + } else if (strcmp(*argv, "local") == 0) { + NEXT_ARG(); + if (strcmp(*argv, "any")) + p->iph.saddr = get_addr32(*argv); + } else if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + strncpy(medium, *argv, IFNAMSIZ-1); + } else if (strcmp(*argv, "ttl") == 0) { + unsigned uval; + NEXT_ARG(); + if (strcmp(*argv, "inherit") != 0) { + if (get_unsigned(&uval, *argv, 0)) + invarg(*argv, "TTL"); + if (uval > 255) + invarg(*argv, "TTL must be <=255"); + p->iph.ttl = uval; + } + } else if (strcmp(*argv, "tos") == 0 || + matches(*argv, "dsfield") == 0) { + __u32 uval; + NEXT_ARG(); + if (strcmp(*argv, "inherit") != 0) { + if (rtnl_dsfield_a2n(&uval, *argv)) + invarg(*argv, "TOS"); + p->iph.tos = uval; + } else + p->iph.tos = 1; + } else { + if (strcmp(*argv, "name") == 0) { + NEXT_ARG(); + } + if (p->name[0]) + duparg2("name", *argv); + strncpy(p->name, *argv, IFNAMSIZ); + if (cmd == SIOCCHGTUNNEL && count == 0) { + struct ip_tunnel_parm old_p; + memset(&old_p, 0, sizeof(old_p)); + if (do_get_ioctl(*argv, &old_p)) + return -1; + *p = old_p; + } + } + count++; + argc--; argv++; + } + + + if (p->iph.protocol == 0) { + if (memcmp(p->name, "gre", 3) == 0) + p->iph.protocol = IPPROTO_GRE; + else if (memcmp(p->name, "ipip", 4) == 0) + p->iph.protocol = IPPROTO_IPIP; + else if (memcmp(p->name, "sit", 3) == 0) + p->iph.protocol = IPPROTO_IPV6; + } + + if (p->iph.protocol == IPPROTO_IPIP || p->iph.protocol == IPPROTO_IPV6) { + if ((p->i_flags & GRE_KEY) || (p->o_flags & GRE_KEY)) { + bb_error_msg("keys are not allowed with ipip and sit"); + return -1; + } + } + + if (medium[0]) { + p->link = do_ioctl_get_ifindex(medium); + if (p->link == 0) + return -1; + } + + if (p->i_key == 0 && IN_MULTICAST(ntohl(p->iph.daddr))) { + p->i_key = p->iph.daddr; + p->i_flags |= GRE_KEY; + } + if (p->o_key == 0 && IN_MULTICAST(ntohl(p->iph.daddr))) { + p->o_key = p->iph.daddr; + p->o_flags |= GRE_KEY; + } + if (IN_MULTICAST(ntohl(p->iph.daddr)) && !p->iph.saddr) { + bb_error_msg("broadcast tunnel requires a source address"); + return -1; + } + return 0; +} + + +static int do_add(int cmd, int argc, char **argv) +{ + struct ip_tunnel_parm p; + + if (parse_args(argc, argv, cmd, &p) < 0) + return -1; + + if (p.iph.ttl && p.iph.frag_off == 0) { + bb_error_msg("ttl != 0 and noptmudisc are incompatible"); + return -1; + } + + switch (p.iph.protocol) { + case IPPROTO_IPIP: + return do_add_ioctl(cmd, "tunl0", &p); + case IPPROTO_GRE: + return do_add_ioctl(cmd, "gre0", &p); + case IPPROTO_IPV6: + return do_add_ioctl(cmd, "sit0", &p); + default: + bb_error_msg("cannot determine tunnel mode (ipip, gre or sit)"); + return -1; + } + return -1; +} + +static int do_del(int argc, char **argv) +{ + struct ip_tunnel_parm p; + + if (parse_args(argc, argv, SIOCDELTUNNEL, &p) < 0) + return -1; + + switch (p.iph.protocol) { + case IPPROTO_IPIP: + return do_del_ioctl("tunl0", &p); + case IPPROTO_GRE: + return do_del_ioctl("gre0", &p); + case IPPROTO_IPV6: + return do_del_ioctl("sit0", &p); + default: + return do_del_ioctl(p.name, &p); + } + return -1; +} + +static void print_tunnel(struct ip_tunnel_parm *p) +{ + char s1[256]; + char s2[256]; + char s3[64]; + char s4[64]; + + format_host(AF_INET, 4, &p->iph.daddr, s1, sizeof(s1)); + format_host(AF_INET, 4, &p->iph.saddr, s2, sizeof(s2)); + inet_ntop(AF_INET, &p->i_key, s3, sizeof(s3)); + inet_ntop(AF_INET, &p->o_key, s4, sizeof(s4)); + + printf("%s: %s/ip remote %s local %s ", + p->name, + p->iph.protocol == IPPROTO_IPIP ? "ip" : + (p->iph.protocol == IPPROTO_GRE ? "gre" : + (p->iph.protocol == IPPROTO_IPV6 ? "ipv6" : "unknown")), + p->iph.daddr ? s1 : "any", p->iph.saddr ? s2 : "any"); + if (p->link) { + char *n = do_ioctl_get_ifname(p->link); + if (n) + printf(" dev %s ", n); + } + if (p->iph.ttl) + printf(" ttl %d ", p->iph.ttl); + else + printf(" ttl inherit "); + if (p->iph.tos) { + SPRINT_BUF(b1); + printf(" tos"); + if (p->iph.tos&1) + printf(" inherit"); + if (p->iph.tos&~1) + printf("%c%s ", p->iph.tos&1 ? '/' : ' ', + rtnl_dsfield_n2a(p->iph.tos&~1, b1, sizeof(b1))); + } + if (!(p->iph.frag_off&htons(IP_DF))) + printf(" nopmtudisc"); + + if ((p->i_flags&GRE_KEY) && (p->o_flags&GRE_KEY) && p->o_key == p->i_key) + printf(" key %s", s3); + else if ((p->i_flags|p->o_flags)&GRE_KEY) { + if (p->i_flags&GRE_KEY) + printf(" ikey %s ", s3); + if (p->o_flags&GRE_KEY) + printf(" okey %s ", s4); + } + + if (p->i_flags&GRE_SEQ) + printf("%s Drop packets out of sequence.\n", _SL_); + if (p->i_flags&GRE_CSUM) + printf("%s Checksum in received packet is required.", _SL_); + if (p->o_flags&GRE_SEQ) + printf("%s Sequence packets on output.", _SL_); + if (p->o_flags&GRE_CSUM) + printf("%s Checksum output packets.", _SL_); +} + +static int do_tunnels_list(struct ip_tunnel_parm *p) +{ + char name[IFNAMSIZ]; + unsigned long rx_bytes, rx_packets, rx_errs, rx_drops, + rx_fifo, rx_frame, + tx_bytes, tx_packets, tx_errs, tx_drops, + tx_fifo, tx_colls, tx_carrier, rx_multi; + int type; + struct ip_tunnel_parm p1; + + char buf[512]; + FILE *fp = fopen("/proc/net/dev", "r"); + if (fp == NULL) { + perror("fopen"); + return -1; + } + + fgets(buf, sizeof(buf), fp); + fgets(buf, sizeof(buf), fp); + + while (fgets(buf, sizeof(buf), fp) != NULL) { + char *ptr; + buf[sizeof(buf) - 1] = 0; + if ((ptr = strchr(buf, ':')) == NULL || + (*ptr++ = 0, sscanf(buf, "%s", name) != 1)) { + bb_error_msg("wrong format of /proc/net/dev. Sorry"); + return -1; + } + if (sscanf(ptr, "%lu%lu%lu%lu%lu%lu%lu%*d%lu%lu%lu%lu%lu%lu%lu", + &rx_bytes, &rx_packets, &rx_errs, &rx_drops, + &rx_fifo, &rx_frame, &rx_multi, + &tx_bytes, &tx_packets, &tx_errs, &tx_drops, + &tx_fifo, &tx_colls, &tx_carrier) != 14) + continue; + if (p->name[0] && strcmp(p->name, name)) + continue; + type = do_ioctl_get_iftype(name); + if (type == -1) { + bb_error_msg("failed to get type of [%s]", name); + continue; + } + if (type != ARPHRD_TUNNEL && type != ARPHRD_IPGRE && type != ARPHRD_SIT) + continue; + memset(&p1, 0, sizeof(p1)); + if (do_get_ioctl(name, &p1)) + continue; + if ((p->link && p1.link != p->link) || + (p->name[0] && strcmp(p1.name, p->name)) || + (p->iph.daddr && p1.iph.daddr != p->iph.daddr) || + (p->iph.saddr && p1.iph.saddr != p->iph.saddr) || + (p->i_key && p1.i_key != p->i_key)) + continue; + print_tunnel(&p1); + puts(""); + } + return 0; +} + +static int do_show(int argc, char **argv) +{ + int err; + struct ip_tunnel_parm p; + + if (parse_args(argc, argv, SIOCGETTUNNEL, &p) < 0) + return -1; + + switch (p.iph.protocol) { + case IPPROTO_IPIP: + err = do_get_ioctl(p.name[0] ? p.name : "tunl0", &p); + break; + case IPPROTO_GRE: + err = do_get_ioctl(p.name[0] ? p.name : "gre0", &p); + break; + case IPPROTO_IPV6: + err = do_get_ioctl(p.name[0] ? p.name : "sit0", &p); + break; + default: + do_tunnels_list(&p); + return 0; + } + if (err) + return -1; + + print_tunnel(&p); + puts(""); + return 0; +} + +int do_iptunnel(int argc, char **argv) +{ + if (argc > 0) { + if (matches(*argv, "add") == 0) + return do_add(SIOCADDTUNNEL, argc-1, argv+1); + if (matches(*argv, "change") == 0) + return do_add(SIOCCHGTUNNEL, argc-1, argv+1); + if (matches(*argv, "del") == 0) + return do_del(argc-1, argv+1); + if (matches(*argv, "show") == 0 || + matches(*argv, "lst") == 0 || + matches(*argv, "list") == 0) + return do_show(argc-1, argv+1); + } else + return do_show(0, NULL); + + bb_error_msg("command \"%s\" is unknown", *argv); + exit(-1); +} diff --git a/networking/libiproute/libnetlink.c b/networking/libiproute/libnetlink.c new file mode 100644 index 000000000..c7e17e1dd --- /dev/null +++ b/networking/libiproute/libnetlink.c @@ -0,0 +1,396 @@ +/* vi: set sw=4 ts=4: */ +/* + * libnetlink.c RTnetlink service routines. + * + * 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. + * + * Authors: Alexey Kuznetsov, + * + */ + +#include "libbb.h" +#include + +#include +#include +#include +#include + +#include + +#include "libnetlink.h" + +void rtnl_close(struct rtnl_handle *rth) +{ + close(rth->fd); +} + +int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions) +{ + socklen_t addr_len; + + memset(rth, 0, sizeof(rth)); + + rth->fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (rth->fd < 0) { + bb_perror_msg("cannot open netlink socket"); + return -1; + } + + memset(&rth->local, 0, sizeof(rth->local)); + rth->local.nl_family = AF_NETLINK; + rth->local.nl_groups = subscriptions; + + if (bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local)) < 0) { + bb_perror_msg("cannot bind netlink socket"); + return -1; + } + addr_len = sizeof(rth->local); + if (getsockname(rth->fd, (struct sockaddr*)&rth->local, &addr_len) < 0) { + bb_perror_msg("cannot getsockname"); + return -1; + } + if (addr_len != sizeof(rth->local)) { + bb_error_msg("wrong address length %d", addr_len); + return -1; + } + if (rth->local.nl_family != AF_NETLINK) { + bb_error_msg("wrong address family %d", rth->local.nl_family); + return -1; + } + rth->seq = time(NULL); + return 0; +} + +int rtnl_wilddump_request(struct rtnl_handle *rth, int family, int type) +{ + struct { + struct nlmsghdr nlh; + struct rtgenmsg g; + } req; + struct sockaddr_nl nladdr; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + req.nlh.nlmsg_len = sizeof(req); + req.nlh.nlmsg_type = type; + req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST; + req.nlh.nlmsg_pid = 0; + req.nlh.nlmsg_seq = rth->dump = ++rth->seq; + req.g.rtgen_family = family; + + return sendto(rth->fd, (void*)&req, sizeof(req), 0, (struct sockaddr*)&nladdr, sizeof(nladdr)); +} + +int rtnl_send(struct rtnl_handle *rth, char *buf, int len) +{ + struct sockaddr_nl nladdr; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + return sendto(rth->fd, buf, len, 0, (struct sockaddr*)&nladdr, sizeof(nladdr)); +} + +int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len) +{ + struct nlmsghdr nlh; + struct sockaddr_nl nladdr; + struct iovec iov[2] = { { &nlh, sizeof(nlh) }, { req, len } }; + struct msghdr msg = { + (void*)&nladdr, sizeof(nladdr), + iov, 2, + NULL, 0, + 0 + }; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + + nlh.nlmsg_len = NLMSG_LENGTH(len); + nlh.nlmsg_type = type; + nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST; + nlh.nlmsg_pid = 0; + nlh.nlmsg_seq = rth->dump = ++rth->seq; + + return sendmsg(rth->fd, &msg, 0); +} + +int rtnl_dump_filter(struct rtnl_handle *rth, + int (*filter)(struct sockaddr_nl *, struct nlmsghdr *n, void *), + void *arg1, + int (*junk)(struct sockaddr_nl *, struct nlmsghdr *n, void *), + void *arg2) +{ + char buf[8192]; + struct sockaddr_nl nladdr; + struct iovec iov = { buf, sizeof(buf) }; + + while (1) { + int status; + struct nlmsghdr *h; + + struct msghdr msg = { + (void*)&nladdr, sizeof(nladdr), + &iov, 1, + NULL, 0, + 0 + }; + + status = recvmsg(rth->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR) + continue; + bb_perror_msg("OVERRUN"); + continue; + } + if (status == 0) { + bb_error_msg("EOF on netlink"); + return -1; + } + if (msg.msg_namelen != sizeof(nladdr)) { + bb_error_msg_and_die("sender address length == %d", msg.msg_namelen); + } + + h = (struct nlmsghdr*)buf; + while (NLMSG_OK(h, status)) { + int err; + + if (nladdr.nl_pid != 0 || + h->nlmsg_pid != rth->local.nl_pid || + h->nlmsg_seq != rth->dump) { + if (junk) { + err = junk(&nladdr, h, arg2); + if (err < 0) { + return err; + } + } + goto skip_it; + } + + if (h->nlmsg_type == NLMSG_DONE) { + return 0; + } + if (h->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *l_err = (struct nlmsgerr*)NLMSG_DATA(h); + if (h->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) { + bb_error_msg("ERROR truncated"); + } else { + errno = -l_err->error; + bb_perror_msg("RTNETLINK answers"); + } + return -1; + } + err = filter(&nladdr, h, arg1); + if (err < 0) { + return err; + } + +skip_it: + h = NLMSG_NEXT(h, status); + } + if (msg.msg_flags & MSG_TRUNC) { + bb_error_msg("message truncated"); + continue; + } + if (status) { + bb_error_msg_and_die("remnant of size %d!", status); + } + } +} + +int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer, + unsigned groups, struct nlmsghdr *answer, + int (*junk)(struct sockaddr_nl *,struct nlmsghdr *n, void *), + void *jarg) +{ + int status; + unsigned seq; + struct nlmsghdr *h; + struct sockaddr_nl nladdr; + struct iovec iov = { (void*)n, n->nlmsg_len }; + char buf[8192]; + struct msghdr msg = { + (void*)&nladdr, sizeof(nladdr), + &iov, 1, + NULL, 0, + 0 + }; + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + nladdr.nl_pid = peer; + nladdr.nl_groups = groups; + + n->nlmsg_seq = seq = ++rtnl->seq; + if (answer == NULL) { + n->nlmsg_flags |= NLM_F_ACK; + } + status = sendmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + bb_perror_msg("cannot talk to rtnetlink"); + return -1; + } + + iov.iov_base = buf; + + while (1) { + iov.iov_len = sizeof(buf); + status = recvmsg(rtnl->fd, &msg, 0); + + if (status < 0) { + if (errno == EINTR) { + continue; + } + bb_perror_msg("OVERRUN"); + continue; + } + if (status == 0) { + bb_error_msg("EOF on netlink"); + return -1; + } + if (msg.msg_namelen != sizeof(nladdr)) { + bb_error_msg_and_die("sender address length == %d", msg.msg_namelen); + } + for (h = (struct nlmsghdr*)buf; status >= sizeof(*h); ) { + int l_err; + int len = h->nlmsg_len; + int l = len - sizeof(*h); + + if (l<0 || len>status) { + if (msg.msg_flags & MSG_TRUNC) { + bb_error_msg("truncated message"); + return -1; + } + bb_error_msg_and_die("malformed message: len=%d!", len); + } + + if (nladdr.nl_pid != peer || + h->nlmsg_pid != rtnl->local.nl_pid || + h->nlmsg_seq != seq) { + if (junk) { + l_err = junk(&nladdr, h, jarg); + if (l_err < 0) { + return l_err; + } + } + continue; + } + + if (h->nlmsg_type == NLMSG_ERROR) { + struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(h); + if (l < sizeof(struct nlmsgerr)) { + bb_error_msg("ERROR truncated"); + } else { + errno = -err->error; + if (errno == 0) { + if (answer) { + memcpy(answer, h, h->nlmsg_len); + } + return 0; + } + bb_perror_msg("RTNETLINK answers"); + } + return -1; + } + if (answer) { + memcpy(answer, h, h->nlmsg_len); + return 0; + } + + bb_error_msg("unexpected reply!"); + + status -= NLMSG_ALIGN(len); + h = (struct nlmsghdr*)((char*)h + NLMSG_ALIGN(len)); + } + if (msg.msg_flags & MSG_TRUNC) { + bb_error_msg("message truncated"); + continue; + } + if (status) { + bb_error_msg_and_die("remnant of size %d!", status); + } + } +} + +int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data) +{ + int len = RTA_LENGTH(4); + struct rtattr *rta; + if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) + return -1; + rta = (struct rtattr*)(((char*)n) + NLMSG_ALIGN(n->nlmsg_len)); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), &data, 4); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; + return 0; +} + +int addattr_l(struct nlmsghdr *n, int maxlen, int type, void *data, int alen) +{ + int len = RTA_LENGTH(alen); + struct rtattr *rta; + + if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen) + return -1; + rta = (struct rtattr*)(((char*)n) + NLMSG_ALIGN(n->nlmsg_len)); + rta->rta_type = type; + rta->rta_len = len; + memcpy(RTA_DATA(rta), data, alen); + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len; + return 0; +} + +int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data) +{ + int len = RTA_LENGTH(4); + struct rtattr *subrta; + + if (RTA_ALIGN(rta->rta_len) + len > maxlen) { + return -1; + } + subrta = (struct rtattr*)(((char*)rta) + RTA_ALIGN(rta->rta_len)); + subrta->rta_type = type; + subrta->rta_len = len; + memcpy(RTA_DATA(subrta), &data, 4); + rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len; + return 0; +} + +int rta_addattr_l(struct rtattr *rta, int maxlen, int type, void *data, int alen) +{ + struct rtattr *subrta; + int len = RTA_LENGTH(alen); + + if (RTA_ALIGN(rta->rta_len) + len > maxlen) { + return -1; + } + subrta = (struct rtattr*)(((char*)rta) + RTA_ALIGN(rta->rta_len)); + subrta->rta_type = type; + subrta->rta_len = len; + memcpy(RTA_DATA(subrta), data, alen); + rta->rta_len = NLMSG_ALIGN(rta->rta_len) + len; + return 0; +} + + +int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) +{ + while (RTA_OK(rta, len)) { + if (rta->rta_type <= max) { + tb[rta->rta_type] = rta; + } + rta = RTA_NEXT(rta,len); + } + if (len) { + bb_error_msg("deficit %d, rta_len=%d!", len, rta->rta_len); + } + return 0; +} diff --git a/networking/libiproute/libnetlink.h b/networking/libiproute/libnetlink.h new file mode 100644 index 000000000..7cfcd2042 --- /dev/null +++ b/networking/libiproute/libnetlink.h @@ -0,0 +1,43 @@ +/* vi: set sw=4 ts=4: */ +#ifndef __LIBNETLINK_H__ +#define __LIBNETLINK_H__ 1 + +#include +#include +#include + +struct rtnl_handle +{ + int fd; + struct sockaddr_nl local; + struct sockaddr_nl peer; + __u32 seq; + __u32 dump; +}; + +extern int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions); +extern void rtnl_close(struct rtnl_handle *rth); +extern int rtnl_wilddump_request(struct rtnl_handle *rth, int fam, int type); +extern int rtnl_dump_request(struct rtnl_handle *rth, int type, void *req, int len); +extern int rtnl_dump_filter(struct rtnl_handle *rth, + int (*filter)(struct sockaddr_nl*, struct nlmsghdr *n, void*), + void *arg1, + int (*junk)(struct sockaddr_nl *, struct nlmsghdr *n, void *), + void *arg2); +extern int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer, + unsigned groups, struct nlmsghdr *answer, + int (*junk)(struct sockaddr_nl *,struct nlmsghdr *n, void *), + void *jarg); +extern int rtnl_send(struct rtnl_handle *rth, char *buf, int); + + +extern int addattr32(struct nlmsghdr *n, int maxlen, int type, __u32 data); +extern int addattr_l(struct nlmsghdr *n, int maxlen, int type, void *data, int alen); +extern int rta_addattr32(struct rtattr *rta, int maxlen, int type, __u32 data); +extern int rta_addattr_l(struct rtattr *rta, int maxlen, int type, void *data, int alen); + +extern int parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len); + + +#endif /* __LIBNETLINK_H__ */ + diff --git a/networking/libiproute/linux/pkt_sched.h b/networking/libiproute/linux/pkt_sched.h new file mode 100644 index 000000000..d6cf1fc06 --- /dev/null +++ b/networking/libiproute/linux/pkt_sched.h @@ -0,0 +1,414 @@ +/* vi: set sw=4 ts=4: */ +#ifndef __LINUX_PKT_SCHED_H +#define __LINUX_PKT_SCHED_H + +/* Logical priority bands not depending on specific packet scheduler. + Every scheduler will map them to real traffic classes, if it has + no more precise mechanism to classify packets. + + These numbers have no special meaning, though their coincidence + with obsolete IPv6 values is not occasional :-). New IPv6 drafts + preferred full anarchy inspired by diffserv group. + + Note: TC_PRIO_BESTEFFORT does not mean that it is the most unhappy + class, actually, as rule it will be handled with more care than + filler or even bulk. + */ + +#include + +#define TC_PRIO_BESTEFFORT 0 +#define TC_PRIO_FILLER 1 +#define TC_PRIO_BULK 2 +#define TC_PRIO_INTERACTIVE_BULK 4 +#define TC_PRIO_INTERACTIVE 6 +#define TC_PRIO_CONTROL 7 + +#define TC_PRIO_MAX 15 + +/* Generic queue statistics, available for all the elements. + Particular schedulers may have also their private records. + */ + +struct tc_stats +{ + __u64 bytes; /* NUmber of enqueues bytes */ + __u32 packets; /* Number of enqueued packets */ + __u32 drops; /* Packets dropped because of lack of resources */ + __u32 overlimits; /* Number of throttle events when this + * flow goes out of allocated bandwidth */ + __u32 bps; /* Current flow byte rate */ + __u32 pps; /* Current flow packet rate */ + __u32 qlen; + __u32 backlog; +#ifdef __KERNEL__ + spinlock_t *lock; +#endif +}; + +struct tc_estimator +{ + char interval; + unsigned char ewma_log; +}; + +/* "Handles" + --------- + + All the traffic control objects have 32bit identifiers, or "handles". + + They can be considered as opaque numbers from user API viewpoint, + but actually they always consist of two fields: major and + minor numbers, which are interpreted by kernel specially, + that may be used by applications, though not recommended. + + F.e. qdisc handles always have minor number equal to zero, + classes (or flows) have major equal to parent qdisc major, and + minor uniquely identifying class inside qdisc. + + Macros to manipulate handles: + */ + +#define TC_H_MAJ_MASK (0xFFFF0000U) +#define TC_H_MIN_MASK (0x0000FFFFU) +#define TC_H_MAJ(h) ((h)&TC_H_MAJ_MASK) +#define TC_H_MIN(h) ((h)&TC_H_MIN_MASK) +#define TC_H_MAKE(maj,min) (((maj)&TC_H_MAJ_MASK)|((min)&TC_H_MIN_MASK)) + +#define TC_H_UNSPEC (0U) +#define TC_H_ROOT (0xFFFFFFFFU) +#define TC_H_INGRESS (0xFFFFFFF1U) + +struct tc_ratespec +{ + unsigned char cell_log; + unsigned char __reserved; + unsigned short feature; + short addend; + unsigned short mpu; + __u32 rate; +}; + +/* FIFO section */ + +struct tc_fifo_qopt +{ + __u32 limit; /* Queue length: bytes for bfifo, packets for pfifo */ +}; + +/* PRIO section */ + +#define TCQ_PRIO_BANDS 16 + +struct tc_prio_qopt +{ + int bands; /* Number of bands */ + __u8 priomap[TC_PRIO_MAX+1]; /* Map: logical priority -> PRIO band */ +}; + +/* CSZ section */ + +struct tc_csz_qopt +{ + int flows; /* Maximal number of guaranteed flows */ + unsigned char R_log; /* Fixed point position for round number */ + unsigned char delta_log; /* Log of maximal managed time interval */ + __u8 priomap[TC_PRIO_MAX+1]; /* Map: logical priority -> CSZ band */ +}; + +struct tc_csz_copt +{ + struct tc_ratespec slice; + struct tc_ratespec rate; + struct tc_ratespec peakrate; + __u32 limit; + __u32 buffer; + __u32 mtu; +}; + +enum +{ + TCA_CSZ_UNSPEC, + TCA_CSZ_PARMS, + TCA_CSZ_RTAB, + TCA_CSZ_PTAB, +}; + +/* TBF section */ + +struct tc_tbf_qopt +{ + struct tc_ratespec rate; + struct tc_ratespec peakrate; + __u32 limit; + __u32 buffer; + __u32 mtu; +}; + +enum +{ + TCA_TBF_UNSPEC, + TCA_TBF_PARMS, + TCA_TBF_RTAB, + TCA_TBF_PTAB, +}; + + +/* TEQL section */ + +/* TEQL does not require any parameters */ + +/* SFQ section */ + +struct tc_sfq_qopt +{ + unsigned quantum; /* Bytes per round allocated to flow */ + int perturb_period; /* Period of hash perturbation */ + __u32 limit; /* Maximal packets in queue */ + unsigned divisor; /* Hash divisor */ + unsigned flows; /* Maximal number of flows */ +}; + +/* + * NOTE: limit, divisor and flows are hardwired to code at the moment. + * + * limit=flows=128, divisor=1024; + * + * The only reason for this is efficiency, it is possible + * to change these parameters in compile time. + */ + +/* RED section */ + +enum +{ + TCA_RED_UNSPEC, + TCA_RED_PARMS, + TCA_RED_STAB, +}; + +struct tc_red_qopt +{ + __u32 limit; /* HARD maximal queue length (bytes) */ + __u32 qth_min; /* Min average length threshold (bytes) */ + __u32 qth_max; /* Max average length threshold (bytes) */ + unsigned char Wlog; /* log(W) */ + unsigned char Plog; /* log(P_max/(qth_max-qth_min)) */ + unsigned char Scell_log; /* cell size for idle damping */ + unsigned char flags; +#define TC_RED_ECN 1 +}; + +struct tc_red_xstats +{ + __u32 early; /* Early drops */ + __u32 pdrop; /* Drops due to queue limits */ + __u32 other; /* Drops due to drop() calls */ + __u32 marked; /* Marked packets */ +}; + +/* GRED section */ + +#define MAX_DPs 16 + +enum +{ + TCA_GRED_UNSPEC, + TCA_GRED_PARMS, + TCA_GRED_STAB, + TCA_GRED_DPS, +}; + +#define TCA_SET_OFF TCA_GRED_PARMS +struct tc_gred_qopt +{ + __u32 limit; /* HARD maximal queue length (bytes) +*/ + __u32 qth_min; /* Min average length threshold (bytes) +*/ + __u32 qth_max; /* Max average length threshold (bytes) +*/ + __u32 DP; /* upto 2^32 DPs */ + __u32 backlog; + __u32 qave; + __u32 forced; + __u32 early; + __u32 other; + __u32 pdrop; + + unsigned char Wlog; /* log(W) */ + unsigned char Plog; /* log(P_max/(qth_max-qth_min)) */ + unsigned char Scell_log; /* cell size for idle damping */ + __u8 prio; /* prio of this VQ */ + __u32 packets; + __u32 bytesin; +}; +/* gred setup */ +struct tc_gred_sopt +{ + __u32 DPs; + __u32 def_DP; + __u8 grio; +}; + +/* HTB section */ +#define TC_HTB_NUMPRIO 4 +#define TC_HTB_MAXDEPTH 4 + +struct tc_htb_opt +{ + struct tc_ratespec rate; + struct tc_ratespec ceil; + __u32 buffer; + __u32 cbuffer; + __u32 quantum; /* out only */ + __u32 level; /* out only */ + __u8 prio; + __u8 injectd; /* inject class distance */ + __u8 pad[2]; +}; +struct tc_htb_glob +{ + __u32 rate2quantum; /* bps->quantum divisor */ + __u32 defcls; /* default class number */ + __u32 use_dcache; /* use dequeue cache ? */ + __u32 debug; /* debug flags */ + + + /* stats */ + __u32 deq_rate; /* dequeue rate */ + __u32 utilz; /* dequeue utilization */ + __u32 trials; /* deq_prio trials per dequeue */ + __u32 dcache_hits; + __u32 direct_pkts; /* count of non shapped packets */ +}; +enum +{ + TCA_HTB_UNSPEC, + TCA_HTB_PARMS, + TCA_HTB_INIT, + TCA_HTB_CTAB, + TCA_HTB_RTAB, +}; +struct tc_htb_xstats +{ + __u32 lends; + __u32 borrows; + __u32 giants; /* too big packets (rate will not be accurate) */ + __u32 injects; /* how many times leaf used injected bw */ + __u32 tokens; + __u32 ctokens; +}; + +/* CBQ section */ + +#define TC_CBQ_MAXPRIO 8 +#define TC_CBQ_MAXLEVEL 8 +#define TC_CBQ_DEF_EWMA 5 + +struct tc_cbq_lssopt +{ + unsigned char change; + unsigned char flags; +#define TCF_CBQ_LSS_BOUNDED 1 +#define TCF_CBQ_LSS_ISOLATED 2 + unsigned char ewma_log; + unsigned char level; +#define TCF_CBQ_LSS_FLAGS 1 +#define TCF_CBQ_LSS_EWMA 2 +#define TCF_CBQ_LSS_MAXIDLE 4 +#define TCF_CBQ_LSS_MINIDLE 8 +#define TCF_CBQ_LSS_OFFTIME 0x10 +#define TCF_CBQ_LSS_AVPKT 0x20 + __u32 maxidle; + __u32 minidle; + __u32 offtime; + __u32 avpkt; +}; + +struct tc_cbq_wrropt +{ + unsigned char flags; + unsigned char priority; + unsigned char cpriority; + unsigned char __reserved; + __u32 allot; + __u32 weight; +}; + +struct tc_cbq_ovl +{ + unsigned char strategy; +#define TC_CBQ_OVL_CLASSIC 0 +#define TC_CBQ_OVL_DELAY 1 +#define TC_CBQ_OVL_LOWPRIO 2 +#define TC_CBQ_OVL_DROP 3 +#define TC_CBQ_OVL_RCLASSIC 4 + unsigned char priority2; + __u32 penalty; +}; + +struct tc_cbq_police +{ + unsigned char police; + unsigned char __res1; + unsigned short __res2; +}; + +struct tc_cbq_fopt +{ + __u32 split; + __u32 defmap; + __u32 defchange; +}; + +struct tc_cbq_xstats +{ + __u32 borrows; + __u32 overactions; + __s32 avgidle; + __s32 undertime; +}; + +enum +{ + TCA_CBQ_UNSPEC, + TCA_CBQ_LSSOPT, + TCA_CBQ_WRROPT, + TCA_CBQ_FOPT, + TCA_CBQ_OVL_STRATEGY, + TCA_CBQ_RATE, + TCA_CBQ_RTAB, + TCA_CBQ_POLICE, +}; + +#define TCA_CBQ_MAX TCA_CBQ_POLICE + +/* dsmark section */ + +enum { + TCA_DSMARK_UNSPEC, + TCA_DSMARK_INDICES, + TCA_DSMARK_DEFAULT_INDEX, + TCA_DSMARK_SET_TC_INDEX, + TCA_DSMARK_MASK, + TCA_DSMARK_VALUE +}; + +#define TCA_DSMARK_MAX TCA_DSMARK_VALUE + +/* ATM section */ + +enum { + TCA_ATM_UNSPEC, + TCA_ATM_FD, /* file/socket descriptor */ + TCA_ATM_PTR, /* pointer to descriptor - later */ + TCA_ATM_HDR, /* LL header */ + TCA_ATM_EXCESS, /* excess traffic class (0 for CLP) */ + TCA_ATM_ADDR, /* PVC address (for output only) */ + TCA_ATM_STATE /* VC state (ATM_VS_*; for output only) */ +}; + +#define TCA_ATM_MAX TCA_ATM_STATE + +#endif diff --git a/networking/libiproute/ll_addr.c b/networking/libiproute/ll_addr.c new file mode 100644 index 000000000..ba0a65a18 --- /dev/null +++ b/networking/libiproute/ll_addr.c @@ -0,0 +1,85 @@ +/* vi: set sw=4 ts=4: */ +/* + * ll_addr.c + * + * 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. + * + * Authors: Alexey Kuznetsov, + */ + +#include "libbb.h" + +#include +#include + +#include "rt_names.h" +#include "utils.h" + + +const char *ll_addr_n2a(unsigned char *addr, int alen, int type, char *buf, int blen) +{ + int i; + int l; + + if (alen == 4 && + (type == ARPHRD_TUNNEL || type == ARPHRD_SIT || type == ARPHRD_IPGRE)) { + return inet_ntop(AF_INET, addr, buf, blen); + } + l = 0; + for (i=0; i 255) { + bb_error_msg("\"%s\" is invalid lladdr", arg); + return -1; + } + lladdr[i] = temp; + if (!cp) { + break; + } + arg = cp; + } + return i+1; + } +} diff --git a/networking/libiproute/ll_map.c b/networking/libiproute/ll_map.c new file mode 100644 index 000000000..a14fa4e42 --- /dev/null +++ b/networking/libiproute/ll_map.c @@ -0,0 +1,186 @@ +/* vi: set sw=4 ts=4: */ +/* + * ll_map.c + * + * 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. + * + * Authors: Alexey Kuznetsov, + * + */ + +#include "libbb.h" +#include + +#include "libnetlink.h" +#include "ll_map.h" + +#include /* socket() */ +#include /* struct ifreq and co. */ +#include /* ioctl() & SIOCGIFINDEX */ + +struct idxmap { + struct idxmap * next; + int index; + int type; + int alen; + unsigned flags; + unsigned char addr[8]; + char name[16]; +}; + +static struct idxmap *idxmap[16]; + +int ll_remember_index(struct sockaddr_nl *who, struct nlmsghdr *n, void *arg) +{ + int h; + struct ifinfomsg *ifi = NLMSG_DATA(n); + struct idxmap *im, **imp; + struct rtattr *tb[IFLA_MAX+1]; + + if (n->nlmsg_type != RTM_NEWLINK) + return 0; + + if (n->nlmsg_len < NLMSG_LENGTH(sizeof(ifi))) + return -1; + + + memset(tb, 0, sizeof(tb)); + parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), IFLA_PAYLOAD(n)); + if (tb[IFLA_IFNAME] == NULL) + return 0; + + h = ifi->ifi_index&0xF; + + for (imp=&idxmap[h]; (im=*imp)!=NULL; imp = &im->next) + if (im->index == ifi->ifi_index) + break; + + if (im == NULL) { + im = xmalloc(sizeof(*im)); + im->next = *imp; + im->index = ifi->ifi_index; + *imp = im; + } + + im->type = ifi->ifi_type; + im->flags = ifi->ifi_flags; + if (tb[IFLA_ADDRESS]) { + int alen; + im->alen = alen = RTA_PAYLOAD(tb[IFLA_ADDRESS]); + if (alen > sizeof(im->addr)) + alen = sizeof(im->addr); + memcpy(im->addr, RTA_DATA(tb[IFLA_ADDRESS]), alen); + } else { + im->alen = 0; + memset(im->addr, 0, sizeof(im->addr)); + } + strcpy(im->name, RTA_DATA(tb[IFLA_IFNAME])); + return 0; +} + +const char *ll_idx_n2a(int idx, char *buf) +{ + struct idxmap *im; + + if (idx == 0) + return "*"; + for (im = idxmap[idx&0xF]; im; im = im->next) + if (im->index == idx) + return im->name; + snprintf(buf, 16, "if%d", idx); + return buf; +} + + +const char *ll_index_to_name(int idx) +{ + static char nbuf[16]; + + return ll_idx_n2a(idx, nbuf); +} + +int ll_index_to_type(int idx) +{ + struct idxmap *im; + + if (idx == 0) + return -1; + for (im = idxmap[idx&0xF]; im; im = im->next) + if (im->index == idx) + return im->type; + return -1; +} + +unsigned ll_index_to_flags(int idx) +{ + struct idxmap *im; + + if (idx == 0) + return 0; + + for (im = idxmap[idx&0xF]; im; im = im->next) + if (im->index == idx) + return im->flags; + return 0; +} + +int ll_name_to_index(char *name) +{ + static char ncache[16]; + static int icache; + struct idxmap *im; + int sock_fd; + int i; + + if (name == NULL) + return 0; + if (icache && strcmp(name, ncache) == 0) + return icache; + for (i=0; i<16; i++) { + for (im = idxmap[i]; im; im = im->next) { + if (strcmp(im->name, name) == 0) { + icache = im->index; + strcpy(ncache, name); + return im->index; + } + } + } + /* We have not found the interface in our cache, but the kernel + * may still know about it. One reason is that we may be using + * module on-demand loading, which means that the kernel will + * load the module and make the interface exist only when + * we explicitely request it (check for dev_load() in net/core/dev.c). + * I can think of other similar scenario, but they are less common... + * Jean II */ + sock_fd = socket(AF_INET, SOCK_DGRAM, 0); + if (sock_fd) { + struct ifreq ifr; + int ret; + strncpy(ifr.ifr_name, name, IFNAMSIZ); + ifr.ifr_ifindex = -1; + ret = ioctl(sock_fd, SIOCGIFINDEX, &ifr); + close(sock_fd); + if (ret >= 0) + /* In theory, we should redump the interface list + * to update our cache, this is left as an exercise + * to the reader... Jean II */ + return ifr.ifr_ifindex; + } + + return 0; +} + +int ll_init_map(struct rtnl_handle *rth) +{ + if (rtnl_wilddump_request(rth, AF_UNSPEC, RTM_GETLINK) < 0) { + bb_perror_msg_and_die("cannot send dump request"); + } + + if (rtnl_dump_filter(rth, ll_remember_index, &idxmap, NULL, NULL) < 0) { + bb_error_msg_and_die("dump terminated"); + } + return 0; +} diff --git a/networking/libiproute/ll_map.h b/networking/libiproute/ll_map.h new file mode 100644 index 000000000..226d48fc2 --- /dev/null +++ b/networking/libiproute/ll_map.h @@ -0,0 +1,13 @@ +/* vi: set sw=4 ts=4: */ +#ifndef __LL_MAP_H__ +#define __LL_MAP_H__ 1 + +extern int ll_remember_index(struct sockaddr_nl *who, struct nlmsghdr *n, void *arg); +extern int ll_init_map(struct rtnl_handle *rth); +extern int ll_name_to_index(char *name); +extern const char *ll_index_to_name(int idx); +extern const char *ll_idx_n2a(int idx, char *buf); +extern int ll_index_to_type(int idx); +extern unsigned ll_index_to_flags(int idx); + +#endif /* __LL_MAP_H__ */ diff --git a/networking/libiproute/ll_proto.c b/networking/libiproute/ll_proto.c new file mode 100644 index 000000000..a3fe9d376 --- /dev/null +++ b/networking/libiproute/ll_proto.c @@ -0,0 +1,122 @@ +/* vi: set sw=4 ts=4: */ +/* + * ll_proto.c + * + * 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. + * + * Authors: Alexey Kuznetsov, + */ + +#include "libbb.h" +#include + +#include "rt_names.h" +#include "utils.h" + +#if __GLIBC__ >=2 && __GLIBC_MINOR__ >= 1 +#include +#else +#include +#endif + +#define __PF(f,n) { ETH_P_##f, #n }, +static struct { + int id; + char *name; +} llproto_names[] = { +__PF(LOOP,loop) +__PF(PUP,pup) +#ifdef ETH_P_PUPAT +__PF(PUPAT,pupat) +#endif +__PF(IP,ip) +__PF(X25,x25) +__PF(ARP,arp) +__PF(BPQ,bpq) +#ifdef ETH_P_IEEEPUP +__PF(IEEEPUP,ieeepup) +#endif +#ifdef ETH_P_IEEEPUPAT +__PF(IEEEPUPAT,ieeepupat) +#endif +__PF(DEC,dec) +__PF(DNA_DL,dna_dl) +__PF(DNA_RC,dna_rc) +__PF(DNA_RT,dna_rt) +__PF(LAT,lat) +__PF(DIAG,diag) +__PF(CUST,cust) +__PF(SCA,sca) +__PF(RARP,rarp) +__PF(ATALK,atalk) +__PF(AARP,aarp) +__PF(IPX,ipx) +__PF(IPV6,ipv6) +#ifdef ETH_P_PPP_DISC +__PF(PPP_DISC,ppp_disc) +#endif +#ifdef ETH_P_PPP_SES +__PF(PPP_SES,ppp_ses) +#endif +#ifdef ETH_P_ATMMPOA +__PF(ATMMPOA,atmmpoa) +#endif +#ifdef ETH_P_ATMFATE +__PF(ATMFATE,atmfate) +#endif + +__PF(802_3,802_3) +__PF(AX25,ax25) +__PF(ALL,all) +__PF(802_2,802_2) +__PF(SNAP,snap) +__PF(DDCMP,ddcmp) +__PF(WAN_PPP,wan_ppp) +__PF(PPP_MP,ppp_mp) +__PF(LOCALTALK,localtalk) +__PF(PPPTALK,ppptalk) +__PF(TR_802_2,tr_802_2) +__PF(MOBITEX,mobitex) +__PF(CONTROL,control) +__PF(IRDA,irda) +#ifdef ETH_P_ECONET +__PF(ECONET,econet) +#endif + +{ 0x8100, "802.1Q" }, +{ ETH_P_IP, "ipv4" }, +}; +#undef __PF + + +const char * ll_proto_n2a(unsigned short id, char *buf, int len) +{ + int i; + + id = ntohs(id); + + for (i=0; i + */ +#include +#include + +#include + +#include "rt_names.h" + +const char * ll_type_n2a(int type, char *buf, int len) +{ +#define __PF(f,n) { ARPHRD_##f, #n }, +static struct { + int type; + char *name; +} arphrd_names[] = { +{ 0, "generic" }, +__PF(ETHER,ether) +__PF(EETHER,eether) +__PF(AX25,ax25) +__PF(PRONET,pronet) +__PF(CHAOS,chaos) +#ifdef ARPHRD_IEEE802_TR +__PF(IEEE802,ieee802) +#else +__PF(IEEE802,tr) +#endif +__PF(ARCNET,arcnet) +__PF(APPLETLK,atalk) +__PF(DLCI,dlci) +#ifdef ARPHRD_ATM +__PF(ATM,atm) +#endif +__PF(METRICOM,metricom) +#ifdef ARPHRD_IEEE1394 +__PF(IEEE1394,ieee1394) +#endif + +__PF(SLIP,slip) +__PF(CSLIP,cslip) +__PF(SLIP6,slip6) +__PF(CSLIP6,cslip6) +__PF(RSRVD,rsrvd) +__PF(ADAPT,adapt) +__PF(ROSE,rose) +__PF(X25,x25) +#ifdef ARPHRD_HWX25 +__PF(HWX25,hwx25) +#endif +__PF(PPP,ppp) +__PF(HDLC,hdlc) +__PF(LAPB,lapb) +#ifdef ARPHRD_DDCMP +__PF(DDCMP,ddcmp) +__PF(RAWHDLC,rawhdlc) +#endif + +__PF(TUNNEL,ipip) +__PF(TUNNEL6,tunnel6) +__PF(FRAD,frad) +__PF(SKIP,skip) +__PF(LOOPBACK,loopback) +__PF(LOCALTLK,ltalk) +__PF(FDDI,fddi) +__PF(BIF,bif) +__PF(SIT,sit) +__PF(IPDDP,ip/ddp) +__PF(IPGRE,gre) +__PF(PIMREG,pimreg) +__PF(HIPPI,hippi) +__PF(ASH,ash) +__PF(ECONET,econet) +__PF(IRDA,irda) +__PF(FCPP,fcpp) +__PF(FCAL,fcal) +__PF(FCPL,fcpl) +__PF(FCFABRIC,fcfb0) +__PF(FCFABRIC+1,fcfb1) +__PF(FCFABRIC+2,fcfb2) +__PF(FCFABRIC+3,fcfb3) +__PF(FCFABRIC+4,fcfb4) +__PF(FCFABRIC+5,fcfb5) +__PF(FCFABRIC+6,fcfb6) +__PF(FCFABRIC+7,fcfb7) +__PF(FCFABRIC+8,fcfb8) +__PF(FCFABRIC+9,fcfb9) +__PF(FCFABRIC+10,fcfb10) +__PF(FCFABRIC+11,fcfb11) +__PF(FCFABRIC+12,fcfb12) +#ifdef ARPHRD_IEEE802_TR +__PF(IEEE802_TR,tr) +#endif +#ifdef ARPHRD_IEEE80211 +__PF(IEEE80211,ieee802.11) +#endif +#ifdef ARPHRD_VOID +__PF(VOID,void) +#endif +}; +#undef __PF + + int i; + for (i=0; i + */ +#include +#include +#include + +#include +#include "rt_names.h" + +static void rtnl_tab_initialize(char *file, const char **tab, int size) +{ + char buf[512]; + FILE *fp; + + fp = fopen(file, "r"); + if (!fp) + return; + while (fgets(buf, sizeof(buf), fp)) { + char *p = buf; + int id; + char namebuf[512]; + + while (*p == ' ' || *p == '\t') + p++; + if (*p == '#' || *p == '\n' || *p == 0) + continue; + if (sscanf(p, "0x%x %s\n", &id, namebuf) != 2 && + sscanf(p, "0x%x %s #", &id, namebuf) != 2 && + sscanf(p, "%d %s\n", &id, namebuf) != 2 && + sscanf(p, "%d %s #", &id, namebuf) != 2) { + fprintf(stderr, "Database %s is corrupted at %s\n", + file, p); + return; + } + + if (id<0 || id>size) + continue; + + tab[id] = strdup(namebuf); + } + fclose(fp); +} + + +static const char * rtnl_rtprot_tab[256] = { + "none", + "redirect", + "kernel", + "boot", + "static", + NULL, + NULL, + NULL, + "gated", + "ra", + "mrt", + "zebra", + "bird", +}; + + + +static int rtnl_rtprot_init; + +static void rtnl_rtprot_initialize(void) +{ + rtnl_rtprot_init = 1; + rtnl_tab_initialize("/etc/iproute2/rt_protos", + rtnl_rtprot_tab, 256); +} + +const char * rtnl_rtprot_n2a(int id, char *buf, int len) +{ + if (id<0 || id>=256) { + snprintf(buf, len, "%d", id); + return buf; + } + if (!rtnl_rtprot_tab[id]) { + if (!rtnl_rtprot_init) + rtnl_rtprot_initialize(); + } + if (rtnl_rtprot_tab[id]) + return rtnl_rtprot_tab[id]; + snprintf(buf, len, "%d", id); + return buf; +} + +int rtnl_rtprot_a2n(uint32_t *id, char *arg) +{ + static const char *cache = NULL; + static unsigned long res; + char *end; + int i; + + if (cache && strcmp(cache, arg) == 0) { + *id = res; + return 0; + } + + if (!rtnl_rtprot_init) + rtnl_rtprot_initialize(); + + for (i=0; i<256; i++) { + if (rtnl_rtprot_tab[i] && + strcmp(rtnl_rtprot_tab[i], arg) == 0) { + cache = rtnl_rtprot_tab[i]; + res = i; + *id = res; + return 0; + } + } + + res = strtoul(arg, &end, 0); + if (!end || end == arg || *end || res > 255) + return -1; + *id = res; + return 0; +} + + + +static const char * rtnl_rtscope_tab[256] = { + "global", +}; + +static int rtnl_rtscope_init; + +static void rtnl_rtscope_initialize(void) +{ + rtnl_rtscope_init = 1; + rtnl_rtscope_tab[255] = "nowhere"; + rtnl_rtscope_tab[254] = "host"; + rtnl_rtscope_tab[253] = "link"; + rtnl_rtscope_tab[200] = "site"; + rtnl_tab_initialize("/etc/iproute2/rt_scopes", + rtnl_rtscope_tab, 256); +} + +const char * rtnl_rtscope_n2a(int id, char *buf, int len) +{ + if (id<0 || id>=256) { + snprintf(buf, len, "%d", id); + return buf; + } + if (!rtnl_rtscope_tab[id]) { + if (!rtnl_rtscope_init) + rtnl_rtscope_initialize(); + } + if (rtnl_rtscope_tab[id]) + return rtnl_rtscope_tab[id]; + snprintf(buf, len, "%d", id); + return buf; +} + +int rtnl_rtscope_a2n(uint32_t *id, char *arg) +{ + static const char *cache = NULL; + static unsigned long res; + char *end; + int i; + + if (cache && strcmp(cache, arg) == 0) { + *id = res; + return 0; + } + + if (!rtnl_rtscope_init) + rtnl_rtscope_initialize(); + + for (i=0; i<256; i++) { + if (rtnl_rtscope_tab[i] && + strcmp(rtnl_rtscope_tab[i], arg) == 0) { + cache = rtnl_rtscope_tab[i]; + res = i; + *id = res; + return 0; + } + } + + res = strtoul(arg, &end, 0); + if (!end || end == arg || *end || res > 255) + return -1; + *id = res; + return 0; +} + + + +static const char * rtnl_rtrealm_tab[256] = { + "unknown", +}; + +static int rtnl_rtrealm_init; + +static void rtnl_rtrealm_initialize(void) +{ + rtnl_rtrealm_init = 1; + rtnl_tab_initialize("/etc/iproute2/rt_realms", + rtnl_rtrealm_tab, 256); +} + +int rtnl_rtrealm_a2n(uint32_t *id, char *arg) +{ + static const char *cache = NULL; + static unsigned long res; + char *end; + int i; + + if (cache && strcmp(cache, arg) == 0) { + *id = res; + return 0; + } + + if (!rtnl_rtrealm_init) + rtnl_rtrealm_initialize(); + + for (i=0; i<256; i++) { + if (rtnl_rtrealm_tab[i] && + strcmp(rtnl_rtrealm_tab[i], arg) == 0) { + cache = rtnl_rtrealm_tab[i]; + res = i; + *id = res; + return 0; + } + } + + res = strtoul(arg, &end, 0); + if (!end || end == arg || *end || res > 255) + return -1; + *id = res; + return 0; +} + +#if ENABLE_FEATURE_IP_RULE +const char * rtnl_rtrealm_n2a(int id, char *buf, int len) +{ + if (id<0 || id>=256) { + snprintf(buf, len, "%d", id); + return buf; + } + if (!rtnl_rtrealm_tab[id]) { + if (!rtnl_rtrealm_init) + rtnl_rtrealm_initialize(); + } + if (rtnl_rtrealm_tab[id]) + return rtnl_rtrealm_tab[id]; + snprintf(buf, len, "%d", id); + return buf; +} +#endif + +static const char * rtnl_rtdsfield_tab[256] = { + "0", +}; + +static int rtnl_rtdsfield_init; + +static void rtnl_rtdsfield_initialize(void) +{ + rtnl_rtdsfield_init = 1; + rtnl_tab_initialize("/etc/iproute2/rt_dsfield", + rtnl_rtdsfield_tab, 256); +} + +const char * rtnl_dsfield_n2a(int id, char *buf, int len) +{ + if (id<0 || id>=256) { + snprintf(buf, len, "%d", id); + return buf; + } + if (!rtnl_rtdsfield_tab[id]) { + if (!rtnl_rtdsfield_init) + rtnl_rtdsfield_initialize(); + } + if (rtnl_rtdsfield_tab[id]) + return rtnl_rtdsfield_tab[id]; + snprintf(buf, len, "0x%02x", id); + return buf; +} + + +int rtnl_dsfield_a2n(uint32_t *id, char *arg) +{ + static const char *cache = NULL; + static unsigned long res; + char *end; + int i; + + if (cache && strcmp(cache, arg) == 0) { + *id = res; + return 0; + } + + if (!rtnl_rtdsfield_init) + rtnl_rtdsfield_initialize(); + + for (i=0; i<256; i++) { + if (rtnl_rtdsfield_tab[i] && + strcmp(rtnl_rtdsfield_tab[i], arg) == 0) { + cache = rtnl_rtdsfield_tab[i]; + res = i; + *id = res; + return 0; + } + } + + res = strtoul(arg, &end, 16); + if (!end || end == arg || *end || res > 255) + return -1; + *id = res; + return 0; +} + +#if ENABLE_FEATURE_IP_RULE +static int rtnl_rttable_init; +static const char * rtnl_rttable_tab[256] = { + "unspec", +}; +static void rtnl_rttable_initialize(void) +{ + rtnl_rttable_init = 1; + rtnl_rttable_tab[255] = "local"; + rtnl_rttable_tab[254] = "main"; + rtnl_rttable_tab[253] = "default"; + rtnl_tab_initialize("/etc/iproute2/rt_tables", rtnl_rttable_tab, 256); +} + +const char *rtnl_rttable_n2a(int id, char *buf, int len) +{ + if (id < 0 || id >= 256) { + snprintf(buf, len, "%d", id); + return buf; + } + if (!rtnl_rttable_tab[id]) { + if (!rtnl_rttable_init) + rtnl_rttable_initialize(); + } + if (rtnl_rttable_tab[id]) + return rtnl_rttable_tab[id]; + snprintf(buf, len, "%d", id); + return buf; +} + +int rtnl_rttable_a2n(uint32_t * id, char *arg) +{ + static char *cache = NULL; + static unsigned long res; + char *end; + int i; + + if (cache && strcmp(cache, arg) == 0) { + *id = res; + return 0; + } + + if (!rtnl_rttable_init) + rtnl_rttable_initialize(); + + for (i = 0; i < 256; i++) { + if (rtnl_rttable_tab[i] && strcmp(rtnl_rttable_tab[i], arg) == 0) { + cache = (char*)rtnl_rttable_tab[i]; + res = i; + *id = res; + return 0; + } + } + + i = strtoul(arg, &end, 0); + if (!end || end == arg || *end || i > 255) + return -1; + *id = i; + return 0; +} + +#endif diff --git a/networking/libiproute/rt_names.h b/networking/libiproute/rt_names.h new file mode 100644 index 000000000..92b807f3d --- /dev/null +++ b/networking/libiproute/rt_names.h @@ -0,0 +1,29 @@ +/* vi: set sw=4 ts=4: */ +#ifndef RT_NAMES_H_ +#define RT_NAMES_H_ 1 + +#include + +extern const char* rtnl_rtprot_n2a(int id, char *buf, int len); +extern const char* rtnl_rtscope_n2a(int id, char *buf, int len); +extern const char* rtnl_rtrealm_n2a(int id, char *buf, int len); +extern const char* rtnl_dsfield_n2a(int id, char *buf, int len); +extern const char* rtnl_rttable_n2a(int id, char *buf, int len); +extern int rtnl_rtprot_a2n(uint32_t *id, char *arg); +extern int rtnl_rtscope_a2n(uint32_t *id, char *arg); +extern int rtnl_rtrealm_a2n(uint32_t *id, char *arg); +extern int rtnl_dsfield_a2n(uint32_t *id, char *arg); +extern int rtnl_rttable_a2n(uint32_t *id, char *arg); + + +extern const char * ll_type_n2a(int type, char *buf, int len); + +extern const char *ll_addr_n2a(unsigned char *addr, int alen, int type, + char *buf, int blen); +extern int ll_addr_a2n(unsigned char *lladdr, int len, char *arg); + +extern const char * ll_proto_n2a(unsigned short id, char *buf, int len); +extern int ll_proto_a2n(unsigned short *id, char *buf); + + +#endif diff --git a/networking/libiproute/rtm_map.c b/networking/libiproute/rtm_map.c new file mode 100644 index 000000000..8aaee4bd7 --- /dev/null +++ b/networking/libiproute/rtm_map.c @@ -0,0 +1,111 @@ +/* vi: set sw=4 ts=4: */ +/* + * rtm_map.c + * + * 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. + * + * Authors: Alexey Kuznetsov, + * + */ + +#include +#include + +#include "rt_names.h" +#include "utils.h" + +char *rtnl_rtntype_n2a(int id, char *buf, int len) +{ + switch (id) { + case RTN_UNSPEC: + return "none"; + case RTN_UNICAST: + return "unicast"; + case RTN_LOCAL: + return "local"; + case RTN_BROADCAST: + return "broadcast"; + case RTN_ANYCAST: + return "anycast"; + case RTN_MULTICAST: + return "multicast"; + case RTN_BLACKHOLE: + return "blackhole"; + case RTN_UNREACHABLE: + return "unreachable"; + case RTN_PROHIBIT: + return "prohibit"; + case RTN_THROW: + return "throw"; + case RTN_NAT: + return "nat"; + case RTN_XRESOLVE: + return "xresolve"; + default: + snprintf(buf, len, "%d", id); + return buf; + } +} + + +int rtnl_rtntype_a2n(int *id, char *arg) +{ + char *end; + unsigned long res; + + if (strcmp(arg, "local") == 0) + res = RTN_LOCAL; + else if (strcmp(arg, "nat") == 0) + res = RTN_NAT; + else if (matches(arg, "broadcast") == 0 || + strcmp(arg, "brd") == 0) + res = RTN_BROADCAST; + else if (matches(arg, "anycast") == 0) + res = RTN_ANYCAST; + else if (matches(arg, "multicast") == 0) + res = RTN_MULTICAST; + else if (matches(arg, "prohibit") == 0) + res = RTN_PROHIBIT; + else if (matches(arg, "unreachable") == 0) + res = RTN_UNREACHABLE; + else if (matches(arg, "blackhole") == 0) + res = RTN_BLACKHOLE; + else if (matches(arg, "xresolve") == 0) + res = RTN_XRESOLVE; + else if (matches(arg, "unicast") == 0) + res = RTN_UNICAST; + else if (strcmp(arg, "throw") == 0) + res = RTN_THROW; + else { + res = strtoul(arg, &end, 0); + if (!end || end == arg || *end || res > 255) + return -1; + } + *id = res; + return 0; +} + +int get_rt_realms(__u32 *realms, char *arg) +{ + __u32 realm = 0; + char *p = strchr(arg, '/'); + + *realms = 0; + if (p) { + *p = 0; + if (rtnl_rtrealm_a2n(realms, arg)) { + *p = '/'; + return -1; + } + *realms <<= 16; + *p = '/'; + arg = p+1; + } + if (*arg && rtnl_rtrealm_a2n(&realm, arg)) + return -1; + *realms |= realm; + return 0; +} diff --git a/networking/libiproute/rtm_map.h b/networking/libiproute/rtm_map.h new file mode 100644 index 000000000..1ffb940b9 --- /dev/null +++ b/networking/libiproute/rtm_map.h @@ -0,0 +1,11 @@ +/* vi: set sw=4 ts=4: */ +#ifndef __RTM_MAP_H__ +#define __RTM_MAP_H__ 1 + +char *rtnl_rtntype_n2a(int id, char *buf, int len); +int rtnl_rtntype_a2n(int *id, char *arg); + +int get_rt_realms(__u32 *realms, char *arg); + + +#endif /* __RTM_MAP_H__ */ diff --git a/networking/libiproute/utils.c b/networking/libiproute/utils.c new file mode 100644 index 000000000..fc6aff1d1 --- /dev/null +++ b/networking/libiproute/utils.c @@ -0,0 +1,354 @@ +/* vi: set sw=4 ts=4: */ +/* + * utils.c + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Authors: Alexey Kuznetsov, + * + * Changes: + * + * Rani Assaf 980929: resolve addresses + */ + +#include "libbb.h" + +#include +#include + +#include "utils.h" +#include "inet_common.h" + +int get_integer(int *val, char *arg, int base) +{ + long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtol(arg, &ptr, base); + if (!ptr || ptr == arg || *ptr || res > INT_MAX || res < INT_MIN) + return -1; + *val = res; + return 0; +} + +int get_unsigned(unsigned *val, char *arg, int base) +{ + unsigned long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtoul(arg, &ptr, base); + if (!ptr || ptr == arg || *ptr || res > UINT_MAX) + return -1; + *val = res; + return 0; +} + +int get_u32(__u32 * val, char *arg, int base) +{ + unsigned long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtoul(arg, &ptr, base); + if (!ptr || ptr == arg || *ptr || res > 0xFFFFFFFFUL) + return -1; + *val = res; + return 0; +} + +int get_u16(__u16 * val, char *arg, int base) +{ + unsigned long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtoul(arg, &ptr, base); + if (!ptr || ptr == arg || *ptr || res > 0xFFFF) + return -1; + *val = res; + return 0; +} + +int get_u8(__u8 * val, char *arg, int base) +{ + unsigned long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtoul(arg, &ptr, base); + if (!ptr || ptr == arg || *ptr || res > 0xFF) + return -1; + *val = res; + return 0; +} + +int get_s16(__s16 * val, char *arg, int base) +{ + long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtol(arg, &ptr, base); + if (!ptr || ptr == arg || *ptr || res > 0x7FFF || res < -0x8000) + return -1; + *val = res; + return 0; +} + +int get_s8(__s8 * val, char *arg, int base) +{ + long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtol(arg, &ptr, base); + if (!ptr || ptr == arg || *ptr || res > 0x7F || res < -0x80) + return -1; + *val = res; + return 0; +} + +int get_addr_1(inet_prefix * addr, char *name, int family) +{ + char *cp; + unsigned char *ap = (unsigned char *) addr->data; + int i; + + memset(addr, 0, sizeof(*addr)); + + if (strcmp(name, bb_str_default) == 0 || + strcmp(name, "all") == 0 || strcmp(name, "any") == 0) { + addr->family = family; + addr->bytelen = (family == AF_INET6 ? 16 : 4); + addr->bitlen = -1; + return 0; + } + + if (strchr(name, ':')) { + addr->family = AF_INET6; + if (family != AF_UNSPEC && family != AF_INET6) + return -1; + if (inet_pton(AF_INET6, name, addr->data) <= 0) + return -1; + addr->bytelen = 16; + addr->bitlen = -1; + return 0; + } + + addr->family = AF_INET; + if (family != AF_UNSPEC && family != AF_INET) + return -1; + addr->bytelen = 4; + addr->bitlen = -1; + for (cp = name, i = 0; *cp; cp++) { + if (*cp <= '9' && *cp >= '0') { + ap[i] = 10 * ap[i] + (*cp - '0'); + continue; + } + if (*cp == '.' && ++i <= 3) + continue; + return -1; + } + return 0; +} + +int get_prefix_1(inet_prefix * dst, char *arg, int family) +{ + int err; + int plen; + char *slash; + + memset(dst, 0, sizeof(*dst)); + + if (strcmp(arg, bb_str_default) == 0 || strcmp(arg, "any") == 0) { + dst->family = family; + dst->bytelen = 0; + dst->bitlen = 0; + return 0; + } + + slash = strchr(arg, '/'); + if (slash) + *slash = 0; + err = get_addr_1(dst, arg, family); + if (err == 0) { + switch (dst->family) { + case AF_INET6: + dst->bitlen = 128; + break; + default: + case AF_INET: + dst->bitlen = 32; + } + if (slash) { + if (get_integer(&plen, slash + 1, 0) || plen > dst->bitlen) { + err = -1; + goto done; + } + dst->bitlen = plen; + } + } + done: + if (slash) + *slash = '/'; + return err; +} + +int get_addr(inet_prefix * dst, char *arg, int family) +{ + if (family == AF_PACKET) { + bb_error_msg_and_die("\"%s\" may be inet address, but it is not allowed in this context", arg); + } + if (get_addr_1(dst, arg, family)) { + bb_error_msg_and_die("an inet address is expected rather than \"%s\"", arg); + } + return 0; +} + +int get_prefix(inet_prefix * dst, char *arg, int family) +{ + if (family == AF_PACKET) { + bb_error_msg_and_die("\"%s\" may be inet address, but it is not allowed in this context", arg); + } + if (get_prefix_1(dst, arg, family)) { + bb_error_msg_and_die("an inet address is expected rather than \"%s\"", arg); + } + return 0; +} + +__u32 get_addr32(char *name) +{ + inet_prefix addr; + + if (get_addr_1(&addr, name, AF_INET)) { + bb_error_msg_and_die("an IP address is expected rather than \"%s\"", name); + } + return addr.data[0]; +} + +void incomplete_command(void) +{ + bb_error_msg("command line is not complete, try option \"help\""); + exit(-1); +} + +void invarg(const char * const arg, const char * const opt) +{ + bb_error_msg(bb_msg_invalid_arg, arg, opt); + exit(-1); +} + +void duparg(char *key, char *arg) +{ + bb_error_msg("duplicate \"%s\": \"%s\" is the second value", key, arg); + exit(-1); +} + +void duparg2(char *key, char *arg) +{ + bb_error_msg("either \"%s\" is duplicate, or \"%s\" is garbage", key, arg); + exit(-1); +} + +int matches(char *cmd, char *pattern) +{ + int len = strlen(cmd); + + return strncmp(pattern, cmd, len); +} + +int inet_addr_match(inet_prefix * a, inet_prefix * b, int bits) +{ + __u32 *a1 = a->data; + __u32 *a2 = b->data; + int words = bits >> 0x05; + + bits &= 0x1f; + + if (words) + if (memcmp(a1, a2, words << 2)) + return -1; + + if (bits) { + __u32 w1, w2; + __u32 mask; + + w1 = a1[words]; + w2 = a2[words]; + + mask = htonl((0xffffffff) << (0x20 - bits)); + + if ((w1 ^ w2) & mask) + return 1; + } + + return 0; +} + +int __iproute2_hz_internal; + +int __get_hz(void) +{ + int hz = 0; + FILE *fp = fopen("/proc/net/psched", "r"); + + if (fp) { + unsigned nom, denom; + + if (fscanf(fp, "%*08x%*08x%08x%08x", &nom, &denom) == 2) + if (nom == 1000000) + hz = denom; + fclose(fp); + } + if (hz) + return hz; + return sysconf(_SC_CLK_TCK); +} + +const char *rt_addr_n2a(int af, int ATTRIBUTE_UNUSED len, + void *addr, char *buf, int buflen) +{ + switch (af) { + case AF_INET: + case AF_INET6: + return inet_ntop(af, addr, buf, buflen); + default: + return "???"; + } +} + + +const char *format_host(int af, int len, void *addr, char *buf, int buflen) +{ +#ifdef RESOLVE_HOSTNAMES + if (resolve_hosts) { + struct hostent *h_ent; + + if (len <= 0) { + switch (af) { + case AF_INET: + len = 4; + break; + case AF_INET6: + len = 16; + break; + default:; + } + } + if (len > 0 && (h_ent = gethostbyaddr(addr, len, af)) != NULL) { + snprintf(buf, buflen - 1, "%s", h_ent->h_name); + return buf; + } + } +#endif + return rt_addr_n2a(af, len, addr, buf, buflen); +} diff --git a/networking/libiproute/utils.h b/networking/libiproute/utils.h new file mode 100644 index 000000000..0b0d7545a --- /dev/null +++ b/networking/libiproute/utils.h @@ -0,0 +1,103 @@ +/* vi: set sw=4 ts=4: */ +#ifndef __UTILS_H__ +#define __UTILS_H__ 1 + +#include "libbb.h" +#include +#include + +#include "libnetlink.h" +#include "ll_map.h" +#include "rtm_map.h" + +extern int preferred_family; +extern int show_stats; +extern int show_details; +extern int show_raw; +extern int resolve_hosts; +extern int oneline; +extern char * _SL_; + +#ifndef IPPROTO_ESP +#define IPPROTO_ESP 50 +#endif +#ifndef IPPROTO_AH +#define IPPROTO_AH 51 +#endif + +#define SPRINT_BSIZE 64 +#define SPRINT_BUF(x) char x[SPRINT_BSIZE] + +extern void incomplete_command(void) ATTRIBUTE_NORETURN; + +#define NEXT_ARG() do { argv++; if (--argc <= 0) incomplete_command(); } while(0) + +typedef struct +{ + __u8 family; + __u8 bytelen; + __s16 bitlen; + __u32 data[4]; +} inet_prefix; + +#define DN_MAXADDL 20 +#ifndef AF_DECnet +#define AF_DECnet 12 +#endif + +struct dn_naddr +{ + unsigned short a_len; + unsigned char a_addr[DN_MAXADDL]; +}; + +#define IPX_NODE_LEN 6 + +struct ipx_addr { + uint32_t ipx_net; + uint8_t ipx_node[IPX_NODE_LEN]; +}; + +extern __u32 get_addr32(char *name); +extern int get_addr_1(inet_prefix *dst, char *arg, int family); +extern int get_prefix_1(inet_prefix *dst, char *arg, int family); +extern int get_addr(inet_prefix *dst, char *arg, int family); +extern int get_prefix(inet_prefix *dst, char *arg, int family); + +extern int get_integer(int *val, char *arg, int base); +extern int get_unsigned(unsigned *val, char *arg, int base); +#define get_byte get_u8 +#define get_ushort get_u16 +#define get_short get_s16 +extern int get_u32(__u32 *val, char *arg, int base); +extern int get_u16(__u16 *val, char *arg, int base); +extern int get_s16(__s16 *val, char *arg, int base); +extern int get_u8(__u8 *val, char *arg, int base); +extern int get_s8(__s8 *val, char *arg, int base); + +extern const char *format_host(int af, int len, void *addr, char *buf, int buflen); +extern const char *rt_addr_n2a(int af, int len, void *addr, char *buf, int buflen); + +void invarg(const char * const, const char * const) ATTRIBUTE_NORETURN; +void duparg(char *, char *) ATTRIBUTE_NORETURN; +void duparg2(char *, char *) ATTRIBUTE_NORETURN; +int matches(char *arg, char *pattern); +extern int inet_addr_match(inet_prefix *a, inet_prefix *b, int bits); + +const char *dnet_ntop(int af, const void *addr, char *str, size_t len); +int dnet_pton(int af, const char *src, void *addr); + +const char *ipx_ntop(int af, const void *addr, char *str, size_t len); +int ipx_pton(int af, const char *src, void *addr); + +extern int __iproute2_hz_internal; +extern int __get_hz(void); + +static __inline__ int get_hz(void) +{ + if (__iproute2_hz_internal == 0) + __iproute2_hz_internal = __get_hz(); + return __iproute2_hz_internal; +} + +#endif /* __UTILS_H__ */ diff --git a/networking/nameif.c b/networking/nameif.c new file mode 100644 index 000000000..52aad2873 --- /dev/null +++ b/networking/nameif.c @@ -0,0 +1,170 @@ +/* vi: set sw=4 ts=4: */ +/* + * nameif.c - Naming Interfaces based on MAC address for busybox. + * + * Written 2000 by Andi Kleen. + * Busybox port 2002 by Nick Fedchik + * Glenn McGrath + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include +#include +#include + + +/* Older versions of net/if.h do not appear to define IF_NAMESIZE. */ +#ifndef IF_NAMESIZE +# ifdef IFNAMSIZ +# define IF_NAMESIZE IFNAMSIZ +# else +# define IF_NAMESIZE 16 +# endif +#endif + +/* take from linux/sockios.h */ +#define SIOCSIFNAME 0x8923 /* set interface name */ + +/* Octets in one Ethernet addr, from */ +#define ETH_ALEN 6 + +#ifndef ifr_newname +#define ifr_newname ifr_ifru.ifru_slave +#endif + +typedef struct mactable_s { + struct mactable_s *next; + struct mactable_s *prev; + char *ifname; + struct ether_addr *mac; +} mactable_t; + +/* Check ascii str_macaddr, convert and copy to *mac */ +static struct ether_addr *cc_macaddr(const char *str_macaddr) +{ + struct ether_addr *lmac, *mac; + + lmac = ether_aton(str_macaddr); + if (lmac == NULL) + bb_error_msg_and_die("cannot parse MAC %s", str_macaddr); + mac = xmalloc(ETH_ALEN); + memcpy(mac, lmac, ETH_ALEN); + + return mac; +} + +int nameif_main(int argc, char **argv) +{ + mactable_t *clist = NULL; + FILE *ifh; + const char *fname = "/etc/mactab"; + char *line; + int ctl_sk; + int if_index = 1; + mactable_t *ch; + + if (1 & getopt32(argc, argv, "sc:", &fname)) { + openlog(applet_name, 0, LOG_LOCAL0); + logmode = LOGMODE_SYSLOG; + } + + if ((argc - optind) & 1) + bb_show_usage(); + + if (optind < argc) { + char **a = argv + optind; + + while (*a) { + if (strlen(*a) > IF_NAMESIZE) + bb_error_msg_and_die("interface name '%s' " + "too long", *a); + ch = xzalloc(sizeof(mactable_t)); + ch->ifname = xstrdup(*a++); + ch->mac = cc_macaddr(*a++); + if (clist) + clist->prev = ch; + ch->next = clist; + clist = ch; + } + } else { + ifh = xfopen(fname, "r"); + + while ((line = xmalloc_fgets(ifh)) != NULL) { + char *line_ptr; + size_t name_length; + + line_ptr = line + strspn(line, " \t"); + if ((line_ptr[0] == '#') || (line_ptr[0] == '\n')) { + free(line); + continue; + } + name_length = strcspn(line_ptr, " \t"); + ch = xzalloc(sizeof(mactable_t)); + ch->ifname = xstrndup(line_ptr, name_length); + if (name_length > IF_NAMESIZE) + bb_error_msg_and_die("interface name '%s' " + "too long", ch->ifname); + line_ptr += name_length; + line_ptr += strspn(line_ptr, " \t"); + name_length = strspn(line_ptr, "0123456789ABCDEFabcdef:"); + line_ptr[name_length] = '\0'; + ch->mac = cc_macaddr(line_ptr); + if (clist) + clist->prev = ch; + ch->next = clist; + clist = ch; + free(line); + } + fclose(ifh); + } + + ctl_sk = xsocket(PF_INET, SOCK_DGRAM, 0); + + while (clist) { + struct ifreq ifr; + + memset(&ifr, 0, sizeof(struct ifreq)); + if_index++; + ifr.ifr_ifindex = if_index; + + /* Get ifname by index or die */ + if (ioctl(ctl_sk, SIOCGIFNAME, &ifr)) + break; + + /* Has this device hwaddr? */ + if (ioctl(ctl_sk, SIOCGIFHWADDR, &ifr)) + continue; + + /* Search for mac like in ifr.ifr_hwaddr.sa_data */ + for (ch = clist; ch; ch = ch->next) + if (!memcmp(ch->mac, ifr.ifr_hwaddr.sa_data, ETH_ALEN)) + break; + + /* Nothing found for current ifr.ifr_hwaddr.sa_data */ + if (ch == NULL) + continue; + + strcpy(ifr.ifr_newname, ch->ifname); + if (ioctl(ctl_sk, SIOCSIFNAME, &ifr) < 0) + bb_perror_msg_and_die("cannot change ifname %s to %s", + ifr.ifr_name, ch->ifname); + + /* Remove list entry of renamed interface */ + if (ch->prev != NULL) { + (ch->prev)->next = ch->next; + } else { + clist = ch->next; + } + if (ch->next != NULL) + (ch->next)->prev = ch->prev; + if (ENABLE_FEATURE_CLEAN_UP) { + free(ch->ifname); + free(ch->mac); + free(ch); + } + } + + return 0; +} diff --git a/networking/nc.c b/networking/nc.c new file mode 100644 index 000000000..5fd9242cc --- /dev/null +++ b/networking/nc.c @@ -0,0 +1,201 @@ +/* vi: set sw=4 ts=4: */ +/* nc: mini-netcat - built from the ground up for LRP + * + * Copyright (C) 1998, 1999 Charles P. Wright + * Copyright (C) 1998 Dave Cinege + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +static void timeout(int signum) +{ + bb_error_msg_and_die("timed out"); +} + +int nc_main(int argc, char **argv) +{ + int sfd = 0; + int cfd = 0; + SKIP_NC_SERVER(const) unsigned do_listen = 0; + SKIP_NC_SERVER(const) unsigned lport = 0; + SKIP_NC_EXTRA (const) unsigned wsecs = 0; + SKIP_NC_EXTRA (const) unsigned delay = 0; + SKIP_NC_EXTRA (const int execparam = 0;) + USE_NC_EXTRA (char **execparam = NULL;) + struct sockaddr_in address; + fd_set readfds, testfds; + int opt; /* must be signed (getopt returns -1) */ + + memset(&address, 0, sizeof(address)); + + if (ENABLE_NC_SERVER || ENABLE_NC_EXTRA) { + /* getopt32 is _almost_ usable: + ** it cannot handle "... -e prog -prog-opt" */ + while ((opt = getopt(argc, argv, + "" USE_NC_SERVER("lp:") USE_NC_EXTRA("w:i:f:e:") )) > 0 + ) { + if (ENABLE_NC_SERVER && opt=='l') USE_NC_SERVER(do_listen++); + else if (ENABLE_NC_SERVER && opt=='p') USE_NC_SERVER(lport = bb_lookup_port(optarg, "tcp", 0)); + else if (ENABLE_NC_EXTRA && opt=='w') USE_NC_EXTRA( wsecs = xatou(optarg)); + else if (ENABLE_NC_EXTRA && opt=='i') USE_NC_EXTRA( delay = xatou(optarg)); + else if (ENABLE_NC_EXTRA && opt=='f') USE_NC_EXTRA( cfd = xopen(optarg, O_RDWR)); + else if (ENABLE_NC_EXTRA && opt=='e' && optind<=argc) { + /* We cannot just 'break'. We should let getopt finish. + ** Or else we won't be able to find where + ** 'host' and 'port' params are + ** (think "nc -w 60 host port -e prog"). */ + USE_NC_EXTRA( + char **p; + // +2: one for progname (optarg) and one for NULL + execparam = xzalloc(sizeof(char*) * (argc - optind + 2)); + p = execparam; + *p++ = optarg; + while (optind < argc) { + *p++ = argv[optind++]; + } + ) + /* optind points to argv[arvc] (NULL) now. + ** FIXME: we assume that getopt will not count options + ** possibly present on "-e prog args" and will not + ** include them into final value of optind + ** which is to be used ... */ + } else bb_show_usage(); + } + argv += optind; /* ... here! */ + argc -= optind; + // -l and -f don't mix + if (do_listen && cfd) bb_show_usage(); + // Listen or file modes need zero arguments, client mode needs 2 + opt = ((do_listen || cfd) ? 0 : 2); + if (argc != opt) + bb_show_usage(); + } else { + if (argc != 3) bb_show_usage(); + argc--; + argv++; + } + + if (wsecs) { + signal(SIGALRM, timeout); + alarm(wsecs); + } + + if (!cfd) { + sfd = xsocket(AF_INET, SOCK_STREAM, 0); + fcntl(sfd, F_SETFD, FD_CLOEXEC); + setsockopt_reuseaddr(sfd); + address.sin_family = AF_INET; + + // Set local port. + + if (lport != 0) { + address.sin_port = lport; + xbind(sfd, (struct sockaddr *) &address, sizeof(address)); + } + + if (do_listen) { + socklen_t addrlen = sizeof(address); + + xlisten(sfd, do_listen); + + // If we didn't specify a port number, query and print it to stderr. + + if (!lport) { + socklen_t len = sizeof(address); + getsockname(sfd, &address, &len); + fdprintf(2, "%d\n", SWAP_BE16(address.sin_port)); + } + repeatyness: + cfd = accept(sfd, (struct sockaddr *) &address, &addrlen); + if (cfd < 0) + bb_perror_msg_and_die("accept"); + + if (!execparam) close(sfd); + } else { + struct hostent *hostinfo; + hostinfo = xgethostbyname(argv[0]); + + address.sin_addr = *(struct in_addr *) *hostinfo->h_addr_list; + address.sin_port = bb_lookup_port(argv[1], "tcp", 0); + + if (connect(sfd, (struct sockaddr *) &address, sizeof(address)) < 0) + bb_perror_msg_and_die("connect"); + cfd = sfd; + } + } + + if (wsecs) { + alarm(0); + signal(SIGALRM, SIG_DFL); + } + + /* -e given? */ + if (execparam) { + if (cfd) { + signal(SIGCHLD, SIG_IGN); + dup2(cfd, 0); + close(cfd); + } + dup2(0, 1); + dup2(0, 2); + + // With more than one -l, repeatedly act as server. + + if (do_listen > 1 && vfork()) { + // This is a bit weird as cleanup goes, since we wind up with no + // stdin/stdout/stderr. But it's small and shouldn't hurt anything. + // We check for cfd == 0 above. + logmode = LOGMODE_NONE; + close(0); + close(1); + close(2); + + goto repeatyness; + } + USE_NC_EXTRA(execvp(execparam[0], execparam);) + /* Don't print stuff or it will go over the wire.... */ + _exit(127); + } + + // Select loop copying stdin to cfd, and cfd to stdout. + + FD_ZERO(&readfds); + FD_SET(cfd, &readfds); + FD_SET(STDIN_FILENO, &readfds); + + for (;;) { + int fd; + int ofd; + int nread; + + testfds = readfds; + + if (select(FD_SETSIZE, &testfds, NULL, NULL, NULL) < 0) + bb_perror_msg_and_die("select"); + + for (fd = 0; fd < FD_SETSIZE; fd++) { + if (FD_ISSET(fd, &testfds)) { + nread = safe_read(fd, bb_common_bufsiz1, + sizeof(bb_common_bufsiz1)); + + if (fd == cfd) { + if (nread<1) exit(0); + ofd = STDOUT_FILENO; + } else { + if (nread<1) { + // Close outgoing half-connection so they get EOF, but + // leave incoming alone so we can see response. + shutdown(cfd, 1); + FD_CLR(STDIN_FILENO, &readfds); + } + ofd = cfd; + } + + xwrite(ofd, bb_common_bufsiz1, nread); + if (delay > 0) sleep(delay); + } + } + } +} diff --git a/networking/netstat.c b/networking/netstat.c new file mode 100644 index 000000000..3dad57a40 --- /dev/null +++ b/networking/netstat.c @@ -0,0 +1,613 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini netstat implementation(s) for busybox + * based in part on the netstat implementation from net-tools. + * + * Copyright (C) 2002 by Bart Visscher + * + * 2002-04-20 + * IPV6 support added by Bart Visscher + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include "inet_common.h" + +#ifdef CONFIG_ROUTE +extern void displayroutes(int noresolve, int netstatfmt); +#endif + +#define NETSTAT_CONNECTED 0x01 +#define NETSTAT_LISTENING 0x02 +#define NETSTAT_NUMERIC 0x04 +/* Must match getopt32 option string */ +#define NETSTAT_TCP 0x10 +#define NETSTAT_UDP 0x20 +#define NETSTAT_RAW 0x40 +#define NETSTAT_UNIX 0x80 +#define NETSTAT_ALLPROTO (NETSTAT_TCP|NETSTAT_UDP|NETSTAT_RAW|NETSTAT_UNIX) + +static int flags = NETSTAT_CONNECTED | NETSTAT_ALLPROTO; + +#define PROGNAME_WIDTHs PROGNAME_WIDTH1(PROGNAME_WIDTH) +#define PROGNAME_WIDTH1(s) PROGNAME_WIDTH2(s) +#define PROGNAME_WIDTH2(s) #s + +#define PRG_HASH_SIZE 211 + +enum { + TCP_ESTABLISHED = 1, + TCP_SYN_SENT, + TCP_SYN_RECV, + TCP_FIN_WAIT1, + TCP_FIN_WAIT2, + TCP_TIME_WAIT, + TCP_CLOSE, + TCP_CLOSE_WAIT, + TCP_LAST_ACK, + TCP_LISTEN, + TCP_CLOSING /* now a valid state */ +}; + +static const char * const tcp_state[] = +{ + "", + "ESTABLISHED", + "SYN_SENT", + "SYN_RECV", + "FIN_WAIT1", + "FIN_WAIT2", + "TIME_WAIT", + "CLOSE", + "CLOSE_WAIT", + "LAST_ACK", + "LISTEN", + "CLOSING" +}; + +typedef enum { + SS_FREE = 0, /* not allocated */ + SS_UNCONNECTED, /* unconnected to any socket */ + SS_CONNECTING, /* in process of connecting */ + SS_CONNECTED, /* connected to socket */ + SS_DISCONNECTING /* in process of disconnecting */ +} socket_state; + +#define SO_ACCEPTCON (1<<16) /* performed a listen */ +#define SO_WAITDATA (1<<17) /* wait data to read */ +#define SO_NOSPACE (1<<18) /* no space to write */ + +static char *get_sname(int port, const char *proto, int num) +{ + char *str=itoa(ntohs(port)); + if (num) { + } else { + struct servent *se=getservbyport(port,proto); + if (se) + str=se->s_name; + } + if (!port) { + str="*"; + } + return str; +} + +static void snprint_ip_port(char *ip_port, int size, struct sockaddr *addr, int port, char *proto, int numeric) +{ + char *port_name; + +#ifdef CONFIG_FEATURE_IPV6 + if (addr->sa_family == AF_INET6) { + INET6_rresolve(ip_port, size, (struct sockaddr_in6 *)addr, + (numeric&NETSTAT_NUMERIC) ? 0x0fff : 0); + } else +#endif + { + INET_rresolve(ip_port, size, (struct sockaddr_in *)addr, + 0x4000 | ((numeric&NETSTAT_NUMERIC) ? 0x0fff : 0), + 0xffffffff); + } + port_name=get_sname(htons(port), proto, numeric); + if ((strlen(ip_port) + strlen(port_name)) > 22) + ip_port[22 - strlen(port_name)] = '\0'; + ip_port+=strlen(ip_port); + strcat(ip_port, ":"); + strcat(ip_port, port_name); +} + +static void tcp_do_one(int lnr, const char *line) +{ + char local_addr[64], rem_addr[64]; + const char *state_str; + char more[512]; + int num, local_port, rem_port, d, state, timer_run, uid, timeout; +#ifdef CONFIG_FEATURE_IPV6 + struct sockaddr_in6 localaddr, remaddr; + char addr6[INET6_ADDRSTRLEN]; + struct in6_addr in6; +#else + struct sockaddr_in localaddr, remaddr; +#endif + unsigned long rxq, txq, time_len, retr, inode; + + if (lnr == 0) + return; + + more[0] = '\0'; + num = sscanf(line, + "%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X %lX:%lX %X:%lX %lX %d %d %ld %512s\n", + &d, local_addr, &local_port, + rem_addr, &rem_port, &state, + &txq, &rxq, &timer_run, &time_len, &retr, &uid, &timeout, &inode, more); + + if (strlen(local_addr) > 8) { +#ifdef CONFIG_FEATURE_IPV6 + sscanf(local_addr, "%08X%08X%08X%08X", + &in6.s6_addr32[0], &in6.s6_addr32[1], + &in6.s6_addr32[2], &in6.s6_addr32[3]); + inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6)); + inet_pton(AF_INET6, addr6, (struct sockaddr *) &localaddr.sin6_addr); + sscanf(rem_addr, "%08X%08X%08X%08X", + &in6.s6_addr32[0], &in6.s6_addr32[1], + &in6.s6_addr32[2], &in6.s6_addr32[3]); + inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6)); + inet_pton(AF_INET6, addr6, (struct sockaddr *) &remaddr.sin6_addr); + localaddr.sin6_family = AF_INET6; + remaddr.sin6_family = AF_INET6; +#endif + } else { + sscanf(local_addr, "%X", + &((struct sockaddr_in *) &localaddr)->sin_addr.s_addr); + sscanf(rem_addr, "%X", + &((struct sockaddr_in *) &remaddr)->sin_addr.s_addr); + ((struct sockaddr *) &localaddr)->sa_family = AF_INET; + ((struct sockaddr *) &remaddr)->sa_family = AF_INET; + } + + if (num < 10) { + bb_error_msg("warning, got bogus tcp line"); + return; + } + state_str = tcp_state[state]; + if ((rem_port && (flags&NETSTAT_CONNECTED)) || + (!rem_port && (flags&NETSTAT_LISTENING))) + { + snprint_ip_port(local_addr, sizeof(local_addr), + (struct sockaddr *) &localaddr, local_port, + "tcp", flags&NETSTAT_NUMERIC); + + snprint_ip_port(rem_addr, sizeof(rem_addr), + (struct sockaddr *) &remaddr, rem_port, + "tcp", flags&NETSTAT_NUMERIC); + + printf("tcp %6ld %6ld %-23s %-23s %-12s\n", + rxq, txq, local_addr, rem_addr, state_str); + + } +} + +static void udp_do_one(int lnr, const char *line) +{ + char local_addr[64], rem_addr[64]; + char *state_str, more[512]; + int num, local_port, rem_port, d, state, timer_run, uid, timeout; +#ifdef CONFIG_FEATURE_IPV6 + struct sockaddr_in6 localaddr, remaddr; + char addr6[INET6_ADDRSTRLEN]; + struct in6_addr in6; +#else + struct sockaddr_in localaddr, remaddr; +#endif + unsigned long rxq, txq, time_len, retr, inode; + + if (lnr == 0) + return; + + more[0] = '\0'; + num = sscanf(line, + "%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X %lX:%lX %X:%lX %lX %d %d %ld %512s\n", + &d, local_addr, &local_port, + rem_addr, &rem_port, &state, + &txq, &rxq, &timer_run, &time_len, &retr, &uid, &timeout, &inode, more); + + if (strlen(local_addr) > 8) { +#ifdef CONFIG_FEATURE_IPV6 + /* Demangle what the kernel gives us */ + sscanf(local_addr, "%08X%08X%08X%08X", + &in6.s6_addr32[0], &in6.s6_addr32[1], + &in6.s6_addr32[2], &in6.s6_addr32[3]); + inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6)); + inet_pton(AF_INET6, addr6, (struct sockaddr *) &localaddr.sin6_addr); + sscanf(rem_addr, "%08X%08X%08X%08X", + &in6.s6_addr32[0], &in6.s6_addr32[1], + &in6.s6_addr32[2], &in6.s6_addr32[3]); + inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6)); + inet_pton(AF_INET6, addr6, (struct sockaddr *) &remaddr.sin6_addr); + localaddr.sin6_family = AF_INET6; + remaddr.sin6_family = AF_INET6; +#endif + } else { + sscanf(local_addr, "%X", + &((struct sockaddr_in *) &localaddr)->sin_addr.s_addr); + sscanf(rem_addr, "%X", + &((struct sockaddr_in *) &remaddr)->sin_addr.s_addr); + ((struct sockaddr *) &localaddr)->sa_family = AF_INET; + ((struct sockaddr *) &remaddr)->sa_family = AF_INET; + } + + if (num < 10) { + bb_error_msg("warning, got bogus udp line"); + return; + } + switch (state) { + case TCP_ESTABLISHED: + state_str = "ESTABLISHED"; + break; + + case TCP_CLOSE: + state_str = ""; + break; + + default: + state_str = "UNKNOWN"; + break; + } + +#ifdef CONFIG_FEATURE_IPV6 +# define notnull(A) (((A.sin6_family == AF_INET6) && \ + ((A.sin6_addr.s6_addr32[0]) || \ + (A.sin6_addr.s6_addr32[1]) || \ + (A.sin6_addr.s6_addr32[2]) || \ + (A.sin6_addr.s6_addr32[3]))) || \ + ((A.sin6_family == AF_INET) && \ + ((struct sockaddr_in *) &A)->sin_addr.s_addr)) +#else +# define notnull(A) (A.sin_addr.s_addr) +#endif + if ((notnull(remaddr) && (flags&NETSTAT_CONNECTED)) || + (!notnull(remaddr) && (flags&NETSTAT_LISTENING))) + { + snprint_ip_port(local_addr, sizeof(local_addr), + (struct sockaddr *) &localaddr, local_port, + "udp", flags&NETSTAT_NUMERIC); + + snprint_ip_port(rem_addr, sizeof(rem_addr), + (struct sockaddr *) &remaddr, rem_port, + "udp", flags&NETSTAT_NUMERIC); + + printf("udp %6ld %6ld %-23s %-23s %-12s\n", + rxq, txq, local_addr, rem_addr, state_str); + + } +} + +static void raw_do_one(int lnr, const char *line) +{ + char local_addr[64], rem_addr[64]; + char *state_str, more[512]; + int num, local_port, rem_port, d, state, timer_run, uid, timeout; +#ifdef CONFIG_FEATURE_IPV6 + struct sockaddr_in6 localaddr, remaddr; + char addr6[INET6_ADDRSTRLEN]; + struct in6_addr in6; +#else + struct sockaddr_in localaddr, remaddr; +#endif + unsigned long rxq, txq, time_len, retr, inode; + + if (lnr == 0) + return; + + more[0] = '\0'; + num = sscanf(line, + "%d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %X %lX:%lX %X:%lX %lX %d %d %ld %512s\n", + &d, local_addr, &local_port, + rem_addr, &rem_port, &state, + &txq, &rxq, &timer_run, &time_len, &retr, &uid, &timeout, &inode, more); + + if (strlen(local_addr) > 8) { +#ifdef CONFIG_FEATURE_IPV6 + sscanf(local_addr, "%08X%08X%08X%08X", + &in6.s6_addr32[0], &in6.s6_addr32[1], + &in6.s6_addr32[2], &in6.s6_addr32[3]); + inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6)); + inet_pton(AF_INET6, addr6, (struct sockaddr *) &localaddr.sin6_addr); + sscanf(rem_addr, "%08X%08X%08X%08X", + &in6.s6_addr32[0], &in6.s6_addr32[1], + &in6.s6_addr32[2], &in6.s6_addr32[3]); + inet_ntop(AF_INET6, &in6, addr6, sizeof(addr6)); + inet_pton(AF_INET6, addr6, (struct sockaddr *) &remaddr.sin6_addr); + localaddr.sin6_family = AF_INET6; + remaddr.sin6_family = AF_INET6; +#endif + } else { + sscanf(local_addr, "%X", + &((struct sockaddr_in *) &localaddr)->sin_addr.s_addr); + sscanf(rem_addr, "%X", + &((struct sockaddr_in *) &remaddr)->sin_addr.s_addr); + ((struct sockaddr *) &localaddr)->sa_family = AF_INET; + ((struct sockaddr *) &remaddr)->sa_family = AF_INET; + } + + if (num < 10) { + bb_error_msg("warning, got bogus raw line"); + return; + } + state_str=itoa(state); + +#ifdef CONFIG_FEATURE_IPV6 +# define notnull(A) (((A.sin6_family == AF_INET6) && \ + ((A.sin6_addr.s6_addr32[0]) || \ + (A.sin6_addr.s6_addr32[1]) || \ + (A.sin6_addr.s6_addr32[2]) || \ + (A.sin6_addr.s6_addr32[3]))) || \ + ((A.sin6_family == AF_INET) && \ + ((struct sockaddr_in *) &A)->sin_addr.s_addr)) +#else +# define notnull(A) (A.sin_addr.s_addr) +#endif + if ((notnull(remaddr) && (flags&NETSTAT_CONNECTED)) || + (!notnull(remaddr) && (flags&NETSTAT_LISTENING))) + { + snprint_ip_port(local_addr, sizeof(local_addr), + (struct sockaddr *) &localaddr, local_port, + "raw", flags&NETSTAT_NUMERIC); + + snprint_ip_port(rem_addr, sizeof(rem_addr), + (struct sockaddr *) &remaddr, rem_port, + "raw", flags&NETSTAT_NUMERIC); + + printf("raw %6ld %6ld %-23s %-23s %-12s\n", + rxq, txq, local_addr, rem_addr, state_str); + + } +} + +#define HAS_INODE 1 + +static void unix_do_one(int nr, const char *line) +{ + static int has = 0; + char path[PATH_MAX], ss_flags[32]; + char *ss_proto, *ss_state, *ss_type; + int num, state, type, inode; + void *d; + unsigned long refcnt, proto, unix_flags; + + if (nr == 0) { + if (strstr(line, "Inode")) + has |= HAS_INODE; + return; + } + path[0] = '\0'; + num = sscanf(line, "%p: %lX %lX %lX %X %X %d %s", + &d, &refcnt, &proto, &unix_flags, &type, &state, &inode, path); + if (num < 6) { + bb_error_msg("warning, got bogus unix line"); + return; + } + if (!(has & HAS_INODE)) + snprintf(path,sizeof(path),"%d",inode); + + if ((flags&(NETSTAT_LISTENING|NETSTAT_CONNECTED))!=(NETSTAT_LISTENING|NETSTAT_CONNECTED)) { + if ((state == SS_UNCONNECTED) && (unix_flags & SO_ACCEPTCON)) { + if (!(flags&NETSTAT_LISTENING)) + return; + } else { + if (!(flags&NETSTAT_CONNECTED)) + return; + } + } + + switch (proto) { + case 0: + ss_proto = "unix"; + break; + + default: + ss_proto = "??"; + } + + switch (type) { + case SOCK_STREAM: + ss_type = "STREAM"; + break; + + case SOCK_DGRAM: + ss_type = "DGRAM"; + break; + + case SOCK_RAW: + ss_type = "RAW"; + break; + + case SOCK_RDM: + ss_type = "RDM"; + break; + + case SOCK_SEQPACKET: + ss_type = "SEQPACKET"; + break; + + default: + ss_type = "UNKNOWN"; + } + + switch (state) { + case SS_FREE: + ss_state = "FREE"; + break; + + case SS_UNCONNECTED: + /* + * Unconnected sockets may be listening + * for something. + */ + if (unix_flags & SO_ACCEPTCON) { + ss_state = "LISTENING"; + } else { + ss_state = ""; + } + break; + + case SS_CONNECTING: + ss_state = "CONNECTING"; + break; + + case SS_CONNECTED: + ss_state = "CONNECTED"; + break; + + case SS_DISCONNECTING: + ss_state = "DISCONNECTING"; + break; + + default: + ss_state = "UNKNOWN"; + } + + strcpy(ss_flags, "[ "); + if (unix_flags & SO_ACCEPTCON) + strcat(ss_flags, "ACC "); + if (unix_flags & SO_WAITDATA) + strcat(ss_flags, "W "); + if (unix_flags & SO_NOSPACE) + strcat(ss_flags, "N "); + + strcat(ss_flags, "]"); + + printf("%-5s %-6ld %-11s %-10s %-13s ", + ss_proto, refcnt, ss_flags, ss_type, ss_state); + if (has & HAS_INODE) + printf("%-6d ",inode); + else + printf("- "); + puts(path); +} + +#define _PATH_PROCNET_UDP "/proc/net/udp" +#define _PATH_PROCNET_UDP6 "/proc/net/udp6" +#define _PATH_PROCNET_TCP "/proc/net/tcp" +#define _PATH_PROCNET_TCP6 "/proc/net/tcp6" +#define _PATH_PROCNET_RAW "/proc/net/raw" +#define _PATH_PROCNET_RAW6 "/proc/net/raw6" +#define _PATH_PROCNET_UNIX "/proc/net/unix" + +static void do_info(const char *file, const char *name, void (*proc)(int, const char *)) +{ + int lnr = 0; + FILE *procinfo; + + procinfo = fopen(file, "r"); + if (procinfo == NULL) { + if (errno != ENOENT) { + perror(file); + } else { + bb_error_msg("no support for '%s' on this system", name); + } + } else { + do { + char *buffer = xmalloc_fgets(procinfo); + if (buffer) { + (proc)(lnr++, buffer); + free(buffer); + } + } while (!feof(procinfo)); + fclose(procinfo); + } +} + +/* + * Our main function. + */ + +int netstat_main(int argc, char **argv) +{ + enum { + OPT_extended = 0x4, + OPT_showroute = 0x100, + }; + unsigned opt; +#ifdef CONFIG_FEATURE_IPV6 + int inet = 1; + int inet6 = 1; +#else +# define inet 1 +# define inet6 0 +#endif + + /* Option string must match NETSTAT_xxx constants */ + opt = getopt32(argc, argv, "laentuwxr"); + if (opt & 0x1) { // -l + flags &= ~NETSTAT_CONNECTED; + flags |= NETSTAT_LISTENING; + } + if (opt & 0x2) flags |= NETSTAT_LISTENING | NETSTAT_CONNECTED; // -a + //if (opt & 0x4) // -e + if (opt & 0x8) flags |= NETSTAT_NUMERIC; // -n + //if (opt & 0x10) // -t: NETSTAT_TCP + //if (opt & 0x20) // -u: NETSTAT_UDP + //if (opt & 0x40) // -w: NETSTAT_RAW + //if (opt & 0x80) // -x: NETSTAT_UNIX + if (opt & OPT_showroute) { // -r +#ifdef CONFIG_ROUTE + displayroutes(flags & NETSTAT_NUMERIC, !(opt & OPT_extended)); + return 0; +#else + bb_error_msg_and_die("-r (display routing table) is not compiled in"); +#endif + } + + opt &= NETSTAT_ALLPROTO; + if (opt) { + flags &= ~NETSTAT_ALLPROTO; + flags |= opt; + } + if (flags & (NETSTAT_TCP|NETSTAT_UDP|NETSTAT_RAW)) { + printf("Active Internet connections "); /* xxx */ + + if ((flags&(NETSTAT_LISTENING|NETSTAT_CONNECTED))==(NETSTAT_LISTENING|NETSTAT_CONNECTED)) + printf("(servers and established)"); + else { + if (flags & NETSTAT_LISTENING) + printf("(only servers)"); + else + printf("(w/o servers)"); + } + printf("\nProto Recv-Q Send-Q Local Address Foreign Address State\n"); + } + if (inet && flags&NETSTAT_TCP) + do_info(_PATH_PROCNET_TCP,"AF INET (tcp)",tcp_do_one); +#ifdef CONFIG_FEATURE_IPV6 + if (inet6 && flags&NETSTAT_TCP) + do_info(_PATH_PROCNET_TCP6,"AF INET6 (tcp)",tcp_do_one); +#endif + if (inet && flags&NETSTAT_UDP) + do_info(_PATH_PROCNET_UDP,"AF INET (udp)",udp_do_one); +#ifdef CONFIG_FEATURE_IPV6 + if (inet6 && flags&NETSTAT_UDP) + do_info(_PATH_PROCNET_UDP6,"AF INET6 (udp)",udp_do_one); +#endif + if (inet && flags&NETSTAT_RAW) + do_info(_PATH_PROCNET_RAW,"AF INET (raw)",raw_do_one); +#ifdef CONFIG_FEATURE_IPV6 + if (inet6 && flags&NETSTAT_RAW) + do_info(_PATH_PROCNET_RAW6,"AF INET6 (raw)",raw_do_one); +#endif + if (flags&NETSTAT_UNIX) { + printf("Active UNIX domain sockets "); + if ((flags&(NETSTAT_LISTENING|NETSTAT_CONNECTED))==(NETSTAT_LISTENING|NETSTAT_CONNECTED)) + printf("(servers and established)"); + else { + if (flags&NETSTAT_LISTENING) + printf("(only servers)"); + else + printf("(w/o servers)"); + } + + printf("\nProto RefCnt Flags Type State I-Node Path\n"); + do_info(_PATH_PROCNET_UNIX,"AF UNIX",unix_do_one); + } + return 0; +} diff --git a/networking/nslookup.c b/networking/nslookup.c new file mode 100644 index 000000000..cc5ff95d6 --- /dev/null +++ b/networking/nslookup.c @@ -0,0 +1,149 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini nslookup implementation for busybox + * + * Copyright (C) 1999,2000 by Lineo, inc. and John Beppu + * Copyright (C) 1999,2000,2001 by John Beppu + * + * Correct default name server display and explicit name server option + * added by Ben Zeckel June 2001 + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include "busybox.h" + +/* + * I'm only implementing non-interactive mode; + * I totally forgot nslookup even had an interactive mode. + */ + +/* Examples of 'standard' nslookup output + * $ nslookup yahoo.com + * Server: 128.193.0.10 + * Address: 128.193.0.10#53 + * + * Non-authoritative answer: + * Name: yahoo.com + * Address: 216.109.112.135 + * Name: yahoo.com + * Address: 66.94.234.13 + * + * $ nslookup 204.152.191.37 + * Server: 128.193.4.20 + * Address: 128.193.4.20#53 + * + * Non-authoritative answer: + * 37.191.152.204.in-addr.arpa canonical name = 37.32-27.191.152.204.in-addr.arpa. + * 37.32-27.191.152.204.in-addr.arpa name = zeus-pub2.kernel.org. + * + * Authoritative answers can be found from: + * 32-27.191.152.204.in-addr.arpa nameserver = ns1.kernel.org. + * 32-27.191.152.204.in-addr.arpa nameserver = ns2.kernel.org. + * 32-27.191.152.204.in-addr.arpa nameserver = ns3.kernel.org. + * ns1.kernel.org internet address = 140.211.167.34 + * ns2.kernel.org internet address = 204.152.191.4 + * ns3.kernel.org internet address = 204.152.191.36 + */ + +static int sockaddr_to_dotted(struct sockaddr *saddr, char *buf, int buflen) +{ + if (buflen <= 0) return -1; + buf[0] = '\0'; + if (saddr->sa_family == AF_INET) { + inet_ntop(AF_INET, &((struct sockaddr_in*)saddr)->sin_addr, buf, buflen); + return 0; + } + if (saddr->sa_family == AF_INET6) { + inet_ntop(AF_INET6, &((struct sockaddr_in6*)saddr)->sin6_addr, buf, buflen); + return 0; + } + return -1; +} + +static int print_host(const char *hostname, const char *header) +{ + char str[128]; /* IPv6 address will fit, hostnames hopefully too */ + struct addrinfo *result = NULL; + int rc; + struct addrinfo hint; + + memset(&hint, 0 , sizeof(hint)); + /* hint.ai_family = AF_UNSPEC; - zero anyway */ + /* Needed. Or else we will get each address thrice (or more) + * for each possible socket type (tcp,udp,raw...): */ + hint.ai_socktype = SOCK_STREAM; + // hint.ai_flags = AI_CANONNAME; + rc = getaddrinfo(hostname, NULL /*service*/, &hint, &result); + if (!rc) { + struct addrinfo *cur = result; + // printf("%s\n", cur->ai_canonname); ? + while (cur) { + sockaddr_to_dotted(cur->ai_addr, str, sizeof(str)); + printf("%s %s\nAddress: %s", header, hostname, str); + str[0] = ' '; + if (getnameinfo(cur->ai_addr, cur->ai_addrlen, str+1, sizeof(str)-1, NULL, 0, NI_NAMEREQD)) + str[0] = '\0'; + puts(str); + cur = cur->ai_next; + } + } else { + bb_error_msg("getaddrinfo('%s') failed: %s", hostname, gai_strerror(rc)); + } + freeaddrinfo(result); + return (rc != 0); +} + + +/* alter the global _res nameserver structure to use + an explicit dns server instead of what is in /etc/resolv.h */ +static void set_default_dns(char *server) +{ + struct in_addr server_in_addr; + + if (inet_pton(AF_INET, server, &server_in_addr) > 0) { + _res.nscount = 1; + _res.nsaddr_list[0].sin_addr = server_in_addr; + } +} + + +/* lookup the default nameserver and display it */ +static void server_print(void) +{ + char str[INET6_ADDRSTRLEN]; + + sockaddr_to_dotted((struct sockaddr*)&_res.nsaddr_list[0], str, sizeof(str)); + print_host(str, "Server:"); + puts(""); +} + + +int nslookup_main(int argc, char **argv) +{ + /* + * initialize DNS structure _res used in printing the default + * name server and in the explicit name server option feature. + */ + + res_init(); + + /* + * We allow 1 or 2 arguments. + * The first is the name to be looked up and the second is an + * optional DNS server with which to do the lookup. + * More than 3 arguments is an error to follow the pattern of the + * standard nslookup + */ + + if (argc < 2 || *argv[1] == '-' || argc > 3) + bb_show_usage(); + else if(argc == 3) + set_default_dns(argv[2]); + + server_print(); + return print_host(argv[1], "Name: "); +} + +/* $Id: nslookup.c,v 1.33 2004/10/13 07:25:01 andersen Exp $ */ diff --git a/networking/ping.c b/networking/ping.c new file mode 100644 index 000000000..658c01518 --- /dev/null +++ b/networking/ping.c @@ -0,0 +1,440 @@ +/* vi: set sw=4 ts=4: */ +/* + * $Id: ping.c,v 1.56 2004/03/15 08:28:48 andersen Exp $ + * Mini ping implementation for busybox + * + * Copyright (C) 1999 by Randolph Chung + * + * Adapted from the ping in netkit-base 0.10: + * Copyright (c) 1989 The Regents of the University of California. + * Derived from software contributed to Berkeley by Mike Muuss. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "busybox.h" + +enum { + DEFDATALEN = 56, + MAXIPLEN = 60, + MAXICMPLEN = 76, + MAXPACKET = 65468, + MAX_DUP_CHK = (8 * 128), + MAXWAIT = 10, + PINGINTERVAL = 1 /* second */ +}; + +static void ping(const char *host); + +/* common routines */ + +static int in_cksum(unsigned short *buf, int sz) +{ + int nleft = sz; + int sum = 0; + unsigned short *w = buf; + unsigned short ans = 0; + + while (nleft > 1) { + sum += *w++; + nleft -= 2; + } + + if (nleft == 1) { + *(unsigned char *) (&ans) = *(unsigned char *) w; + sum += ans; + } + + sum = (sum >> 16) + (sum & 0xFFFF); + sum += (sum >> 16); + ans = ~sum; + return ans; +} + +#ifndef CONFIG_FEATURE_FANCY_PING + +/* simple version */ + +static char *hostname; + +static void noresp(int ign) +{ + printf("No response from %s\n", hostname); + exit(EXIT_FAILURE); +} + +static void ping(const char *host) +{ + struct hostent *h; + struct sockaddr_in pingaddr; + struct icmp *pkt; + int pingsock, c; + char packet[DEFDATALEN + MAXIPLEN + MAXICMPLEN]; + + pingsock = create_icmp_socket(); + + memset(&pingaddr, 0, sizeof(struct sockaddr_in)); + + pingaddr.sin_family = AF_INET; + h = xgethostbyname(host); + memcpy(&pingaddr.sin_addr, h->h_addr, sizeof(pingaddr.sin_addr)); + hostname = h->h_name; + + pkt = (struct icmp *) packet; + memset(pkt, 0, sizeof(packet)); + pkt->icmp_type = ICMP_ECHO; + pkt->icmp_cksum = in_cksum((unsigned short *) pkt, sizeof(packet)); + + c = sendto(pingsock, packet, DEFDATALEN + ICMP_MINLEN, 0, + (struct sockaddr *) &pingaddr, sizeof(struct sockaddr_in)); + + if (c < 0) { + if (ENABLE_FEATURE_CLEAN_UP) close(pingsock); + bb_perror_msg_and_die("sendto"); + } + + signal(SIGALRM, noresp); + alarm(5); /* give the host 5000ms to respond */ + /* listen for replies */ + while (1) { + struct sockaddr_in from; + socklen_t fromlen = sizeof(from); + + if ((c = recvfrom(pingsock, packet, sizeof(packet), 0, + (struct sockaddr *) &from, &fromlen)) < 0) { + if (errno == EINTR) + continue; + bb_perror_msg("recvfrom"); + continue; + } + if (c >= 76) { /* ip + icmp */ + struct iphdr *iphdr = (struct iphdr *) packet; + + pkt = (struct icmp *) (packet + (iphdr->ihl << 2)); /* skip ip hdr */ + if (pkt->icmp_type == ICMP_ECHOREPLY) + break; + } + } + if (ENABLE_FEATURE_CLEAN_UP) close(pingsock); + printf("%s is alive!\n", hostname); + return; +} + +int ping_main(int argc, char **argv) +{ + argc--; + argv++; + if (argc < 1) + bb_show_usage(); + ping(*argv); + return EXIT_SUCCESS; +} + +#else /* ! CONFIG_FEATURE_FANCY_PING */ + +/* full(er) version */ + +#define OPT_STRING "qc:s:I:" +enum { + OPT_QUIET = 1 << 0, +}; + +static struct sockaddr_in pingaddr; +static struct sockaddr_in sourceaddr; +static int pingsock = -1; +static unsigned datalen; /* intentionally uninitialized to work around gcc bug */ + +static unsigned long ntransmitted, nreceived, nrepeats, pingcount; +static int myid; +static unsigned long tmin = ULONG_MAX, tmax, tsum; +static char rcvd_tbl[MAX_DUP_CHK / 8]; + +static struct hostent *hostent; + +static void sendping(int); +static void pingstats(int); +static void unpack(char *, int, struct sockaddr_in *); + +#define A(bit) rcvd_tbl[(bit)>>3] /* identify byte in array */ +#define B(bit) (1 << ((bit) & 0x07)) /* identify bit in byte */ +#define SET(bit) (A(bit) |= B(bit)) +#define CLR(bit) (A(bit) &= (~B(bit))) +#define TST(bit) (A(bit) & B(bit)) + +/**************************************************************************/ + +static void pingstats(int junk) +{ + int status; + + signal(SIGINT, SIG_IGN); + + printf("\n--- %s ping statistics ---\n", hostent->h_name); + printf("%lu packets transmitted, ", ntransmitted); + printf("%lu packets received, ", nreceived); + if (nrepeats) + printf("%lu duplicates, ", nrepeats); + if (ntransmitted) + printf("%lu%% packet loss\n", + (ntransmitted - nreceived) * 100 / ntransmitted); + if (nreceived) + printf("round-trip min/avg/max = %lu.%lu/%lu.%lu/%lu.%lu ms\n", + tmin / 10, tmin % 10, + (tsum / (nreceived + nrepeats)) / 10, + (tsum / (nreceived + nrepeats)) % 10, tmax / 10, tmax % 10); + if (nreceived != 0) + status = EXIT_SUCCESS; + else + status = EXIT_FAILURE; + exit(status); +} + +static void sendping(int junk) +{ + struct icmp *pkt; + int i; + char packet[datalen + ICMP_MINLEN]; + + pkt = (struct icmp *) packet; + + pkt->icmp_type = ICMP_ECHO; + pkt->icmp_code = 0; + pkt->icmp_cksum = 0; + pkt->icmp_seq = htons(ntransmitted++); + pkt->icmp_id = myid; + CLR(ntohs(pkt->icmp_seq) % MAX_DUP_CHK); + + gettimeofday((struct timeval *) &pkt->icmp_dun, NULL); + pkt->icmp_cksum = in_cksum((unsigned short *) pkt, sizeof(packet)); + + i = sendto(pingsock, packet, sizeof(packet), 0, + (struct sockaddr *) &pingaddr, sizeof(struct sockaddr_in)); + + if (i < 0) + bb_perror_msg_and_die("sendto"); + else if ((size_t)i != sizeof(packet)) + bb_error_msg_and_die("ping wrote %d chars; %d expected", i, + (int)sizeof(packet)); + + signal(SIGALRM, sendping); + if (pingcount == 0 || ntransmitted < pingcount) { /* schedule next in 1s */ + alarm(PINGINTERVAL); + } else { /* done, wait for the last ping to come back */ + /* todo, don't necessarily need to wait so long... */ + signal(SIGALRM, pingstats); + alarm(MAXWAIT); + } +} + +static char *icmp_type_name(int id) +{ + switch (id) { + case ICMP_ECHOREPLY: return "Echo Reply"; + case ICMP_DEST_UNREACH: return "Destination Unreachable"; + case ICMP_SOURCE_QUENCH: return "Source Quench"; + case ICMP_REDIRECT: return "Redirect (change route)"; + case ICMP_ECHO: return "Echo Request"; + case ICMP_TIME_EXCEEDED: return "Time Exceeded"; + case ICMP_PARAMETERPROB: return "Parameter Problem"; + case ICMP_TIMESTAMP: return "Timestamp Request"; + case ICMP_TIMESTAMPREPLY: return "Timestamp Reply"; + case ICMP_INFO_REQUEST: return "Information Request"; + case ICMP_INFO_REPLY: return "Information Reply"; + case ICMP_ADDRESS: return "Address Mask Request"; + case ICMP_ADDRESSREPLY: return "Address Mask Reply"; + default: return "unknown ICMP type"; + } +} + +static void unpack(char *buf, int sz, struct sockaddr_in *from) +{ + struct icmp *icmppkt; + struct iphdr *iphdr; + struct timeval tv, *tp; + int hlen, dupflag; + unsigned long triptime; + + gettimeofday(&tv, NULL); + + /* discard if too short */ + if (sz < (datalen + ICMP_MINLEN)) + return; + + /* check IP header */ + iphdr = (struct iphdr *) buf; + hlen = iphdr->ihl << 2; + sz -= hlen; + icmppkt = (struct icmp *) (buf + hlen); + if (icmppkt->icmp_id != myid) + return; /* not our ping */ + + if (icmppkt->icmp_type == ICMP_ECHOREPLY) { + u_int16_t recv_seq = ntohs(icmppkt->icmp_seq); + ++nreceived; + tp = (struct timeval *) icmppkt->icmp_data; + + if ((tv.tv_usec -= tp->tv_usec) < 0) { + --tv.tv_sec; + tv.tv_usec += 1000000; + } + tv.tv_sec -= tp->tv_sec; + + triptime = tv.tv_sec * 10000 + (tv.tv_usec / 100); + tsum += triptime; + if (triptime < tmin) + tmin = triptime; + if (triptime > tmax) + tmax = triptime; + + if (TST(recv_seq % MAX_DUP_CHK)) { + ++nrepeats; + --nreceived; + dupflag = 1; + } else { + SET(recv_seq % MAX_DUP_CHK); + dupflag = 0; + } + + if (option_mask32 & OPT_QUIET) + return; + + printf("%d bytes from %s: icmp_seq=%u", sz, + inet_ntoa(*(struct in_addr *) &from->sin_addr.s_addr), + recv_seq); + printf(" ttl=%d", iphdr->ttl); + printf(" time=%lu.%lu ms", triptime / 10, triptime % 10); + if (dupflag) + printf(" (DUP!)"); + puts(""); + } else + if (icmppkt->icmp_type != ICMP_ECHO) + bb_error_msg("warning: got ICMP %d (%s)", + icmppkt->icmp_type, icmp_type_name(icmppkt->icmp_type)); + fflush(stdout); +} + +static void ping(const char *host) +{ + char packet[datalen + MAXIPLEN + MAXICMPLEN]; + int sockopt; + + pingsock = create_icmp_socket(); + + if (sourceaddr.sin_addr.s_addr) { + xbind(pingsock, (struct sockaddr*)&sourceaddr, sizeof(sourceaddr)); + } + + memset(&pingaddr, 0, sizeof(struct sockaddr_in)); + + pingaddr.sin_family = AF_INET; + hostent = xgethostbyname(host); + if (hostent->h_addrtype != AF_INET) + bb_error_msg_and_die("unknown address type; only AF_INET is currently supported"); + + memcpy(&pingaddr.sin_addr, hostent->h_addr, sizeof(pingaddr.sin_addr)); + + /* enable broadcast pings */ + setsockopt_broadcast(pingsock); + + /* set recv buf for broadcast pings */ + sockopt = 48 * 1024; + setsockopt(pingsock, SOL_SOCKET, SO_RCVBUF, (char *) &sockopt, + sizeof(sockopt)); + + printf("PING %s (%s)", + hostent->h_name, + inet_ntoa(*(struct in_addr *) &pingaddr.sin_addr.s_addr)); + if (sourceaddr.sin_addr.s_addr) { + printf(" from %s", + inet_ntoa(*(struct in_addr *) &sourceaddr.sin_addr.s_addr)); + } + printf(": %d data bytes\n", datalen); + + signal(SIGINT, pingstats); + + /* start the ping's going ... */ + sendping(0); + + /* listen for replies */ + while (1) { + struct sockaddr_in from; + socklen_t fromlen = (socklen_t) sizeof(from); + int c; + + if ((c = recvfrom(pingsock, packet, sizeof(packet), 0, + (struct sockaddr *) &from, &fromlen)) < 0) { + if (errno == EINTR) + continue; + bb_perror_msg("recvfrom"); + continue; + } + unpack(packet, c, &from); + if (pingcount > 0 && nreceived >= pingcount) + break; + } + pingstats(0); +} + +/* TODO: consolidate ether-wake.c, dnsd.c, ifupdown.c, nslookup.c + * versions of below thing. BTW we have far too many "%u.%u.%u.%u" too... +*/ +static int parse_nipquad(const char *str, struct sockaddr_in* addr) +{ + char dummy; + unsigned i1, i2, i3, i4; + if (sscanf(str, "%u.%u.%u.%u%c", + &i1, &i2, &i3, &i4, &dummy) == 4 + && ( (i1|i2|i3|i4) <= 0xff ) + ) { + uint8_t* ptr = (uint8_t*)&addr->sin_addr; + ptr[0] = i1; + ptr[1] = i2; + ptr[2] = i3; + ptr[3] = i4; + return 0; + } + return 1; /* error */ +} + +int ping_main(int argc, char **argv) +{ + char *opt_c, *opt_s, *opt_I; + + datalen = DEFDATALEN; /* initialized here rather than in global scope to work around gcc bug */ + + /* exactly one argument needed */ + opt_complementary = "=1"; + getopt32(argc, argv, OPT_STRING, &opt_c, &opt_s, &opt_I); + if (option_mask32 & 2) pingcount = xatoul(opt_c); // -c + if (option_mask32 & 4) datalen = xatou16(opt_s); // -s + if (option_mask32 & 8) { // -I +/* TODO: ping6 accepts iface too: + if_index = if_nametoindex(*argv); + if (!if_index) ... +make it true for ping. */ + if (parse_nipquad(opt_I, &sourceaddr)) + bb_show_usage(); + } + + myid = (int16_t) getpid(); + ping(argv[optind]); + return EXIT_SUCCESS; +} +#endif /* ! CONFIG_FEATURE_FANCY_PING */ diff --git a/networking/ping6.c b/networking/ping6.c new file mode 100644 index 000000000..9f0509e66 --- /dev/null +++ b/networking/ping6.c @@ -0,0 +1,480 @@ +/* vi: set sw=4 ts=4: */ +/* + * $Id: ping6.c,v 1.6 2004/03/15 08:28:48 andersen Exp $ + * Mini ping implementation for busybox + * + * Copyright (C) 1999 by Randolph Chung + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * This version of ping is adapted from the ping in netkit-base 0.10, + * which is: + * + * Copyright (c) 1989 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Muuss. + * + * Original copyright notice is retained at the end of this file. + * + * This version is an adaptation of ping.c from busybox. + * The code was modified by Bart Visscher + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* offsetof */ +#include "busybox.h" + +enum { + DEFDATALEN = 56, + MAXIPLEN = 60, + MAXICMPLEN = 76, + MAXPACKET = 65468, + MAX_DUP_CHK = (8 * 128), + MAXWAIT = 10, + PINGINTERVAL = 1 /* second */ +}; + +static void ping(const char *host); + +#ifndef CONFIG_FEATURE_FANCY_PING6 + +/* simple version */ + +static struct hostent *h; + +static void noresp(int ign) +{ + printf("No response from %s\n", h->h_name); + exit(EXIT_FAILURE); +} + +static void ping(const char *host) +{ + struct sockaddr_in6 pingaddr; + struct icmp6_hdr *pkt; + int pingsock, c; + int sockopt; + char packet[DEFDATALEN + MAXIPLEN + MAXICMPLEN]; + + pingsock = create_icmp6_socket(); + + memset(&pingaddr, 0, sizeof(struct sockaddr_in)); + + pingaddr.sin6_family = AF_INET6; + h = xgethostbyname2(host, AF_INET6); + memcpy(&pingaddr.sin6_addr, h->h_addr, sizeof(pingaddr.sin6_addr)); + + pkt = (struct icmp6_hdr *) packet; + memset(pkt, 0, sizeof(packet)); + pkt->icmp6_type = ICMP6_ECHO_REQUEST; + + sockopt = offsetof(struct icmp6_hdr, icmp6_cksum); + setsockopt(pingsock, SOL_RAW, IPV6_CHECKSUM, (char *) &sockopt, + sizeof(sockopt)); + + c = sendto(pingsock, packet, DEFDATALEN + sizeof (struct icmp6_hdr), 0, + (struct sockaddr *) &pingaddr, sizeof(struct sockaddr_in6)); + + if (c < 0 || c != sizeof(packet)) { + if (ENABLE_FEATURE_CLEAN_UP) close(pingsock); + bb_perror_msg_and_die("sendto"); + } + + signal(SIGALRM, noresp); + alarm(5); /* give the host 5000ms to respond */ + /* listen for replies */ + while (1) { + struct sockaddr_in6 from; + size_t fromlen = sizeof(from); + + if ((c = recvfrom(pingsock, packet, sizeof(packet), 0, + (struct sockaddr *) &from, &fromlen)) < 0) { + if (errno == EINTR) + continue; + bb_perror_msg("recvfrom"); + continue; + } + if (c >= 8) { /* icmp6_hdr */ + pkt = (struct icmp6_hdr *) packet; + if (pkt->icmp6_type == ICMP6_ECHO_REPLY) + break; + } + } + if (ENABLE_FEATURE_CLEAN_UP) close(pingsock); + printf("%s is alive!\n", h->h_name); + return; +} + +int ping6_main(int argc, char **argv) +{ + argc--; + argv++; + if (argc < 1) + bb_show_usage(); + ping(*argv); + return EXIT_SUCCESS; +} + +#else /* ! CONFIG_FEATURE_FANCY_PING6 */ + +/* full(er) version */ + +#define OPT_STRING "qvc:s:I:" +enum { + OPT_QUIET = 1 << 0, + OPT_VERBOSE = 1 << 1, +}; + +static struct sockaddr_in6 pingaddr; +static int pingsock = -1; +static unsigned datalen; /* intentionally uninitialized to work around gcc bug */ +static int if_index; + +static unsigned long ntransmitted, nreceived, nrepeats, pingcount; +static int myid; +static unsigned long tmin = ULONG_MAX, tmax, tsum; +static char rcvd_tbl[MAX_DUP_CHK / 8]; + +static struct hostent *hostent; + +static void sendping(int); +static void pingstats(int); +static void unpack(char *, int, struct sockaddr_in6 *, int); + +#define A(bit) rcvd_tbl[(bit)>>3] /* identify byte in array */ +#define B(bit) (1 << ((bit) & 0x07)) /* identify bit in byte */ +#define SET(bit) (A(bit) |= B(bit)) +#define CLR(bit) (A(bit) &= (~B(bit))) +#define TST(bit) (A(bit) & B(bit)) + +/**************************************************************************/ + +static void pingstats(int junk) +{ + int status; + + signal(SIGINT, SIG_IGN); + + printf("\n--- %s ping statistics ---\n", hostent->h_name); + printf("%lu packets transmitted, ", ntransmitted); + printf("%lu packets received, ", nreceived); + if (nrepeats) + printf("%lu duplicates, ", nrepeats); + if (ntransmitted) + printf("%lu%% packet loss\n", + (ntransmitted - nreceived) * 100 / ntransmitted); + if (nreceived) + printf("round-trip min/avg/max = %lu.%lu/%lu.%lu/%lu.%lu ms\n", + tmin / 10, tmin % 10, + (tsum / (nreceived + nrepeats)) / 10, + (tsum / (nreceived + nrepeats)) % 10, tmax / 10, tmax % 10); + if (nreceived != 0) + status = EXIT_SUCCESS; + else + status = EXIT_FAILURE; + exit(status); +} + +static void sendping(int junk) +{ + struct icmp6_hdr *pkt; + int i; + char packet[datalen + sizeof (struct icmp6_hdr)]; + + pkt = (struct icmp6_hdr *) packet; + + pkt->icmp6_type = ICMP6_ECHO_REQUEST; + pkt->icmp6_code = 0; + pkt->icmp6_cksum = 0; + pkt->icmp6_seq = htons(ntransmitted++); + pkt->icmp6_id = myid; + CLR(pkt->icmp6_seq % MAX_DUP_CHK); + + gettimeofday((struct timeval *) &pkt->icmp6_data8[4], NULL); + + i = sendto(pingsock, packet, sizeof(packet), 0, + (struct sockaddr *) &pingaddr, sizeof(struct sockaddr_in6)); + + if (i < 0) + bb_perror_msg_and_die("sendto"); + else if ((size_t)i != sizeof(packet)) + bb_error_msg_and_die("ping wrote %d chars; %d expected", i, + (int)sizeof(packet)); + + signal(SIGALRM, sendping); + if (pingcount == 0 || ntransmitted < pingcount) { /* schedule next in 1s */ + alarm(PINGINTERVAL); + } else { /* done, wait for the last ping to come back */ + /* todo, don't necessarily need to wait so long... */ + signal(SIGALRM, pingstats); + alarm(MAXWAIT); + } +} + +/* RFC3542 changed some definitions from RFC2292 for no good reason, whee ! + * the newer 3542 uses a MLD_ prefix where as 2292 uses ICMP6_ prefix */ +#ifndef MLD_LISTENER_QUERY +# define MLD_LISTENER_QUERY ICMP6_MEMBERSHIP_QUERY +#endif +#ifndef MLD_LISTENER_REPORT +# define MLD_LISTENER_REPORT ICMP6_MEMBERSHIP_REPORT +#endif +#ifndef MLD_LISTENER_REDUCTION +# define MLD_LISTENER_REDUCTION ICMP6_MEMBERSHIP_REDUCTION +#endif +static char *icmp6_type_name(int id) +{ + switch (id) { + case ICMP6_DST_UNREACH: return "Destination Unreachable"; + case ICMP6_PACKET_TOO_BIG: return "Packet too big"; + case ICMP6_TIME_EXCEEDED: return "Time Exceeded"; + case ICMP6_PARAM_PROB: return "Parameter Problem"; + case ICMP6_ECHO_REPLY: return "Echo Reply"; + case ICMP6_ECHO_REQUEST: return "Echo Request"; + case MLD_LISTENER_QUERY: return "Listener Query"; + case MLD_LISTENER_REPORT: return "Listener Report"; + case MLD_LISTENER_REDUCTION: return "Listener Reduction"; + default: return "unknown ICMP type"; + } +} + +static void unpack(char *packet, int sz, struct sockaddr_in6 *from, int hoplimit) +{ + struct icmp6_hdr *icmppkt; + struct timeval tv, *tp; + int dupflag; + unsigned long triptime; + char buf[INET6_ADDRSTRLEN]; + + gettimeofday(&tv, NULL); + + /* discard if too short */ + if (sz < (datalen + sizeof(struct icmp6_hdr))) + return; + + icmppkt = (struct icmp6_hdr *) packet; + if (icmppkt->icmp6_id != myid) + return; /* not our ping */ + + if (icmppkt->icmp6_type == ICMP6_ECHO_REPLY) { + ++nreceived; + tp = (struct timeval *) &icmppkt->icmp6_data8[4]; + + if ((tv.tv_usec -= tp->tv_usec) < 0) { + --tv.tv_sec; + tv.tv_usec += 1000000; + } + tv.tv_sec -= tp->tv_sec; + + triptime = tv.tv_sec * 10000 + (tv.tv_usec / 100); + tsum += triptime; + if (triptime < tmin) + tmin = triptime; + if (triptime > tmax) + tmax = triptime; + + if (TST(icmppkt->icmp6_seq % MAX_DUP_CHK)) { + ++nrepeats; + --nreceived; + dupflag = 1; + } else { + SET(icmppkt->icmp6_seq % MAX_DUP_CHK); + dupflag = 0; + } + + if (option_mask32 & OPT_QUIET) + return; + + printf("%d bytes from %s: icmp6_seq=%u", sz, + inet_ntop(AF_INET6, &pingaddr.sin6_addr, + buf, sizeof(buf)), + icmppkt->icmp6_seq); + printf(" ttl=%d time=%lu.%lu ms", hoplimit, + triptime / 10, triptime % 10); + if (dupflag) + printf(" (DUP!)"); + puts(""); + } else + if (icmppkt->icmp6_type != ICMP6_ECHO_REQUEST) + bb_error_msg("warning: got ICMP %d (%s)", + icmppkt->icmp6_type, icmp6_type_name(icmppkt->icmp6_type)); +} + +static void ping(const char *host) +{ + char packet[datalen + MAXIPLEN + MAXICMPLEN]; + char buf[INET6_ADDRSTRLEN]; + int sockopt; + struct msghdr msg; + struct sockaddr_in6 from; + struct iovec iov; + char control_buf[CMSG_SPACE(36)]; + + pingsock = create_icmp6_socket(); + + memset(&pingaddr, 0, sizeof(struct sockaddr_in)); + + pingaddr.sin6_family = AF_INET6; + hostent = xgethostbyname2(host, AF_INET6); + if (hostent->h_addrtype != AF_INET6) + bb_error_msg_and_die("unknown address type; only AF_INET6 is currently supported"); + + memcpy(&pingaddr.sin6_addr, hostent->h_addr, sizeof(pingaddr.sin6_addr)); + +#ifdef ICMP6_FILTER + { + struct icmp6_filter filt; + if (!(option_mask32 & OPT_VERBOSE)) { + ICMP6_FILTER_SETBLOCKALL(&filt); + ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &filt); + } else { + ICMP6_FILTER_SETPASSALL(&filt); + } + if (setsockopt(pingsock, IPPROTO_ICMPV6, ICMP6_FILTER, &filt, + sizeof(filt)) < 0) + bb_error_msg_and_die("setsockopt(ICMP6_FILTER)"); + } +#endif /*ICMP6_FILTER*/ + + /* enable broadcast pings */ + setsockopt_broadcast(pingsock); + + /* set recv buf for broadcast pings */ + sockopt = 48 * 1024; + setsockopt(pingsock, SOL_SOCKET, SO_RCVBUF, (char *) &sockopt, + sizeof(sockopt)); + + sockopt = offsetof(struct icmp6_hdr, icmp6_cksum); + setsockopt(pingsock, SOL_RAW, IPV6_CHECKSUM, (char *) &sockopt, + sizeof(sockopt)); + + sockopt = 1; + setsockopt(pingsock, SOL_IPV6, IPV6_HOPLIMIT, (char *) &sockopt, + sizeof(sockopt)); + + if (if_index) + pingaddr.sin6_scope_id = if_index; + + printf("PING %s (%s): %d data bytes\n", + hostent->h_name, + inet_ntop(AF_INET6, &pingaddr.sin6_addr, + buf, sizeof(buf)), + datalen); + + signal(SIGINT, pingstats); + + /* start the ping's going ... */ + sendping(0); + + /* listen for replies */ + msg.msg_name = &from; + msg.msg_namelen = sizeof(from); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control_buf; + iov.iov_base = packet; + iov.iov_len = sizeof(packet); + while (1) { + int c; + struct cmsghdr *cmsgptr = NULL; + int hoplimit = -1; + msg.msg_controllen = sizeof(control_buf); + + if ((c = recvmsg(pingsock, &msg, 0)) < 0) { + if (errno == EINTR) + continue; + bb_perror_msg("recvfrom"); + continue; + } + for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL; + cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) { + if (cmsgptr->cmsg_level == SOL_IPV6 && + cmsgptr->cmsg_type == IPV6_HOPLIMIT ) { + hoplimit = *(int*)CMSG_DATA(cmsgptr); + } + } + unpack(packet, c, &from, hoplimit); + if (pingcount > 0 && nreceived >= pingcount) + break; + } + pingstats(0); +} + +int ping6_main(int argc, char **argv) +{ + char *opt_c, *opt_s, *opt_I; + + datalen = DEFDATALEN; /* initialized here rather than in global scope to work around gcc bug */ + + /* exactly one argument needed, -v and -q don't mix */ + opt_complementary = "=1:q--v:v--q"; + getopt32(argc, argv, OPT_STRING, &opt_c, &opt_s, &opt_I); + if (option_mask32 & 4) pingcount = xatoul(opt_c); // -c + if (option_mask32 & 8) datalen = xatou16(opt_s); // -s + if (option_mask32 & 0x10) { // -I + if_index = if_nametoindex(opt_I); + if (!if_index) + bb_error_msg_and_die( + "%s: invalid interface name", opt_I); + } + + myid = (int16_t)getpid(); + ping(argv[optind]); + return EXIT_SUCCESS; +} +#endif /* ! CONFIG_FEATURE_FANCY_PING6 */ + +/* + * Copyright (c) 1989 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Mike Muuss. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. + * + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ diff --git a/networking/route.c b/networking/route.c new file mode 100644 index 000000000..a8926f44e --- /dev/null +++ b/networking/route.c @@ -0,0 +1,703 @@ +/* vi: set sw=4 ts=4: */ +/* route + * + * Similar to the standard Unix route, but with only the necessary + * parts for AF_INET and AF_INET6 + * + * Bjorn Wesen, Axis Communications AB + * + * Author of the original route: + * Fred N. van Kempen, + * (derived from FvK's 'route.c 1.70 01/04/94') + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * $Id: route.c,v 1.26 2004/03/19 23:27:08 mjn3 Exp $ + * + * displayroute() code added by Vladimir N. Oleynik + * adjustments by Larry Doolittle + * + * IPV6 support added by Bart Visscher + */ + +/* 2004/03/09 Manuel Novoa III + * + * Rewritten to fix several bugs, add additional error checking, and + * remove ridiculous amounts of bloat. + */ + +#include "busybox.h" +#include "inet_common.h" +#include +#include +#include + + +#ifndef RTF_UP +/* Keep this in sync with /usr/src/linux/include/linux/route.h */ +#define RTF_UP 0x0001 /* route usable */ +#define RTF_GATEWAY 0x0002 /* destination is a gateway */ +#define RTF_HOST 0x0004 /* host entry (net otherwise) */ +#define RTF_REINSTATE 0x0008 /* reinstate route after tmout */ +#define RTF_DYNAMIC 0x0010 /* created dyn. (by redirect) */ +#define RTF_MODIFIED 0x0020 /* modified dyn. (by redirect) */ +#define RTF_MTU 0x0040 /* specific MTU for this route */ +#ifndef RTF_MSS +#define RTF_MSS RTF_MTU /* Compatibility :-( */ +#endif +#define RTF_WINDOW 0x0080 /* per route window clamping */ +#define RTF_IRTT 0x0100 /* Initial round trip time */ +#define RTF_REJECT 0x0200 /* Reject route */ +#endif + +#if defined (SIOCADDRTOLD) || defined (RTF_IRTT) /* route */ +#define HAVE_NEW_ADDRT 1 +#endif + +#if HAVE_NEW_ADDRT +#define mask_in_addr(x) (((struct sockaddr_in *)&((x).rt_genmask))->sin_addr.s_addr) +#define full_mask(x) (x) +#else +#define mask_in_addr(x) ((x).rt_genmask) +#define full_mask(x) (((struct sockaddr_in *)&(x))->sin_addr.s_addr) +#endif + +/* The RTACTION entries must agree with tbl_verb[] below! */ +#define RTACTION_ADD 1 +#define RTACTION_DEL 2 + +/* For the various tbl_*[] arrays, the 1st byte is the offset to + * the next entry and the 2nd byte is return value. */ + +#define NET_FLAG 1 +#define HOST_FLAG 2 + +/* We remap '-' to '#' to avoid problems with getopt. */ +static const char tbl_hash_net_host[] = + "\007\001#net\0" +/* "\010\002#host\0" */ + "\007\002#host" /* Since last, we can save a byte. */ +; + +#define KW_TAKES_ARG 020 +#define KW_SETS_FLAG 040 + +#define KW_IPVx_METRIC 020 +#define KW_IPVx_NETMASK 021 +#define KW_IPVx_GATEWAY 022 +#define KW_IPVx_MSS 023 +#define KW_IPVx_WINDOW 024 +#define KW_IPVx_IRTT 025 +#define KW_IPVx_DEVICE 026 + +#define KW_IPVx_FLAG_ONLY 040 +#define KW_IPVx_REJECT 040 +#define KW_IPVx_MOD 041 +#define KW_IPVx_DYN 042 +#define KW_IPVx_REINSTATE 043 + +static const char tbl_ipvx[] = + /* 020 is the "takes an arg" bit */ +#if HAVE_NEW_ADDRT + "\011\020metric\0" +#endif + "\012\021netmask\0" + "\005\022gw\0" + "\012\022gateway\0" + "\006\023mss\0" + "\011\024window\0" +#ifdef RTF_IRTT + "\007\025irtt\0" +#endif + "\006\026dev\0" + "\011\026device\0" + /* 040 is the "sets a flag" bit - MUST match flags_ipvx[] values below. */ +#ifdef RTF_REJECT + "\011\040reject\0" +#endif + "\006\041mod\0" + "\006\042dyn\0" +/* "\014\043reinstate\0" */ + "\013\043reinstate" /* Since last, we can save a byte. */ +; + +static const int flags_ipvx[] = { /* MUST match tbl_ipvx[] values above. */ +#ifdef RTF_REJECT + RTF_REJECT, +#endif + RTF_MODIFIED, + RTF_DYNAMIC, + RTF_REINSTATE +}; + +static int kw_lookup(const char *kwtbl, char ***pargs) +{ + if (**pargs) { + do { + if (strcmp(kwtbl+2, **pargs) == 0) { /* Found a match. */ + *pargs += 1; + if (kwtbl[1] & KW_TAKES_ARG) { + if (!**pargs) { /* No more args! */ + bb_show_usage(); + } + *pargs += 1; /* Calling routine will use args[-1]. */ + } + return kwtbl[1]; + } + kwtbl += *kwtbl; + } while (*kwtbl); + } + return 0; +} + +/* Add or delete a route, depending on action. */ + +static void INET_setroute(int action, char **args) +{ + struct rtentry rt; + const char *netmask = NULL; + int skfd, isnet, xflag; + + /* Grab the -net or -host options. Remember they were transformed. */ + xflag = kw_lookup(tbl_hash_net_host, &args); + + /* If we did grab -net or -host, make sure we still have an arg left. */ + if (*args == NULL) { + bb_show_usage(); + } + + /* Clean out the RTREQ structure. */ + memset((char *) &rt, 0, sizeof(struct rtentry)); + + { + const char *target = *args++; + char *prefix; + + /* recognize x.x.x.x/mask format. */ + prefix = strchr(target, '/'); + if(prefix) { + int prefix_len; + + prefix_len = xatoul_range(prefix+1, 0, 32); + mask_in_addr(rt) = htonl( ~ (0xffffffffUL >> prefix_len)); + *prefix = '\0'; +#if HAVE_NEW_ADDRT + rt.rt_genmask.sa_family = AF_INET; +#endif + } else { + /* Default netmask. */ + netmask = bb_str_default; + } + /* Prefer hostname lookup is -host flag (xflag==1) was given. */ + isnet = INET_resolve(target, (struct sockaddr_in *) &rt.rt_dst, + (xflag & HOST_FLAG)); + if (isnet < 0) { + bb_error_msg_and_die("resolving %s", target); + } + if(prefix) { + /* do not destroy prefix for process args */ + *prefix = '/'; + } + } + + if (xflag) { /* Reinit isnet if -net or -host was specified. */ + isnet = (xflag & NET_FLAG); + } + + /* Fill in the other fields. */ + rt.rt_flags = ((isnet) ? RTF_UP : (RTF_UP | RTF_HOST)); + + while (*args) { + int k = kw_lookup(tbl_ipvx, &args); + const char *args_m1 = args[-1]; + + if (k & KW_IPVx_FLAG_ONLY) { + rt.rt_flags |= flags_ipvx[k & 3]; + continue; + } + +#if HAVE_NEW_ADDRT + if (k == KW_IPVx_METRIC) { + rt.rt_metric = xatoul(args_m1) + 1; + continue; + } +#endif + + if (k == KW_IPVx_NETMASK) { + struct sockaddr mask; + + if (mask_in_addr(rt)) { + bb_show_usage(); + } + + netmask = args_m1; + isnet = INET_resolve(netmask, (struct sockaddr_in *) &mask, 0); + if (isnet < 0) { + bb_error_msg_and_die("resolving %s", netmask); + } + rt.rt_genmask = full_mask(mask); + continue; + } + + if (k == KW_IPVx_GATEWAY) { + if (rt.rt_flags & RTF_GATEWAY) { + bb_show_usage(); + } + + isnet = INET_resolve(args_m1, + (struct sockaddr_in *) &rt.rt_gateway, 1); + rt.rt_flags |= RTF_GATEWAY; + + if (isnet) { + if (isnet < 0) { + bb_error_msg_and_die("resolving %s", args_m1); + } + bb_error_msg_and_die("gateway %s is a NETWORK", args_m1); + } + continue; + } + + if (k == KW_IPVx_MSS) { /* Check valid MSS bounds. */ + rt.rt_flags |= RTF_MSS; + rt.rt_mss = xatoul_range(args_m1, 64, 32768); + continue; + } + + if (k == KW_IPVx_WINDOW) { /* Check valid window bounds. */ + rt.rt_flags |= RTF_WINDOW; + rt.rt_window = xatoul_range(args_m1, 128, INT_MAX); + continue; + } + +#ifdef RTF_IRTT + if (k == KW_IPVx_IRTT) { + rt.rt_flags |= RTF_IRTT; + rt.rt_irtt = xatoul(args_m1); + rt.rt_irtt *= (sysconf(_SC_CLK_TCK) / 100); /* FIXME */ +#if 0 /* FIXME: do we need to check anything of this? */ + if (rt.rt_irtt < 1 || rt.rt_irtt > (120 * HZ)) { + bb_error_msg_and_die("bad irtt"); + } +#endif + continue; + } +#endif + + /* Device is special in that it can be the last arg specified + * and doesn't requre the dev/device keyword in that case. */ + if (!rt.rt_dev && ((k == KW_IPVx_DEVICE) || (!k && !*++args))) { + /* Don't use args_m1 here since args may have changed! */ + rt.rt_dev = args[-1]; + continue; + } + + /* Nothing matched. */ + bb_show_usage(); + } + +#ifdef RTF_REJECT + if ((rt.rt_flags & RTF_REJECT) && !rt.rt_dev) { + rt.rt_dev = "lo"; + } +#endif + + /* sanity checks.. */ + if (mask_in_addr(rt)) { + unsigned long mask = mask_in_addr(rt); + + mask = ~ntohl(mask); + if ((rt.rt_flags & RTF_HOST) && mask != 0xffffffff) { + bb_error_msg_and_die("netmask %.8x and host route conflict", + (unsigned int) mask); + } + if (mask & (mask + 1)) { + bb_error_msg_and_die("bogus netmask %s", netmask); + } + mask = ((struct sockaddr_in *) &rt.rt_dst)->sin_addr.s_addr; + if (mask & ~mask_in_addr(rt)) { + bb_error_msg_and_die("netmask and route address conflict"); + } + } + + /* Fill out netmask if still unset */ + if ((action == RTACTION_ADD) && (rt.rt_flags & RTF_HOST)) { + mask_in_addr(rt) = 0xffffffff; + } + + /* Create a socket to the INET kernel. */ + skfd = xsocket(AF_INET, SOCK_DGRAM, 0); + + if (ioctl(skfd, ((action==RTACTION_ADD) ? SIOCADDRT : SIOCDELRT), &rt)<0) { + bb_perror_msg_and_die("SIOC[ADD|DEL]RT"); + } + + if (ENABLE_FEATURE_CLEAN_UP) close(skfd); +} + +#ifdef CONFIG_FEATURE_IPV6 + +static void INET6_setroute(int action, char **args) +{ + struct sockaddr_in6 sa6; + struct in6_rtmsg rt; + int prefix_len, skfd; + const char *devname; + + /* We know args isn't NULL from the check in route_main. */ + const char *target = *args++; + + if (strcmp(target, bb_str_default) == 0) { + prefix_len = 0; + memset(&sa6, 0, sizeof(sa6)); + } else { + char *cp; + if ((cp = strchr(target, '/'))) { /* Yes... const to non is ok. */ + *cp = 0; + prefix_len = xatoul_range(cp+1, 0, 128); + } else { + prefix_len = 128; + } + if (INET6_resolve(target, (struct sockaddr_in6 *) &sa6) < 0) { + bb_error_msg_and_die("resolving %s", target); + } + } + + /* Clean out the RTREQ structure. */ + memset((char *) &rt, 0, sizeof(struct in6_rtmsg)); + + memcpy(&rt.rtmsg_dst, sa6.sin6_addr.s6_addr, sizeof(struct in6_addr)); + + /* Fill in the other fields. */ + rt.rtmsg_dst_len = prefix_len; + rt.rtmsg_flags = ((prefix_len == 128) ? (RTF_UP|RTF_HOST) : RTF_UP); + rt.rtmsg_metric = 1; + + devname = NULL; + + while (*args) { + int k = kw_lookup(tbl_ipvx, &args); + const char *args_m1 = args[-1]; + + if ((k == KW_IPVx_MOD) || (k == KW_IPVx_DYN)) { + rt.rtmsg_flags |= flags_ipvx[k & 3]; + continue; + } + + if (k == KW_IPVx_METRIC) { + rt.rtmsg_metric = xatoul(args_m1); + continue; + } + + if (k == KW_IPVx_GATEWAY) { + if (rt.rtmsg_flags & RTF_GATEWAY) { + bb_show_usage(); + } + + if (INET6_resolve(args_m1, (struct sockaddr_in6 *) &sa6) < 0) { + bb_error_msg_and_die("resolving %s", args_m1); + } + memcpy(&rt.rtmsg_gateway, sa6.sin6_addr.s6_addr, + sizeof(struct in6_addr)); + rt.rtmsg_flags |= RTF_GATEWAY; + continue; + } + + /* Device is special in that it can be the last arg specified + * and doesn't requre the dev/device keyword in that case. */ + if (!devname && ((k == KW_IPVx_DEVICE) || (!k && !*++args))) { + /* Don't use args_m1 here since args may have changed! */ + devname = args[-1]; + continue; + } + + /* Nothing matched. */ + bb_show_usage(); + } + + /* Create a socket to the INET6 kernel. */ + skfd = xsocket(AF_INET6, SOCK_DGRAM, 0); + + rt.rtmsg_ifindex = 0; + + if (devname) { + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, devname, sizeof(ifr.ifr_name)); + + if (ioctl(skfd, SIOGIFINDEX, &ifr) < 0) { + bb_perror_msg_and_die("SIOGIFINDEX"); + } + rt.rtmsg_ifindex = ifr.ifr_ifindex; + } + + /* Tell the kernel to accept this route. */ + if (ioctl(skfd, ((action==RTACTION_ADD) ? SIOCADDRT : SIOCDELRT), &rt)<0) { + bb_perror_msg_and_die("SIOC[ADD|DEL]RT"); + } + + if (ENABLE_FEATURE_CLEAN_UP) close(skfd); +} +#endif + +static const unsigned int flagvals[] = { /* Must agree with flagchars[]. */ + RTF_GATEWAY, + RTF_HOST, + RTF_REINSTATE, + RTF_DYNAMIC, + RTF_MODIFIED, +#ifdef CONFIG_FEATURE_IPV6 + RTF_DEFAULT, + RTF_ADDRCONF, + RTF_CACHE +#endif +}; + +#define IPV4_MASK (RTF_GATEWAY|RTF_HOST|RTF_REINSTATE|RTF_DYNAMIC|RTF_MODIFIED) +#define IPV6_MASK (RTF_GATEWAY|RTF_HOST|RTF_DEFAULT|RTF_ADDRCONF|RTF_CACHE) + +static const char flagchars[] = /* Must agree with flagvals[]. */ + "GHRDM" +#ifdef CONFIG_FEATURE_IPV6 + "DAC" +#endif +; + +static +#ifndef CONFIG_FEATURE_IPV6 +__inline +#endif +void set_flags(char *flagstr, int flags) +{ + int i; + + *flagstr++ = 'U'; + + for (i=0 ; (*flagstr = flagchars[i]) != 0 ; i++) { + if (flags & flagvals[i]) { + ++flagstr; + } + } +} + +/* also used in netstat */ +void displayroutes(int noresolve, int netstatfmt); +void displayroutes(int noresolve, int netstatfmt) +{ + char devname[64], flags[16], sdest[16], sgw[16]; + unsigned long int d, g, m; + int flgs, ref, use, metric, mtu, win, ir; + struct sockaddr_in s_addr; + struct in_addr mask; + + FILE *fp = xfopen("/proc/net/route", "r"); + + printf("Kernel IP routing table\n" + "Destination Gateway Genmask" + " Flags %s Iface\n", + netstatfmt ? " MSS Window irtt" : "Metric Ref Use"); + + if (fscanf(fp, "%*[^\n]\n") < 0) { /* Skip the first line. */ + goto ERROR; /* Empty or missing line, or read error. */ + } + while (1) { + int r; + r = fscanf(fp, "%63s%lx%lx%X%d%d%d%lx%d%d%d\n", + devname, &d, &g, &flgs, &ref, &use, &metric, &m, + &mtu, &win, &ir); + if (r != 11) { + if ((r < 0) && feof(fp)) { /* EOF with no (nonspace) chars read. */ + break; + } + ERROR: + bb_error_msg_and_die("fscanf"); + } + + if (!(flgs & RTF_UP)) { /* Skip interfaces that are down. */ + continue; + } + + set_flags(flags, (flgs & IPV4_MASK)); +#ifdef RTF_REJECT + if (flgs & RTF_REJECT) { + flags[0] = '!'; + } +#endif + + memset(&s_addr, 0, sizeof(struct sockaddr_in)); + s_addr.sin_family = AF_INET; + s_addr.sin_addr.s_addr = d; + INET_rresolve(sdest, sizeof(sdest), &s_addr, + (noresolve | 0x8000), m); /* Default instead of *. */ + + s_addr.sin_addr.s_addr = g; + INET_rresolve(sgw, sizeof(sgw), &s_addr, + (noresolve | 0x4000), m); /* Host instead of net. */ + + mask.s_addr = m; + printf("%-16s%-16s%-16s%-6s", sdest, sgw, inet_ntoa(mask), flags); + if (netstatfmt) { + printf("%5d %-5d %6d %s\n", mtu, win, ir, devname); + } else { + printf("%-6d %-2d %7d %s\n", metric, ref, use, devname); + } + } +} + +#ifdef CONFIG_FEATURE_IPV6 + +static void INET6_displayroutes(int noresolve) +{ + char addr6[128], naddr6[128]; + /* In addr6x, we store both 40-byte ':'-delimited ipv6 addresses. + * We read the non-delimited strings into the tail of the buffer + * using fscanf and then modify the buffer by shifting forward + * while inserting ':'s and the nul terminator for the first string. + * Hence the strings are at addr6x and addr6x+40. This generates + * _much_ less code than the previous (upstream) approach. */ + char addr6x[80]; + char iface[16], flags[16]; + int iflags, metric, refcnt, use, prefix_len, slen; + struct sockaddr_in6 snaddr6; + + FILE *fp = xfopen("/proc/net/ipv6_route", "r"); + + printf("Kernel IPv6 routing table\n%-44s%-40s" + "Flags Metric Ref Use Iface\n", + "Destination", "Next Hop"); + + while (1) { + int r; + r = fscanf(fp, "%32s%x%*s%x%32s%x%x%x%x%s\n", + addr6x+14, &prefix_len, &slen, addr6x+40+7, + &metric, &use, &refcnt, &iflags, iface); + if (r != 9) { + if ((r < 0) && feof(fp)) { /* EOF with no (nonspace) chars read. */ + break; + } + ERROR: + bb_error_msg_and_die("fscanf"); + } + + /* Do the addr6x shift-and-insert changes to ':'-delimit addresses. + * For now, always do this to validate the proc route format, even + * if the interface is down. */ + { + int i = 0; + char *p = addr6x+14; + + do { + if (!*p) { + if (i==40) { /* nul terminator for 1st address? */ + addr6x[39] = 0; /* Fixup... need 0 instead of ':'. */ + ++p; /* Skip and continue. */ + continue; + } + goto ERROR; + } + addr6x[i++] = *p++; + if (!((i+1)%5)) { + addr6x[i++] = ':'; + } + } while (i < 40+28+7); + } + + if (!(iflags & RTF_UP)) { /* Skip interfaces that are down. */ + continue; + } + + set_flags(flags, (iflags & IPV6_MASK)); + + r = 0; + do { + inet_pton(AF_INET6, addr6x + r, + (struct sockaddr *) &snaddr6.sin6_addr); + snaddr6.sin6_family = AF_INET6; + INET6_rresolve(naddr6, sizeof(naddr6), + (struct sockaddr_in6 *) &snaddr6, + 0x0fff /* Apparently, upstream never resolves. */ + ); + + if (!r) { /* 1st pass */ + snprintf(addr6, sizeof(addr6), "%s/%d", naddr6, prefix_len); + r += 40; + } else { /* 2nd pass */ + /* Print the info. */ + printf("%-43s %-39s %-5s %-6d %-2d %7d %-8s\n", + addr6, naddr6, flags, metric, refcnt, use, iface); + break; + } + } while (1); + } +} + +#endif + +#define ROUTE_OPT_A 0x01 +#define ROUTE_OPT_n 0x02 +#define ROUTE_OPT_e 0x04 +#define ROUTE_OPT_INET6 0x08 /* Not an actual option. See below. */ + +/* 1st byte is offset to next entry offset. 2nd byte is return value. */ +static const char tbl_verb[] = /* 2nd byte matches RTACTION_* code */ + "\006\001add\0" + "\006\002del\0" +/* "\011\002delete\0" */ + "\010\002delete" /* Since last, we can save a byte. */ +; + +int route_main(int argc, char **argv) +{ + unsigned opt; + int what; + char *family; + char **p; + + /* First, remap '-net' and '-host' to avoid getopt problems. */ + p = argv; + while (*++p) { + if ((strcmp(*p, "-net") == 0) || (strcmp(*p, "-host") == 0)) { + p[0][0] = '#'; + } + } + + opt = getopt32(argc, argv, "A:ne", &family); + + if ((opt & ROUTE_OPT_A) && strcmp(family, "inet")) { +#ifdef CONFIG_FEATURE_IPV6 + if (strcmp(family, "inet6") == 0) { + opt |= ROUTE_OPT_INET6; /* Set flag for ipv6. */ + } else +#endif + bb_show_usage(); + } + + argv += optind; + + /* No more args means display the routing table. */ + if (!*argv) { + int noresolve = (opt & ROUTE_OPT_n) ? 0x0fff : 0; +#ifdef CONFIG_FEATURE_IPV6 + if (opt & ROUTE_OPT_INET6) + INET6_displayroutes(noresolve); + else +#endif + displayroutes(noresolve, opt & ROUTE_OPT_e); + + fflush_stdout_and_exit(EXIT_SUCCESS); + } + + /* Check verb. At the moment, must be add, del, or delete. */ + what = kw_lookup(tbl_verb, &argv); + if (!what || !*argv) { /* Unknown verb or no more args. */ + bb_show_usage(); + } + +#ifdef CONFIG_FEATURE_IPV6 + if (opt & ROUTE_OPT_INET6) + INET6_setroute(what, argv); + else +#endif + INET_setroute(what, argv); + + return EXIT_SUCCESS; +} diff --git a/networking/telnet.c b/networking/telnet.c new file mode 100644 index 000000000..6085d885a --- /dev/null +++ b/networking/telnet.c @@ -0,0 +1,714 @@ +/* vi: set sw=4 ts=4: */ +/* + * telnet implementation for busybox + * + * Author: Tomi Ollila + * Copyright (C) 1994-2000 by Tomi Ollila + * + * Created: Thu Apr 7 13:29:41 1994 too + * Last modified: Fri Jun 9 14:34:24 2000 too + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * HISTORY + * Revision 3.1 1994/04/17 11:31:54 too + * initial revision + * Modified 2000/06/13 for inclusion into BusyBox by Erik Andersen + * Modified 2001/05/07 to add ability to pass TTYPE to remote host by Jim McQuillan + * + * Modified 2004/02/11 to add ability to pass the USER variable to remote host + * by Fernando Silveira + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "busybox.h" + +#ifdef DOTRACE +#include /* for inet_ntoa()... */ +#define TRACE(x, y) do { if (x) printf y; } while (0) +#else +#define TRACE(x, y) +#endif + +#define DATABUFSIZE 128 +#define IACBUFSIZE 128 + +enum { + CHM_TRY = 0, + CHM_ON = 1, + CHM_OFF = 2, + + UF_ECHO = 0x01, + UF_SGA = 0x02, + + TS_0 = 1, + TS_IAC = 2, + TS_OPT = 3, + TS_SUB1 = 4, + TS_SUB2 = 5, +}; + +#define WriteCS(fd, str) write(fd, str, sizeof str -1) + +typedef unsigned char byte; + +/* use globals to reduce size ??? */ /* test this hypothesis later */ +static struct Globalvars { + int netfd; /* console fd:s are 0 and 1 (and 2) */ + /* same buffer used both for network and console read/write */ + char buf[DATABUFSIZE]; /* allocating so static size is smaller */ + byte telstate; /* telnet negotiation state from network input */ + byte telwish; /* DO, DONT, WILL, WONT */ + byte charmode; + byte telflags; + byte gotsig; + byte do_termios; + /* buffer to handle telnet negotiations */ + char iacbuf[IACBUFSIZE]; + short iaclen; /* could even use byte */ + struct termios termios_def; + struct termios termios_raw; +} G; + +#define xUSE_GLOBALVAR_PTR /* xUSE... -> don't use :D (makes smaller code) */ + +#ifdef USE_GLOBALVAR_PTR +struct Globalvars * Gptr; +#define G (*Gptr) +#endif + +static void iacflush(void) +{ + write(G.netfd, G.iacbuf, G.iaclen); + G.iaclen = 0; +} + +/* Function prototypes */ +static void rawmode(void); +static void cookmode(void); +static void do_linemode(void); +static void will_charmode(void); +static void telopt(byte c); +static int subneg(byte c); + +/* Some globals */ +static const int one = 1; + +#ifdef CONFIG_FEATURE_TELNET_TTYPE +static char *ttype; +#endif + +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN +static const char *autologin; +#endif + +#ifdef CONFIG_FEATURE_AUTOWIDTH +static int win_width, win_height; +#endif + +static void doexit(int ev) +{ + cookmode(); + exit(ev); +} + +static void conescape(void) +{ + char b; + + if (G.gotsig) /* came from line mode... go raw */ + rawmode(); + + WriteCS(1, "\r\nConsole escape. Commands are:\r\n\n" + " l go to line mode\r\n" + " c go to character mode\r\n" + " z suspend telnet\r\n" + " e exit telnet\r\n"); + + if (read(0, &b, 1) <= 0) + doexit(1); + + switch (b) + { + case 'l': + if (!G.gotsig) + { + do_linemode(); + goto rrturn; + } + break; + case 'c': + if (G.gotsig) + { + will_charmode(); + goto rrturn; + } + break; + case 'z': + cookmode(); + kill(0, SIGTSTP); + rawmode(); + break; + case 'e': + doexit(0); + } + + WriteCS(1, "continuing...\r\n"); + + if (G.gotsig) + cookmode(); + + rrturn: + G.gotsig = 0; + +} +static void handlenetoutput(int len) +{ + /* here we could do smart tricks how to handle 0xFF:s in output + * stream like writing twice every sequence of FF:s (thus doing + * many write()s. But I think interactive telnet application does + * not need to be 100% 8-bit clean, so changing every 0xff:s to + * 0x7f:s + * + * 2002-mar-21, Przemyslaw Czerpak (druzus@polbox.com) + * I don't agree. + * first - I cannot use programs like sz/rz + * second - the 0x0D is sent as one character and if the next + * char is 0x0A then it's eaten by a server side. + * third - whay doy you have to make 'many write()s'? + * I don't understand. + * So I implemented it. It's realy useful for me. I hope that + * others people will find it interesting to. + */ + + int i, j; + byte * p = (byte*)G.buf; + byte outbuf[4*DATABUFSIZE]; + + for (i = len, j = 0; i > 0; i--, p++) + { + if (*p == 0x1d) + { + conescape(); + return; + } + outbuf[j++] = *p; + if (*p == 0xff) + outbuf[j++] = 0xff; + else if (*p == 0x0d) + outbuf[j++] = 0x00; + } + if (j > 0 ) + write(G.netfd, outbuf, j); +} + + +static void handlenetinput(int len) +{ + int i; + int cstart = 0; + + for (i = 0; i < len; i++) + { + byte c = G.buf[i]; + + if (G.telstate == 0) /* most of the time state == 0 */ + { + if (c == IAC) + { + cstart = i; + G.telstate = TS_IAC; + } + } + else + switch (G.telstate) + { + case TS_0: + if (c == IAC) + G.telstate = TS_IAC; + else + G.buf[cstart++] = c; + break; + + case TS_IAC: + if (c == IAC) /* IAC IAC -> 0xFF */ + { + G.buf[cstart++] = c; + G.telstate = TS_0; + break; + } + /* else */ + switch (c) + { + case SB: + G.telstate = TS_SUB1; + break; + case DO: + case DONT: + case WILL: + case WONT: + G.telwish = c; + G.telstate = TS_OPT; + break; + default: + G.telstate = TS_0; /* DATA MARK must be added later */ + } + break; + case TS_OPT: /* WILL, WONT, DO, DONT */ + telopt(c); + G.telstate = TS_0; + break; + case TS_SUB1: /* Subnegotiation */ + case TS_SUB2: /* Subnegotiation */ + if (subneg(c)) + G.telstate = TS_0; + break; + } + } + if (G.telstate) + { + if (G.iaclen) iacflush(); + if (G.telstate == TS_0) G.telstate = 0; + + len = cstart; + } + + if (len) + write(1, G.buf, len); +} + + +/* ******************************* */ + +static void putiac(int c) +{ + G.iacbuf[G.iaclen++] = c; +} + + +static void putiac2(byte wwdd, byte c) +{ + if (G.iaclen + 3 > IACBUFSIZE) + iacflush(); + + putiac(IAC); + putiac(wwdd); + putiac(c); +} + +#ifdef CONFIG_FEATURE_TELNET_TTYPE +static void putiac_subopt(byte c, char *str) +{ + int len = strlen(str) + 6; // ( 2 + 1 + 1 + strlen + 2 ) + + if (G.iaclen + len > IACBUFSIZE) + iacflush(); + + putiac(IAC); + putiac(SB); + putiac(c); + putiac(0); + + while(*str) + putiac(*str++); + + putiac(IAC); + putiac(SE); +} +#endif + +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN +static void putiac_subopt_autologin(void) +{ + int len = strlen(autologin) + 6; // (2 + 1 + 1 + strlen + 2) + char *user = "USER"; + + if (G.iaclen + len > IACBUFSIZE) + iacflush(); + + putiac(IAC); + putiac(SB); + putiac(TELOPT_NEW_ENVIRON); + putiac(TELQUAL_IS); + putiac(NEW_ENV_VAR); + + while(*user) + putiac(*user++); + + putiac(NEW_ENV_VALUE); + + while(*autologin) + putiac(*autologin++); + + putiac(IAC); + putiac(SE); +} +#endif + +#ifdef CONFIG_FEATURE_AUTOWIDTH +static void putiac_naws(byte c, int x, int y) +{ + if (G.iaclen + 9 > IACBUFSIZE) + iacflush(); + + putiac(IAC); + putiac(SB); + putiac(c); + + putiac((x >> 8) & 0xff); + putiac(x & 0xff); + putiac((y >> 8) & 0xff); + putiac(y & 0xff); + + putiac(IAC); + putiac(SE); +} +#endif + +/* void putiacstring (subneg strings) */ + +/* ******************************* */ + +static char const escapecharis[] = "\r\nEscape character is "; + +static void setConMode(void) +{ + if (G.telflags & UF_ECHO) + { + if (G.charmode == CHM_TRY) { + G.charmode = CHM_ON; + printf("\r\nEntering character mode%s'^]'.\r\n", escapecharis); + rawmode(); + } + } + else + { + if (G.charmode != CHM_OFF) { + G.charmode = CHM_OFF; + printf("\r\nEntering line mode%s'^C'.\r\n", escapecharis); + cookmode(); + } + } +} + +/* ******************************* */ + +static void will_charmode(void) +{ + G.charmode = CHM_TRY; + G.telflags |= (UF_ECHO | UF_SGA); + setConMode(); + + putiac2(DO, TELOPT_ECHO); + putiac2(DO, TELOPT_SGA); + iacflush(); +} + +static void do_linemode(void) +{ + G.charmode = CHM_TRY; + G.telflags &= ~(UF_ECHO | UF_SGA); + setConMode(); + + putiac2(DONT, TELOPT_ECHO); + putiac2(DONT, TELOPT_SGA); + iacflush(); +} + +/* ******************************* */ + +static void to_notsup(char c) +{ + if (G.telwish == WILL) putiac2(DONT, c); + else if (G.telwish == DO) putiac2(WONT, c); +} + +static void to_echo(void) +{ + /* if server requests ECHO, don't agree */ + if (G.telwish == DO) { putiac2(WONT, TELOPT_ECHO); return; } + else if (G.telwish == DONT) return; + + if (G.telflags & UF_ECHO) + { + if (G.telwish == WILL) + return; + } + else + if (G.telwish == WONT) + return; + + if (G.charmode != CHM_OFF) + G.telflags ^= UF_ECHO; + + if (G.telflags & UF_ECHO) + putiac2(DO, TELOPT_ECHO); + else + putiac2(DONT, TELOPT_ECHO); + + setConMode(); + WriteCS(1, "\r\n"); /* sudden modec */ +} + +static void to_sga(void) +{ + /* daemon always sends will/wont, client do/dont */ + + if (G.telflags & UF_SGA) + { + if (G.telwish == WILL) + return; + } + else + if (G.telwish == WONT) + return; + + if ((G.telflags ^= UF_SGA) & UF_SGA) /* toggle */ + putiac2(DO, TELOPT_SGA); + else + putiac2(DONT, TELOPT_SGA); + + return; +} + +#ifdef CONFIG_FEATURE_TELNET_TTYPE +static void to_ttype(void) +{ + /* Tell server we will (or won't) do TTYPE */ + + if(ttype) + putiac2(WILL, TELOPT_TTYPE); + else + putiac2(WONT, TELOPT_TTYPE); + + return; +} +#endif + +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN +static void to_new_environ(void) +{ + /* Tell server we will (or will not) do AUTOLOGIN */ + + if (autologin) + putiac2(WILL, TELOPT_NEW_ENVIRON); + else + putiac2(WONT, TELOPT_NEW_ENVIRON); + + return; +} +#endif + +#ifdef CONFIG_FEATURE_AUTOWIDTH +static void to_naws(void) +{ + /* Tell server we will do NAWS */ + putiac2(WILL, TELOPT_NAWS); + return; +} +#endif + +static void telopt(byte c) +{ + switch (c) + { + case TELOPT_ECHO: to_echo(); break; + case TELOPT_SGA: to_sga(); break; +#ifdef CONFIG_FEATURE_TELNET_TTYPE + case TELOPT_TTYPE: to_ttype();break; +#endif +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN + case TELOPT_NEW_ENVIRON: to_new_environ(); break; +#endif +#ifdef CONFIG_FEATURE_AUTOWIDTH + case TELOPT_NAWS: to_naws(); + putiac_naws(c, win_width, win_height); + break; +#endif + default: to_notsup(c); + break; + } +} + + +/* ******************************* */ + +/* subnegotiation -- ignore all (except TTYPE,NAWS) */ + +static int subneg(byte c) +{ + switch (G.telstate) + { + case TS_SUB1: + if (c == IAC) + G.telstate = TS_SUB2; +#ifdef CONFIG_FEATURE_TELNET_TTYPE + else + if (c == TELOPT_TTYPE) + putiac_subopt(TELOPT_TTYPE,ttype); +#endif +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN + else + if (c == TELOPT_NEW_ENVIRON) + putiac_subopt_autologin(); +#endif + break; + case TS_SUB2: + if (c == SE) + return TRUE; + G.telstate = TS_SUB1; + /* break; */ + } + return FALSE; +} + +/* ******************************* */ + +static void fgotsig(int sig) +{ + G.gotsig = sig; +} + + +static void rawmode(void) +{ + if (G.do_termios) tcsetattr(0, TCSADRAIN, &G.termios_raw); +} + +static void cookmode(void) +{ + if (G.do_termios) tcsetattr(0, TCSADRAIN, &G.termios_def); +} + +int telnet_main(int argc, char** argv) +{ + int len; + struct sockaddr_in s_in; +#ifdef USE_POLL + struct pollfd ufds[2]; +#else + fd_set readfds; + int maxfd; +#endif + +#ifdef CONFIG_FEATURE_AUTOWIDTH + get_terminal_width_height(0, &win_width, &win_height); +#endif + +#ifdef CONFIG_FEATURE_TELNET_TTYPE + ttype = getenv("TERM"); +#endif + + memset(&G, 0, sizeof G); + + if (tcgetattr(0, &G.termios_def) >= 0) { + G.do_termios = 1; + + G.termios_raw = G.termios_def; + cfmakeraw(&G.termios_raw); + } + + if (argc < 2) + bb_show_usage(); + +#ifdef CONFIG_FEATURE_TELNET_AUTOLOGIN + if (1 & getopt32(argc, argv, "al:", &autologin)) + autologin = getenv("USER"); + + if (optind < argc) { + bb_lookup_host(&s_in, argv[optind++]); + s_in.sin_port = bb_lookup_port((optind < argc) ? argv[optind++] : + "telnet", "tcp", 23); + if (optind < argc) + bb_show_usage(); + } else + bb_show_usage(); +#else + bb_lookup_host(&s_in, argv[1]); + s_in.sin_port = bb_lookup_port((argc == 3) ? argv[2] : "telnet", "tcp", 23); +#endif + + G.netfd = xconnect_tcp_v4(&s_in); + + setsockopt(G.netfd, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof one); + + signal(SIGINT, fgotsig); + +#ifdef USE_POLL + ufds[0].fd = 0; ufds[1].fd = G.netfd; + ufds[0].events = ufds[1].events = POLLIN; +#else + FD_ZERO(&readfds); + FD_SET(0, &readfds); + FD_SET(G.netfd, &readfds); + maxfd = G.netfd + 1; +#endif + + while (1) + { +#ifndef USE_POLL + fd_set rfds = readfds; + + switch (select(maxfd, &rfds, NULL, NULL, NULL)) +#else + switch (poll(ufds, 2, -1)) +#endif + { + case 0: + /* timeout */ + case -1: + /* error, ignore and/or log something, bay go to loop */ + if (G.gotsig) + conescape(); + else + sleep(1); + break; + default: + +#ifdef USE_POLL + if (ufds[0].revents) /* well, should check POLLIN, but ... */ +#else + if (FD_ISSET(0, &rfds)) +#endif + { + len = read(0, G.buf, DATABUFSIZE); + + if (len <= 0) + doexit(0); + + TRACE(0, ("Read con: %d\n", len)); + + handlenetoutput(len); + } + +#ifdef USE_POLL + if (ufds[1].revents) /* well, should check POLLIN, but ... */ +#else + if (FD_ISSET(G.netfd, &rfds)) +#endif + { + len = read(G.netfd, G.buf, DATABUFSIZE); + + if (len <= 0) + { + WriteCS(1, "Connection closed by foreign host.\r\n"); + doexit(1); + } + TRACE(0, ("Read netfd (%d): %d\n", G.netfd, len)); + + handlenetinput(len); + } + } + } +} diff --git a/networking/telnetd.c b/networking/telnetd.c new file mode 100644 index 000000000..604f65c91 --- /dev/null +++ b/networking/telnetd.c @@ -0,0 +1,578 @@ +/* vi: set sw=4 ts=4: */ +/* + * Simple telnet server + * Bjorn Wesen, Axis Communications AB (bjornw@axis.com) + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * --------------------------------------------------------------------------- + * (C) Copyright 2000, Axis Communications AB, LUND, SWEDEN + **************************************************************************** + * + * The telnetd manpage says it all: + * + * Telnetd operates by allocating a pseudo-terminal device (see pty(4)) for + * a client, then creating a login process which has the slave side of the + * pseudo-terminal as stdin, stdout, and stderr. Telnetd manipulates the + * master side of the pseudo-terminal, implementing the telnet protocol and + * passing characters between the remote client and the login process. + * + * Vladimir Oleynik 2001 + * Set process group corrections, initial busybox port + */ + +/*#define DEBUG 1 */ +#define DEBUG 0 + +#include "busybox.h" + +#if DEBUG +#define TELCMDS +#define TELOPTS +#endif +#include +#include + + +#define BUFSIZE 4000 + +#if ENABLE_FEATURE_IPV6 +typedef struct sockaddr_in6 sockaddr_type; +#else +typedef struct sockaddr_in sockaddr_type; +#endif + +#if ENABLE_LOGIN +static const char *loginpath = "/bin/login"; +#else +static const char *loginpath = DEFAULT_SHELL; +#endif + +static const char *issuefile = "/etc/issue.net"; + +/* shell name and arguments */ + +static const char *argv_init[2]; + +/* structure that describes a session */ + +struct tsession { + struct tsession *next; + int sockfd_read, sockfd_write, ptyfd; + int shell_pid; + /* two circular buffers */ + char *buf1, *buf2; + int rdidx1, wridx1, size1; + int rdidx2, wridx2, size2; +}; + +/* + This is how the buffers are used. The arrows indicate the movement + of data. + + +-------+ wridx1++ +------+ rdidx1++ +----------+ + | | <-------------- | buf1 | <-------------- | | + | | size1-- +------+ size1++ | | + | pty | | socket | + | | rdidx2++ +------+ wridx2++ | | + | | --------------> | buf2 | --------------> | | + +-------+ size2++ +------+ size2-- +----------+ + + Each session has got two buffers. +*/ + +static int maxfd; + +static struct tsession *sessions; + + +/* + Remove all IAC's from the buffer pointed to by bf (received IACs are ignored + and must be removed so as to not be interpreted by the terminal). Make an + uninterrupted string of characters fit for the terminal. Do this by packing + all characters meant for the terminal sequentially towards the end of bf. + + Return a pointer to the beginning of the characters meant for the terminal. + and make *num_totty the number of characters that should be sent to + the terminal. + + Note - If an IAC (3 byte quantity) starts before (bf + len) but extends + past (bf + len) then that IAC will be left unprocessed and *processed will be + less than len. + + FIXME - if we mean to send 0xFF to the terminal then it will be escaped, + what is the escape character? We aren't handling that situation here. + + CR-LF ->'s CR mapping is also done here, for convenience + */ +static char * +remove_iacs(struct tsession *ts, int *pnum_totty) +{ + unsigned char *ptr0 = (unsigned char *)ts->buf1 + ts->wridx1; + unsigned char *ptr = ptr0; + unsigned char *totty = ptr; + unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1); + int processed; + int num_totty; + + while (ptr < end) { + if (*ptr != IAC) { + int c = *ptr; + *totty++ = *ptr++; + /* We now map \r\n ==> \r for pragmatic reasons. + * Many client implementations send \r\n when + * the user hits the CarriageReturn key. + */ + if (c == '\r' && (*ptr == '\n' || *ptr == 0) && ptr < end) + ptr++; + } else { + /* + * TELOPT_NAWS support! + */ + if ((ptr+2) >= end) { + /* only the beginning of the IAC is in the + buffer we were asked to process, we can't + process this char. */ + break; + } + + /* + * IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE + */ + else if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) { + struct winsize ws; + if ((ptr+8) >= end) + break; /* incomplete, can't process */ + ws.ws_col = (ptr[3] << 8) | ptr[4]; + ws.ws_row = (ptr[5] << 8) | ptr[6]; + ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws); + ptr += 9; + } else { + /* skip 3-byte IAC non-SB cmd */ +#if DEBUG + fprintf(stderr, "Ignoring IAC %s,%s\n", + TELCMD(ptr[1]), TELOPT(ptr[2])); +#endif + ptr += 3; + } + } + } + + processed = ptr - ptr0; + num_totty = totty - ptr0; + /* the difference between processed and num_to tty + is all the iacs we removed from the stream. + Adjust buf1 accordingly. */ + ts->wridx1 += processed - num_totty; + ts->size1 -= processed - num_totty; + *pnum_totty = num_totty; + /* move the chars meant for the terminal towards the end of the + buffer. */ + return memmove(ptr - num_totty, ptr0, num_totty); +} + + +static int +getpty(char *line, int size) +{ + int p; +#if ENABLE_FEATURE_DEVPTS + p = open("/dev/ptmx", O_RDWR); + if (p > 0) { + const char *name; + grantpt(p); + unlockpt(p); + name = ptsname(p); + if (!name) { + bb_perror_msg("ptsname error (is /dev/pts mounted?)"); + return -1; + } + safe_strncpy(line, name, size); + return p; + } +#else + struct stat stb; + int i; + int j; + + strcpy(line, "/dev/ptyXX"); + + for (i = 0; i < 16; i++) { + line[8] = "pqrstuvwxyzabcde"[i]; + line[9] = '0'; + if (stat(line, &stb) < 0) { + continue; + } + for (j = 0; j < 16; j++) { + line[9] = j < 10 ? j + '0' : j - 10 + 'a'; + if (DEBUG) + fprintf(stderr, "Trying to open device: %s\n", line); + p = open(line, O_RDWR | O_NOCTTY); + if (p >= 0) { + line[5] = 't'; + return p; + } + } + } +#endif /* FEATURE_DEVPTS */ + return -1; +} + + +static void +send_iac(struct tsession *ts, unsigned char command, int option) +{ + /* We rely on that there is space in the buffer for now. */ + char *b = ts->buf2 + ts->rdidx2; + *b++ = IAC; + *b++ = command; + *b++ = option; + ts->rdidx2 += 3; + ts->size2 += 3; +} + + +static struct tsession * +make_new_session( + USE_FEATURE_TELNETD_STANDALONE(int sock_r, int sock_w) + SKIP_FEATURE_TELNETD_STANDALONE(void) +) { + struct termios termbuf; + int fd, pid; + char tty_name[32]; + struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2); + + ts->buf1 = (char *)(&ts[1]); + ts->buf2 = ts->buf1 + BUFSIZE; + + /* Got a new connection, set up a tty. */ + fd = getpty(tty_name, 32); + if (fd < 0) { + bb_error_msg("all terminals in use"); + return NULL; + } + if (fd > maxfd) maxfd = fd; + ndelay_on(ts->ptyfd = fd); +#if ENABLE_FEATURE_TELNETD_STANDALONE + if (sock_w > maxfd) maxfd = sock_w; + if (sock_r > maxfd) maxfd = sock_r; + ndelay_on(ts->sockfd_write = sock_w); + ndelay_on(ts->sockfd_read = sock_r); +#else + ts->sockfd_write = 1; + /* xzalloc: ts->sockfd_read = 0; */ + ndelay_on(0); + ndelay_on(1); +#endif + /* Make the telnet client understand we will echo characters so it + * should not do it locally. We don't tell the client to run linemode, + * because we want to handle line editing and tab completion and other + * stuff that requires char-by-char support. */ + send_iac(ts, DO, TELOPT_ECHO); + send_iac(ts, DO, TELOPT_NAWS); + send_iac(ts, DO, TELOPT_LFLOW); + send_iac(ts, WILL, TELOPT_ECHO); + send_iac(ts, WILL, TELOPT_SGA); + + pid = fork(); + if (pid < 0) { + free(ts); + close(fd); + bb_perror_msg("fork"); + return NULL; + } + if (pid > 0) { + /* parent */ + ts->shell_pid = pid; + return ts; + } + + /* child */ + + /* open the child's side of the tty. */ + fd = xopen(tty_name, O_RDWR /*| O_NOCTTY*/); + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + while (fd > 2) close(fd--); + /* make new process group */ + setsid(); + tcsetpgrp(0, getpid()); + + /* The pseudo-terminal allocated to the client is configured to operate in + * cooked mode, and with XTABS CRMOD enabled (see tty(4)). */ + tcgetattr(0, &termbuf); + termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */ + termbuf.c_oflag |= ONLCR|XTABS; + termbuf.c_iflag |= ICRNL; + termbuf.c_iflag &= ~IXOFF; + /*termbuf.c_lflag &= ~ICANON;*/ + tcsetattr(0, TCSANOW, &termbuf); + + print_login_issue(issuefile, NULL); + + /* exec shell, with correct argv and env */ + execv(loginpath, (char *const *)argv_init); + bb_perror_msg_and_die("execv"); +} + +#if ENABLE_FEATURE_TELNETD_STANDALONE + +static void +free_session(struct tsession *ts) +{ + struct tsession *t = sessions; + + /* unlink this telnet session from the session list */ + if (t == ts) + sessions = ts->next; + else { + while (t->next != ts) + t = t->next; + t->next = ts->next; + } + + kill(ts->shell_pid, SIGKILL); + wait4(ts->shell_pid, NULL, 0, NULL); + close(ts->ptyfd); + close(ts->sockfd_read); + /* error if ts->sockfd_read == ts->sockfd_write. So what? ;) */ + close(ts->sockfd_write); + free(ts); + + /* scan all sessions and find new maxfd */ + ts = sessions; + maxfd = 0; + while (ts) { + if (maxfd < ts->ptyfd) + maxfd = ts->ptyfd; + if (maxfd < ts->sockfd_read) + maxfd = ts->sockfd_read; + if (maxfd < ts->sockfd_write) + maxfd = ts->sockfd_write; + ts = ts->next; + } +} + +#else /* !FEATURE_TELNETD_STANDALONE */ + +/* Never actually called */ +void free_session(struct tsession *ts); + +#endif + + +int +telnetd_main(int argc, char **argv) +{ + fd_set rdfdset, wrfdset; + unsigned opt; + int selret, maxlen, w, r; + struct tsession *ts; +#if ENABLE_FEATURE_TELNETD_STANDALONE +#define IS_INETD (opt & OPT_INETD) + int master_fd = -1; /* be happy, gcc */ + unsigned portnbr = 23; + char *opt_bindaddr = NULL; + char *opt_portnbr; +#else + enum { + IS_INETD = 1, + master_fd = -1, + portnbr = 23, + }; +#endif + enum { + OPT_PORT = 4 * ENABLE_FEATURE_TELNETD_STANDALONE, + OPT_FOREGROUND = 0x10 * ENABLE_FEATURE_TELNETD_STANDALONE, + OPT_INETD = 0x20 * ENABLE_FEATURE_TELNETD_STANDALONE, + }; + + opt = getopt32(argc, argv, "f:l:" USE_FEATURE_TELNETD_STANDALONE("p:b:Fi"), + &issuefile, &loginpath + USE_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr)); + /* Redirect log to syslog early, if needed */ + if (IS_INETD || !(opt & OPT_FOREGROUND)) { + openlog(applet_name, 0, LOG_USER); + logmode = LOGMODE_SYSLOG; + } + //if (opt & 1) // -f + //if (opt & 2) // -l + USE_FEATURE_TELNETD_STANDALONE( + if (opt & OPT_PORT) // -p + portnbr = xatou16(opt_portnbr); + //if (opt & 8) // -b + //if (opt & 0x10) // -F + //if (opt & 0x20) // -i + ); + + /* Used to check access(loginpath, X_OK) here. Pointless. + * exec will do this for us for free later. */ + argv_init[0] = loginpath; + +#if ENABLE_FEATURE_TELNETD_STANDALONE + if (IS_INETD) { + sessions = make_new_session(0, 1); + } else { + master_fd = create_and_bind_socket_ip4or6(opt_bindaddr, portnbr); + xlisten(master_fd, 1); + if (!(opt & OPT_FOREGROUND)) + xdaemon(0, 0); + } +#else + sessions = make_new_session(); +#endif + + /* We don't want to die if just one session is broken */ + signal(SIGPIPE, SIG_IGN); + + again: + FD_ZERO(&rdfdset); + FD_ZERO(&wrfdset); + if (!IS_INETD) { + FD_SET(master_fd, &rdfdset); + /* This is needed because free_session() does not + * take into account master_fd when it finds new + * maxfd among remaining fd's: */ + if (master_fd > maxfd) + maxfd = master_fd; + } + + /* select on the master socket, all telnet sockets and their + * ptys if there is room in their session buffers. */ + ts = sessions; + while (ts) { + /* buf1 is used from socket to pty + * buf2 is used from pty to socket */ + if (ts->size1 > 0) /* can write to pty */ + FD_SET(ts->ptyfd, &wrfdset); + if (ts->size1 < BUFSIZE) /* can read from socket */ + FD_SET(ts->sockfd_read, &rdfdset); + if (ts->size2 > 0) /* can write to socket */ + FD_SET(ts->sockfd_write, &wrfdset); + if (ts->size2 < BUFSIZE) /* can read from pty */ + FD_SET(ts->ptyfd, &rdfdset); + ts = ts->next; + } + + selret = select(maxfd + 1, &rdfdset, &wrfdset, 0, 0); + if (!selret) + return 0; + +#if ENABLE_FEATURE_TELNETD_STANDALONE + /* First check for and accept new sessions. */ + if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) { + sockaddr_type sa; + int fd; + socklen_t salen; + struct tsession *new_ts; + + salen = sizeof(sa); + fd = accept(master_fd, (struct sockaddr *)&sa, &salen); + if (fd < 0) + goto again; + /* Create a new session and link it into our active list */ + new_ts = make_new_session(fd, fd); + if (new_ts) { + new_ts->next = sessions; + sessions = new_ts; + } else { + close(fd); + } + } +#endif + + /* Then check for data tunneling. */ + ts = sessions; + while (ts) { /* For all sessions... */ + struct tsession *next = ts->next; /* in case we free ts. */ + + if (ts->size1 && FD_ISSET(ts->ptyfd, &wrfdset)) { + int num_totty; + char *ptr; + /* Write to pty from buffer 1. */ + ptr = remove_iacs(ts, &num_totty); + w = safe_write(ts->ptyfd, ptr, num_totty); + /* needed? if (w < 0 && errno == EAGAIN) continue; */ + if (w < 0) { + if (IS_INETD) + return 0; + free_session(ts); + ts = next; + continue; + } + ts->wridx1 += w; + ts->size1 -= w; + if (ts->wridx1 == BUFSIZE) + ts->wridx1 = 0; + } + + if (ts->size2 && FD_ISSET(ts->sockfd_write, &wrfdset)) { + /* Write to socket from buffer 2. */ + maxlen = MIN(BUFSIZE - ts->wridx2, ts->size2); + w = safe_write(ts->sockfd_write, ts->buf2 + ts->wridx2, maxlen); + /* needed? if (w < 0 && errno == EAGAIN) continue; */ + if (w < 0) { + if (IS_INETD) + return 0; + free_session(ts); + ts = next; + continue; + } + ts->wridx2 += w; + ts->size2 -= w; + if (ts->wridx2 == BUFSIZE) + ts->wridx2 = 0; + } + + if (ts->size1 < BUFSIZE && FD_ISSET(ts->sockfd_read, &rdfdset)) { + /* Read from socket to buffer 1. */ + maxlen = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1); + r = safe_read(ts->sockfd_read, ts->buf1 + ts->rdidx1, maxlen); + if (r < 0 && errno == EAGAIN) continue; + if (r <= 0) { + if (IS_INETD) + return 0; + free_session(ts); + ts = next; + continue; + } + if (!ts->buf1[ts->rdidx1 + r - 1]) + if (!--r) + continue; + ts->rdidx1 += r; + ts->size1 += r; + if (ts->rdidx1 == BUFSIZE) + ts->rdidx1 = 0; + } + + if (ts->size2 < BUFSIZE && FD_ISSET(ts->ptyfd, &rdfdset)) { + /* Read from pty to buffer 2. */ + maxlen = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2); + r = safe_read(ts->ptyfd, ts->buf2 + ts->rdidx2, maxlen); + if (r < 0 && errno == EAGAIN) continue; + if (r <= 0) { + if (IS_INETD) + return 0; + free_session(ts); + ts = next; + continue; + } + ts->rdidx2 += r; + ts->size2 += r; + if (ts->rdidx2 == BUFSIZE) + ts->rdidx2 = 0; + } + + if (ts->size1 == 0) { + ts->rdidx1 = 0; + ts->wridx1 = 0; + } + if (ts->size2 == 0) { + ts->rdidx2 = 0; + ts->wridx2 = 0; + } + ts = next; + } + goto again; +} diff --git a/networking/tftp.c b/networking/tftp.c new file mode 100644 index 000000000..64d376fa7 --- /dev/null +++ b/networking/tftp.c @@ -0,0 +1,561 @@ +/* vi: set sw=4 ts=4: */ +/* ------------------------------------------------------------------------- + * tftp.c + * + * A simple tftp client for busybox. + * Tries to follow RFC1350. + * Only "octet" mode supported. + * Optional blocksize negotiation (RFC2347 + RFC2348) + * + * Copyright (C) 2001 Magnus Damm + * + * Parts of the code based on: + * + * atftp: Copyright (C) 2000 Jean-Pierre Lefebvre + * and Remi Lefebvre + * + * utftp: Copyright (C) 1999 Uwe Ohse + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * ------------------------------------------------------------------------- */ + +#include "busybox.h" + + +#define TFTP_BLOCKSIZE_DEFAULT 512 /* according to RFC 1350, don't change */ +#define TFTP_TIMEOUT 5 /* seconds */ +#define TFTP_NUM_RETRIES 5 /* number of retries */ + +static const char * const MODE_OCTET = "octet"; +#define MODE_OCTET_LEN 6 /* sizeof(MODE_OCTET)*/ + +static const char * const OPTION_BLOCKSIZE = "blksize"; +#define OPTION_BLOCKSIZE_LEN 8 /* sizeof(OPTION_BLOCKSIZE) */ + +/* opcodes we support */ +#define TFTP_RRQ 1 +#define TFTP_WRQ 2 +#define TFTP_DATA 3 +#define TFTP_ACK 4 +#define TFTP_ERROR 5 +#define TFTP_OACK 6 + +static const char *const tftp_bb_error_msg[] = { + "Undefined error", + "File not found", + "Access violation", + "Disk full or allocation error", + "Illegal TFTP operation", + "Unknown transfer ID", + "File already exists", + "No such user" +}; + +#define tftp_cmd_get ENABLE_FEATURE_TFTP_GET + +#if ENABLE_FEATURE_TFTP_PUT +# define tftp_cmd_put (tftp_cmd_get+ENABLE_FEATURE_TFTP_PUT) +#else +# define tftp_cmd_put 0 +#endif + + +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + +static int tftp_blocksize_check(int blocksize, int bufsize) +{ + /* Check if the blocksize is valid: + * RFC2348 says between 8 and 65464, + * but our implementation makes it impossible + * to use blocksizes smaller than 22 octets. + */ + + if ((bufsize && (blocksize > bufsize)) || + (blocksize < 8) || (blocksize > 65564)) { + bb_error_msg("bad blocksize"); + return 0; + } + + return blocksize; +} + +static char *tftp_option_get(char *buf, int len, const char * const option) +{ + int opt_val = 0; + int opt_found = 0; + int k; + + while (len > 0) { + + /* Make sure the options are terminated correctly */ + + for (k = 0; k < len; k++) { + if (buf[k] == '\0') { + break; + } + } + + if (k >= len) { + break; + } + + if (opt_val == 0) { + if (strcasecmp(buf, option) == 0) { + opt_found = 1; + } + } else { + if (opt_found) { + return buf; + } + } + + k++; + + buf += k; + len -= k; + + opt_val ^= 1; + } + + return NULL; +} + +#endif + +static int tftp(const int cmd, const struct hostent *host, + const char *remotefile, const int localfd, + const unsigned short port, int tftp_bufsize) +{ + struct sockaddr_in sa; + struct sockaddr_in from; + struct timeval tv; + socklen_t fromlen; + fd_set rfds; + int socketfd; + int len; + int opcode = 0; + int finished = 0; + int timeout = TFTP_NUM_RETRIES; + unsigned short block_nr = 1; + unsigned short tmp; + char *cp; + + USE_FEATURE_TFTP_BLOCKSIZE(int want_option_ack = 0;) + + /* Can't use RESERVE_CONFIG_BUFFER here since the allocation + * size varies meaning BUFFERS_GO_ON_STACK would fail */ + char *buf=xmalloc(tftp_bufsize += 4); + + if ((socketfd = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { + /* need to unlink the localfile, so don't use xsocket here. */ + bb_perror_msg("socket"); + return EXIT_FAILURE; + } + + len = sizeof(sa); + + memset(&sa, 0, len); + xbind(socketfd, (struct sockaddr *)&sa, len); + + sa.sin_family = host->h_addrtype; + sa.sin_port = port; + memcpy(&sa.sin_addr, (struct in_addr *) host->h_addr, + sizeof(sa.sin_addr)); + + /* build opcode */ + if (cmd & tftp_cmd_get) { + opcode = TFTP_RRQ; + } + if (cmd & tftp_cmd_put) { + opcode = TFTP_WRQ; + } + + while (1) { + + cp = buf; + + /* first create the opcode part */ + *((unsigned short *) cp) = htons(opcode); + cp += 2; + + /* add filename and mode */ + if (((cmd & tftp_cmd_get) && (opcode == TFTP_RRQ)) || + ((cmd & tftp_cmd_put) && (opcode == TFTP_WRQ))) + { + int too_long = 0; + + /* see if the filename fits into buf + * and fill in packet. */ + len = strlen(remotefile) + 1; + + if ((cp + len) >= &buf[tftp_bufsize - 1]) { + too_long = 1; + } else { + safe_strncpy(cp, remotefile, len); + cp += len; + } + + if (too_long || ((&buf[tftp_bufsize - 1] - cp) < MODE_OCTET_LEN)) { + bb_error_msg("remote filename too long"); + break; + } + + /* add "mode" part of the package */ + memcpy(cp, MODE_OCTET, MODE_OCTET_LEN); + cp += MODE_OCTET_LEN; + +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + + len = tftp_bufsize - 4; /* data block size */ + + if (len != TFTP_BLOCKSIZE_DEFAULT) { + + if ((&buf[tftp_bufsize - 1] - cp) < 15) { + bb_error_msg("remote filename too long"); + break; + } + + /* add "blksize" + number of blocks */ + memcpy(cp, OPTION_BLOCKSIZE, OPTION_BLOCKSIZE_LEN); + cp += OPTION_BLOCKSIZE_LEN; + cp += snprintf(cp, 6, "%d", len) + 1; + + want_option_ack = 1; + } +#endif + } + + /* add ack and data */ + + if (((cmd & tftp_cmd_get) && (opcode == TFTP_ACK)) || + ((cmd & tftp_cmd_put) && (opcode == TFTP_DATA))) { + + *((unsigned short *) cp) = htons(block_nr); + + cp += 2; + + block_nr++; + + if ((cmd & tftp_cmd_put) && (opcode == TFTP_DATA)) { + len = full_read(localfd, cp, tftp_bufsize - 4); + + if (len < 0) { + bb_perror_msg(bb_msg_read_error); + break; + } + + if (len != (tftp_bufsize - 4)) { + finished++; + } + + cp += len; + } + } + + + /* send packet */ + + + timeout = TFTP_NUM_RETRIES; /* re-initialize */ + do { + + len = cp - buf; + +#if ENABLE_DEBUG_TFTP + fprintf(stderr, "sending %u bytes\n", len); + for (cp = buf; cp < &buf[len]; cp++) + fprintf(stderr, "%02x ", (unsigned char) *cp); + fprintf(stderr, "\n"); +#endif + if (sendto(socketfd, buf, len, 0, + (struct sockaddr *) &sa, sizeof(sa)) < 0) { + bb_perror_msg("send"); + len = -1; + break; + } + + + if (finished && (opcode == TFTP_ACK)) { + break; + } + + /* receive packet */ + + memset(&from, 0, sizeof(from)); + fromlen = sizeof(from); + + tv.tv_sec = TFTP_TIMEOUT; + tv.tv_usec = 0; + + FD_ZERO(&rfds); + FD_SET(socketfd, &rfds); + + switch (select(socketfd + 1, &rfds, NULL, NULL, &tv)) { + case 1: + len = recvfrom(socketfd, buf, tftp_bufsize, 0, + (struct sockaddr *) &from, &fromlen); + + if (len < 0) { + bb_perror_msg("recvfrom"); + break; + } + + timeout = 0; + + if (sa.sin_port == port) { + sa.sin_port = from.sin_port; + } + if (sa.sin_port == from.sin_port) { + break; + } + + /* fall-through for bad packets! */ + /* discard the packet - treat as timeout */ + timeout = TFTP_NUM_RETRIES; + case 0: + bb_error_msg("timeout"); + + timeout--; + if (timeout == 0) { + len = -1; + bb_error_msg("last timeout"); + } + break; + default: + bb_perror_msg("select"); + len = -1; + } + + } while (timeout && (len >= 0)); + + if ((finished) || (len < 0)) { + break; + } + + /* process received packet */ + + opcode = ntohs(*((unsigned short *) buf)); + tmp = ntohs(*((unsigned short *) &buf[2])); + +#if ENABLE_DEBUG_TFTP + fprintf(stderr, "received %d bytes: %04x %04x\n", len, opcode, tmp); +#endif + + if (opcode == TFTP_ERROR) { + const char *msg = NULL; + + if (buf[4] != '\0') { + msg = &buf[4]; + buf[tftp_bufsize - 1] = '\0'; + } else if (tmp < (sizeof(tftp_bb_error_msg) + / sizeof(char *))) { + + msg = tftp_bb_error_msg[tmp]; + } + + if (msg) { + bb_error_msg("server says: %s", msg); + } + + break; + } +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + if (want_option_ack) { + + want_option_ack = 0; + + if (opcode == TFTP_OACK) { + + /* server seems to support options */ + + char *res; + + res = tftp_option_get(&buf[2], len - 2, OPTION_BLOCKSIZE); + + if (res) { + int blksize = xatoi_u(res); + + if (tftp_blocksize_check(blksize, tftp_bufsize - 4)) { + + if (cmd & tftp_cmd_put) { + opcode = TFTP_DATA; + } else { + opcode = TFTP_ACK; + } +#if ENABLE_DEBUG_TFTP + fprintf(stderr, "using %s %u\n", OPTION_BLOCKSIZE, + blksize); +#endif + tftp_bufsize = blksize + 4; + block_nr = 0; + continue; + } + } + /* FIXME: + * we should send ERROR 8 */ + bb_error_msg("bad server option"); + break; + } + + bb_error_msg("warning: blksize not supported by server" + " - reverting to 512"); + + tftp_bufsize = TFTP_BLOCKSIZE_DEFAULT + 4; + } +#endif + + if ((cmd & tftp_cmd_get) && (opcode == TFTP_DATA)) { + + if (tmp == block_nr) { + + len = full_write(localfd, &buf[4], len - 4); + + if (len < 0) { + bb_perror_msg(bb_msg_write_error); + break; + } + + if (len != (tftp_bufsize - 4)) { + finished++; + } + + opcode = TFTP_ACK; + continue; + } + /* in case the last ack disappeared into the ether */ + if (tmp == (block_nr - 1)) { + --block_nr; + opcode = TFTP_ACK; + continue; + } else if (tmp + 1 == block_nr) { + /* Server lost our TFTP_ACK. Resend it */ + block_nr = tmp; + opcode = TFTP_ACK; + continue; + } + } + + if ((cmd & tftp_cmd_put) && (opcode == TFTP_ACK)) { + + if (tmp == (unsigned short) (block_nr - 1)) { + if (finished) { + break; + } + + opcode = TFTP_DATA; + continue; + } + } + } + +#if ENABLE_FEATURE_CLEAN_UP + close(socketfd); + free(buf); +#endif + + return finished ? EXIT_SUCCESS : EXIT_FAILURE; +} + +int tftp_main(int argc, char **argv) +{ + struct hostent *host = NULL; + const char *localfile = NULL; + const char *remotefile = NULL; + int port; + int cmd = 0; + int fd = -1; + int flags = 0; + int result; + int blocksize = TFTP_BLOCKSIZE_DEFAULT; + + /* figure out what to pass to getopt */ + +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + char *sblocksize = NULL; + +#define BS "b:" +#define BS_ARG , &sblocksize +#else +#define BS +#define BS_ARG +#endif + +#if ENABLE_FEATURE_TFTP_GET +#define GET "g" +#define GET_COMPL ":g" +#else +#define GET +#define GET_COMPL +#endif + +#if ENABLE_FEATURE_TFTP_PUT +#define PUT "p" +#define PUT_COMPL ":p" +#else +#define PUT +#define PUT_COMPL +#endif + +#if defined(CONFIG_FEATURE_TFTP_GET) && defined(CONFIG_FEATURE_TFTP_PUT) + opt_complementary = GET_COMPL PUT_COMPL ":?g--p:p--g"; +#elif defined(CONFIG_FEATURE_TFTP_GET) || defined(CONFIG_FEATURE_TFTP_PUT) + opt_complementary = GET_COMPL PUT_COMPL; +#endif + + cmd = getopt32(argc, argv, GET PUT "l:r:" BS, &localfile, &remotefile BS_ARG); + + cmd &= (tftp_cmd_get | tftp_cmd_put); +#if ENABLE_FEATURE_TFTP_GET + if (cmd == tftp_cmd_get) + flags = O_WRONLY | O_CREAT | O_TRUNC; +#endif +#if ENABLE_FEATURE_TFTP_PUT + if (cmd == tftp_cmd_put) + flags = O_RDONLY; +#endif + +#if ENABLE_FEATURE_TFTP_BLOCKSIZE + if (sblocksize) { + blocksize = xatoi_u(sblocksize); + if (!tftp_blocksize_check(blocksize, 0)) { + return EXIT_FAILURE; + } + } +#endif + + if (localfile == NULL) + localfile = remotefile; + if (remotefile == NULL) + remotefile = localfile; + if ((localfile == NULL && remotefile == NULL) || (argv[optind] == NULL)) + bb_show_usage(); + + if (localfile == NULL || strcmp(localfile, "-") == 0) { + fd = (cmd == tftp_cmd_get) ? STDOUT_FILENO : STDIN_FILENO; + } else { + fd = open(localfile, flags, 0644); /* fail below */ + } + if (fd < 0) { + bb_perror_msg_and_die("local file"); + } + + host = xgethostbyname(argv[optind]); + port = bb_lookup_port(argv[optind + 1], "udp", 69); + +#if ENABLE_DEBUG_TFTP + fprintf(stderr, "using server \"%s\", remotefile \"%s\", " + "localfile \"%s\".\n", + inet_ntoa(*((struct in_addr *) host->h_addr)), + remotefile, localfile); +#endif + + result = tftp(cmd, host, remotefile, fd, port, blocksize); + + if (!(fd == STDOUT_FILENO || fd == STDIN_FILENO)) { + if (ENABLE_FEATURE_CLEAN_UP) + close(fd); + if (cmd == tftp_cmd_get && result != EXIT_SUCCESS) + unlink(localfile); + } + return result; +} diff --git a/networking/traceroute.c b/networking/traceroute.c new file mode 100644 index 000000000..490076543 --- /dev/null +++ b/networking/traceroute.c @@ -0,0 +1,1350 @@ +/* vi: set sw=4 ts=4: */ +/* + * Copyright (c) 1988, 1989, 1991, 1994, 1995, 1996, 1997, 1998, 1999, 2000 + * The Regents of the University of California. All rights reserved. + * + * Busybox port by Vladimir Oleynik (C) 2005 + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that: (1) source code distributions + * retain the above copyright notice and this paragraph in its entirety, (2) + * distributions including binary code include the above copyright notice and + * this paragraph in its entirety in the documentation or other materials + * provided with the distribution, and (3) all advertising materials mentioning + * features or use of this software display the following acknowledgement: + * ``This product includes software developed by the University of California, + * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of + * the University nor the names of its contributors may be used to endorse + * or promote products derived from this software without specific prior + * written permission. + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +//#define version "1.4a12" + + +/* + * traceroute host - trace the route ip packets follow going to "host". + * + * Attempt to trace the route an ip packet would follow to some + * internet host. We find out intermediate hops by launching probe + * packets with a small ttl (time to live) then listening for an + * icmp "time exceeded" reply from a gateway. We start our probes + * with a ttl of one and increase by one until we get an icmp "port + * unreachable" (which means we got to "host") or hit a max (which + * defaults to 30 hops & can be changed with the -m flag). Three + * probes (change with -q flag) are sent at each ttl setting and a + * line is printed showing the ttl, address of the gateway and + * round trip time of each probe. If the probe answers come from + * different gateways, the address of each responding system will + * be printed. If there is no response within a 5 sec. timeout + * interval (changed with the -w flag), a "*" is printed for that + * probe. + * + * Probe packets are UDP format. We don't want the destination + * host to process them so the destination port is set to an + * unlikely value (if some clod on the destination is using that + * value, it can be changed with the -p flag). + * + * A sample use might be: + * + * [yak 71]% traceroute nis.nsf.net. + * traceroute to nis.nsf.net (35.1.1.48), 30 hops max, 56 byte packet + * 1 helios.ee.lbl.gov (128.3.112.1) 19 ms 19 ms 0 ms + * 2 lilac-dmc.Berkeley.EDU (128.32.216.1) 39 ms 39 ms 19 ms + * 3 lilac-dmc.Berkeley.EDU (128.32.216.1) 39 ms 39 ms 19 ms + * 4 ccngw-ner-cc.Berkeley.EDU (128.32.136.23) 39 ms 40 ms 39 ms + * 5 ccn-nerif22.Berkeley.EDU (128.32.168.22) 39 ms 39 ms 39 ms + * 6 128.32.197.4 (128.32.197.4) 40 ms 59 ms 59 ms + * 7 131.119.2.5 (131.119.2.5) 59 ms 59 ms 59 ms + * 8 129.140.70.13 (129.140.70.13) 99 ms 99 ms 80 ms + * 9 129.140.71.6 (129.140.71.6) 139 ms 239 ms 319 ms + * 10 129.140.81.7 (129.140.81.7) 220 ms 199 ms 199 ms + * 11 nic.merit.edu (35.1.1.48) 239 ms 239 ms 239 ms + * + * Note that lines 2 & 3 are the same. This is due to a buggy + * kernel on the 2nd hop system -- lbl-csam.arpa -- that forwards + * packets with a zero ttl. + * + * A more interesting example is: + * + * [yak 72]% traceroute allspice.lcs.mit.edu. + * traceroute to allspice.lcs.mit.edu (18.26.0.115), 30 hops max + * 1 helios.ee.lbl.gov (128.3.112.1) 0 ms 0 ms 0 ms + * 2 lilac-dmc.Berkeley.EDU (128.32.216.1) 19 ms 19 ms 19 ms + * 3 lilac-dmc.Berkeley.EDU (128.32.216.1) 39 ms 19 ms 19 ms + * 4 ccngw-ner-cc.Berkeley.EDU (128.32.136.23) 19 ms 39 ms 39 ms + * 5 ccn-nerif22.Berkeley.EDU (128.32.168.22) 20 ms 39 ms 39 ms + * 6 128.32.197.4 (128.32.197.4) 59 ms 119 ms 39 ms + * 7 131.119.2.5 (131.119.2.5) 59 ms 59 ms 39 ms + * 8 129.140.70.13 (129.140.70.13) 80 ms 79 ms 99 ms + * 9 129.140.71.6 (129.140.71.6) 139 ms 139 ms 159 ms + * 10 129.140.81.7 (129.140.81.7) 199 ms 180 ms 300 ms + * 11 129.140.72.17 (129.140.72.17) 300 ms 239 ms 239 ms + * 12 * * * + * 13 128.121.54.72 (128.121.54.72) 259 ms 499 ms 279 ms + * 14 * * * + * 15 * * * + * 16 * * * + * 17 * * * + * 18 ALLSPICE.LCS.MIT.EDU (18.26.0.115) 339 ms 279 ms 279 ms + * + * (I start to see why I'm having so much trouble with mail to + * MIT.) Note that the gateways 12, 14, 15, 16 & 17 hops away + * either don't send ICMP "time exceeded" messages or send them + * with a ttl too small to reach us. 14 - 17 are running the + * MIT C Gateway code that doesn't send "time exceeded"s. God + * only knows what's going on with 12. + * + * The silent gateway 12 in the above may be the result of a bug in + * the 4.[23]BSD network code (and its derivatives): 4.x (x <= 3) + * sends an unreachable message using whatever ttl remains in the + * original datagram. Since, for gateways, the remaining ttl is + * zero, the icmp "time exceeded" is guaranteed to not make it back + * to us. The behavior of this bug is slightly more interesting + * when it appears on the destination system: + * + * 1 helios.ee.lbl.gov (128.3.112.1) 0 ms 0 ms 0 ms + * 2 lilac-dmc.Berkeley.EDU (128.32.216.1) 39 ms 19 ms 39 ms + * 3 lilac-dmc.Berkeley.EDU (128.32.216.1) 19 ms 39 ms 19 ms + * 4 ccngw-ner-cc.Berkeley.EDU (128.32.136.23) 39 ms 40 ms 19 ms + * 5 ccn-nerif35.Berkeley.EDU (128.32.168.35) 39 ms 39 ms 39 ms + * 6 csgw.Berkeley.EDU (128.32.133.254) 39 ms 59 ms 39 ms + * 7 * * * + * 8 * * * + * 9 * * * + * 10 * * * + * 11 * * * + * 12 * * * + * 13 rip.Berkeley.EDU (128.32.131.22) 59 ms ! 39 ms ! 39 ms ! + * + * Notice that there are 12 "gateways" (13 is the final + * destination) and exactly the last half of them are "missing". + * What's really happening is that rip (a Sun-3 running Sun OS3.5) + * is using the ttl from our arriving datagram as the ttl in its + * icmp reply. So, the reply will time out on the return path + * (with no notice sent to anyone since icmp's aren't sent for + * icmp's) until we probe with a ttl that's at least twice the path + * length. I.e., rip is really only 7 hops away. A reply that + * returns with a ttl of 1 is a clue this problem exists. + * Traceroute prints a "!" after the time if the ttl is <= 1. + * Since vendors ship a lot of obsolete (DEC's Ultrix, Sun 3.x) or + * non-standard (HPUX) software, expect to see this problem + * frequently and/or take care picking the target host of your + * probes. + * + * Other possible annotations after the time are !H, !N, !P (got a host, + * network or protocol unreachable, respectively), !S or !F (source + * route failed or fragmentation needed -- neither of these should + * ever occur and the associated gateway is busted if you see one). If + * almost all the probes result in some kind of unreachable, traceroute + * will give up and exit. + * + * Notes + * ----- + * This program must be run by root or be setuid. (I suggest that + * you *don't* make it setuid -- casual use could result in a lot + * of unnecessary traffic on our poor, congested nets.) + * + * This program requires a kernel mod that does not appear in any + * system available from Berkeley: A raw ip socket using proto + * IPPROTO_RAW must interpret the data sent as an ip datagram (as + * opposed to data to be wrapped in a ip datagram). See the README + * file that came with the source to this program for a description + * of the mods I made to /sys/netinet/raw_ip.c. Your mileage may + * vary. But, again, ANY 4.x (x < 4) BSD KERNEL WILL HAVE TO BE + * MODIFIED TO RUN THIS PROGRAM. + * + * The udp port usage may appear bizarre (well, ok, it is bizarre). + * The problem is that an icmp message only contains 8 bytes of + * data from the original datagram. 8 bytes is the size of a udp + * header so, if we want to associate replies with the original + * datagram, the necessary information must be encoded into the + * udp header (the ip id could be used but there's no way to + * interlock with the kernel's assignment of ip id's and, anyway, + * it would have taken a lot more kernel hacking to allow this + * code to set the ip id). So, to allow two or more users to + * use traceroute simultaneously, we use this task's pid as the + * source port (the high bit is set to move the port number out + * of the "likely" range). To keep track of which probe is being + * replied to (so times and/or hop counts don't get confused by a + * reply that was delayed in transit), we increment the destination + * port number before each probe. + * + * Don't use this as a coding example. I was trying to find a + * routing problem and this code sort-of popped out after 48 hours + * without sleep. I was amazed it ever compiled, much less ran. + * + * I stole the idea for this program from Steve Deering. Since + * the first release, I've learned that had I attended the right + * IETF working group meetings, I also could have stolen it from Guy + * Almes or Matt Mathis. I don't know (or care) who came up with + * the idea first. I envy the originators' perspicacity and I'm + * glad they didn't keep the idea a secret. + * + * Tim Seaver, Ken Adelman and C. Philip Wood provided bug fixes and/or + * enhancements to the original distribution. + * + * I've hacked up a round-trip-route version of this that works by + * sending a loose-source-routed udp datagram through the destination + * back to yourself. Unfortunately, SO many gateways botch source + * routing, the thing is almost worthless. Maybe one day... + * + * -- Van Jacobson (van@ee.lbl.gov) + * Tue Dec 20 03:50:13 PST 1988 + */ + +#define TRACEROUTE_SO_DEBUG 0 + +/* TODO: undefs were uncommented - ??! we have config system for that! */ +/* probably ok to remove altogether */ +//#undef CONFIG_FEATURE_TRACEROUTE_VERBOSE +//#define CONFIG_FEATURE_TRACEROUTE_VERBOSE +//#undef CONFIG_FEATURE_TRACEROUTE_SOURCE_ROUTE +//#define CONFIG_FEATURE_TRACEROUTE_SOURCE_ROUTE +//#undef CONFIG_FEATURE_TRACEROUTE_USE_ICMP +//#define CONFIG_FEATURE_TRACEROUTE_USE_ICMP + +#include "inet_common.h" + +#include +#include +#include +#include +#include +#include + +#include "busybox.h" + + +/* + * Definitions for internet protocol version 4. + * Per RFC 791, September 1981. + */ +#define IPVERSION 4 + +#ifndef IPPROTO_ICMP +/* Grrrr.... */ +#define IPPROTO_ICMP 1 +#endif +#ifndef IPPROTO_IP +#define IPPROTO_IP 0 +#endif + +/* + * Overlay for ip header used by other protocols (tcp, udp). + */ +struct ipovly { + unsigned char ih_x1[9]; /* (unused) */ + unsigned char ih_pr; /* protocol */ + short ih_len; /* protocol length */ + struct in_addr ih_src; /* source internet address */ + struct in_addr ih_dst; /* destination internet address */ +}; + +/* + * UDP kernel structures and variables. + */ +struct udpiphdr { + struct ipovly ui_i; /* overlaid ip structure */ + struct udphdr ui_u; /* udp header */ +}; +#define ui_next ui_i.ih_next +#define ui_prev ui_i.ih_prev +#define ui_x1 ui_i.ih_x1 +#define ui_pr ui_i.ih_pr +#define ui_len ui_i.ih_len +#define ui_src ui_i.ih_src +#define ui_dst ui_i.ih_dst +#define ui_sport ui_u.uh_sport +#define ui_dport ui_u.uh_dport +#define ui_ulen ui_u.uh_ulen +#define ui_sum ui_u.uh_sum + + +/* Host name and address list */ +struct hostinfo { + char *name; + int n; + u_int32_t *addrs; +}; + +/* Data section of the probe packet */ +struct outdata { + unsigned char seq; /* sequence number of this packet */ + unsigned char ttl; /* ttl packet left with */ + struct timeval tv ATTRIBUTE_PACKED; /* time packet left */ +}; + +struct IFADDRLIST { + u_int32_t addr; + char device[sizeof(struct ifreq)]; +}; + + +static const char route[] = "/proc/net/route"; + +/* last inbound (icmp) packet */ +static unsigned char packet[512] ATTRIBUTE_ALIGNED(32); + +static struct ip *outip; /* last output (udp) packet */ +static struct udphdr *outudp; /* last output (udp) packet */ +static struct outdata *outdata; /* last output (udp) packet */ + +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP +static struct icmp *outicmp; /* last output (icmp) packet */ +#endif + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE +/* Maximum number of gateways (include room for one noop) */ +#define NGATEWAYS ((int)((MAX_IPOPTLEN - IPOPT_MINOFF - 1) / sizeof(u_int32_t))) +/* loose source route gateway list (including room for final destination) */ +static u_int32_t gwlist[NGATEWAYS + 1]; +#endif + +static int s; /* receive (icmp) socket file descriptor */ +static int sndsock; /* send (udp/icmp) socket file descriptor */ + +static struct sockaddr_storage whereto; /* Who to try to reach */ +static struct sockaddr_storage wherefrom; /* Who we are */ +static int packlen; /* total length of packet */ +static int minpacket; /* min ip packet size */ +static int maxpacket = 32 * 1024; /* max ip packet size */ +static int pmtu; /* Path MTU Discovery (RFC1191) */ + +static char *hostname; + +static u_short ident; +static u_short port = 32768 + 666; /* start udp dest port # for probe packets */ + +static int waittime = 5; /* time to wait for response (in seconds) */ +static int nflag; /* print addresses numerically */ +static int doipcksum = 1; /* calculate ip checksums by default */ + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE +static int optlen; /* length of ip options */ +#else +#define optlen 0 +#endif + +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP +static int useicmp; /* use icmp echo instead of udp packets */ +#endif +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE +static int verbose; +#endif + +/* + * Return the interface list + */ +static int +ifaddrlist(struct IFADDRLIST **ipaddrp) +{ + int fd, nipaddr; +#ifdef HAVE_SOCKADDR_SA_LEN + int n; +#endif + struct ifreq *ifrp, *ifend, *ifnext; + struct sockaddr_in *addr_sin; + struct IFADDRLIST *al; + struct ifconf ifc; + struct ifreq ibuf[(32 * 1024) / sizeof(struct ifreq)], ifr; + struct IFADDRLIST *st_ifaddrlist; + + fd = xsocket(AF_INET, SOCK_DGRAM, 0); + + ifc.ifc_len = sizeof(ibuf); + ifc.ifc_buf = (caddr_t)ibuf; + + if (ioctl(fd, SIOCGIFCONF, (char *)&ifc) < 0 || + ifc.ifc_len < sizeof(struct ifreq)) { + if (errno == EINVAL) + bb_error_msg_and_die( + "SIOCGIFCONF: ifreq struct too small (%d bytes)", + (int)sizeof(ibuf)); + else + bb_perror_msg_and_die("SIOCGIFCONF"); + } + ifrp = ibuf; + ifend = (struct ifreq *)((char *)ibuf + ifc.ifc_len); + + nipaddr = 1 + (ifc.ifc_len / sizeof(struct ifreq)); + st_ifaddrlist = xzalloc(nipaddr * sizeof(struct IFADDRLIST)); + al = st_ifaddrlist; + nipaddr = 0; + + for (; ifrp < ifend; ifrp = ifnext) { +#ifdef HAVE_SOCKADDR_SA_LEN + n = ifrp->ifr_addr.sa_len + sizeof(ifrp->ifr_name); + if (n < sizeof(*ifrp)) + ifnext = ifrp + 1; + else + ifnext = (struct ifreq *)((char *)ifrp + n); + if (ifrp->ifr_addr.sa_family != AF_INET) + continue; +#else + ifnext = ifrp + 1; +#endif + /* + * Need a template to preserve address info that is + * used below to locate the next entry. (Otherwise, + * SIOCGIFFLAGS stomps over it because the requests + * are returned in a union.) + */ + strncpy(ifr.ifr_name, ifrp->ifr_name, sizeof(ifr.ifr_name)); + if (ioctl(fd, SIOCGIFFLAGS, (char *)&ifr) < 0) { + if (errno == ENXIO) + continue; + bb_perror_msg_and_die("SIOCGIFFLAGS: %.*s", + (int)sizeof(ifr.ifr_name), ifr.ifr_name); + } + + /* Must be up */ + if ((ifr.ifr_flags & IFF_UP) == 0) + continue; + + safe_strncpy(al->device, ifr.ifr_name, sizeof(ifr.ifr_name) + 1); +#ifdef sun + /* Ignore sun virtual interfaces */ + if (strchr(al->device, ':') != NULL) + continue; +#endif + if (ioctl(fd, SIOCGIFADDR, (char *)&ifr) < 0) + bb_perror_msg_and_die("SIOCGIFADDR: %s", al->device); + + addr_sin = (struct sockaddr_in *)&ifr.ifr_addr; + al->addr = addr_sin->sin_addr.s_addr; + ++al; + ++nipaddr; + } + if (nipaddr == 0) + bb_error_msg_and_die ("can't find any network interfaces"); + (void)close(fd); + + *ipaddrp = st_ifaddrlist; + return nipaddr; +} + + +static void +setsin(struct sockaddr_in *addr_sin, u_int32_t addr) +{ + memset(addr_sin, 0, sizeof(*addr_sin)); +#ifdef HAVE_SOCKADDR_SA_LEN + addr_sin->sin_len = sizeof(*addr_sin); +#endif + addr_sin->sin_family = AF_INET; + addr_sin->sin_addr.s_addr = addr; +} + + +/* + * Return the source address for the given destination address + */ +static void +findsaddr(const struct sockaddr_in *to, struct sockaddr_in *from) +{ + int i, n; + FILE *f; + u_int32_t mask; + u_int32_t dest, tmask; + struct IFADDRLIST *al; + char buf[256], tdevice[256], device[256]; + + f = xfopen(route, "r"); + + /* Find the appropriate interface */ + n = 0; + mask = 0; + device[0] = '\0'; + while (fgets(buf, sizeof(buf), f) != NULL) { + ++n; + if (n == 1 && strncmp(buf, "Iface", 5) == 0) + continue; + if ((i = sscanf(buf, "%255s %x %*s %*s %*s %*s %*s %x", + tdevice, &dest, &tmask)) != 3) + bb_error_msg_and_die ("junk in buffer"); + if ((to->sin_addr.s_addr & tmask) == dest && + (tmask > mask || mask == 0)) { + mask = tmask; + strcpy(device, tdevice); + } + } + fclose(f); + + if (device[0] == '\0') + bb_error_msg_and_die ("can't find interface"); + + /* Get the interface address list */ + n = ifaddrlist(&al); + + /* Find our appropriate source address */ + for (i = n; i > 0; --i, ++al) + if (strcmp(device, al->device) == 0) + break; + if (i <= 0) + bb_error_msg_and_die("can't find interface %s", device); + + setsin(from, al->addr); +} + +/* +"Usage: %s [-dFIlnrvx] [-g gateway] [-i iface] [-f first_ttl]\n" +"\t[-m max_ttl] [ -p port] [-q nqueries] [-s src_addr] [-t tos]\n" +"\t[-w waittime] [-z pausemsecs] host [packetlen]" + +*/ + +/* + * Subtract 2 timeval structs: out = out - in. + * Out is assumed to be >= in. + */ +static inline void +tvsub(struct timeval *out, struct timeval *in) +{ + + if ((out->tv_usec -= in->tv_usec) < 0) { + --out->tv_sec; + out->tv_usec += 1000000; + } + out->tv_sec -= in->tv_sec; +} + +static int +wait_for_reply(int sock, struct sockaddr_in *fromp, const struct timeval *tp) +{ + fd_set fds; + struct timeval now, tvwait; + struct timezone tz; + int cc = 0; + socklen_t fromlen = sizeof(*fromp); + + FD_ZERO(&fds); + FD_SET(sock, &fds); + + tvwait.tv_sec = tp->tv_sec + waittime; + tvwait.tv_usec = tp->tv_usec; + (void)gettimeofday(&now, &tz); + tvsub(&tvwait, &now); + + if (select(sock + 1, &fds, NULL, NULL, &tvwait) > 0) + cc = recvfrom(sock, (char *)packet, sizeof(packet), 0, + (struct sockaddr *)fromp, &fromlen); + + return cc; +} + +/* + * Checksum routine for Internet Protocol family headers (C Version) + */ +static u_short +in_cksum(u_short *addr, int len) +{ + int nleft = len; + u_short *w = addr; + u_short answer; + int sum = 0; + + /* + * Our algorithm is simple, using a 32 bit accumulator (sum), + * we add sequential 16 bit words to it, and at the end, fold + * back all the carry bits from the top 16 bits into the lower + * 16 bits. + */ + while (nleft > 1) { + sum += *w++; + nleft -= 2; + } + + /* mop up an odd byte, if necessary */ + if (nleft == 1) + sum += *(unsigned char *)w; + + /* + * add back carry outs from top 16 bits to low 16 bits + */ + sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ + sum += (sum >> 16); /* add carry */ + answer = ~sum; /* truncate to 16 bits */ + return answer; +} + + +static void +send_probe(int seq, int ttl, struct timeval *tp) +{ + int cc; + struct udpiphdr *ui, *oui; + struct ip tip; + + outip->ip_ttl = ttl; + outip->ip_id = htons(ident + seq); + + /* + * In most cases, the kernel will recalculate the ip checksum. + * But we must do it anyway so that the udp checksum comes out + * right. + */ + if (doipcksum) { + outip->ip_sum = + in_cksum((u_short *)outip, sizeof(*outip) + optlen); + if (outip->ip_sum == 0) + outip->ip_sum = 0xffff; + } + + /* Payload */ + outdata->seq = seq; + outdata->ttl = ttl; + memcpy(&outdata->tv, tp, sizeof(outdata->tv)); + +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP + if (useicmp) + outicmp->icmp_seq = htons(seq); + else +#endif + outudp->dest = htons(port + seq); + +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP + if (useicmp) { + /* Always calculate checksum for icmp packets */ + outicmp->icmp_cksum = 0; + outicmp->icmp_cksum = in_cksum((u_short *)outicmp, + packlen - (sizeof(*outip) + optlen)); + if (outicmp->icmp_cksum == 0) + outicmp->icmp_cksum = 0xffff; + } else +#endif + if (doipcksum) { + /* Checksum (we must save and restore ip header) */ + tip = *outip; + ui = (struct udpiphdr *)outip; + oui = (struct udpiphdr *)&tip; + /* Easier to zero and put back things that are ok */ + memset((char *)ui, 0, sizeof(ui->ui_i)); + ui->ui_src = oui->ui_src; + ui->ui_dst = oui->ui_dst; + ui->ui_pr = oui->ui_pr; + ui->ui_len = outudp->len; + outudp->check = 0; + outudp->check = in_cksum((u_short *)ui, packlen); + if (outudp->check == 0) + outudp->check = 0xffff; + *outip = tip; + } + +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE + /* XXX undocumented debugging hack */ + if (verbose > 1) { + const u_short *sp; + int nshorts, i; + + sp = (u_short *)outip; + nshorts = (u_int)packlen / sizeof(u_short); + i = 0; + printf("[ %d bytes", packlen); + while (--nshorts >= 0) { + if ((i++ % 8) == 0) + printf("\n\t"); + printf(" %04x", ntohs(*sp)); + sp++; + } + if (packlen & 1) { + if ((i % 8) == 0) + printf("\n\t"); + printf(" %02x", *(unsigned char *)sp); + } + printf("]\n"); + } +#endif + +#if !defined(IP_HDRINCL) && defined(IP_TTL) + if (setsockopt(sndsock, IPPROTO_IP, IP_TTL, + (char *)&ttl, sizeof(ttl)) < 0) { + bb_perror_msg_and_die("setsockopt ttl %d", ttl); + } +#endif + + cc = sendto(sndsock, (char *)outip, + packlen, 0, (struct sockaddr *)&whereto, sizeof(whereto)); + if (cc < 0 || cc != packlen) { + if (cc < 0) + bb_perror_msg_and_die("sendto"); + printf("%s: wrote %s %d chars, ret=%d\n", + applet_name, hostname, packlen, cc); + (void)fflush(stdout); + } +} + +static inline double +deltaT(struct timeval *t1p, struct timeval *t2p) +{ + double dt; + + dt = (double)(t2p->tv_sec - t1p->tv_sec) * 1000.0 + + (double)(t2p->tv_usec - t1p->tv_usec) / 1000.0; + return dt; +} + +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE +/* + * Convert an ICMP "type" field to a printable string. + */ +static inline const char * +pr_type(unsigned char t) +{ + static const char * const ttab[] = { + "Echo Reply", "ICMP 1", "ICMP 2", "Dest Unreachable", + "Source Quench", "Redirect", "ICMP 6", "ICMP 7", + "Echo", "Router Advert", "Router Solicit", "Time Exceeded", + "Param Problem", "Timestamp", "Timestamp Reply", "Info Request", + "Info Reply", "Mask Request", "Mask Reply" + }; + + if (t > 18) + return "OUT-OF-RANGE"; + + return ttab[t]; +} +#endif + +static int +packet_ok(unsigned char *buf, int cc, struct sockaddr_in *from, int seq) +{ + struct icmp *icp; + unsigned char type, code; + int hlen; + struct ip *ip; + + ip = (struct ip *) buf; + hlen = ip->ip_hl << 2; + if (cc < hlen + ICMP_MINLEN) { +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE + if (verbose) + printf("packet too short (%d bytes) from %s\n", cc, + inet_ntoa(from->sin_addr)); +#endif + return 0; + } + cc -= hlen; + icp = (struct icmp *)(buf + hlen); + type = icp->icmp_type; + code = icp->icmp_code; + /* Path MTU Discovery (RFC1191) */ + if (code != ICMP_UNREACH_NEEDFRAG) + pmtu = 0; + else { + pmtu = ntohs(icp->icmp_nextmtu); + } + if ((type == ICMP_TIMXCEED && code == ICMP_TIMXCEED_INTRANS) || + type == ICMP_UNREACH || type == ICMP_ECHOREPLY) { + struct ip *hip; + struct udphdr *up; + + hip = &icp->icmp_ip; + hlen = hip->ip_hl << 2; +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP + if (useicmp) { + struct icmp *hicmp; + + /* XXX */ + if (type == ICMP_ECHOREPLY && + icp->icmp_id == htons(ident) && + icp->icmp_seq == htons(seq)) + return -2; + + hicmp = (struct icmp *)((unsigned char *)hip + hlen); + /* XXX 8 is a magic number */ + if (hlen + 8 <= cc && + hip->ip_p == IPPROTO_ICMP && + hicmp->icmp_id == htons(ident) && + hicmp->icmp_seq == htons(seq)) + return (type == ICMP_TIMXCEED ? -1 : code + 1); + } else +#endif + { + up = (struct udphdr *)((unsigned char *)hip + hlen); + /* XXX 8 is a magic number */ + if (hlen + 12 <= cc && + hip->ip_p == IPPROTO_UDP && + up->source == htons(ident) && + up->dest == htons(port + seq)) + return (type == ICMP_TIMXCEED ? -1 : code + 1); + } + } +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE + if (verbose) { + int i; + u_int32_t *lp = (u_int32_t *)&icp->icmp_ip; + + printf("\n%d bytes from %s to " + "%s: icmp type %d (%s) code %d\n", + cc, inet_ntoa(from->sin_addr), + inet_ntoa(ip->ip_dst), type, pr_type(type), icp->icmp_code); + for (i = 4; i < cc ; i += sizeof(*lp)) + printf("%2d: x%8.8x\n", i, *lp++); + } +#endif + return 0; +} + + +/* + * Construct an Internet address representation. + * If the nflag has been supplied, give + * numeric value, otherwise try for symbolic name. + */ +static inline void +inetname(struct sockaddr_in *from) +{ + const char *n = NULL; + const char *ina; + char name[257]; + + if (!nflag && from->sin_addr.s_addr != INADDR_ANY) { + if (INET_rresolve(name, sizeof(name), from, 0x4000, 0xffffffff) >= 0) + n = name; + } + ina = inet_ntoa(from->sin_addr); + if (nflag) + printf(" %s", ina); + else + printf(" %s (%s)", (n ? n : ina), ina); +} + +static inline void +print(unsigned char *buf, int cc, struct sockaddr_in *from) +{ + struct ip *ip; + int hlen; + + ip = (struct ip *) buf; + hlen = ip->ip_hl << 2; + cc -= hlen; + + inetname(from); +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE + if (verbose) + printf(" %d bytes to %s", cc, inet_ntoa (ip->ip_dst)); +#endif +} + + +static struct hostinfo * +gethostinfo(const char *host) +{ + int n; + struct hostent *hp; + struct hostinfo *hi; + char **p; + u_int32_t addr, *ap; + + hi = xzalloc(sizeof(*hi)); + addr = inet_addr(host); + if ((int32_t)addr != -1) { + hi->name = xstrdup(host); + hi->n = 1; + hi->addrs = xzalloc(sizeof(hi->addrs[0])); + hi->addrs[0] = addr; + return hi; + } + + hp = xgethostbyname(host); + if (hp->h_addrtype != AF_INET || hp->h_length != 4) + bb_perror_msg_and_die("bad host %s", host); + hi->name = xstrdup(hp->h_name); + for (n = 0, p = hp->h_addr_list; *p != NULL; ++n, ++p) + continue; + hi->n = n; + hi->addrs = xzalloc(n * sizeof(hi->addrs[0])); + for (ap = hi->addrs, p = hp->h_addr_list; *p != NULL; ++ap, ++p) + memcpy(ap, *p, sizeof(*ap)); + return hi; +} + +static void +freehostinfo(struct hostinfo *hi) +{ + free(hi->name); + hi->name = NULL; + free((char *)hi->addrs); + free((char *)hi); +} + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE +static void +getaddr(u_int32_t *ap, const char *host) +{ + struct hostinfo *hi; + + hi = gethostinfo(host); + *ap = hi->addrs[0]; + freehostinfo(hi); +} +#endif + + +int +traceroute_main(int argc, char *argv[]) +{ + static const int on = 1; + + int code, n; + unsigned char *outp; + u_int32_t *ap; + struct sockaddr_in *from = (struct sockaddr_in *)&wherefrom; + struct sockaddr_in *to = (struct sockaddr_in *)&whereto; + struct hostinfo *hi; + int ttl, probe, i; + int seq = 0; + int tos = 0; + char *tos_str = NULL; + char *source = NULL; + unsigned long op; + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + int lsrr = 0; +#endif + u_short off = 0; + struct IFADDRLIST *al; + char *device = NULL; + int max_ttl = 30; + char *max_ttl_str = NULL; + char *port_str = NULL; + int nprobes = 3; + char *nprobes_str = NULL; + char *waittime_str = NULL; + u_int pausemsecs = 0; + char *pausemsecs_str = NULL; + int first_ttl = 1; + char *first_ttl_str = NULL; +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + llist_t *sourse_route_list = NULL; +#endif + + opterr = 0; +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + opt_complementary = "x-x:g::"; +#else + opt_complementary = "x-x"; +#endif + + op = getopt32(argc, argv, "FIlnrdvxt:i:m:p:q:s:w:z:f:" +#define USAGE_OP_DONT_FRAGMNT (1<<0) /* F */ +#define USAGE_OP_USE_ICMP (1<<1) /* I */ +#define USAGE_OP_TTL_FLAG (1<<2) /* l */ +#define USAGE_OP_ADDR_NUM (1<<3) /* n */ +#define USAGE_OP_BYPASS_ROUTE (1<<4) /* r */ +#define USAGE_OP_DEBUG (1<<5) /* d */ +#define USAGE_OP_VERBOSE (1<<6) /* v */ +#define USAGE_OP_IP_CHKSUM (1<<7) /* x */ + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + "g:" +#endif + , &tos_str, &device, &max_ttl_str, &port_str, &nprobes_str, + &source, &waittime_str, &pausemsecs_str, &first_ttl_str +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + , &sourse_route_list +#endif + ); + + if (op & USAGE_OP_DONT_FRAGMNT) + off = IP_DF; +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP + useicmp = op & USAGE_OP_USE_ICMP; +#endif + nflag = op & USAGE_OP_ADDR_NUM; +#if ENABLE_FEATURE_TRACEROUTE_VERBOSE + verbose = op & USAGE_OP_VERBOSE; +#endif + if (op & USAGE_OP_IP_CHKSUM) { + doipcksum = 0; + bb_error_msg("warning: ip checksums disabled"); + } + if (tos_str) + tos = xatoul_range(tos_str, 0, 255); + if (max_ttl_str) + max_ttl = xatoul_range(max_ttl_str, 1, 255); + if (port_str) + port = xatou16(port_str); + if (nprobes_str) + nprobes = xatoul_range(nprobes_str, 1, INT_MAX); + if (source) { + /* + * set the ip source address of the outbound + * probe (e.g., on a multi-homed host). + */ + if (getuid()) bb_error_msg_and_die("-s %s: permission denied", source); + } + if (waittime_str) + waittime = xatoul_range(waittime_str, 2, 24 * 60 * 60); + if (pausemsecs_str) + pausemsecs = xatoul_range(pausemsecs_str, 0, 60 * 60 * 1000); + if (first_ttl_str) + first_ttl = xatoul_range(first_ttl_str, 1, 255); + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE + if (sourse_route_list) { + llist_t *l_sr; + + for(l_sr = sourse_route_list; l_sr; ) { + if (lsrr >= NGATEWAYS) + bb_error_msg_and_die("no more than %d gateways", NGATEWAYS); + getaddr(gwlist + lsrr, l_sr->data); + ++lsrr; + l_sr = l_sr->link; + free(sourse_route_list); + sourse_route_list = l_sr; + } + optlen = (lsrr + 1) * sizeof(gwlist[0]); + } +#endif + + if (first_ttl > max_ttl) { + bb_error_msg_and_die( + "first ttl (%d) may not be greater than max ttl (%d)", + first_ttl, max_ttl); + } + + minpacket = sizeof(*outip) + sizeof(*outdata) + optlen; + +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP + if (useicmp) + minpacket += 8; /* XXX magic number */ + else +#endif + minpacket += sizeof(*outudp); + packlen = minpacket; /* minimum sized packet */ + + /* Process destination and optional packet size */ + switch (argc - optind) { + + case 2: + packlen = xatoul_range(argv[optind + 1], minpacket, maxpacket); + /* Fall through */ + + case 1: + hostname = argv[optind]; + hi = gethostinfo(hostname); + setsin(to, hi->addrs[0]); + if (hi->n > 1) + bb_error_msg("warning: %s has multiple addresses; using %s", + hostname, inet_ntoa(to->sin_addr)); + hostname = hi->name; + hi->name = NULL; + freehostinfo(hi); + break; + + default: + bb_show_usage(); + } + + /* Insure the socket fds won't be 0, 1 or 2 */ + do n = xopen(bb_dev_null, O_RDONLY); while (n < 2); + if (n > 2) + close(n); + + s = xsocket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + +#if TRACEROUTE_SO_DEBUG + if (op & USAGE_OP_DEBUG) + (void)setsockopt(s, SOL_SOCKET, SO_DEBUG, (char *)&on, + sizeof(on)); +#endif + if (op & USAGE_OP_BYPASS_ROUTE) + (void)setsockopt(s, SOL_SOCKET, SO_DONTROUTE, (char *)&on, + sizeof(on)); + + sndsock = xsocket(AF_INET, SOCK_RAW, IPPROTO_RAW); + +#if ENABLE_FEATURE_TRACEROUTE_SOURCE_ROUTE +#if defined(IP_OPTIONS) + if (lsrr > 0) { + unsigned char optlist[MAX_IPOPTLEN]; + + /* final hop */ + gwlist[lsrr] = to->sin_addr.s_addr; + ++lsrr; + + /* force 4 byte alignment */ + optlist[0] = IPOPT_NOP; + /* loose source route option */ + optlist[1] = IPOPT_LSRR; + i = lsrr * sizeof(gwlist[0]); + optlist[2] = i + 3; + /* Pointer to LSRR addresses */ + optlist[3] = IPOPT_MINOFF; + memcpy(optlist + 4, gwlist, i); + + if ((setsockopt(sndsock, IPPROTO_IP, IP_OPTIONS, + (char *)optlist, i + sizeof(gwlist[0]))) < 0) { + bb_perror_msg_and_die("IP_OPTIONS"); + } + } +#endif /* IP_OPTIONS */ +#endif /* CONFIG_FEATURE_TRACEROUTE_SOURCE_ROUTE */ + +#ifdef SO_SNDBUF + if (setsockopt(sndsock, SOL_SOCKET, SO_SNDBUF, (char *)&packlen, + sizeof(packlen)) < 0) { + bb_perror_msg_and_die("SO_SNDBUF"); + } +#endif +#ifdef IP_HDRINCL + if (setsockopt(sndsock, IPPROTO_IP, IP_HDRINCL, (char *)&on, + sizeof(on)) < 0 && errno != ENOPROTOOPT) { + bb_perror_msg_and_die("IP_HDRINCL"); + } +#else +#ifdef IP_TOS + if (tos_str && setsockopt(sndsock, IPPROTO_IP, IP_TOS, + (char *)&tos, sizeof(tos)) < 0) { + bb_perror_msg_and_die("setsockopt tos %d", tos); + } +#endif +#endif +#if TRACEROUTE_SO_DEBUG + if (op & USAGE_OP_DEBUG) + (void)setsockopt(sndsock, SOL_SOCKET, SO_DEBUG, (char *)&on, + sizeof(on)); +#endif + if (op & USAGE_OP_BYPASS_ROUTE) + (void)setsockopt(sndsock, SOL_SOCKET, SO_DONTROUTE, (char *)&on, + sizeof(on)); + + /* Revert to non-privileged user after opening sockets */ + xsetgid(getgid()); + xsetuid(getuid()); + + outip = (struct ip *)xzalloc(packlen); + + outip->ip_v = IPVERSION; + if (tos_str) + outip->ip_tos = tos; + outip->ip_len = htons(packlen); + outip->ip_off = htons(off); + outp = (unsigned char *)(outip + 1); + outip->ip_dst = to->sin_addr; + + outip->ip_hl = (outp - (unsigned char *)outip) >> 2; + ident = (getpid() & 0xffff) | 0x8000; +#if ENABLE_FEATURE_TRACEROUTE_USE_ICMP + if (useicmp) { + outip->ip_p = IPPROTO_ICMP; + + outicmp = (struct icmp *)outp; + outicmp->icmp_type = ICMP_ECHO; + outicmp->icmp_id = htons(ident); + + outdata = (struct outdata *)(outp + 8); /* XXX magic number */ + } else +#endif + { + outip->ip_p = IPPROTO_UDP; + + outudp = (struct udphdr *)outp; + outudp->source = htons(ident); + outudp->len = + htons((u_short)(packlen - (sizeof(*outip) + optlen))); + outdata = (struct outdata *)(outudp + 1); + } + + /* Get the interface address list */ + n = ifaddrlist(&al); + + /* Look for a specific device */ + if (device != NULL) { + for (i = n; i > 0; --i, ++al) + if (strcmp(device, al->device) == 0) + break; + if (i <= 0) { + bb_error_msg_and_die("can't find interface %s", device); + } + } + + /* Determine our source address */ + if (source == NULL) { + /* + * If a device was specified, use the interface address. + * Otherwise, try to determine our source address. + */ + if (device != NULL) + setsin(from, al->addr); + findsaddr(to, from); + } else { + hi = gethostinfo(source); + source = hi->name; + hi->name = NULL; + /* + * If the device was specified make sure it + * corresponds to the source address specified. + * Otherwise, use the first address (and warn if + * there are more than one). + */ + if (device != NULL) { + for (i = hi->n, ap = hi->addrs; i > 0; --i, ++ap) + if (*ap == al->addr) + break; + if (i <= 0) { + bb_error_msg_and_die( + "%s is not on interface %s", + source, device); + } + setsin(from, *ap); + } else { + setsin(from, hi->addrs[0]); + if (hi->n > 1) + bb_error_msg( + "Warning: %s has multiple addresses; using %s", + source, inet_ntoa(from->sin_addr)); + } + freehostinfo(hi); + } + + outip->ip_src = from->sin_addr; +#ifndef IP_HDRINCL + xbind(sndsock, (struct sockaddr *)from, sizeof(*from)); +#endif + + fprintf(stderr, "traceroute to %s (%s)", hostname, inet_ntoa(to->sin_addr)); + if (source) + fprintf(stderr, " from %s", source); + fprintf(stderr, ", %d hops max, %d byte packets\n", max_ttl, packlen); + (void)fflush(stderr); + + for (ttl = first_ttl; ttl <= max_ttl; ++ttl) { + u_int32_t lastaddr = 0; + int gotlastaddr = 0; + int got_there = 0; + int unreachable = 0; + int sentfirst = 0; + + printf("%2d ", ttl); + for (probe = 0; probe < nprobes; ++probe) { + int cc; + struct timeval t1, t2; + struct timezone tz; + struct ip *ip; + + if (sentfirst && pausemsecs > 0) + usleep(pausemsecs * 1000); + (void)gettimeofday(&t1, &tz); + send_probe(++seq, ttl, &t1); + ++sentfirst; + while ((cc = wait_for_reply(s, from, &t1)) != 0) { + (void)gettimeofday(&t2, &tz); + i = packet_ok(packet, cc, from, seq); + /* Skip short packet */ + if (i == 0) + continue; + if (!gotlastaddr || + from->sin_addr.s_addr != lastaddr) { + print(packet, cc, from); + lastaddr = from->sin_addr.s_addr; + ++gotlastaddr; + } + printf(" %.3f ms", deltaT(&t1, &t2)); + ip = (struct ip *)packet; + if (op & USAGE_OP_TTL_FLAG) + printf(" (%d)", ip->ip_ttl); + if (i == -2) { + if (ip->ip_ttl <= 1) + printf(" !"); + ++got_there; + break; + } + /* time exceeded in transit */ + if (i == -1) + break; + code = i - 1; + switch (code) { + + case ICMP_UNREACH_PORT: + if (ip->ip_ttl <= 1) + printf(" !"); + ++got_there; + break; + + case ICMP_UNREACH_NET: + ++unreachable; + printf(" !N"); + break; + + case ICMP_UNREACH_HOST: + ++unreachable; + printf(" !H"); + break; + + case ICMP_UNREACH_PROTOCOL: + ++got_there; + printf(" !P"); + break; + + case ICMP_UNREACH_NEEDFRAG: + ++unreachable; + printf(" !F-%d", pmtu); + break; + + case ICMP_UNREACH_SRCFAIL: + ++unreachable; + printf(" !S"); + break; + + case ICMP_UNREACH_FILTER_PROHIB: + case ICMP_UNREACH_NET_PROHIB: /* misuse */ + ++unreachable; + printf(" !A"); + break; + + case ICMP_UNREACH_HOST_PROHIB: + ++unreachable; + printf(" !C"); + break; + + case ICMP_UNREACH_HOST_PRECEDENCE: + ++unreachable; + printf(" !V"); + break; + + case ICMP_UNREACH_PRECEDENCE_CUTOFF: + ++unreachable; + printf(" !C"); + break; + + case ICMP_UNREACH_NET_UNKNOWN: + case ICMP_UNREACH_HOST_UNKNOWN: + ++unreachable; + printf(" !U"); + break; + + case ICMP_UNREACH_ISOLATED: + ++unreachable; + printf(" !I"); + break; + + case ICMP_UNREACH_TOSNET: + case ICMP_UNREACH_TOSHOST: + ++unreachable; + printf(" !T"); + break; + + default: + ++unreachable; + printf(" !<%d>", code); + break; + } + break; + } + if (cc == 0) + printf(" *"); + (void)fflush(stdout); + } + putchar('\n'); + if (got_there || + (unreachable > 0 && unreachable >= nprobes - 1)) + break; + } + return 0; +} diff --git a/networking/udhcp/Config.in b/networking/udhcp/Config.in new file mode 100644 index 000000000..f633473eb --- /dev/null +++ b/networking/udhcp/Config.in @@ -0,0 +1,67 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +config APP_UDHCPD + bool "udhcp Server (udhcpd)" + default n + help + uDHCPd is a DHCP server geared primarily toward embedded systems, + while striving to be fully functional and RFC compliant. + + See http://udhcp.busybox.net for further details. + +config APP_DHCPRELAY + bool "dhcprelay" + default n + depends on APP_UDHCPD + help + dhcprelay listens for dhcp requests on one or more interfaces + and forwards these requests to a different interface or dhcp + server. + +config APP_DUMPLEASES + bool "Lease display utility (dumpleases)" + default n + depends on APP_UDHCPD + help + dumpleases displays the leases written out by the udhcpd server. + Lease times are stored in the file by time remaining in lease, or + by the absolute time that it expires in seconds from epoch. + + See http://udhcp.busybox.net for further details. + +config APP_UDHCPC + bool "udhcp Client (udhcpc)" + default n + help + uDHCPc is a DHCP client geared primarily toward embedded systems, + while striving to be fully functional and RFC compliant. + + The udhcp client negotiates a lease with the DHCP server and + notifies a set of scripts when a lease is obtained or lost. + + See http://udhcp.busybox.net for further details. + +config FEATURE_UDHCP_SYSLOG + bool "Log udhcp messages to syslog" + default n + depends on APP_UDHCPD || APP_UDHCPC + select FEATURE_SYSLOG + help + If not daemonized, udhcpd prints its messages to stdout/stderr. + If this option is selected, it will also log them to syslog. + + See http://udhcp.busybox.net for further details. + +config FEATURE_UDHCP_DEBUG + bool "Compile udhcp with noisy debugging messages" + default n + depends on APP_UDHCPD || APP_UDHCPC + help + If selected, udhcpd will output extra debugging output. If using + this option, compile uDHCP with "-g", and do not fork the daemon to + the background. + + See http://udhcp.busybox.net for further details. diff --git a/networking/udhcp/Kbuild b/networking/udhcp/Kbuild new file mode 100644 index 000000000..dc2c01f61 --- /dev/null +++ b/networking/udhcp/Kbuild @@ -0,0 +1,18 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2004 by Erik Andersen +# +# Licensed under the GPL v2 or later, see the file LICENSE in this tarball. +# + +lib-y:= +lib-$(CONFIG_APP_UDHCPC) += common.o options.o packet.o pidfile.o \ + signalpipe.o socket.o +lib-$(CONFIG_APP_UDHCPD) += common.o options.o packet.o pidfile.o \ + signalpipe.o socket.o +lib-$(CONFIG_APP_UDHCPC) += dhcpc.o clientpacket.o clientsocket.o \ + script.o +lib-$(CONFIG_APP_UDHCPD) += dhcpd.o arpping.o files.o leases.o \ + serverpacket.o static_leases.o +lib-$(CONFIG_APP_DUMPLEASES) += dumpleases.o +lib-$(CONFIG_APP_DHCPRELAY) += dhcprelay.o diff --git a/networking/udhcp/arpping.c b/networking/udhcp/arpping.c new file mode 100644 index 000000000..9c8b9c562 --- /dev/null +++ b/networking/udhcp/arpping.c @@ -0,0 +1,114 @@ +/* vi: set sw=4 ts=4: */ +/* + * arpping.c + * + * Mostly stolen from: dhcpcd - DHCP client daemon + * by Yoichi Hariguchi + */ + +#include +#include + +#include "common.h" +#include "dhcpd.h" + + +struct arpMsg { + /* Ethernet header */ + u_char h_dest[6]; /* destination ether addr */ + u_char h_source[6]; /* source ether addr */ + u_short h_proto; /* packet type ID field */ + + /* ARP packet */ + uint16_t htype; /* hardware type (must be ARPHRD_ETHER) */ + uint16_t ptype; /* protocol type (must be ETH_P_IP) */ + uint8_t hlen; /* hardware address length (must be 6) */ + uint8_t plen; /* protocol address length (must be 4) */ + uint16_t operation; /* ARP opcode */ + uint8_t sHaddr[6]; /* sender's hardware address */ + uint8_t sInaddr[4]; /* sender's IP address */ + uint8_t tHaddr[6]; /* target's hardware address */ + uint8_t tInaddr[4]; /* target's IP address */ + uint8_t pad[18]; /* pad for min. Ethernet payload (60 bytes) */ +} ATTRIBUTE_PACKED; + +/* args: yiaddr - what IP to ping + * ip - our ip + * mac - our arp address + * interface - interface to use + * retn: 1 addr free + * 0 addr used + * -1 error + */ + +/* FIXME: match response against chaddr */ +int arpping(uint32_t yiaddr, uint32_t ip, uint8_t *mac, char *interface) +{ + int timeout = 2; + int s; /* socket */ + int rv = 1; /* return value */ + struct sockaddr addr; /* for interface name */ + struct arpMsg arp; + fd_set fdset; + struct timeval tm; + time_t prevTime; + + + s = socket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ARP)); + if (s == -1) { + bb_perror_msg(bb_msg_can_not_create_raw_socket); + return -1; + } + + if (setsockopt_broadcast(s) == -1) { + bb_perror_msg("cannot setsocketopt on raw socket"); + close(s); + return -1; + } + + /* send arp request */ + memset(&arp, 0, sizeof(arp)); + memcpy(arp.h_dest, MAC_BCAST_ADDR, 6); /* MAC DA */ + memcpy(arp.h_source, mac, 6); /* MAC SA */ + arp.h_proto = htons(ETH_P_ARP); /* protocol type (Ethernet) */ + arp.htype = htons(ARPHRD_ETHER); /* hardware type */ + arp.ptype = htons(ETH_P_IP); /* protocol type (ARP message) */ + arp.hlen = 6; /* hardware address length */ + arp.plen = 4; /* protocol address length */ + arp.operation = htons(ARPOP_REQUEST); /* ARP op code */ + memcpy(arp.sInaddr, &ip, sizeof(ip)); /* source IP address */ + memcpy(arp.sHaddr, mac, 6); /* source hardware address */ + memcpy(arp.tInaddr, &yiaddr, sizeof(yiaddr)); /* target IP address */ + + memset(&addr, 0, sizeof(addr)); + strcpy(addr.sa_data, interface); + if (sendto(s, &arp, sizeof(arp), 0, &addr, sizeof(addr)) < 0) + rv = 0; + + /* wait arp reply, and check it */ + tm.tv_usec = 0; + prevTime = uptime(); + while (timeout > 0) { + FD_ZERO(&fdset); + FD_SET(s, &fdset); + tm.tv_sec = timeout; + if (select(s + 1, &fdset, (fd_set *) NULL, (fd_set *) NULL, &tm) < 0) { + bb_perror_msg("error on ARPING request"); + if (errno != EINTR) rv = 0; + } else if (FD_ISSET(s, &fdset)) { + if (recv(s, &arp, sizeof(arp), 0) < 0 ) rv = 0; + if (arp.operation == htons(ARPOP_REPLY) && + memcmp(arp.tHaddr, mac, 6) == 0 && + *((uint32_t *) arp.sInaddr) == yiaddr) { + DEBUG("Valid arp reply received for this address"); + rv = 0; + break; + } + } + timeout -= uptime() - prevTime; + prevTime = uptime(); + } + close(s); + DEBUG("%salid arp replies for this address", rv ? "No v" : "V"); + return rv; +} diff --git a/networking/udhcp/clientpacket.c b/networking/udhcp/clientpacket.c new file mode 100644 index 000000000..15cbda2f5 --- /dev/null +++ b/networking/udhcp/clientpacket.c @@ -0,0 +1,224 @@ +/* vi: set sw=4 ts=4: */ +/* clientpacket.c + * + * Packet generation and dispatching functions for the DHCP client. + * + * Russ Dill July 2001 + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include +#if (__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined _NEWLIB_VERSION +#include +#include +#else +#include +#include +#include +#endif + +#include "common.h" +#include "dhcpd.h" +#include "dhcpc.h" +#include "options.h" + + +/* Create a random xid */ +unsigned long random_xid(void) +{ + static int initialized; + if (!initialized) { + unsigned long seed; + + if (open_read_close("/dev/urandom", &seed, sizeof(seed)) < 0) { + bb_info_msg("Cannot load seed " + "from /dev/urandom: %s", strerror(errno)); + seed = time(0); + } + srand(seed); + initialized++; + } + return rand(); +} + + +/* initialize a packet with the proper defaults */ +static void init_packet(struct dhcpMessage *packet, char type) +{ + udhcp_init_header(packet, type); + memcpy(packet->chaddr, client_config.arp, 6); + if (client_config.clientid) + add_option_string(packet->options, client_config.clientid); + if (client_config.hostname) add_option_string(packet->options, client_config.hostname); + if (client_config.fqdn) add_option_string(packet->options, client_config.fqdn); + add_option_string(packet->options, client_config.vendorclass); +} + + +/* Add a parameter request list for stubborn DHCP servers. Pull the data + * from the struct in options.c. Don't do bounds checking here because it + * goes towards the head of the packet. */ +static void add_requests(struct dhcpMessage *packet) +{ + int end = end_option(packet->options); + int i, len = 0; + + packet->options[end + OPT_CODE] = DHCP_PARAM_REQ; + for (i = 0; dhcp_options[i].code; i++) + if (dhcp_options[i].flags & OPTION_REQ) + packet->options[end + OPT_DATA + len++] = dhcp_options[i].code; + packet->options[end + OPT_LEN] = len; + packet->options[end + OPT_DATA + len] = DHCP_END; + +} + + +/* Broadcast a DHCP discover packet to the network, with an optionally requested IP */ +int send_discover(unsigned long xid, unsigned long requested) +{ + struct dhcpMessage packet; + + init_packet(&packet, DHCPDISCOVER); + packet.xid = xid; + if (requested) + add_simple_option(packet.options, DHCP_REQUESTED_IP, requested); + + add_requests(&packet); + bb_info_msg("Sending discover..."); + return udhcp_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST, + SERVER_PORT, MAC_BCAST_ADDR, client_config.ifindex); +} + + +/* Broadcasts a DHCP request message */ +int send_selecting(unsigned long xid, unsigned long server, unsigned long requested) +{ + struct dhcpMessage packet; + struct in_addr addr; + + init_packet(&packet, DHCPREQUEST); + packet.xid = xid; + + add_simple_option(packet.options, DHCP_REQUESTED_IP, requested); + add_simple_option(packet.options, DHCP_SERVER_ID, server); + + add_requests(&packet); + addr.s_addr = requested; + bb_info_msg("Sending select for %s...", inet_ntoa(addr)); + return udhcp_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST, + SERVER_PORT, MAC_BCAST_ADDR, client_config.ifindex); +} + + +/* Unicasts or broadcasts a DHCP renew message */ +int send_renew(unsigned long xid, unsigned long server, unsigned long ciaddr) +{ + struct dhcpMessage packet; + int ret = 0; + + init_packet(&packet, DHCPREQUEST); + packet.xid = xid; + packet.ciaddr = ciaddr; + + add_requests(&packet); + bb_info_msg("Sending renew..."); + if (server) + ret = udhcp_kernel_packet(&packet, ciaddr, CLIENT_PORT, server, SERVER_PORT); + else ret = udhcp_raw_packet(&packet, INADDR_ANY, CLIENT_PORT, INADDR_BROADCAST, + SERVER_PORT, MAC_BCAST_ADDR, client_config.ifindex); + return ret; +} + + +/* Unicasts a DHCP release message */ +int send_release(unsigned long server, unsigned long ciaddr) +{ + struct dhcpMessage packet; + + init_packet(&packet, DHCPRELEASE); + packet.xid = random_xid(); + packet.ciaddr = ciaddr; + + add_simple_option(packet.options, DHCP_REQUESTED_IP, ciaddr); + add_simple_option(packet.options, DHCP_SERVER_ID, server); + + bb_info_msg("Sending release..."); + return udhcp_kernel_packet(&packet, ciaddr, CLIENT_PORT, server, SERVER_PORT); +} + + +/* return -1 on errors that are fatal for the socket, -2 for those that aren't */ +int get_raw_packet(struct dhcpMessage *payload, int fd) +{ + int bytes; + struct udp_dhcp_packet packet; + uint32_t source, dest; + uint16_t check; + + memset(&packet, 0, sizeof(struct udp_dhcp_packet)); + bytes = read(fd, &packet, sizeof(struct udp_dhcp_packet)); + if (bytes < 0) { + DEBUG("Cannot read on raw listening socket - ignoring"); + usleep(500000); /* possible down interface, looping condition */ + return -1; + } + + if (bytes < (int) (sizeof(struct iphdr) + sizeof(struct udphdr))) { + DEBUG("Message too short, ignoring"); + return -2; + } + + if (bytes < ntohs(packet.ip.tot_len)) { + DEBUG("Truncated packet"); + return -2; + } + + /* ignore any extra garbage bytes */ + bytes = ntohs(packet.ip.tot_len); + + /* Make sure its the right packet for us, and that it passes sanity checks */ + if (packet.ip.protocol != IPPROTO_UDP || packet.ip.version != IPVERSION + || packet.ip.ihl != sizeof(packet.ip) >> 2 + || packet.udp.dest != htons(CLIENT_PORT) + || bytes > (int) sizeof(struct udp_dhcp_packet) + || ntohs(packet.udp.len) != (uint16_t)(bytes - sizeof(packet.ip)) + ) { + DEBUG("Unrelated/bogus packet"); + return -2; + } + + /* check IP checksum */ + check = packet.ip.check; + packet.ip.check = 0; + if (check != udhcp_checksum(&(packet.ip), sizeof(packet.ip))) { + DEBUG("bad IP header checksum, ignoring"); + return -1; + } + + /* verify the UDP checksum by replacing the header with a psuedo header */ + source = packet.ip.saddr; + dest = packet.ip.daddr; + check = packet.udp.check; + packet.udp.check = 0; + memset(&packet.ip, 0, sizeof(packet.ip)); + + packet.ip.protocol = IPPROTO_UDP; + packet.ip.saddr = source; + packet.ip.daddr = dest; + packet.ip.tot_len = packet.udp.len; /* cheat on the psuedo-header */ + if (check && check != udhcp_checksum(&packet, bytes)) { + bb_error_msg("packet with bad UDP checksum received, ignoring"); + return -2; + } + + memcpy(payload, &(packet.data), bytes - (sizeof(packet.ip) + sizeof(packet.udp))); + + if (ntohl(payload->cookie) != DHCP_MAGIC) { + bb_error_msg("received bogus message (bad magic) - ignoring"); + return -2; + } + DEBUG("oooooh!!! got some!"); + return bytes - (sizeof(packet.ip) + sizeof(packet.udp)); + +} diff --git a/networking/udhcp/clientsocket.c b/networking/udhcp/clientsocket.c new file mode 100644 index 000000000..852061968 --- /dev/null +++ b/networking/udhcp/clientsocket.c @@ -0,0 +1,59 @@ +/* vi: set sw=4 ts=4: */ +/* + * clientsocket.c -- DHCP client socket creation + * + * udhcp client + * + * Russ Dill July 2001 + * + * 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. + */ + +#include +#if (__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined(_NEWLIB_VERSION) +#include +#include +#else +#include +#include +#include +#endif + +#include "common.h" + + +int raw_socket(int ifindex) +{ + int fd; + struct sockaddr_ll sock; + + DEBUG("Opening raw socket on ifindex %d", ifindex); + fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); + if (fd < 0) { + bb_perror_msg("socket"); + return -1; + } + + sock.sll_family = AF_PACKET; + sock.sll_protocol = htons(ETH_P_IP); + sock.sll_ifindex = ifindex; + if (bind(fd, (struct sockaddr *) &sock, sizeof(sock)) < 0) { + bb_perror_msg("bind"); + close(fd); + return -1; + } + + return fd; +} diff --git a/networking/udhcp/common.c b/networking/udhcp/common.c new file mode 100644 index 000000000..3e916f422 --- /dev/null +++ b/networking/udhcp/common.c @@ -0,0 +1,75 @@ +/* vi: set sw=4 ts=4: */ +/* common.c + * + * Functions for debugging and logging as well as some other + * simple helper functions. + * + * Russ Dill 2001-2003 + * Rewritten by Vladimir Oleynik (C) 2003 + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include + +#include "common.h" + + +long uptime(void) +{ + struct sysinfo info; + sysinfo(&info); + return info.uptime; +} + +/* + * This function makes sure our first socket calls + * aren't going to fd 1 (printf badness...) and are + * not later closed by daemon() + */ +static inline void sanitize_fds(void) +{ + int fd = xopen(bb_dev_null, O_RDWR); + while (fd < 3) + fd = dup(fd); + close(fd); +} + + +void udhcp_background(const char *pidfile) +{ +#ifdef __uClinux__ + bb_error_msg("cannot background in uclinux (yet)"); +#else /* __uClinux__ */ + int pid_fd; + + /* hold lock during fork. */ + pid_fd = pidfile_acquire(pidfile); + setsid(); + xdaemon(0, 0); + logmode &= ~LOGMODE_STDIO; + pidfile_write_release(pid_fd); +#endif /* __uClinux__ */ +} + +void udhcp_start_log_and_pid(const char *pidfile) +{ + int pid_fd; + + /* Make sure our syslog fd isn't overwritten */ + sanitize_fds(); + + /* do some other misc startup stuff while we are here to save bytes */ + pid_fd = pidfile_acquire(pidfile); + pidfile_write_release(pid_fd); + + /* equivelent of doing a fflush after every \n */ + setlinebuf(stdout); + + if (ENABLE_FEATURE_UDHCP_SYSLOG) { + openlog(applet_name, LOG_PID, LOG_LOCAL0); + logmode |= LOGMODE_SYSLOG; + } + + bb_info_msg("%s (v%s) started", applet_name, BB_VER); +} diff --git a/networking/udhcp/common.h b/networking/udhcp/common.h new file mode 100644 index 000000000..70a769342 --- /dev/null +++ b/networking/udhcp/common.h @@ -0,0 +1,108 @@ +/* vi: set sw=4 ts=4: */ +/* common.h + * + * Russ Dill September 2001 + * Rewritten by Vladimir Oleynik (C) 2003 + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#ifndef _COMMON_H +#define _COMMON_H + +#include "busybox.h" + +#ifdef CONFIG_INSTALL_NO_USR +# define DEFAULT_SCRIPT "/share/udhcpc/default.script" +#else +# define DEFAULT_SCRIPT "/usr/share/udhcpc/default.script" +#endif + +#define COMBINED_BINARY + + +/*** packet.h ***/ + +#include +#include + +struct dhcpMessage { + uint8_t op; + uint8_t htype; + uint8_t hlen; + uint8_t hops; + uint32_t xid; + uint16_t secs; + uint16_t flags; + uint32_t ciaddr; + uint32_t yiaddr; + uint32_t siaddr; + uint32_t giaddr; + uint8_t chaddr[16]; + uint8_t sname[64]; + uint8_t file[128]; + uint32_t cookie; + uint8_t options[308]; /* 312 - cookie */ +}; + +struct udp_dhcp_packet { + struct iphdr ip; + struct udphdr udp; + struct dhcpMessage data; +}; + +void udhcp_init_header(struct dhcpMessage *packet, char type); +int udhcp_get_packet(struct dhcpMessage *packet, int fd); +uint16_t udhcp_checksum(void *addr, int count); +int udhcp_raw_packet(struct dhcpMessage *payload, + uint32_t source_ip, int source_port, + uint32_t dest_ip, int dest_port, uint8_t *dest_arp, int ifindex); +int udhcp_kernel_packet(struct dhcpMessage *payload, + uint32_t source_ip, int source_port, + uint32_t dest_ip, int dest_port); + + +/**/ + +void udhcp_background(const char *pidfile); +void udhcp_start_log_and_pid(const char *pidfile); + +void udhcp_run_script(struct dhcpMessage *packet, const char *name); + +// Still need to clean these up... + +/* from pidfile.h */ +#define pidfile_acquire udhcp_pidfile_acquire +#define pidfile_write_release udhcp_pidfile_write_release +/* from options.h */ +#define get_option udhcp_get_option +#define end_option udhcp_end_option +#define add_option_string udhcp_add_option_string +#define add_simple_option udhcp_add_simple_option +#define option_lengths udhcp_option_lengths +/* from socket.h */ +#define listen_socket udhcp_listen_socket +#define read_interface udhcp_read_interface +/* from dhcpc.h */ +#define client_config udhcp_client_config +/* from dhcpd.h */ +#define server_config udhcp_server_config + +long uptime(void); +void udhcp_sp_setup(void); +int udhcp_sp_fd_set(fd_set *rfds, int extra_fd); +int udhcp_sp_read(fd_set *rfds); +int raw_socket(int ifindex); +int read_interface(char *interface, int *ifindex, uint32_t *addr, uint8_t *arp); +int listen_socket(uint32_t ip, int port, char *inf); +int pidfile_acquire(const char *pidfile); +void pidfile_write_release(int pid_fd); +int arpping(uint32_t yiaddr, uint32_t ip, uint8_t *arp, char *interface); + +#if ENABLE_FEATURE_UDHCP_DEBUG +# define DEBUG(str, args...) bb_info_msg(str, ## args) +#else +# define DEBUG(str, args...) do {;} while (0) +#endif + +#endif diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c new file mode 100644 index 000000000..71315ff0a --- /dev/null +++ b/networking/udhcp/dhcpc.c @@ -0,0 +1,509 @@ +/* vi: set sw=4 ts=4: */ +/* dhcpc.c + * + * udhcp DHCP client + * + * Russ Dill July 2001 + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include + +#include "common.h" +#include "dhcpd.h" +#include "dhcpc.h" +#include "options.h" + + +static int state; +/* Something is definitely wrong here. IPv4 addresses + * in variables of type long?? BTW, we use inet_ntoa() + * in the code. Manpage says that struct in_addr has a member of type long (!) + * which holds IPv4 address, and the struct is passed by value (!!) + */ +static unsigned long requested_ip; /* = 0 */ +static unsigned long server_addr; +static unsigned long timeout; +static int packet_num; /* = 0 */ +static int fd = -1; + +#define LISTEN_NONE 0 +#define LISTEN_KERNEL 1 +#define LISTEN_RAW 2 +static int listen_mode; + +struct client_config_t client_config; + + +/* just a little helper */ +static void change_mode(int new_mode) +{ + DEBUG("entering %s listen mode", + new_mode ? (new_mode == 1 ? "kernel" : "raw") : "none"); + if (fd >= 0) close(fd); + fd = -1; + listen_mode = new_mode; +} + + +/* perform a renew */ +static void perform_renew(void) +{ + bb_info_msg("Performing a DHCP renew"); + switch (state) { + case BOUND: + change_mode(LISTEN_KERNEL); + case RENEWING: + case REBINDING: + state = RENEW_REQUESTED; + break; + case RENEW_REQUESTED: /* impatient are we? fine, square 1 */ + udhcp_run_script(NULL, "deconfig"); + case REQUESTING: + case RELEASED: + change_mode(LISTEN_RAW); + state = INIT_SELECTING; + break; + case INIT_SELECTING: + break; + } + + /* start things over */ + packet_num = 0; + + /* Kill any timeouts because the user wants this to hurry along */ + timeout = 0; +} + + +/* perform a release */ +static void perform_release(void) +{ + char buffer[16]; + struct in_addr temp_addr; + + /* send release packet */ + if (state == BOUND || state == RENEWING || state == REBINDING) { + temp_addr.s_addr = server_addr; + sprintf(buffer, "%s", inet_ntoa(temp_addr)); + temp_addr.s_addr = requested_ip; + bb_info_msg("Unicasting a release of %s to %s", + inet_ntoa(temp_addr), buffer); + send_release(server_addr, requested_ip); /* unicast */ + udhcp_run_script(NULL, "deconfig"); + } + bb_info_msg("Entering released state"); + + change_mode(LISTEN_NONE); + state = RELEASED; + timeout = 0x7fffffff; +} + + +static void client_background(void) +{ + udhcp_background(client_config.pidfile); + client_config.foreground = 1; /* Do not fork again. */ + client_config.background_if_no_lease = 0; +} + + +static uint8_t* alloc_dhcp_option(int code, const char *str, int extra) +{ + uint8_t *storage; + int len = strlen(str); + if (len > 255) len = 255; + storage = xzalloc(len + extra + OPT_DATA); + storage[OPT_CODE] = code; + storage[OPT_LEN] = len + extra; + memcpy(storage + extra + OPT_DATA, str, len); + return storage; +} + + +int udhcpc_main(int argc, char *argv[]) +{ + uint8_t *temp, *message; + char *str_c, *str_V, *str_h, *str_F, *str_r, *str_T, *str_t; + unsigned long t1 = 0, t2 = 0, xid = 0; + unsigned long start = 0, lease = 0; + long now; + unsigned opt; + int max_fd; + int sig; + int retval; + int len; + int no_clientid = 0; + fd_set rfds; + struct timeval tv; + struct dhcpMessage packet; + struct in_addr temp_addr; + + enum { + OPT_c = 1 << 0, + OPT_C = 1 << 1, + OPT_V = 1 << 2, + OPT_f = 1 << 3, + OPT_b = 1 << 4, + OPT_H = 1 << 5, + OPT_h = 1 << 6, + OPT_F = 1 << 7, + OPT_i = 1 << 8, + OPT_n = 1 << 9, + OPT_p = 1 << 10, + OPT_q = 1 << 11, + OPT_R = 1 << 12, + OPT_r = 1 << 13, + OPT_s = 1 << 14, + OPT_T = 1 << 15, + OPT_t = 1 << 16, + OPT_v = 1 << 17, + }; +#if ENABLE_GETOPT_LONG + static const struct option arg_options[] = { + { "clientid", required_argument, 0, 'c' }, + { "clientid-none", no_argument, 0, 'C' }, + { "vendorclass", required_argument, 0, 'V' }, + { "foreground", no_argument, 0, 'f' }, + { "background", no_argument, 0, 'b' }, + { "hostname", required_argument, 0, 'H' }, + { "hostname", required_argument, 0, 'h' }, + { "fqdn", required_argument, 0, 'F' }, + { "interface", required_argument, 0, 'i' }, + { "now", no_argument, 0, 'n' }, + { "pidfile", required_argument, 0, 'p' }, + { "quit", no_argument, 0, 'q' }, + { "release", no_argument, 0, 'R' }, + { "request", required_argument, 0, 'r' }, + { "script", required_argument, 0, 's' }, + { "timeout", required_argument, 0, 'T' }, + { "version", no_argument, 0, 'v' }, + { "retries", required_argument, 0, 't' }, + { 0, 0, 0, 0 } + }; +#endif + /* Default options. */ + client_config.interface = "eth0"; + client_config.script = DEFAULT_SCRIPT; + client_config.retries = 3; + client_config.timeout = 3; + + /* Parse command line */ + opt_complementary = "?:c--C:C--c" // mutually exclusive + ":hH:Hh"; // -h and -H are the same +#if ENABLE_GETOPT_LONG + applet_long_options = arg_options; +#endif + opt = getopt32(argc, argv, "c:CV:fbH:h:F:i:np:qRr:s:T:t:v", + &str_c, &str_V, &str_h, &str_h, &str_F, + &client_config.interface, &client_config.pidfile, &str_r, + &client_config.script, &str_T, &str_t + ); + + if (opt & OPT_c) + client_config.clientid = alloc_dhcp_option(DHCP_CLIENT_ID, str_c, 0); + if (opt & OPT_C) + no_clientid = 1; + if (opt & OPT_V) + client_config.vendorclass = alloc_dhcp_option(DHCP_VENDOR, str_V, 0); + if (opt & OPT_f) + client_config.foreground = 1; + if (opt & OPT_b) + client_config.background_if_no_lease = 1; + if (opt & OPT_h) + client_config.hostname = alloc_dhcp_option(DHCP_HOST_NAME, str_h, 0); + if (opt & OPT_F) { + client_config.fqdn = alloc_dhcp_option(DHCP_FQDN, str_F, 3); + /* Flags: 0000NEOS + S: 1 => Client requests Server to update A RR in DNS as well as PTR + O: 1 => Server indicates to client that DNS has been updated regardless + E: 1 => Name data is DNS format, i.e. <4>host<6>domain<4>com<0> not "host.domain.com" + N: 1 => Client requests Server to not update DNS + */ + client_config.fqdn[OPT_DATA + 0] = 0x1; + /* client_config.fqdn[OPT_DATA + 1] = 0; - redundant */ + /* client_config.fqdn[OPT_DATA + 2] = 0; - redundant */ + } + // if (opt & OPT_i) client_config.interface = ... + if (opt & OPT_n) + client_config.abort_if_no_lease = 1; + // if (opt & OPT_p) client_config.pidfile = ... + if (opt & OPT_q) + client_config.quit_after_lease = 1; + if (opt & OPT_R) + client_config.release_on_quit = 1; + if (opt & OPT_r) + requested_ip = inet_addr(str_r); + // if (opt & OPT_s) client_config.script = ... + if (opt & OPT_T) + client_config.timeout = xatoi_u(str_T); + if (opt & OPT_t) + client_config.retries = xatoi_u(str_t); + if (opt & OPT_v) { + printf("version %s\n\n", BB_VER); + return 0; + } + + /* Start the log, sanitize fd's, and write a pid file */ + udhcp_start_log_and_pid(client_config.pidfile); + + if (read_interface(client_config.interface, &client_config.ifindex, + NULL, client_config.arp) < 0) + return 1; + + /* if not set, and not suppressed, setup the default client ID */ + if (!client_config.clientid && !no_clientid) { + client_config.clientid = alloc_dhcp_option(DHCP_CLIENT_ID, "", 7); + client_config.clientid[OPT_DATA] = 1; + memcpy(client_config.clientid + OPT_DATA+1, client_config.arp, 6); + } + + if (!client_config.vendorclass) + client_config.vendorclass = alloc_dhcp_option(DHCP_VENDOR, "udhcp "BB_VER, 0); + + /* setup the signal pipe */ + udhcp_sp_setup(); + + state = INIT_SELECTING; + udhcp_run_script(NULL, "deconfig"); + change_mode(LISTEN_RAW); + + for (;;) { + tv.tv_sec = timeout - uptime(); + tv.tv_usec = 0; + + if (listen_mode != LISTEN_NONE && fd < 0) { + if (listen_mode == LISTEN_KERNEL) + fd = listen_socket(INADDR_ANY, CLIENT_PORT, client_config.interface); + else + fd = raw_socket(client_config.ifindex); + if (fd < 0) { + bb_perror_msg("FATAL: cannot listen on socket"); + return 0; + } + } + max_fd = udhcp_sp_fd_set(&rfds, fd); + + if (tv.tv_sec > 0) { + DEBUG("Waiting on select..."); + retval = select(max_fd + 1, &rfds, NULL, NULL, &tv); + } else retval = 0; /* If we already timed out, fall through */ + + now = uptime(); + if (retval == 0) { + /* timeout dropped to zero */ + switch (state) { + case INIT_SELECTING: + if (packet_num < client_config.retries) { + if (packet_num == 0) + xid = random_xid(); + + /* send discover packet */ + send_discover(xid, requested_ip); /* broadcast */ + + timeout = now + client_config.timeout; + packet_num++; + } else { + udhcp_run_script(NULL, "leasefail"); + if (client_config.background_if_no_lease) { + bb_info_msg("No lease, forking to background"); + client_background(); + } else if (client_config.abort_if_no_lease) { + bb_info_msg("No lease, failing"); + return 1; + } + /* wait to try again */ + packet_num = 0; + timeout = now + 60; + } + break; + case RENEW_REQUESTED: + case REQUESTING: + if (packet_num < client_config.retries) { + /* send request packet */ + if (state == RENEW_REQUESTED) + send_renew(xid, server_addr, requested_ip); /* unicast */ + else send_selecting(xid, server_addr, requested_ip); /* broadcast */ + + timeout = now + ((packet_num == 2) ? 10 : 2); + packet_num++; + } else { + /* timed out, go back to init state */ + if (state == RENEW_REQUESTED) udhcp_run_script(NULL, "deconfig"); + state = INIT_SELECTING; + timeout = now; + packet_num = 0; + change_mode(LISTEN_RAW); + } + break; + case BOUND: + /* Lease is starting to run out, time to enter renewing state */ + state = RENEWING; + change_mode(LISTEN_KERNEL); + DEBUG("Entering renew state"); + /* fall right through */ + case RENEWING: + /* Either set a new T1, or enter REBINDING state */ + if ((t2 - t1) <= (lease / 14400 + 1)) { + /* timed out, enter rebinding state */ + state = REBINDING; + timeout = now + (t2 - t1); + DEBUG("Entering rebinding state"); + } else { + /* send a request packet */ + send_renew(xid, server_addr, requested_ip); /* unicast */ + + t1 = (t2 - t1) / 2 + t1; + timeout = t1 + start; + } + break; + case REBINDING: + /* Either set a new T2, or enter INIT state */ + if ((lease - t2) <= (lease / 14400 + 1)) { + /* timed out, enter init state */ + state = INIT_SELECTING; + bb_info_msg("Lease lost, entering init state"); + udhcp_run_script(NULL, "deconfig"); + timeout = now; + packet_num = 0; + change_mode(LISTEN_RAW); + } else { + /* send a request packet */ + send_renew(xid, 0, requested_ip); /* broadcast */ + + t2 = (lease - t2) / 2 + t2; + timeout = t2 + start; + } + break; + case RELEASED: + /* yah, I know, *you* say it would never happen */ + timeout = 0x7fffffff; + break; + } + } else if (retval > 0 && listen_mode != LISTEN_NONE && FD_ISSET(fd, &rfds)) { + /* a packet is ready, read it */ + + if (listen_mode == LISTEN_KERNEL) + len = udhcp_get_packet(&packet, fd); + else len = get_raw_packet(&packet, fd); + + if (len == -1 && errno != EINTR) { + DEBUG("error on read, %s, reopening socket", strerror(errno)); + change_mode(listen_mode); /* just close and reopen */ + } + if (len < 0) continue; + + if (packet.xid != xid) { + DEBUG("Ignoring XID %lx (our xid is %lx)", + (unsigned long) packet.xid, xid); + continue; + } + + /* Ignore packets that aren't for us */ + if (memcmp(packet.chaddr, client_config.arp, 6)) { + DEBUG("Packet does not have our chaddr - ignoring"); + continue; + } + + if ((message = get_option(&packet, DHCP_MESSAGE_TYPE)) == NULL) { + bb_error_msg("cannot get option from packet - ignoring"); + continue; + } + + switch (state) { + case INIT_SELECTING: + /* Must be a DHCPOFFER to one of our xid's */ + if (*message == DHCPOFFER) { + temp = get_option(&packet, DHCP_SERVER_ID); + if (temp) { + server_addr = *(uint32_t*)temp; + xid = packet.xid; + requested_ip = packet.yiaddr; + + /* enter requesting state */ + state = REQUESTING; + timeout = now; + packet_num = 0; + } else { + bb_error_msg("no server ID in message"); + } + } + break; + case RENEW_REQUESTED: + case REQUESTING: + case RENEWING: + case REBINDING: + if (*message == DHCPACK) { + temp = get_option(&packet, DHCP_LEASE_TIME); + if (!temp) { + bb_error_msg("no lease time with ACK, using 1 hour lease"); + lease = 60 * 60; + } else { + lease = ntohl(*(uint32_t*)temp); + } + + /* enter bound state */ + t1 = lease / 2; + + /* little fixed point for n * .875 */ + t2 = (lease * 0x7) >> 3; + temp_addr.s_addr = packet.yiaddr; + bb_info_msg("Lease of %s obtained, lease time %ld", + inet_ntoa(temp_addr), lease); + start = now; + timeout = t1 + start; + requested_ip = packet.yiaddr; + udhcp_run_script(&packet, + ((state == RENEWING || state == REBINDING) ? "renew" : "bound")); + + state = BOUND; + change_mode(LISTEN_NONE); + if (client_config.quit_after_lease) { + if (client_config.release_on_quit) + perform_release(); + return 0; + } + if (!client_config.foreground) + client_background(); + + } else if (*message == DHCPNAK) { + /* return to init state */ + bb_info_msg("Received DHCP NAK"); + udhcp_run_script(&packet, "nak"); + if (state != REQUESTING) + udhcp_run_script(NULL, "deconfig"); + state = INIT_SELECTING; + timeout = now; + requested_ip = 0; + packet_num = 0; + change_mode(LISTEN_RAW); + sleep(3); /* avoid excessive network traffic */ + } + break; + /* case BOUND, RELEASED: - ignore all packets */ + } + } else if (retval > 0 && (sig = udhcp_sp_read(&rfds))) { + switch (sig) { + case SIGUSR1: + perform_renew(); + break; + case SIGUSR2: + perform_release(); + break; + case SIGTERM: + bb_info_msg("Received SIGTERM"); + if (client_config.release_on_quit) + perform_release(); + return 0; + } + } else if (retval == -1 && errno == EINTR) { + /* a signal was caught */ + } else { + /* An error occured */ + bb_perror_msg("select"); + } + + } + return 0; +} diff --git a/networking/udhcp/dhcpc.h b/networking/udhcp/dhcpc.h new file mode 100644 index 000000000..fd17917d0 --- /dev/null +++ b/networking/udhcp/dhcpc.h @@ -0,0 +1,50 @@ +/* vi: set sw=4 ts=4: */ +/* dhcpc.h */ +#ifndef _DHCPC_H +#define _DHCPC_H + +#define INIT_SELECTING 0 +#define REQUESTING 1 +#define BOUND 2 +#define RENEWING 3 +#define REBINDING 4 +#define INIT_REBOOT 5 +#define RENEW_REQUESTED 6 +#define RELEASED 7 + +struct client_config_t { + /* TODO: combine flag fields into single "unsigned opt" */ + /* (can be set directly to the result of getopt32) */ + char foreground; /* Do not fork */ + char quit_after_lease; /* Quit after obtaining lease */ + char release_on_quit; /* perform release on quit */ + char abort_if_no_lease; /* Abort if no lease */ + char background_if_no_lease; /* Fork to background if no lease */ + char *interface; /* The name of the interface to use */ + char *pidfile; /* Optionally store the process ID */ + char *script; /* User script to run at dhcp events */ + uint8_t *clientid; /* Optional client id to use */ + uint8_t *vendorclass; /* Optional vendor class-id to use */ + uint8_t *hostname; /* Optional hostname to use */ + uint8_t *fqdn; /* Optional fully qualified domain name to use */ + int ifindex; /* Index number of the interface to use */ + int retries; /* Max number of request packets */ + int timeout; /* Number of seconds to try to get a lease */ + uint8_t arp[6]; /* Our arp address */ +}; + +extern struct client_config_t client_config; + + +/*** clientpacket.h ***/ + +unsigned long random_xid(void); +int send_discover(unsigned long xid, unsigned long requested); +int send_selecting(unsigned long xid, unsigned long server, unsigned long requested); +int send_renew(unsigned long xid, unsigned long server, unsigned long ciaddr); +int send_renew(unsigned long xid, unsigned long server, unsigned long ciaddr); +int send_release(unsigned long server, unsigned long ciaddr); +int get_raw_packet(struct dhcpMessage *payload, int fd); + + +#endif diff --git a/networking/udhcp/dhcpd.c b/networking/udhcp/dhcpd.c new file mode 100644 index 000000000..74380367f --- /dev/null +++ b/networking/udhcp/dhcpd.c @@ -0,0 +1,226 @@ +/* vi: set sw=4 ts=4: */ +/* dhcpd.c + * + * udhcp Server + * Copyright (C) 1999 Matthew Ramsay + * Chris Trew + * + * Rewrite by Russ Dill July 2001 + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "common.h" +#include "dhcpd.h" +#include "options.h" + + +/* globals */ +struct dhcpOfferedAddr *leases; +struct server_config_t server_config; + + +int udhcpd_main(int argc, char *argv[]) +{ + fd_set rfds; + struct timeval tv; + int server_socket = -1, bytes, retval, max_sock; + struct dhcpMessage packet; + uint8_t *state, *server_id, *requested; + uint32_t server_id_align, requested_align, static_lease_ip; + unsigned long timeout_end, num_ips; + struct option_set *option; + struct dhcpOfferedAddr *lease, static_lease; + + read_config(argc < 2 ? DHCPD_CONF_FILE : argv[1]); + + /* Start the log, sanitize fd's, and write a pid file */ + udhcp_start_log_and_pid(server_config.pidfile); + + if ((option = find_option(server_config.options, DHCP_LEASE_TIME))) { + memcpy(&server_config.lease, option->data + 2, 4); + server_config.lease = ntohl(server_config.lease); + } + else server_config.lease = LEASE_TIME; + + /* Sanity check */ + num_ips = ntohl(server_config.end) - ntohl(server_config.start) + 1; + if (server_config.max_leases > num_ips) { + bb_error_msg("max_leases value (%lu) not sane, " + "setting to %lu instead", + server_config.max_leases, num_ips); + server_config.max_leases = num_ips; + } + + leases = xzalloc(server_config.max_leases * sizeof(struct dhcpOfferedAddr)); + read_leases(server_config.lease_file); + + if (read_interface(server_config.interface, &server_config.ifindex, + &server_config.server, server_config.arp) < 0) + return 1; + + if (!ENABLE_FEATURE_UDHCP_DEBUG) + udhcp_background(server_config.pidfile); /* hold lock during fork. */ + + /* Setup the signal pipe */ + udhcp_sp_setup(); + + timeout_end = time(0) + server_config.auto_time; + while (1) { /* loop until universe collapses */ + + if (server_socket < 0) { + server_socket = listen_socket(INADDR_ANY, SERVER_PORT, server_config.interface); + if (server_socket < 0) { + bb_perror_msg("FATAL: cannot create server socket"); + return 2; + } + } + + max_sock = udhcp_sp_fd_set(&rfds, server_socket); + if (server_config.auto_time) { + tv.tv_sec = timeout_end - time(0); + tv.tv_usec = 0; + } + if (!server_config.auto_time || tv.tv_sec > 0) { + retval = select(max_sock + 1, &rfds, NULL, NULL, + server_config.auto_time ? &tv : NULL); + } else retval = 0; /* If we already timed out, fall through */ + + if (retval == 0) { + write_leases(); + timeout_end = time(0) + server_config.auto_time; + continue; + } else if (retval < 0 && errno != EINTR) { + DEBUG("error on select"); + continue; + } + + switch (udhcp_sp_read(&rfds)) { + case SIGUSR1: + bb_info_msg("Received a SIGUSR1"); + write_leases(); + /* why not just reset the timeout, eh */ + timeout_end = time(0) + server_config.auto_time; + continue; + case SIGTERM: + bb_info_msg("Received a SIGTERM"); + return 0; + case 0: break; /* no signal */ + default: continue; /* signal or error (probably EINTR) */ + } + + if ((bytes = udhcp_get_packet(&packet, server_socket)) < 0) { /* this waits for a packet - idle */ + if (bytes == -1 && errno != EINTR) { + DEBUG("error on read, %s, reopening socket", strerror(errno)); + close(server_socket); + server_socket = -1; + } + continue; + } + + if ((state = get_option(&packet, DHCP_MESSAGE_TYPE)) == NULL) { + bb_error_msg("cannot get option from packet, ignoring"); + continue; + } + + /* Look for a static lease */ + static_lease_ip = getIpByMac(server_config.static_leases, &packet.chaddr); + + if (static_lease_ip) { + bb_info_msg("Found static lease: %x", static_lease_ip); + + memcpy(&static_lease.chaddr, &packet.chaddr, 16); + static_lease.yiaddr = static_lease_ip; + static_lease.expires = 0; + + lease = &static_lease; + + } else { + lease = find_lease_by_chaddr(packet.chaddr); + } + + switch (state[0]) { + case DHCPDISCOVER: + DEBUG("Received DISCOVER"); + + if (sendOffer(&packet) < 0) { + bb_error_msg("send OFFER failed"); + } + break; + case DHCPREQUEST: + DEBUG("received REQUEST"); + + requested = get_option(&packet, DHCP_REQUESTED_IP); + server_id = get_option(&packet, DHCP_SERVER_ID); + + if (requested) memcpy(&requested_align, requested, 4); + if (server_id) memcpy(&server_id_align, server_id, 4); + + if (lease) { + if (server_id) { + /* SELECTING State */ + DEBUG("server_id = %08x", ntohl(server_id_align)); + if (server_id_align == server_config.server && requested && + requested_align == lease->yiaddr) { + sendACK(&packet, lease->yiaddr); + } + } else { + if (requested) { + /* INIT-REBOOT State */ + if (lease->yiaddr == requested_align) + sendACK(&packet, lease->yiaddr); + else sendNAK(&packet); + } else { + /* RENEWING or REBINDING State */ + if (lease->yiaddr == packet.ciaddr) + sendACK(&packet, lease->yiaddr); + else { + /* don't know what to do!!!! */ + sendNAK(&packet); + } + } + } + + /* what to do if we have no record of the client */ + } else if (server_id) { + /* SELECTING State */ + + } else if (requested) { + /* INIT-REBOOT State */ + if ((lease = find_lease_by_yiaddr(requested_align))) { + if (lease_expired(lease)) { + /* probably best if we drop this lease */ + memset(lease->chaddr, 0, 16); + /* make some contention for this address */ + } else sendNAK(&packet); + } else if (requested_align < server_config.start || + requested_align > server_config.end) { + sendNAK(&packet); + } /* else remain silent */ + + } else { + /* RENEWING or REBINDING State */ + } + break; + case DHCPDECLINE: + DEBUG("Received DECLINE"); + if (lease) { + memset(lease->chaddr, 0, 16); + lease->expires = time(0) + server_config.decline_time; + } + break; + case DHCPRELEASE: + DEBUG("Received RELEASE"); + if (lease) lease->expires = time(0); + break; + case DHCPINFORM: + DEBUG("Received INFORM"); + send_inform(&packet); + break; + default: + bb_info_msg("Unsupported DHCP message (%02x) - ignoring", state[0]); + } + } + + return 0; +} diff --git a/networking/udhcp/dhcpd.h b/networking/udhcp/dhcpd.h new file mode 100644 index 000000000..40959e4ae --- /dev/null +++ b/networking/udhcp/dhcpd.h @@ -0,0 +1,190 @@ +/* vi: set sw=4 ts=4: */ +/* dhcpd.h */ +#ifndef _DHCPD_H +#define _DHCPD_H + +/************************************/ +/* Defaults _you_ may want to tweak */ +/************************************/ + +/* the period of time the client is allowed to use that address */ +#define LEASE_TIME (60*60*24*10) /* 10 days of seconds */ +#define LEASES_FILE "/var/lib/misc/udhcpd.leases" + +/* where to find the DHCP server configuration file */ +#define DHCPD_CONF_FILE "/etc/udhcpd.conf" + +/*****************************************************************/ +/* Do not modify below here unless you know what you are doing!! */ +/*****************************************************************/ + +/* DHCP protocol -- see RFC 2131 */ +#define SERVER_PORT 67 +#define CLIENT_PORT 68 + +#define DHCP_MAGIC 0x63825363 + +/* DHCP option codes (partial list) */ +#define DHCP_PADDING 0x00 +#define DHCP_SUBNET 0x01 +#define DHCP_TIME_OFFSET 0x02 +#define DHCP_ROUTER 0x03 +#define DHCP_TIME_SERVER 0x04 +#define DHCP_NAME_SERVER 0x05 +#define DHCP_DNS_SERVER 0x06 +#define DHCP_LOG_SERVER 0x07 +#define DHCP_COOKIE_SERVER 0x08 +#define DHCP_LPR_SERVER 0x09 +#define DHCP_HOST_NAME 0x0c +#define DHCP_BOOT_SIZE 0x0d +#define DHCP_DOMAIN_NAME 0x0f +#define DHCP_SWAP_SERVER 0x10 +#define DHCP_ROOT_PATH 0x11 +#define DHCP_IP_TTL 0x17 +#define DHCP_MTU 0x1a +#define DHCP_BROADCAST 0x1c +#define DHCP_NTP_SERVER 0x2a +#define DHCP_WINS_SERVER 0x2c +#define DHCP_REQUESTED_IP 0x32 +#define DHCP_LEASE_TIME 0x33 +#define DHCP_OPTION_OVER 0x34 +#define DHCP_MESSAGE_TYPE 0x35 +#define DHCP_SERVER_ID 0x36 +#define DHCP_PARAM_REQ 0x37 +#define DHCP_MESSAGE 0x38 +#define DHCP_MAX_SIZE 0x39 +#define DHCP_T1 0x3a +#define DHCP_T2 0x3b +#define DHCP_VENDOR 0x3c +#define DHCP_CLIENT_ID 0x3d +#define DHCP_FQDN 0x51 + +#define DHCP_END 0xFF + + +#define BOOTREQUEST 1 +#define BOOTREPLY 2 + +#define ETH_10MB 1 +#define ETH_10MB_LEN 6 + +#define DHCPDISCOVER 1 +#define DHCPOFFER 2 +#define DHCPREQUEST 3 +#define DHCPDECLINE 4 +#define DHCPACK 5 +#define DHCPNAK 6 +#define DHCPRELEASE 7 +#define DHCPINFORM 8 + +#define BROADCAST_FLAG 0x8000 + +#define OPTION_FIELD 0 +#define FILE_FIELD 1 +#define SNAME_FIELD 2 + +/* miscellaneous defines */ +#define MAC_BCAST_ADDR (uint8_t *) "\xff\xff\xff\xff\xff\xff" +#define OPT_CODE 0 +#define OPT_LEN 1 +#define OPT_DATA 2 + +struct option_set { + uint8_t *data; + struct option_set *next; +}; + +struct static_lease { + uint8_t *mac; + uint32_t *ip; + struct static_lease *next; +}; + +struct server_config_t { + uint32_t server; /* Our IP, in network order */ + uint32_t start; /* Start address of leases, network order */ + uint32_t end; /* End of leases, network order */ + struct option_set *options; /* List of DHCP options loaded from the config file */ + char *interface; /* The name of the interface to use */ + int ifindex; /* Index number of the interface to use */ + uint8_t arp[6]; /* Our arp address */ + unsigned long lease; /* lease time in seconds (host order) */ + unsigned long max_leases; /* maximum number of leases (including reserved address) */ + char remaining; /* should the lease file be interpreted as lease time remaining, or + * as the time the lease expires */ + unsigned long auto_time; /* how long should udhcpd wait before writing a config file. + * if this is zero, it will only write one on SIGUSR1 */ + unsigned long decline_time; /* how long an address is reserved if a client returns a + * decline message */ + unsigned long conflict_time; /* how long an arp conflict offender is leased for */ + unsigned long offer_time; /* how long an offered address is reserved */ + unsigned long min_lease; /* minimum lease a client can request*/ + char *lease_file; + char *pidfile; + char *notify_file; /* What to run whenever leases are written */ + uint32_t siaddr; /* next server bootp option */ + char *sname; /* bootp server name */ + char *boot_file; /* bootp boot file option */ + struct static_lease *static_leases; /* List of ip/mac pairs to assign static leases */ +}; + +extern struct server_config_t server_config; +extern struct dhcpOfferedAddr *leases; + + +/*** leases.h ***/ + +struct dhcpOfferedAddr { + uint8_t chaddr[16]; + uint32_t yiaddr; /* network order */ + uint32_t expires; /* host order */ +}; + +extern uint8_t blank_chaddr[]; + +void clear_lease(uint8_t *chaddr, uint32_t yiaddr); +struct dhcpOfferedAddr *add_lease(uint8_t *chaddr, uint32_t yiaddr, unsigned long lease); +int lease_expired(struct dhcpOfferedAddr *lease); +struct dhcpOfferedAddr *oldest_expired_lease(void); +struct dhcpOfferedAddr *find_lease_by_chaddr(uint8_t *chaddr); +struct dhcpOfferedAddr *find_lease_by_yiaddr(uint32_t yiaddr); +uint32_t find_address(int check_expired); + + +/*** static_leases.h ***/ + +/* Config file will pass static lease info to this function which will add it + * to a data structure that can be searched later */ +int addStaticLease(struct static_lease **lease_struct, uint8_t *mac, uint32_t *ip); +/* Check to see if a mac has an associated static lease */ +uint32_t getIpByMac(struct static_lease *lease_struct, void *arg); +/* Check to see if an ip is reserved as a static ip */ +uint32_t reservedIp(struct static_lease *lease_struct, uint32_t ip); +/* Print out static leases just to check what's going on (debug code) */ +void printStaticLeases(struct static_lease **lease_struct); + + +/*** serverpacket.h ***/ + +int sendOffer(struct dhcpMessage *oldpacket); +int sendNAK(struct dhcpMessage *oldpacket); +int sendACK(struct dhcpMessage *oldpacket, uint32_t yiaddr); +int send_inform(struct dhcpMessage *oldpacket); + + +/*** files.h ***/ + +struct config_keyword { + const char *keyword; + int (* const handler)(const char *line, void *var); + void *var; + const char *def; +}; + +int read_config(const char *file); +void write_leases(void); +void read_leases(const char *file); +struct option_set *find_option(struct option_set *opt_list, char code); + + +#endif diff --git a/networking/udhcp/dhcprelay.c b/networking/udhcp/dhcprelay.c new file mode 100644 index 000000000..e3a816886 --- /dev/null +++ b/networking/udhcp/dhcprelay.c @@ -0,0 +1,340 @@ +/* vi: set sw=4 ts=4: */ +/* Port to Busybox Copyright (C) 2006 Jesse Dutton + * + * Licensed under GPL v2, see file LICENSE in this tarball for details. + * + * DHCP Relay for 'DHCPv4 Configuration of IPSec Tunnel Mode' support + * Copyright (C) 2002 Mario Strasser , + * Zuercher Hochschule Winterthur, + * Netbeat AG + * Upstream has GPL v2 or later + */ + +#include "common.h" +#include "dhcpd.h" +#include "options.h" + +/* constants */ +#define SELECT_TIMEOUT 5 /* select timeout in sec. */ +#define MAX_LIFETIME 2*60 /* lifetime of an xid entry in sec. */ +#define MAX_INTERFACES 9 + + +/* This list holds information about clients. The xid_* functions manipulate this list. */ +static struct xid_item { + u_int32_t xid; + struct sockaddr_in ip; + int client; + time_t timestamp; + struct xid_item *next; +} dhcprelay_xid_list = {0, {0}, 0, 0, NULL}; + + +static struct xid_item * xid_add(u_int32_t xid, struct sockaddr_in *ip, int client) +{ + struct xid_item *item; + + /* create new xid entry */ + item = xmalloc(sizeof(struct xid_item)); + + /* add xid entry */ + item->ip = *ip; + item->xid = xid; + item->client = client; + item->timestamp = time(NULL); + item->next = dhcprelay_xid_list.next; + dhcprelay_xid_list.next = item; + + return item; +} + + +static void xid_expire(void) +{ + struct xid_item *item = dhcprelay_xid_list.next; + struct xid_item *last = &dhcprelay_xid_list; + time_t current_time = time(NULL); + + while (item != NULL) { + if ((current_time-item->timestamp) > MAX_LIFETIME) { + last->next = item->next; + free(item); + item = last->next; + } else { + last = item; + item = item->next; + } + } +} + +static struct xid_item * xid_find(u_int32_t xid) +{ + struct xid_item *item = dhcprelay_xid_list.next; + while (item != NULL) { + if (item->xid == xid) { + return item; + } + item = item->next; + } + return NULL; +} + +static void xid_del(u_int32_t xid) +{ + struct xid_item *item = dhcprelay_xid_list.next; + struct xid_item *last = &dhcprelay_xid_list; + while (item != NULL) { + if (item->xid == xid) { + last->next = item->next; + free(item); + item = last->next; + } else { + last = item; + item = item->next; + } + } +} + + +/** + * get_dhcp_packet_type - gets the message type of a dhcp packet + * p - pointer to the dhcp packet + * returns the message type on success, -1 otherwise + */ +static int get_dhcp_packet_type(struct dhcpMessage *p) +{ + u_char *op; + + /* it must be either a BOOTREQUEST or a BOOTREPLY */ + if (p->op != BOOTREQUEST && p->op != BOOTREPLY) + return -1; + /* get message type option */ + op = get_option(p, DHCP_MESSAGE_TYPE); + if (op != NULL) + return op[0]; + return -1; +} + +/** + * signal_handler - handles signals ;-) + * sig - sent signal + */ +static int dhcprelay_stopflag; +static void dhcprelay_signal_handler(int sig) +{ + dhcprelay_stopflag = 1; +} + +/** + * get_client_devices - parses the devices list + * dev_list - comma separated list of devices + * returns array + */ +static char ** get_client_devices(char *dev_list, int *client_number) +{ + char *s, *list, **client_dev; + int i, cn; + + /* copy list */ + list = xstrdup(dev_list); + if (list == NULL) return NULL; + + /* get number of items */ + for (s = dev_list, cn = 1; *s; s++) + if (*s == ',') + cn++; + + client_dev = xzalloc(cn * sizeof(*client_dev)); + + /* parse list */ + s = strtok(list, ","); + i = 0; + while (s != NULL) { + client_dev[i++] = xstrdup(s); + s = strtok(NULL, ","); + } + + /* free copy and exit */ + free(list); + *client_number = cn; + return client_dev; +} + + +/* Creates listen sockets (in fds) and returns the number allocated. */ +static int init_sockets(char **client, int num_clients, + char *server, int *fds, int *max_socket) +{ + int i; + + /* talk to real server on bootps */ + fds[0] = listen_socket(htonl(INADDR_ANY), 67, server); + if (fds[0] < 0) return -1; + *max_socket = fds[0]; + + /* array starts at 1 since server is 0 */ + num_clients++; + + for (i=1; i < num_clients; i++) { + /* listen for clients on bootps */ + fds[i] = listen_socket(htonl(INADDR_ANY), 67, client[i-1]); + if (fds[i] < 0) return -1; + if (fds[i] > *max_socket) *max_socket = fds[i]; + } + + return i; +} + + +/** + * pass_on() - forwards dhcp packets from client to server + * p - packet to send + * client - number of the client + */ +static void pass_on(struct dhcpMessage *p, int packet_len, int client, int *fds, + struct sockaddr_in *client_addr, struct sockaddr_in *server_addr) +{ + int res, type; + struct xid_item *item; + + /* check packet_type */ + type = get_dhcp_packet_type(p); + if (type != DHCPDISCOVER && type != DHCPREQUEST + && type != DHCPDECLINE && type != DHCPRELEASE + && type != DHCPINFORM + ) { + return; + } + + /* create new xid entry */ + item = xid_add(p->xid, client_addr, client); + + /* forward request to LAN (server) */ + res = sendto(fds[0], p, packet_len, 0, (struct sockaddr*)server_addr, + sizeof(struct sockaddr_in)); + if (res != packet_len) { + bb_perror_msg("pass_on"); + return; + } +} + +/** + * pass_back() - forwards dhcp packets from server to client + * p - packet to send + */ +static void pass_back(struct dhcpMessage *p, int packet_len, int *fds) +{ + int res, type; + struct xid_item *item; + + /* check xid */ + item = xid_find(p->xid); + if (!item) { + return; + } + + /* check packet type */ + type = get_dhcp_packet_type(p); + if (type != DHCPOFFER && type != DHCPACK && type != DHCPNAK) { + return; + } + + if (item->ip.sin_addr.s_addr == htonl(INADDR_ANY)) + item->ip.sin_addr.s_addr = htonl(INADDR_BROADCAST); + if (item->client > MAX_INTERFACES) + return; + res = sendto(fds[item->client], p, packet_len, 0, (struct sockaddr*)(&item->ip), + sizeof(item->ip)); + if (res != packet_len) { + bb_perror_msg("pass_back"); + return; + } + + /* remove xid entry */ + xid_del(p->xid); +} + +static void dhcprelay_loop(int *fds, int num_sockets, int max_socket, char **clients, + struct sockaddr_in *server_addr, uint32_t gw_ip) +{ + struct dhcpMessage dhcp_msg; + fd_set rfds; + size_t packlen, addr_size; + struct sockaddr_in client_addr; + struct timeval tv; + int i; + + while (!dhcprelay_stopflag) { + FD_ZERO(&rfds); + for (i = 0; i < num_sockets; i++) + FD_SET(fds[i], &rfds); + tv.tv_sec = SELECT_TIMEOUT; + tv.tv_usec = 0; + if (select(max_socket + 1, &rfds, NULL, NULL, &tv) > 0) { + /* server */ + if (FD_ISSET(fds[0], &rfds)) { + packlen = udhcp_get_packet(&dhcp_msg, fds[0]); + if (packlen > 0) { + pass_back(&dhcp_msg, packlen, fds); + } + } + for (i = 1; i < num_sockets; i++) { + /* clients */ + if (!FD_ISSET(fds[i], &rfds)) + continue; + addr_size = sizeof(struct sockaddr_in); + packlen = recvfrom(fds[i], &dhcp_msg, sizeof(dhcp_msg), 0, + (struct sockaddr *)(&client_addr), &addr_size); + if (packlen <= 0) + continue; + if (read_interface(clients[i-1], NULL, &dhcp_msg.giaddr, NULL) < 0) + dhcp_msg.giaddr = gw_ip; + pass_on(&dhcp_msg, packlen, i, fds, &client_addr, server_addr); + } + } + xid_expire(); + } +} + +int dhcprelay_main(int argc, char **argv) +{ + int i, num_sockets, max_socket, fds[MAX_INTERFACES]; + uint32_t gw_ip; + char **clients; + struct sockaddr_in server_addr; + + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(67); + if (argc == 4) { + if (!inet_aton(argv[3], &server_addr.sin_addr)) + bb_perror_msg_and_die("didn't grok server"); + } else if (argc == 3) { + server_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST); + } else { + bb_show_usage(); + } + clients = get_client_devices(argv[1], &num_sockets); + if (!clients) return 0; + + signal(SIGTERM, dhcprelay_signal_handler); + signal(SIGQUIT, dhcprelay_signal_handler); + signal(SIGINT, dhcprelay_signal_handler); + + num_sockets = init_sockets(clients, num_sockets, argv[2], fds, &max_socket); + if (num_sockets == -1) + bb_perror_msg_and_die("init_sockets() failed"); + + if (read_interface(argv[2], NULL, &gw_ip, NULL) == -1) + return 1; + + dhcprelay_loop(fds, num_sockets, max_socket, clients, &server_addr, gw_ip); + + if (ENABLE_FEATURE_CLEAN_UP) { + for (i = 0; i < num_sockets; i++) { + close(fds[i]); + free(clients[i]); + } + } + + return 0; +} diff --git a/networking/udhcp/dumpleases.c b/networking/udhcp/dumpleases.c new file mode 100644 index 000000000..a0e81bb13 --- /dev/null +++ b/networking/udhcp/dumpleases.c @@ -0,0 +1,74 @@ +/* vi: set sw=4 ts=4: */ +/* + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ +#include + +#include "common.h" +#include "dhcpd.h" + + +#define REMAINING 0 +#define ABSOLUTE 1 + +int dumpleases_main(int argc, char *argv[]) +{ + int fp; + int i, c, mode = REMAINING; + unsigned long expires; + const char *file = LEASES_FILE; + struct dhcpOfferedAddr lease; + struct in_addr addr; + + static const struct option options[] = { + {"absolute", 0, 0, 'a'}, + {"remaining", 0, 0, 'r'}, + {"file", 1, 0, 'f'}, + {0, 0, 0, 0} + }; + + while (1) { + int option_index = 0; + c = getopt_long(argc, argv, "arf:", options, &option_index); + if (c == -1) break; + + switch (c) { + case 'a': mode = ABSOLUTE; break; + case 'r': mode = REMAINING; break; + case 'f': + file = optarg; + break; + default: + bb_show_usage(); + } + } + + fp = xopen(file, O_RDONLY); + + printf("Mac Address IP-Address Expires %s\n", mode == REMAINING ? "in" : "at"); + /* "00:00:00:00:00:00 255.255.255.255 Wed Jun 30 21:49:08 1993" */ + while (full_read(fp, &lease, sizeof(lease)) == sizeof(lease)) { + printf(":%02x"+1, lease.chaddr[0]); + for (i = 1; i < 6; i++) { + printf(":%02x", lease.chaddr[i]); + } + addr.s_addr = lease.yiaddr; + printf(" %-15s ", inet_ntoa(addr)); + expires = ntohl(lease.expires); + if (mode == REMAINING) { + if (!expires) + printf("expired\n"); + else { + unsigned d, h, m; + d = expires / (24*60*60); expires %= (24*60*60); + h = expires / (60*60); expires %= (60*60); + m = expires / 60; expires %= 60; + if (d) printf("%u days ", d); + printf("%02u:%02u:%02u\n", h, m, (unsigned)expires); + } + } else fputs(ctime(&expires), stdout); + } + /* close(fp); */ + + return 0; +} diff --git a/networking/udhcp/files.c b/networking/udhcp/files.c new file mode 100644 index 000000000..5e399e1f8 --- /dev/null +++ b/networking/udhcp/files.c @@ -0,0 +1,395 @@ +/* vi: set sw=4 ts=4: */ +/* + * files.c -- DHCP server file manipulation * + * Rewrite by Russ Dill July 2001 + */ + +#include + +#include "common.h" +#include "dhcpd.h" +#include "options.h" + + +/* + * Domain names may have 254 chars, and string options can be 254 + * chars long. However, 80 bytes will be enough for most, and won't + * hog up memory. If you have a special application, change it + */ +#define READ_CONFIG_BUF_SIZE 80 + +/* on these functions, make sure you datatype matches */ +static int read_ip(const char *line, void *arg) +{ + struct in_addr *addr = arg; + struct hostent *host; + int retval = 1; + + if (!inet_aton(line, addr)) { + host = gethostbyname(line); + if (host) + addr->s_addr = *((unsigned long *) host->h_addr_list[0]); + else retval = 0; + } + return retval; +} + +static int read_mac(const char *line, void *arg) +{ + uint8_t *mac_bytes = arg; + struct ether_addr *temp_ether_addr; + int retval = 1; + + temp_ether_addr = ether_aton(line); + + if (temp_ether_addr == NULL) + retval = 0; + else + memcpy(mac_bytes, temp_ether_addr, 6); + + return retval; +} + + +static int read_str(const char *line, void *arg) +{ + char **dest = arg; + + free(*dest); + *dest = strdup(line); + + return 1; +} + + +static int read_u32(const char *line, void *arg) +{ + *((uint32_t*)arg) = bb_strtou32(line, NULL, 10); + return errno == 0; +} + + +static int read_yn(const char *line, void *arg) +{ + char *dest = arg; + int retval = 1; + + if (!strcasecmp("yes", line)) + *dest = 1; + else if (!strcasecmp("no", line)) + *dest = 0; + else retval = 0; + + return retval; +} + + +/* find option 'code' in opt_list */ +struct option_set *find_option(struct option_set *opt_list, char code) +{ + while (opt_list && opt_list->data[OPT_CODE] < code) + opt_list = opt_list->next; + + if (opt_list && opt_list->data[OPT_CODE] == code) return opt_list; + else return NULL; +} + + +/* add an option to the opt_list */ +static void attach_option(struct option_set **opt_list, + const struct dhcp_option *option, char *buffer, int length) +{ + struct option_set *existing, *new, **curr; + + /* add it to an existing option */ + existing = find_option(*opt_list, option->code); + if (existing) { + DEBUG("Attaching option %s to existing member of list", option->name); + if (option->flags & OPTION_LIST) { + if (existing->data[OPT_LEN] + length <= 255) { + existing->data = realloc(existing->data, + existing->data[OPT_LEN] + length + 2); + memcpy(existing->data + existing->data[OPT_LEN] + 2, buffer, length); + existing->data[OPT_LEN] += length; + } /* else, ignore the data, we could put this in a second option in the future */ + } /* else, ignore the new data */ + } else { + DEBUG("Attaching option %s to list", option->name); + + /* make a new option */ + new = xmalloc(sizeof(struct option_set)); + new->data = xmalloc(length + 2); + new->data[OPT_CODE] = option->code; + new->data[OPT_LEN] = length; + memcpy(new->data + 2, buffer, length); + + curr = opt_list; + while (*curr && (*curr)->data[OPT_CODE] < option->code) + curr = &(*curr)->next; + + new->next = *curr; + *curr = new; + } +} + + +/* read a dhcp option and add it to opt_list */ +static int read_opt(const char *const_line, void *arg) +{ + struct option_set **opt_list = arg; + char *opt, *val, *endptr; + const struct dhcp_option *option; + int retval = 0, length; + char buffer[8]; + char *line; + uint16_t *result_u16 = (uint16_t *) buffer; + uint32_t *result_u32 = (uint32_t *) buffer; + + /* Cheat, the only const line we'll actually get is "" */ + line = (char *) const_line; + opt = strtok(line, " \t="); + if (!opt) return 0; + + option = dhcp_options; + while (1) { + if (!option->code) + return 0; + if (!strcasecmp(option->name, opt)) + break; + option++; + } + + do { + val = strtok(NULL, ", \t"); + if (!val) break; + length = option_lengths[option->flags & TYPE_MASK]; + retval = 0; + opt = buffer; /* new meaning for variable opt */ + switch (option->flags & TYPE_MASK) { + case OPTION_IP: + retval = read_ip(val, buffer); + break; + case OPTION_IP_PAIR: + retval = read_ip(val, buffer); + if (!(val = strtok(NULL, ", \t/-"))) retval = 0; + if (retval) retval = read_ip(val, buffer + 4); + break; + case OPTION_STRING: + length = strlen(val); + if (length > 0) { + if (length > 254) length = 254; + opt = val; + retval = 1; + } + break; + case OPTION_BOOLEAN: + retval = read_yn(val, buffer); + break; + case OPTION_U8: + buffer[0] = strtoul(val, &endptr, 0); + retval = (endptr[0] == '\0'); + break; + case OPTION_U16: + *result_u16 = htons(strtoul(val, &endptr, 0)); + retval = (endptr[0] == '\0'); + break; + case OPTION_S16: + *result_u16 = htons(strtol(val, &endptr, 0)); + retval = (endptr[0] == '\0'); + break; + case OPTION_U32: + *result_u32 = htonl(strtoul(val, &endptr, 0)); + retval = (endptr[0] == '\0'); + break; + case OPTION_S32: + *result_u32 = htonl(strtol(val, &endptr, 0)); + retval = (endptr[0] == '\0'); + break; + default: + break; + } + if (retval) + attach_option(opt_list, option, opt, length); + } while (retval && option->flags & OPTION_LIST); + return retval; +} + +static int read_staticlease(const char *const_line, void *arg) +{ + char *line; + char *mac_string; + char *ip_string; + uint8_t *mac_bytes; + uint32_t *ip; + + + /* Allocate memory for addresses */ + mac_bytes = xmalloc(sizeof(unsigned char) * 8); + ip = xmalloc(sizeof(uint32_t)); + + /* Read mac */ + line = (char *) const_line; + mac_string = strtok(line, " \t"); + read_mac(mac_string, mac_bytes); + + /* Read ip */ + ip_string = strtok(NULL, " \t"); + read_ip(ip_string, ip); + + addStaticLease(arg, mac_bytes, ip); + + if (ENABLE_FEATURE_UDHCP_DEBUG) printStaticLeases(arg); + + return 1; +} + + +static const struct config_keyword keywords[] = { + /* keyword handler variable address default */ + {"start", read_ip, &(server_config.start), "192.168.0.20"}, + {"end", read_ip, &(server_config.end), "192.168.0.254"}, + {"interface", read_str, &(server_config.interface), "eth0"}, + {"option", read_opt, &(server_config.options), ""}, + {"opt", read_opt, &(server_config.options), ""}, + {"max_leases", read_u32, &(server_config.max_leases), "254"}, + {"remaining", read_yn, &(server_config.remaining), "yes"}, + {"auto_time", read_u32, &(server_config.auto_time), "7200"}, + {"decline_time",read_u32, &(server_config.decline_time),"3600"}, + {"conflict_time",read_u32,&(server_config.conflict_time),"3600"}, + {"offer_time", read_u32, &(server_config.offer_time), "60"}, + {"min_lease", read_u32, &(server_config.min_lease), "60"}, + {"lease_file", read_str, &(server_config.lease_file), LEASES_FILE}, + {"pidfile", read_str, &(server_config.pidfile), "/var/run/udhcpd.pid"}, + {"notify_file", read_str, &(server_config.notify_file), ""}, + {"siaddr", read_ip, &(server_config.siaddr), "0.0.0.0"}, + {"sname", read_str, &(server_config.sname), ""}, + {"boot_file", read_str, &(server_config.boot_file), ""}, + {"static_lease",read_staticlease, &(server_config.static_leases), ""}, + /*ADDME: static lease */ + {"", NULL, NULL, ""} +}; + + +int read_config(const char *file) +{ + FILE *in; + char buffer[READ_CONFIG_BUF_SIZE], *token, *line; + int i, lm = 0; + + for (i = 0; keywords[i].keyword[0]; i++) + if (keywords[i].def[0]) + keywords[i].handler(keywords[i].def, keywords[i].var); + + in = fopen(file, "r"); + if (!in) { + bb_error_msg("cannot open config file: %s", file); + return 0; + } + + while (fgets(buffer, READ_CONFIG_BUF_SIZE, in)) { + char debug_orig[READ_CONFIG_BUF_SIZE]; + char *p; + + lm++; + p = strchr(buffer, '\n'); + if (p) *p = '\0'; + if (ENABLE_FEATURE_UDHCP_DEBUG) strcpy(debug_orig, buffer); + p = strchr(buffer, '#'); + if (p) *p = '\0'; + + if (!(token = strtok(buffer, " \t"))) continue; + if (!(line = strtok(NULL, ""))) continue; + + /* eat leading whitespace */ + line = skip_whitespace(line); + /* eat trailing whitespace */ + i = strlen(line) - 1; + while (i >= 0 && isspace(line[i])) + line[i--] = '\0'; + + for (i = 0; keywords[i].keyword[0]; i++) + if (!strcasecmp(token, keywords[i].keyword)) + if (!keywords[i].handler(line, keywords[i].var)) { + bb_error_msg("cannot parse line %d of %s", lm, file); + if (ENABLE_FEATURE_UDHCP_DEBUG) + bb_error_msg("cannot parse '%s'", debug_orig); + /* reset back to the default value */ + keywords[i].handler(keywords[i].def, keywords[i].var); + } + } + fclose(in); + return 1; +} + + +void write_leases(void) +{ + int fp; + unsigned i; + time_t curr = time(0); + unsigned long tmp_time; + + fp = open(server_config.lease_file, O_WRONLY|O_CREAT|O_TRUNC, 0666); + if (fp < 0) { + bb_error_msg("cannot open %s for writing", server_config.lease_file); + return; + } + + for (i = 0; i < server_config.max_leases; i++) { + if (leases[i].yiaddr != 0) { + + /* screw with the time in the struct, for easier writing */ + tmp_time = leases[i].expires; + + if (server_config.remaining) { + if (lease_expired(&(leases[i]))) + leases[i].expires = 0; + else leases[i].expires -= curr; + } /* else stick with the time we got */ + leases[i].expires = htonl(leases[i].expires); + // FIXME: error check?? + full_write(fp, &leases[i], sizeof(leases[i])); + + /* then restore it when done */ + leases[i].expires = tmp_time; + } + } + close(fp); + + if (server_config.notify_file) { + char *cmd = xasprintf("%s %s", server_config.notify_file, server_config.lease_file); + system(cmd); + free(cmd); + } +} + + +void read_leases(const char *file) +{ + int fp; + unsigned int i = 0; + struct dhcpOfferedAddr lease; + + fp = open(file, O_RDONLY); + if (fp < 0) { + bb_error_msg("cannot open %s for reading", file); + return; + } + + while (i < server_config.max_leases + && full_read(fp, &lease, sizeof(lease)) == sizeof(lease) + ) { + /* ADDME: is it a static lease */ + if (lease.yiaddr >= server_config.start && lease.yiaddr <= server_config.end) { + lease.expires = ntohl(lease.expires); + if (!server_config.remaining) lease.expires -= time(0); + if (!(add_lease(lease.chaddr, lease.yiaddr, lease.expires))) { + bb_error_msg("too many leases while loading %s", file); + break; + } + i++; + } + } + DEBUG("Read %d leases", i); + close(fp); +} diff --git a/networking/udhcp/leases.c b/networking/udhcp/leases.c new file mode 100644 index 000000000..2f7847d74 --- /dev/null +++ b/networking/udhcp/leases.c @@ -0,0 +1,145 @@ +/* vi: set sw=4 ts=4: */ +/* + * leases.c -- tools to manage DHCP leases + * Russ Dill July 2001 + */ + +#include "common.h" +#include "dhcpd.h" + + +uint8_t blank_chaddr[] = {[0 ... 15] = 0}; + +/* clear every lease out that chaddr OR yiaddr matches and is nonzero */ +void clear_lease(uint8_t *chaddr, uint32_t yiaddr) +{ + unsigned int i, j; + + for (j = 0; j < 16 && !chaddr[j]; j++); + + for (i = 0; i < server_config.max_leases; i++) + if ((j != 16 && !memcmp(leases[i].chaddr, chaddr, 16)) || + (yiaddr && leases[i].yiaddr == yiaddr)) { + memset(&(leases[i]), 0, sizeof(struct dhcpOfferedAddr)); + } +} + + +/* add a lease into the table, clearing out any old ones */ +struct dhcpOfferedAddr *add_lease(uint8_t *chaddr, uint32_t yiaddr, unsigned long lease) +{ + struct dhcpOfferedAddr *oldest; + + /* clean out any old ones */ + clear_lease(chaddr, yiaddr); + + oldest = oldest_expired_lease(); + + if (oldest) { + memcpy(oldest->chaddr, chaddr, 16); + oldest->yiaddr = yiaddr; + oldest->expires = time(0) + lease; + } + + return oldest; +} + + +/* true if a lease has expired */ +int lease_expired(struct dhcpOfferedAddr *lease) +{ + return (lease->expires < (unsigned long) time(0)); +} + + +/* Find the oldest expired lease, NULL if there are no expired leases */ +struct dhcpOfferedAddr *oldest_expired_lease(void) +{ + struct dhcpOfferedAddr *oldest = NULL; + unsigned long oldest_lease = time(0); + unsigned int i; + + + for (i = 0; i < server_config.max_leases; i++) + if (oldest_lease > leases[i].expires) { + oldest_lease = leases[i].expires; + oldest = &(leases[i]); + } + return oldest; + +} + + +/* Find the first lease that matches chaddr, NULL if no match */ +struct dhcpOfferedAddr *find_lease_by_chaddr(uint8_t *chaddr) +{ + unsigned int i; + + for (i = 0; i < server_config.max_leases; i++) + if (!memcmp(leases[i].chaddr, chaddr, 16)) return &(leases[i]); + + return NULL; +} + + +/* Find the first lease that matches yiaddr, NULL is no match */ +struct dhcpOfferedAddr *find_lease_by_yiaddr(uint32_t yiaddr) +{ + unsigned int i; + + for (i = 0; i < server_config.max_leases; i++) + if (leases[i].yiaddr == yiaddr) return &(leases[i]); + + return NULL; +} + + +/* check is an IP is taken, if it is, add it to the lease table */ +static int check_ip(uint32_t addr) +{ + struct in_addr temp; + + if (arpping(addr, server_config.server, server_config.arp, server_config.interface) == 0) { + temp.s_addr = addr; + bb_info_msg("%s belongs to someone, reserving it for %ld seconds", + inet_ntoa(temp), server_config.conflict_time); + add_lease(blank_chaddr, addr, server_config.conflict_time); + return 1; + } else return 0; +} + + +/* find an assignable address, it check_expired is true, we check all the expired leases as well. + * Maybe this should try expired leases by age... */ +uint32_t find_address(int check_expired) +{ + uint32_t addr, ret; + struct dhcpOfferedAddr *lease = NULL; + + addr = ntohl(server_config.start); /* addr is in host order here */ + for (;addr <= ntohl(server_config.end); addr++) { + + /* ie, 192.168.55.0 */ + if (!(addr & 0xFF)) continue; + + /* ie, 192.168.55.255 */ + if ((addr & 0xFF) == 0xFF) continue; + + /* Only do if it isn't an assigned as a static lease */ + if (!reservedIp(server_config.static_leases, htonl(addr))) { + + /* lease is not taken */ + ret = htonl(addr); + lease = find_lease_by_yiaddr(ret); + + /* no lease or it expired and we are checking for expired leases */ + if ( (!lease || (check_expired && lease_expired(lease))) + && /* and it isn't on the network */ !check_ip(ret) + ) { + return ret; + break; + } + } + } + return 0; +} diff --git a/networking/udhcp/options.c b/networking/udhcp/options.c new file mode 100644 index 000000000..4a46da579 --- /dev/null +++ b/networking/udhcp/options.c @@ -0,0 +1,174 @@ +/* vi: set sw=4 ts=4: */ +/* + * options.c -- DHCP server option packet tools + * Rewrite by Russ Dill July 2001 + */ + +#include "common.h" +#include "dhcpd.h" +#include "options.h" + + +/* supported options are easily added here */ +const struct dhcp_option dhcp_options[] = { + /* name[10] flags code */ + {"subnet", OPTION_IP | OPTION_REQ, 0x01}, + {"timezone", OPTION_S32, 0x02}, + {"router", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x03}, + {"timesvr", OPTION_IP | OPTION_LIST, 0x04}, + {"namesvr", OPTION_IP | OPTION_LIST, 0x05}, + {"dns", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x06}, + {"logsvr", OPTION_IP | OPTION_LIST, 0x07}, + {"cookiesvr", OPTION_IP | OPTION_LIST, 0x08}, + {"lprsvr", OPTION_IP | OPTION_LIST, 0x09}, + {"hostname", OPTION_STRING | OPTION_REQ, 0x0c}, + {"bootsize", OPTION_U16, 0x0d}, + {"domain", OPTION_STRING | OPTION_REQ, 0x0f}, + {"swapsvr", OPTION_IP, 0x10}, + {"rootpath", OPTION_STRING, 0x11}, + {"ipttl", OPTION_U8, 0x17}, + {"mtu", OPTION_U16, 0x1a}, + {"broadcast", OPTION_IP | OPTION_REQ, 0x1c}, + {"nisdomain", OPTION_STRING | OPTION_REQ, 0x28}, + {"nissrv", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x29}, + {"ntpsrv", OPTION_IP | OPTION_LIST | OPTION_REQ, 0x2a}, + {"wins", OPTION_IP | OPTION_LIST, 0x2c}, + {"requestip", OPTION_IP, 0x32}, + {"lease", OPTION_U32, 0x33}, + {"dhcptype", OPTION_U8, 0x35}, + {"serverid", OPTION_IP, 0x36}, + {"message", OPTION_STRING, 0x38}, + {"vendorclass", OPTION_STRING, 0x3C}, + {"clientid", OPTION_STRING, 0x3D}, + {"tftp", OPTION_STRING, 0x42}, + {"bootfile", OPTION_STRING, 0x43}, + {"userclass", OPTION_STRING, 0x4D}, + /* MSIE's "Web Proxy Autodiscovery Protocol" support */ + {"wpad", OPTION_STRING, 0xfc}, + {"", 0x00, 0x00} +}; + +/* Lengths of the different option types */ +const unsigned char option_lengths[] = { + [OPTION_IP] = 4, + [OPTION_IP_PAIR] = 8, + [OPTION_BOOLEAN] = 1, + [OPTION_STRING] = 1, + [OPTION_U8] = 1, + [OPTION_U16] = 2, + [OPTION_S16] = 2, + [OPTION_U32] = 4, + [OPTION_S32] = 4 +}; + + +/* get an option with bounds checking (warning, not aligned). */ +uint8_t *get_option(struct dhcpMessage *packet, int code) +{ + int i, length; + uint8_t *optionptr; + int over = 0, done = 0, curr = OPTION_FIELD; + + optionptr = packet->options; + i = 0; + length = 308; + while (!done) { + if (i >= length) { + bb_error_msg("bogus packet, option fields too long"); + return NULL; + } + if (optionptr[i + OPT_CODE] == code) { + if (i + 1 + optionptr[i + OPT_LEN] >= length) { + bb_error_msg("bogus packet, option fields too long"); + return NULL; + } + return optionptr + i + 2; + } + switch (optionptr[i + OPT_CODE]) { + case DHCP_PADDING: + i++; + break; + case DHCP_OPTION_OVER: + if (i + 1 + optionptr[i + OPT_LEN] >= length) { + bb_error_msg("bogus packet, option fields too long"); + return NULL; + } + over = optionptr[i + 3]; + i += optionptr[OPT_LEN] + 2; + break; + case DHCP_END: + if (curr == OPTION_FIELD && over & FILE_FIELD) { + optionptr = packet->file; + i = 0; + length = 128; + curr = FILE_FIELD; + } else if (curr == FILE_FIELD && over & SNAME_FIELD) { + optionptr = packet->sname; + i = 0; + length = 64; + curr = SNAME_FIELD; + } else done = 1; + break; + default: + i += optionptr[OPT_LEN + i] + 2; + } + } + return NULL; +} + + +/* return the position of the 'end' option (no bounds checking) */ +int end_option(uint8_t *optionptr) +{ + int i = 0; + + while (optionptr[i] != DHCP_END) { + if (optionptr[i] == DHCP_PADDING) i++; + else i += optionptr[i + OPT_LEN] + 2; + } + return i; +} + + +/* add an option string to the options (an option string contains an option code, + * length, then data) */ +int add_option_string(uint8_t *optionptr, uint8_t *string) +{ + int end = end_option(optionptr); + + /* end position + string length + option code/length + end option */ + if (end + string[OPT_LEN] + 2 + 1 >= 308) { + bb_error_msg("option 0x%02x did not fit into the packet", + string[OPT_CODE]); + return 0; + } + DEBUG("adding option 0x%02x", string[OPT_CODE]); + memcpy(optionptr + end, string, string[OPT_LEN] + 2); + optionptr[end + string[OPT_LEN] + 2] = DHCP_END; + return string[OPT_LEN] + 2; +} + + +/* add a one to four byte option to a packet */ +int add_simple_option(uint8_t *optionptr, uint8_t code, uint32_t data) +{ + const struct dhcp_option *dh; + + for (dh = dhcp_options; dh->code; dh++) { + if (dh->code == code) { + uint8_t option[6], len; + + option[OPT_CODE] = code; + len = option_lengths[dh->flags & TYPE_MASK]; + option[OPT_LEN] = len; + if (BB_BIG_ENDIAN) data <<= 8 * (4 - len); + /* This memcpy is for broken processors which can't + * handle a simple unaligned 32-bit assignment */ + memcpy(&option[OPT_DATA], &data, 4); + return add_option_string(optionptr, option); + } + } + + bb_error_msg("cannot add option 0x%02x", code); + return 0; +} diff --git a/networking/udhcp/options.h b/networking/udhcp/options.h new file mode 100644 index 000000000..588504e5d --- /dev/null +++ b/networking/udhcp/options.h @@ -0,0 +1,37 @@ +/* vi: set sw=4 ts=4: */ +/* options.h */ +#ifndef _OPTIONS_H +#define _OPTIONS_H + +#define TYPE_MASK 0x0F + +enum { + OPTION_IP=1, + OPTION_IP_PAIR, + OPTION_STRING, + OPTION_BOOLEAN, + OPTION_U8, + OPTION_U16, + OPTION_S16, + OPTION_U32, + OPTION_S32 +}; + +#define OPTION_REQ 0x10 /* have the client request this option */ +#define OPTION_LIST 0x20 /* There can be a list of 1 or more of these */ + +struct dhcp_option { + char name[12]; + char flags; + uint8_t code; +}; + +extern const struct dhcp_option dhcp_options[]; +extern const unsigned char option_lengths[]; + +uint8_t *get_option(struct dhcpMessage *packet, int code); +int end_option(uint8_t *optionptr); +int add_option_string(uint8_t *optionptr, uint8_t *string); +int add_simple_option(uint8_t *optionptr, uint8_t code, uint32_t data); + +#endif diff --git a/networking/udhcp/packet.c b/networking/udhcp/packet.c new file mode 100644 index 000000000..dec9d0ab3 --- /dev/null +++ b/networking/udhcp/packet.c @@ -0,0 +1,211 @@ +/* vi: set sw=4 ts=4: */ + +#include +#if (__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined _NEWLIB_VERSION +#include +#include +#else +#include +#include +#include +#endif + +#include "common.h" +#include "dhcpd.h" +#include "options.h" + + +void udhcp_init_header(struct dhcpMessage *packet, char type) +{ + memset(packet, 0, sizeof(struct dhcpMessage)); + switch (type) { + case DHCPDISCOVER: + case DHCPREQUEST: + case DHCPRELEASE: + case DHCPINFORM: + packet->op = BOOTREQUEST; + break; + case DHCPOFFER: + case DHCPACK: + case DHCPNAK: + packet->op = BOOTREPLY; + } + packet->htype = ETH_10MB; + packet->hlen = ETH_10MB_LEN; + packet->cookie = htonl(DHCP_MAGIC); + packet->options[0] = DHCP_END; + add_simple_option(packet->options, DHCP_MESSAGE_TYPE, type); +} + + +/* read a packet from socket fd, return -1 on read error, -2 on packet error */ +int udhcp_get_packet(struct dhcpMessage *packet, int fd) +{ + static const char broken_vendors[][8] = { + "MSFT 98", + "" + }; + int bytes; + int i; + char unsigned *vendor; + + memset(packet, 0, sizeof(struct dhcpMessage)); + bytes = read(fd, packet, sizeof(struct dhcpMessage)); + if (bytes < 0) { + DEBUG("cannot read on listening socket, ignoring"); + return -1; + } + + if (ntohl(packet->cookie) != DHCP_MAGIC) { + bb_error_msg("received bogus message, ignoring"); + return -2; + } + DEBUG("Received a packet"); + + if (packet->op == BOOTREQUEST && (vendor = get_option(packet, DHCP_VENDOR))) { + for (i = 0; broken_vendors[i][0]; i++) { + if (vendor[OPT_LEN - 2] == (uint8_t)strlen(broken_vendors[i]) + && !strncmp((char*)vendor, broken_vendors[i], vendor[OPT_LEN - 2]) + ) { + DEBUG("broken client (%s), forcing broadcast", + broken_vendors[i]); + packet->flags |= htons(BROADCAST_FLAG); + } + } + } + + return bytes; +} + + +uint16_t udhcp_checksum(void *addr, int count) +{ + /* Compute Internet Checksum for "count" bytes + * beginning at location "addr". + */ + int32_t sum = 0; + uint16_t *source = (uint16_t *) addr; + + while (count > 1) { + /* This is the inner loop */ + sum += *source++; + count -= 2; + } + + /* Add left-over byte, if any */ + if (count > 0) { + /* Make sure that the left-over byte is added correctly both + * with little and big endian hosts */ + uint16_t tmp = 0; + *(uint8_t *) (&tmp) = * (uint8_t *) source; + sum += tmp; + } + /* Fold 32-bit sum to 16 bits */ + while (sum >> 16) + sum = (sum & 0xffff) + (sum >> 16); + + return ~sum; +} + + +/* Construct a ip/udp header for a packet, and specify the source and dest hardware address */ +void BUG_sizeof_struct_udp_dhcp_packet_must_be_576(void); +int udhcp_raw_packet(struct dhcpMessage *payload, + uint32_t source_ip, int source_port, + uint32_t dest_ip, int dest_port, uint8_t *dest_arp, int ifindex) +{ + int fd; + int result; + struct sockaddr_ll dest; + struct udp_dhcp_packet packet; + + fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP)); + if (fd < 0) { + bb_perror_msg("socket"); + return -1; + } + + memset(&dest, 0, sizeof(dest)); + memset(&packet, 0, sizeof(packet)); + + dest.sll_family = AF_PACKET; + dest.sll_protocol = htons(ETH_P_IP); + dest.sll_ifindex = ifindex; + dest.sll_halen = 6; + memcpy(dest.sll_addr, dest_arp, 6); + if (bind(fd, (struct sockaddr *)&dest, sizeof(struct sockaddr_ll)) < 0) { + bb_perror_msg("bind"); + close(fd); + return -1; + } + + packet.ip.protocol = IPPROTO_UDP; + packet.ip.saddr = source_ip; + packet.ip.daddr = dest_ip; + packet.udp.source = htons(source_port); + packet.udp.dest = htons(dest_port); + packet.udp.len = htons(sizeof(packet.udp) + sizeof(struct dhcpMessage)); /* cheat on the psuedo-header */ + packet.ip.tot_len = packet.udp.len; + memcpy(&(packet.data), payload, sizeof(struct dhcpMessage)); + packet.udp.check = udhcp_checksum(&packet, sizeof(struct udp_dhcp_packet)); + + packet.ip.tot_len = htons(sizeof(struct udp_dhcp_packet)); + packet.ip.ihl = sizeof(packet.ip) >> 2; + packet.ip.version = IPVERSION; + packet.ip.ttl = IPDEFTTL; + packet.ip.check = udhcp_checksum(&(packet.ip), sizeof(packet.ip)); + + if (sizeof(struct udp_dhcp_packet) != 576) + BUG_sizeof_struct_udp_dhcp_packet_must_be_576(); + + result = sendto(fd, &packet, sizeof(struct udp_dhcp_packet), 0, + (struct sockaddr *) &dest, sizeof(dest)); + if (result <= 0) { + bb_perror_msg("sendto"); + } + close(fd); + return result; +} + + +/* Let the kernel do all the work for packet generation */ +int udhcp_kernel_packet(struct dhcpMessage *payload, + uint32_t source_ip, int source_port, + uint32_t dest_ip, int dest_port) +{ + int fd, result; + struct sockaddr_in client; + + fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) + return -1; + + if (setsockopt_reuseaddr(fd) == -1) { + close(fd); + return -1; + } + + memset(&client, 0, sizeof(client)); + client.sin_family = AF_INET; + client.sin_port = htons(source_port); + client.sin_addr.s_addr = source_ip; + + if (bind(fd, (struct sockaddr *)&client, sizeof(struct sockaddr)) == -1) { + close(fd); + return -1; + } + + memset(&client, 0, sizeof(client)); + client.sin_family = AF_INET; + client.sin_port = htons(dest_port); + client.sin_addr.s_addr = dest_ip; + + if (connect(fd, (struct sockaddr *)&client, sizeof(struct sockaddr)) == -1) { + close(fd); + return -1; + } + + result = write(fd, payload, sizeof(struct dhcpMessage)); + close(fd); + return result; +} diff --git a/networking/udhcp/pidfile.c b/networking/udhcp/pidfile.c new file mode 100644 index 000000000..bcb2608c5 --- /dev/null +++ b/networking/udhcp/pidfile.c @@ -0,0 +1,66 @@ +/* vi: set sw=4 ts=4: */ +/* pidfile.c + * + * Functions to assist in the writing and removing of pidfiles. + * + * Russ Dill September 2001 + * + * 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. + */ + +#include "common.h" + + +static const char *saved_pidfile; + +static void pidfile_delete(void) +{ + if (saved_pidfile) unlink(saved_pidfile); +} + + +int pidfile_acquire(const char *pidfile) +{ + int pid_fd; + if (!pidfile) return -1; + + pid_fd = open(pidfile, O_CREAT|O_WRONLY|O_TRUNC, 0644); + if (pid_fd < 0) { + bb_perror_msg("cannot open pidfile %s", pidfile); + } else { + lockf(pid_fd, F_LOCK, 0); + if (!saved_pidfile) + atexit(pidfile_delete); + saved_pidfile = pidfile; + } + + return pid_fd; +} + + +void pidfile_write_release(int pid_fd) +{ + FILE *out; + + if (pid_fd < 0) return; + + out = fdopen(pid_fd, "w"); + if (out) { + fprintf(out, "%d\n", getpid()); + fclose(out); + } + lockf(pid_fd, F_UNLCK, 0); + close(pid_fd); +} diff --git a/networking/udhcp/script.c b/networking/udhcp/script.c new file mode 100644 index 000000000..07f68362c --- /dev/null +++ b/networking/udhcp/script.c @@ -0,0 +1,213 @@ +/* vi: set sw=4 ts=4: */ +/* script.c + * + * Functions to call the DHCP client notification scripts + * + * Russ Dill July 2001 + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "common.h" +#include "dhcpd.h" +#include "dhcpc.h" +#include "options.h" + + +/* get a rough idea of how long an option will be (rounding up...) */ +static const int max_option_length[] = { + [OPTION_IP] = sizeof("255.255.255.255 "), + [OPTION_IP_PAIR] = sizeof("255.255.255.255 ") * 2, + [OPTION_STRING] = 1, + [OPTION_BOOLEAN] = sizeof("yes "), + [OPTION_U8] = sizeof("255 "), + [OPTION_U16] = sizeof("65535 "), + [OPTION_S16] = sizeof("-32768 "), + [OPTION_U32] = sizeof("4294967295 "), + [OPTION_S32] = sizeof("-2147483684 "), +}; + + +static inline int upper_length(int length, int opt_index) +{ + return max_option_length[opt_index] * + (length / option_lengths[opt_index]); +} + + +static int sprintip(char *dest, char *pre, uint8_t *ip) +{ + return sprintf(dest, "%s%d.%d.%d.%d", pre, ip[0], ip[1], ip[2], ip[3]); +} + + +/* really simple implementation, just count the bits */ +static int mton(struct in_addr *mask) +{ + int i; + unsigned long bits = ntohl(mask->s_addr); + /* too bad one can't check the carry bit, etc in c bit + * shifting */ + for (i = 0; i < 32 && !((bits >> i) & 1); i++); + return 32 - i; +} + + +/* Fill dest with the text of option 'option'. */ +static void fill_options(char *dest, uint8_t *option, + const struct dhcp_option *type_p) +{ + int type, optlen; + uint16_t val_u16; + int16_t val_s16; + uint32_t val_u32; + int32_t val_s32; + int len = option[OPT_LEN - 2]; + + dest += sprintf(dest, "%s=", type_p->name); + + type = type_p->flags & TYPE_MASK; + optlen = option_lengths[type]; + for (;;) { + switch (type) { + case OPTION_IP_PAIR: + dest += sprintip(dest, "", option); + *(dest++) = '/'; + option += 4; + optlen = 4; + case OPTION_IP: /* Works regardless of host byte order. */ + dest += sprintip(dest, "", option); + break; + case OPTION_BOOLEAN: + dest += sprintf(dest, *option ? "yes" : "no"); + break; + case OPTION_U8: + dest += sprintf(dest, "%u", *option); + break; + case OPTION_U16: + memcpy(&val_u16, option, 2); + dest += sprintf(dest, "%u", ntohs(val_u16)); + break; + case OPTION_S16: + memcpy(&val_s16, option, 2); + dest += sprintf(dest, "%d", ntohs(val_s16)); + break; + case OPTION_U32: + memcpy(&val_u32, option, 4); + dest += sprintf(dest, "%lu", (unsigned long) ntohl(val_u32)); + break; + case OPTION_S32: + memcpy(&val_s32, option, 4); + dest += sprintf(dest, "%ld", (long) ntohl(val_s32)); + break; + case OPTION_STRING: + memcpy(dest, option, len); + dest[len] = '\0'; + return; /* Short circuit this case */ + } + option += optlen; + len -= optlen; + if (len <= 0) break; + dest += sprintf(dest, " "); + } +} + + +/* put all the parameters into an environment */ +static char **fill_envp(struct dhcpMessage *packet) +{ + int num_options = 0; + int i, j; + char **envp; + uint8_t *temp; + struct in_addr subnet; + char over = 0; + + if (packet == NULL) + num_options = 0; + else { + for (i = 0; dhcp_options[i].code; i++) + if (get_option(packet, dhcp_options[i].code)) { + num_options++; + if (dhcp_options[i].code == DHCP_SUBNET) + num_options++; /* for mton */ + } + if (packet->siaddr) num_options++; + if ((temp = get_option(packet, DHCP_OPTION_OVER))) + over = *temp; + if (!(over & FILE_FIELD) && packet->file[0]) num_options++; + if (!(over & SNAME_FIELD) && packet->sname[0]) num_options++; + } + + envp = xzalloc(sizeof(char *) * (num_options + 5)); + j = 0; + envp[j++] = xasprintf("interface=%s", client_config.interface); + envp[j++] = xasprintf("PATH=%s", + getenv("PATH") ? : "/bin:/usr/bin:/sbin:/usr/sbin"); + envp[j++] = xasprintf("HOME=%s", getenv("HOME") ? : "/"); + + if (packet == NULL) return envp; + + envp[j] = xmalloc(sizeof("ip=255.255.255.255")); + sprintip(envp[j++], "ip=", (uint8_t *) &packet->yiaddr); + + for (i = 0; dhcp_options[i].code; i++) { + temp = get_option(packet, dhcp_options[i].code); + if (!temp) + continue; + envp[j] = xmalloc(upper_length(temp[OPT_LEN - 2], + dhcp_options[i].flags & TYPE_MASK) + strlen(dhcp_options[i].name) + 2); + fill_options(envp[j++], temp, &dhcp_options[i]); + + /* Fill in a subnet bits option for things like /24 */ + if (dhcp_options[i].code == DHCP_SUBNET) { + memcpy(&subnet, temp, 4); + envp[j++] = xasprintf("mask=%d", mton(&subnet)); + } + } + if (packet->siaddr) { + envp[j] = xmalloc(sizeof("siaddr=255.255.255.255")); + sprintip(envp[j++], "siaddr=", (uint8_t *) &packet->siaddr); + } + if (!(over & FILE_FIELD) && packet->file[0]) { + /* watch out for invalid packets */ + packet->file[sizeof(packet->file) - 1] = '\0'; + envp[j++] = xasprintf("boot_file=%s", packet->file); + } + if (!(over & SNAME_FIELD) && packet->sname[0]) { + /* watch out for invalid packets */ + packet->sname[sizeof(packet->sname) - 1] = '\0'; + envp[j++] = xasprintf("sname=%s", packet->sname); + } + return envp; +} + + +/* Call a script with a par file and env vars */ +void udhcp_run_script(struct dhcpMessage *packet, const char *name) +{ + int pid; + char **envp, **curr; + + if (client_config.script == NULL) + return; + + DEBUG("vfork'ing and execle'ing %s", client_config.script); + + envp = fill_envp(packet); + /* call script */ + pid = vfork(); + if (pid) { + waitpid(pid, NULL, 0); + for (curr = envp; *curr; curr++) free(*curr); + free(envp); + return; + } else if (pid == 0) { + /* close fd's? */ + /* exec script */ + execle(client_config.script, client_config.script, + name, NULL, envp); + bb_perror_msg("script %s failed", client_config.script); + exit(1); + } +} diff --git a/networking/udhcp/serverpacket.c b/networking/udhcp/serverpacket.c new file mode 100644 index 000000000..8889fda86 --- /dev/null +++ b/networking/udhcp/serverpacket.c @@ -0,0 +1,261 @@ +/* vi: set sw=4 ts=4: */ +/* serverpacket.c + * + * Construct and send DHCP server packets + * + * Russ Dill July 2001 + * + * 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. + */ + +#include "common.h" +#include "dhcpd.h" +#include "options.h" + + +/* send a packet to giaddr using the kernel ip stack */ +static int send_packet_to_relay(struct dhcpMessage *payload) +{ + DEBUG("Forwarding packet to relay"); + + return udhcp_kernel_packet(payload, server_config.server, SERVER_PORT, + payload->giaddr, SERVER_PORT); +} + + +/* send a packet to a specific arp address and ip address by creating our own ip packet */ +static int send_packet_to_client(struct dhcpMessage *payload, int force_broadcast) +{ + uint8_t *chaddr; + uint32_t ciaddr; + + if (force_broadcast) { + DEBUG("broadcasting packet to client (NAK)"); + ciaddr = INADDR_BROADCAST; + chaddr = MAC_BCAST_ADDR; + } else if (payload->ciaddr) { + DEBUG("unicasting packet to client ciaddr"); + ciaddr = payload->ciaddr; + chaddr = payload->chaddr; + } else if (ntohs(payload->flags) & BROADCAST_FLAG) { + DEBUG("broadcasting packet to client (requested)"); + ciaddr = INADDR_BROADCAST; + chaddr = MAC_BCAST_ADDR; + } else { + DEBUG("unicasting packet to client yiaddr"); + ciaddr = payload->yiaddr; + chaddr = payload->chaddr; + } + return udhcp_raw_packet(payload, server_config.server, SERVER_PORT, + ciaddr, CLIENT_PORT, chaddr, server_config.ifindex); +} + + +/* send a dhcp packet, if force broadcast is set, the packet will be broadcast to the client */ +static int send_packet(struct dhcpMessage *payload, int force_broadcast) +{ + int ret; + + if (payload->giaddr) + ret = send_packet_to_relay(payload); + else ret = send_packet_to_client(payload, force_broadcast); + return ret; +} + + +static void init_packet(struct dhcpMessage *packet, struct dhcpMessage *oldpacket, char type) +{ + udhcp_init_header(packet, type); + packet->xid = oldpacket->xid; + memcpy(packet->chaddr, oldpacket->chaddr, 16); + packet->flags = oldpacket->flags; + packet->giaddr = oldpacket->giaddr; + packet->ciaddr = oldpacket->ciaddr; + add_simple_option(packet->options, DHCP_SERVER_ID, server_config.server); +} + + +/* add in the bootp options */ +static void add_bootp_options(struct dhcpMessage *packet) +{ + packet->siaddr = server_config.siaddr; + if (server_config.sname) + strncpy((char*)packet->sname, server_config.sname, sizeof(packet->sname) - 1); + if (server_config.boot_file) + strncpy((char*)packet->file, server_config.boot_file, sizeof(packet->file) - 1); +} + + +/* send a DHCP OFFER to a DHCP DISCOVER */ +int sendOffer(struct dhcpMessage *oldpacket) +{ + struct dhcpMessage packet; + struct dhcpOfferedAddr *lease = NULL; + uint32_t req_align, lease_time_align = server_config.lease; + uint8_t *req, *lease_time; + struct option_set *curr; + struct in_addr addr; + + uint32_t static_lease_ip; + + init_packet(&packet, oldpacket, DHCPOFFER); + + static_lease_ip = getIpByMac(server_config.static_leases, oldpacket->chaddr); + + /* ADDME: if static, short circuit */ + if (!static_lease_ip) { + /* the client is in our lease/offered table */ + lease = find_lease_by_chaddr(oldpacket->chaddr); + if (lease) { + if (!lease_expired(lease)) + lease_time_align = lease->expires - time(0); + packet.yiaddr = lease->yiaddr; + + /* Or the client has a requested ip */ + } else if ((req = get_option(oldpacket, DHCP_REQUESTED_IP)) + /* Don't look here (ugly hackish thing to do) */ + && memcpy(&req_align, req, 4) + /* and the ip is in the lease range */ + && ntohl(req_align) >= ntohl(server_config.start) + && ntohl(req_align) <= ntohl(server_config.end) + && !static_lease_ip /* Check that its not a static lease */ + /* and is not already taken/offered */ + && (!(lease = find_lease_by_yiaddr(req_align)) + /* or its taken, but expired */ /* ADDME: or maybe in here */ + || lease_expired(lease)) + ) { + packet.yiaddr = req_align; /* FIXME: oh my, is there a host using this IP? */ + /* otherwise, find a free IP */ + } else { + /* Is it a static lease? (No, because find_address skips static lease) */ + packet.yiaddr = find_address(0); + /* try for an expired lease */ + if (!packet.yiaddr) packet.yiaddr = find_address(1); + } + + if (!packet.yiaddr) { + bb_error_msg("no IP addresses to give - OFFER abandoned"); + return -1; + } + if (!add_lease(packet.chaddr, packet.yiaddr, server_config.offer_time)) { + bb_error_msg("lease pool is full - OFFER abandoned"); + return -1; + } + lease_time = get_option(oldpacket, DHCP_LEASE_TIME); + if (lease_time) { + memcpy(&lease_time_align, lease_time, 4); + lease_time_align = ntohl(lease_time_align); + if (lease_time_align > server_config.lease) + lease_time_align = server_config.lease; + } + + /* Make sure we aren't just using the lease time from the previous offer */ + if (lease_time_align < server_config.min_lease) + lease_time_align = server_config.lease; + /* ADDME: end of short circuit */ + } else { + /* It is a static lease... use it */ + packet.yiaddr = static_lease_ip; + } + + add_simple_option(packet.options, DHCP_LEASE_TIME, htonl(lease_time_align)); + + curr = server_config.options; + while (curr) { + if (curr->data[OPT_CODE] != DHCP_LEASE_TIME) + add_option_string(packet.options, curr->data); + curr = curr->next; + } + + add_bootp_options(&packet); + + addr.s_addr = packet.yiaddr; + bb_info_msg("Sending OFFER of %s", inet_ntoa(addr)); + return send_packet(&packet, 0); +} + + +int sendNAK(struct dhcpMessage *oldpacket) +{ + struct dhcpMessage packet; + + init_packet(&packet, oldpacket, DHCPNAK); + + DEBUG("Sending NAK"); + return send_packet(&packet, 1); +} + + +int sendACK(struct dhcpMessage *oldpacket, uint32_t yiaddr) +{ + struct dhcpMessage packet; + struct option_set *curr; + uint8_t *lease_time; + uint32_t lease_time_align = server_config.lease; + struct in_addr addr; + + init_packet(&packet, oldpacket, DHCPACK); + packet.yiaddr = yiaddr; + + if ((lease_time = get_option(oldpacket, DHCP_LEASE_TIME))) { + memcpy(&lease_time_align, lease_time, 4); + lease_time_align = ntohl(lease_time_align); + if (lease_time_align > server_config.lease) + lease_time_align = server_config.lease; + else if (lease_time_align < server_config.min_lease) + lease_time_align = server_config.lease; + } + + add_simple_option(packet.options, DHCP_LEASE_TIME, htonl(lease_time_align)); + + curr = server_config.options; + while (curr) { + if (curr->data[OPT_CODE] != DHCP_LEASE_TIME) + add_option_string(packet.options, curr->data); + curr = curr->next; + } + + add_bootp_options(&packet); + + addr.s_addr = packet.yiaddr; + bb_info_msg("Sending ACK to %s", inet_ntoa(addr)); + + if (send_packet(&packet, 0) < 0) + return -1; + + add_lease(packet.chaddr, packet.yiaddr, lease_time_align); + + return 0; +} + + +int send_inform(struct dhcpMessage *oldpacket) +{ + struct dhcpMessage packet; + struct option_set *curr; + + init_packet(&packet, oldpacket, DHCPACK); + + curr = server_config.options; + while (curr) { + if (curr->data[OPT_CODE] != DHCP_LEASE_TIME) + add_option_string(packet.options, curr->data); + curr = curr->next; + } + + add_bootp_options(&packet); + + return send_packet(&packet, 0); +} diff --git a/networking/udhcp/signalpipe.c b/networking/udhcp/signalpipe.c new file mode 100644 index 000000000..361596580 --- /dev/null +++ b/networking/udhcp/signalpipe.c @@ -0,0 +1,77 @@ +/* vi: set sw=4 ts=4: */ +/* signalpipe.c + * + * Signal pipe infrastructure. A reliable way of delivering signals. + * + * Russ Dill December 2003 + * + * 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. + */ + +#include "common.h" + + +static int signal_pipe[2]; + +static void signal_handler(int sig) +{ + if (send(signal_pipe[1], &sig, sizeof(sig), MSG_DONTWAIT) < 0) + bb_perror_msg("cannot send signal"); +} + + +/* Call this before doing anything else. Sets up the socket pair + * and installs the signal handler */ +void udhcp_sp_setup(void) +{ + socketpair(AF_UNIX, SOCK_STREAM, 0, signal_pipe); + fcntl(signal_pipe[0], F_SETFD, FD_CLOEXEC); + fcntl(signal_pipe[1], F_SETFD, FD_CLOEXEC); + signal(SIGUSR1, signal_handler); + signal(SIGUSR2, signal_handler); + signal(SIGTERM, signal_handler); +} + + +/* Quick little function to setup the rfds. Will return the + * max_fd for use with select. Limited in that you can only pass + * one extra fd */ +int udhcp_sp_fd_set(fd_set *rfds, int extra_fd) +{ + FD_ZERO(rfds); + FD_SET(signal_pipe[0], rfds); + if (extra_fd >= 0) { + fcntl(extra_fd, F_SETFD, FD_CLOEXEC); + FD_SET(extra_fd, rfds); + } + return signal_pipe[0] > extra_fd ? signal_pipe[0] : extra_fd; +} + + +/* Read a signal from the signal pipe. Returns 0 if there is + * no signal, -1 on error (and sets errno appropriately), and + * your signal on success */ +int udhcp_sp_read(fd_set *rfds) +{ + int sig; + + if (!FD_ISSET(signal_pipe[0], rfds)) + return 0; + + if (read(signal_pipe[0], &sig, sizeof(sig)) < 0) + return -1; + + return sig; +} diff --git a/networking/udhcp/socket.c b/networking/udhcp/socket.c new file mode 100644 index 000000000..2bae68f27 --- /dev/null +++ b/networking/udhcp/socket.c @@ -0,0 +1,130 @@ +/* vi: set sw=4 ts=4: */ +/* + * socket.c -- DHCP server client/server socket creation + * + * udhcp client/server + * Copyright (C) 1999 Matthew Ramsay + * Chris Trew + * + * Rewrite by Russ Dill July 2001 + * + * 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. + */ + +#include +#include +#if (__GLIBC__ >= 2 && __GLIBC_MINOR__ >= 1) || defined _NEWLIB_VERSION +#include +#include +#else +#include +#include +#include +#endif + +#include "common.h" + + +int read_interface(char *interface, int *ifindex, uint32_t *addr, uint8_t *arp) +{ + int fd; + struct ifreq ifr; + struct sockaddr_in *our_ip; + + memset(&ifr, 0, sizeof(struct ifreq)); + fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + if (fd < 0) { + bb_perror_msg("socket failed"); + return -1; + } + + ifr.ifr_addr.sa_family = AF_INET; + strncpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name)); + if (addr) { + if (ioctl(fd, SIOCGIFADDR, &ifr) != 0) { + bb_perror_msg("SIOCGIFADDR failed, is the interface up and configured?"); + close(fd); + return -1; + } + our_ip = (struct sockaddr_in *) &ifr.ifr_addr; + *addr = our_ip->sin_addr.s_addr; + DEBUG("%s (our ip) = %s", ifr.ifr_name, inet_ntoa(our_ip->sin_addr)); + } + + if (ifindex) { + if (ioctl(fd, SIOCGIFINDEX, &ifr) != 0) { + bb_perror_msg("SIOCGIFINDEX failed"); + close(fd); + return -1; + } + DEBUG("adapter index %d", ifr.ifr_ifindex); + *ifindex = ifr.ifr_ifindex; + } + + if (arp) { + if (ioctl(fd, SIOCGIFHWADDR, &ifr) != 0) { + bb_perror_msg("SIOCGIFHWADDR failed"); + close(fd); + return -1; + } + memcpy(arp, ifr.ifr_hwaddr.sa_data, 6); + DEBUG("adapter hardware address %02x:%02x:%02x:%02x:%02x:%02x", + arp[0], arp[1], arp[2], arp[3], arp[4], arp[5]); + } + + return 0; +} + + +int listen_socket(uint32_t ip, int port, char *inf) +{ + struct ifreq interface; + int fd; + struct sockaddr_in addr; + + DEBUG("Opening listen socket on 0x%08x:%d %s", ip, port, inf); + fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) { + bb_perror_msg("socket"); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = ip; + + if (setsockopt_reuseaddr(fd) == -1) { + close(fd); + return -1; + } + if (setsockopt_broadcast(fd) == -1) { + close(fd); + return -1; + } + + strncpy(interface.ifr_name, inf, IFNAMSIZ); + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, (char *)&interface, sizeof(interface)) < 0) { + close(fd); + return -1; + } + + if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) == -1) { + close(fd); + return -1; + } + + return fd; +} diff --git a/networking/udhcp/static_leases.c b/networking/udhcp/static_leases.c new file mode 100644 index 000000000..aabfb81aa --- /dev/null +++ b/networking/udhcp/static_leases.c @@ -0,0 +1,99 @@ +/* vi: set sw=4 ts=4: */ +/* + * static_leases.c -- Couple of functions to assist with storing and + * retrieving data for static leases + * + * Wade Berrier September 2004 + * + */ + +#include "common.h" +#include "dhcpd.h" + + +/* Takes the address of the pointer to the static_leases linked list, + * Address to a 6 byte mac address + * Address to a 4 byte ip address */ +int addStaticLease(struct static_lease **lease_struct, uint8_t *mac, uint32_t *ip) +{ + struct static_lease *cur; + struct static_lease *new_static_lease; + + /* Build new node */ + new_static_lease = xmalloc(sizeof(struct static_lease)); + new_static_lease->mac = mac; + new_static_lease->ip = ip; + new_static_lease->next = NULL; + + /* If it's the first node to be added... */ + if (*lease_struct == NULL) { + *lease_struct = new_static_lease; + } else { + cur = *lease_struct; + while (cur->next) { + cur = cur->next; + } + + cur->next = new_static_lease; + } + + return 1; +} + +/* Check to see if a mac has an associated static lease */ +uint32_t getIpByMac(struct static_lease *lease_struct, void *arg) +{ + uint32_t return_ip; + struct static_lease *cur = lease_struct; + uint8_t *mac = arg; + + return_ip = 0; + + while (cur) { + /* If the client has the correct mac */ + if (memcmp(cur->mac, mac, 6) == 0) { + return_ip = *(cur->ip); + } + + cur = cur->next; + } + + return return_ip; +} + +/* Check to see if an ip is reserved as a static ip */ +uint32_t reservedIp(struct static_lease *lease_struct, uint32_t ip) +{ + struct static_lease *cur = lease_struct; + + uint32_t return_val = 0; + + while (cur) { + /* If the client has the correct ip */ + if (*cur->ip == ip) + return_val = 1; + + cur = cur->next; + } + + return return_val; +} + +#if ENABLE_FEATURE_UDHCP_DEBUG +/* Print out static leases just to check what's going on */ +/* Takes the address of the pointer to the static_leases linked list */ +void printStaticLeases(struct static_lease **arg) +{ + /* Get a pointer to the linked list */ + struct static_lease *cur = *arg; + + while (cur) { + /* printf("PrintStaticLeases: Lease mac Address: %x\n", cur->mac); */ + printf("PrintStaticLeases: Lease mac Value: %x\n", *(cur->mac)); + /* printf("PrintStaticLeases: Lease ip Address: %x\n", cur->ip); */ + printf("PrintStaticLeases: Lease ip Value: %x\n", *(cur->ip)); + + cur = cur->next; + } +} +#endif diff --git a/networking/vconfig.c b/networking/vconfig.c new file mode 100644 index 000000000..003c1a8f7 --- /dev/null +++ b/networking/vconfig.c @@ -0,0 +1,164 @@ +/* vi: set sw=4 ts=4: */ +/* + * vconfig implementation for busybox + * + * Copyright (C) 2001 Manuel Novoa III + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* BB_AUDIT SUSv3 N/A */ + +#include "busybox.h" +#include + +/* Stuff from linux/if_vlan.h, kernel version 2.4.23 */ +enum vlan_ioctl_cmds { + ADD_VLAN_CMD, + DEL_VLAN_CMD, + SET_VLAN_INGRESS_PRIORITY_CMD, + SET_VLAN_EGRESS_PRIORITY_CMD, + GET_VLAN_INGRESS_PRIORITY_CMD, + GET_VLAN_EGRESS_PRIORITY_CMD, + SET_VLAN_NAME_TYPE_CMD, + SET_VLAN_FLAG_CMD +}; +enum vlan_name_types { + VLAN_NAME_TYPE_PLUS_VID, /* Name will look like: vlan0005 */ + VLAN_NAME_TYPE_RAW_PLUS_VID, /* name will look like: eth1.0005 */ + VLAN_NAME_TYPE_PLUS_VID_NO_PAD, /* Name will look like: vlan5 */ + VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD, /* Name will look like: eth0.5 */ + VLAN_NAME_TYPE_HIGHEST +}; + +struct vlan_ioctl_args { + int cmd; /* Should be one of the vlan_ioctl_cmds enum above. */ + char device1[24]; + + union { + char device2[24]; + int VID; + unsigned int skb_priority; + unsigned int name_type; + unsigned int bind_type; + unsigned int flag; /* Matches vlan_dev_info flags */ + } u; + + short vlan_qos; +}; + +#define VLAN_GROUP_ARRAY_LEN 4096 +#define SIOCSIFVLAN 0x8983 /* Set 802.1Q VLAN options */ + +/* On entry, table points to the length of the current string plus + * nul terminator plus data length for the subsequent entry. The + * return value is the last data entry for the matching string. */ +static const char *xfind_str(const char *table, const char *str) +{ + while (strcasecmp(str, table+1) != 0) { + if (!*(table += table[0])) { + bb_show_usage(); + } + } + return table - 1; +} + +static const char cmds[] = { + 4, ADD_VLAN_CMD, 7, + 'a', 'd', 'd', 0, + 3, DEL_VLAN_CMD, 7, + 'r', 'e', 'm', 0, + 3, SET_VLAN_NAME_TYPE_CMD, 17, + 's', 'e', 't', '_', + 'n', 'a', 'm', 'e', '_', + 't', 'y', 'p', 'e', 0, + 5, SET_VLAN_FLAG_CMD, 12, + 's', 'e', 't', '_', + 'f', 'l', 'a', 'g', 0, + 5, SET_VLAN_EGRESS_PRIORITY_CMD, 18, + 's', 'e', 't', '_', + 'e', 'g', 'r', 'e', 's', 's', '_', + 'm', 'a', 'p', 0, + 5, SET_VLAN_INGRESS_PRIORITY_CMD, 16, + 's', 'e', 't', '_', + 'i', 'n', 'g', 'r', 'e', 's', 's', '_', + 'm', 'a', 'p', 0, +}; + +static const char name_types[] = { + VLAN_NAME_TYPE_PLUS_VID, 16, + 'V', 'L', 'A', 'N', + '_', 'P', 'L', 'U', 'S', '_', 'V', 'I', 'D', + 0, + VLAN_NAME_TYPE_PLUS_VID_NO_PAD, 22, + 'V', 'L', 'A', 'N', + '_', 'P', 'L', 'U', 'S', '_', 'V', 'I', 'D', + '_', 'N', 'O', '_', 'P', 'A', 'D', 0, + VLAN_NAME_TYPE_RAW_PLUS_VID, 15, + 'D', 'E', 'V', + '_', 'P', 'L', 'U', 'S', '_', 'V', 'I', 'D', + 0, + VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD, 20, + 'D', 'E', 'V', + '_', 'P', 'L', 'U', 'S', '_', 'V', 'I', 'D', + '_', 'N', 'O', '_', 'P', 'A', 'D', 0, +}; + +static const char conf_file_name[] = "/proc/net/vlan/config"; + +int vconfig_main(int argc, char **argv) +{ + struct vlan_ioctl_args ifr; + const char *p; + int fd; + + if (argc < 3) { + bb_show_usage(); + } + + /* Don't bother closing the filedes. It will be closed on cleanup. */ + /* Will die if 802.1q is not present */ + xopen(conf_file_name, O_RDONLY); + + memset(&ifr, 0, sizeof(struct vlan_ioctl_args)); + + ++argv; + p = xfind_str(cmds+2, *argv); + ifr.cmd = *p; + if (argc != p[-1]) { + bb_show_usage(); + } + + if (ifr.cmd == SET_VLAN_NAME_TYPE_CMD) { /* set_name_type */ + ifr.u.name_type = *xfind_str(name_types+1, argv[1]); + } else { + if (strlen(argv[1]) >= IF_NAMESIZE) { + bb_error_msg_and_die("if_name >= %d chars", IF_NAMESIZE); + } + strcpy(ifr.device1, argv[1]); + p = argv[2]; + + /* I suppose one could try to combine some of the function calls below, + * since ifr.u.flag, ifr.u.VID, and ifr.u.skb_priority are all same-sized + * (unsigned) int members of a unions. But because of the range checking, + * doing so wouldn't save that much space and would also make maintainence + * more of a pain. */ + if (ifr.cmd == SET_VLAN_FLAG_CMD) { /* set_flag */ + ifr.u.flag = xatoul_range(p, 0, 1); + /* DM: in order to set reorder header, qos must be set */ + ifr.vlan_qos = xatoul_range(argv[3], 0, 7); + } else if (ifr.cmd == ADD_VLAN_CMD) { /* add */ + ifr.u.VID = xatoul_range(p, 0, VLAN_GROUP_ARRAY_LEN-1); + } else if (ifr.cmd != DEL_VLAN_CMD) { /* set_{egress|ingress}_map */ + ifr.u.skb_priority = xatou(p); + ifr.vlan_qos = xatoul_range(argv[3], 0, 7); + } + } + + fd = xsocket(AF_INET, SOCK_STREAM, 0); + if (ioctl(fd, SIOCSIFVLAN, &ifr) < 0) { + bb_perror_msg_and_die("ioctl error for %s", *argv); + } + + return 0; +} diff --git a/networking/wget.c b/networking/wget.c new file mode 100644 index 000000000..028e18c73 --- /dev/null +++ b/networking/wget.c @@ -0,0 +1,832 @@ +/* vi: set sw=4 ts=4: */ +/* + * wget - retrieve a file using HTTP or FTP + * + * Chip Rosenthal Covad Communications + * + */ + +/* We want libc to give us xxx64 functions also */ +/* http://www.unix.org/version2/whatsnew/lfs20mar.html */ +#define _LARGEFILE64_SOURCE 1 + +#include "busybox.h" +#include /* for struct option */ + +struct host_info { + // May be used if we ever will want to free() all xstrdup()s... + /* char *allocated; */ + char *host; + int port; + char *path; + int is_ftp; + char *user; +}; + +static void parse_url(char *url, struct host_info *h); +static FILE *open_socket(struct sockaddr_in *s_in); +static char *gethdr(char *buf, size_t bufsiz, FILE *fp, int *istrunc); +static int ftpcmd(char *s1, char *s2, FILE *fp, char *buf); + +/* Globals (can be accessed from signal handlers */ +static off_t content_len; /* Content-length of the file */ +static off_t beg_range; /* Range at which continue begins */ +#if ENABLE_FEATURE_WGET_STATUSBAR +static off_t transferred; /* Number of bytes transferred so far */ +#endif +static int chunked; /* chunked transfer encoding */ +#if ENABLE_FEATURE_WGET_STATUSBAR +static void progressmeter(int flag); +static char *curfile; /* Name of current file being transferred */ +static struct timeval start; /* Time a transfer started */ +enum { + STALLTIME = 5 /* Seconds when xfer considered "stalled" */ +}; +#else +static void progressmeter(int flag) {} +#endif + +/* Read NMEMB elements of SIZE bytes into PTR from STREAM. Returns the + * number of elements read, and a short count if an eof or non-interrupt + * error is encountered. */ +static size_t safe_fread(void *ptr, size_t size, size_t nmemb, FILE *stream) +{ + size_t ret = 0; + + do { + clearerr(stream); + ret += fread((char *)ptr + (ret * size), size, nmemb - ret, stream); + } while (ret < nmemb && ferror(stream) && errno == EINTR); + + return ret; +} + +/* Read a line or SIZE - 1 bytes into S, whichever is less, from STREAM. + * Returns S, or NULL if an eof or non-interrupt error is encountered. */ +static char *safe_fgets(char *s, int size, FILE *stream) +{ + char *ret; + + do { + clearerr(stream); + ret = fgets(s, size, stream); + } while (ret == NULL && ferror(stream) && errno == EINTR); + + return ret; +} + +#if ENABLE_FEATURE_WGET_AUTHENTICATION +/* + * Base64-encode character string and return the string. + */ +static char *base64enc(unsigned char *p, char *buf, int len) +{ + bb_uuencode(p, buf, len, bb_uuenc_tbl_base64); + return buf; +} +#endif + +int wget_main(int argc, char **argv) +{ + char buf[512]; + struct host_info server, target; + struct sockaddr_in s_in; + int n, status; + int port; + int try = 5; + unsigned opt; + char *s; + char *proxy = 0; + char *dir_prefix = NULL; +#if ENABLE_FEATURE_WGET_LONG_OPTIONS + char *extra_headers = NULL; + llist_t *headers_llist = NULL; +#endif + + /* server.allocated = target.allocated = NULL; */ + + FILE *sfp = NULL; /* socket to web/ftp server */ + FILE *dfp = NULL; /* socket to ftp server (data) */ + char *fname_out = NULL; /* where to direct output (-O) */ + int got_clen = 0; /* got content-length: from server */ + int output_fd = -1; + int use_proxy = 1; /* Use proxies if env vars are set */ + const char *proxy_flag = "on"; /* Use proxies if env vars are set */ + const char *user_agent = "Wget";/* Content of the "User-Agent" header field */ + + /* + * Crack command line. + */ + enum { + WGET_OPT_CONTINUE = 0x1, + WGET_OPT_QUIET = 0x2, + WGET_OPT_OUTNAME = 0x4, + WGET_OPT_PREFIX = 0x8, + WGET_OPT_PROXY = 0x10, + WGET_OPT_USER_AGENT = 0x20, + WGET_OPT_PASSIVE = 0x40, + WGET_OPT_HEADER = 0x80, + }; +#if ENABLE_FEATURE_WGET_LONG_OPTIONS + static const struct option wget_long_options[] = { + // name, has_arg, flag, val + { "continue", no_argument, NULL, 'c' }, + { "quiet", no_argument, NULL, 'q' }, + { "output-document", required_argument, NULL, 'O' }, + { "directory-prefix", required_argument, NULL, 'P' }, + { "proxy", required_argument, NULL, 'Y' }, + { "user-agent", required_argument, NULL, 'U' }, + { "passive-ftp", no_argument, NULL, 0xff }, + { "header", required_argument, NULL, 0xfe }, + { 0, 0, 0, 0 } + }; + applet_long_options = wget_long_options; +#endif + opt_complementary = "-1" USE_FEATURE_WGET_LONG_OPTIONS(":\xfe::"); + opt = getopt32(argc, argv, "cqO:P:Y:U:", + &fname_out, &dir_prefix, + &proxy_flag, &user_agent + USE_FEATURE_WGET_LONG_OPTIONS(, &headers_llist) + ); + if (strcmp(proxy_flag, "off") == 0) { + /* Use the proxy if necessary. */ + use_proxy = 0; + } +#if ENABLE_FEATURE_WGET_LONG_OPTIONS + if (headers_llist) { + int size = 1; + char *cp; + llist_t *ll = headers_llist = rev_llist(headers_llist); + while (ll) { + size += strlen(ll->data) + 2; + ll = ll->link; + } + extra_headers = cp = xmalloc(size); + while (headers_llist) { + cp += sprintf(cp, "%s\r\n", headers_llist->data); + headers_llist = headers_llist->link; + } + } +#endif + + parse_url(argv[optind], &target); + server.host = target.host; + server.port = target.port; + + /* + * Use the proxy if necessary. + */ + if (use_proxy) { + proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy"); + if (proxy && *proxy) { + parse_url(proxy, &server); + } else { + use_proxy = 0; + } + } + + /* Guess an output filename */ + if (!fname_out) { + // Dirty hack. Needed because bb_get_last_path_component + // will destroy trailing / by storing '\0' in last byte! + if (*target.path && target.path[strlen(target.path)-1] != '/') { + fname_out = +#if ENABLE_FEATURE_WGET_STATUSBAR + curfile = +#endif + bb_get_last_path_component(target.path); + } + if (!fname_out || !fname_out[0]) { + fname_out = +#if ENABLE_FEATURE_WGET_STATUSBAR + curfile = +#endif + "index.html"; + } + if (dir_prefix != NULL) + fname_out = concat_path_file(dir_prefix, fname_out); +#if ENABLE_FEATURE_WGET_STATUSBAR + } else { + curfile = bb_get_last_path_component(fname_out); +#endif + } + /* Impossible? + if ((opt & WGET_OPT_CONTINUE) && !fname_out) + bb_error_msg_and_die("cannot specify continue (-c) without a filename (-O)"); */ + + /* + * Determine where to start transfer. + */ + if (fname_out[0] == '-' && !fname_out[1]) { + output_fd = 1; + opt &= ~WGET_OPT_CONTINUE; + } + if (opt & WGET_OPT_CONTINUE) { + output_fd = open(fname_out, O_WRONLY); + if (output_fd >= 0) { + beg_range = xlseek(output_fd, 0, SEEK_END); + } + /* File doesn't exist. We do not create file here yet. + We are not sure it exists on remove side */ + } + + /* We want to do exactly _one_ DNS lookup, since some + * sites (i.e. ftp.us.debian.org) use round-robin DNS + * and we want to connect to only one IP... */ + bb_lookup_host(&s_in, server.host); + s_in.sin_port = server.port; + if (!(opt & WGET_OPT_QUIET)) { + fprintf(stderr, "Connecting to %s[%s]:%d\n", + server.host, inet_ntoa(s_in.sin_addr), ntohs(server.port)); + } + + if (use_proxy || !target.is_ftp) { + /* + * HTTP session + */ + do { + got_clen = chunked = 0; + + if (!--try) + bb_error_msg_and_die("too many redirections"); + + /* + * Open socket to http server + */ + if (sfp) fclose(sfp); + sfp = open_socket(&s_in); + + /* + * Send HTTP request. + */ + if (use_proxy) { + const char *format = "GET %stp://%s:%d/%s HTTP/1.1\r\n"; +#if ENABLE_FEATURE_WGET_IP6_LITERAL + if (strchr(target.host, ':')) + format = "GET %stp://[%s]:%d/%s HTTP/1.1\r\n"; +#endif + fprintf(sfp, format, + target.is_ftp ? "f" : "ht", target.host, + ntohs(target.port), target.path); + } else { + fprintf(sfp, "GET /%s HTTP/1.1\r\n", target.path); + } + + fprintf(sfp, "Host: %s\r\nUser-Agent: %s\r\n", target.host, + user_agent); + +#if ENABLE_FEATURE_WGET_AUTHENTICATION + if (target.user) { + fprintf(sfp, "Authorization: Basic %s\r\n", + base64enc((unsigned char*)target.user, buf, sizeof(buf))); + } + if (use_proxy && server.user) { + fprintf(sfp, "Proxy-Authorization: Basic %s\r\n", + base64enc((unsigned char*)server.user, buf, sizeof(buf))); + } +#endif + + if (beg_range) + fprintf(sfp, "Range: bytes=%"OFF_FMT"d-\r\n", beg_range); +#if ENABLE_FEATURE_WGET_LONG_OPTIONS + if (extra_headers) + fputs(extra_headers, sfp); +#endif + fprintf(sfp, "Connection: close\r\n\r\n"); + + /* + * Retrieve HTTP response line and check for "200" status code. + */ + read_response: + if (fgets(buf, sizeof(buf), sfp) == NULL) + bb_error_msg_and_die("no response from server"); + + s = buf; + while (*s != '\0' && !isspace(*s)) ++s; + s = skip_whitespace(s); + // FIXME: no error check + // xatou wouldn't work: "200 OK" + status = atoi(s); + switch (status) { + case 0: + case 100: + while (gethdr(buf, sizeof(buf), sfp, &n) != NULL) + /* eat all remaining headers */; + goto read_response; + case 200: + break; + case 300: /* redirection */ + case 301: + case 302: + case 303: + break; + case 206: + if (beg_range) + break; + /*FALLTHRU*/ + default: + /* Show first line only and kill any ESC tricks */ + buf[strcspn(buf, "\n\r\x1b")] = '\0'; + bb_error_msg_and_die("server returned error: %s", buf); + } + + /* + * Retrieve HTTP headers. + */ + while ((s = gethdr(buf, sizeof(buf), sfp, &n)) != NULL) { + if (strcasecmp(buf, "content-length") == 0) { + content_len = BB_STRTOOFF(s, NULL, 10); + if (errno || content_len < 0) { + bb_error_msg_and_die("content-length %s is garbage", s); + } + got_clen = 1; + continue; + } + if (strcasecmp(buf, "transfer-encoding") == 0) { + if (strcasecmp(s, "chunked") != 0) + bb_error_msg_and_die("server wants to do %s transfer encoding", s); + chunked = got_clen = 1; + } + if (strcasecmp(buf, "location") == 0) { + if (s[0] == '/') + /* free(target.allocated); */ + target.path = /* target.allocated = */ xstrdup(s+1); + else { + parse_url(s, &target); + if (use_proxy == 0) { + server.host = target.host; + server.port = target.port; + } + bb_lookup_host(&s_in, server.host); + s_in.sin_port = server.port; + break; + } + } + } + } while(status >= 300); + + dfp = sfp; + + } else { + + /* + * FTP session + */ + if (!target.user) + target.user = xstrdup("anonymous:busybox@"); + + sfp = open_socket(&s_in); + if (ftpcmd(NULL, NULL, sfp, buf) != 220) + bb_error_msg_and_die("%s", buf+4); + + /* + * Splitting username:password pair, + * trying to log in + */ + s = strchr(target.user, ':'); + if (s) + *(s++) = '\0'; + switch (ftpcmd("USER ", target.user, sfp, buf)) { + case 230: + break; + case 331: + if (ftpcmd("PASS ", s, sfp, buf) == 230) + break; + /* FALLTHRU (failed login) */ + default: + bb_error_msg_and_die("ftp login: %s", buf+4); + } + + ftpcmd("TYPE I", NULL, sfp, buf); + + /* + * Querying file size + */ + if (ftpcmd("SIZE ", target.path, sfp, buf) == 213) { + content_len = BB_STRTOOFF(buf+4, NULL, 10); + if (errno || content_len < 0) { + bb_error_msg_and_die("SIZE value is garbage"); + } + got_clen = 1; + } + + /* + * Entering passive mode + */ + if (ftpcmd("PASV", NULL, sfp, buf) != 227) { + pasv_error: + bb_error_msg_and_die("bad response to %s: %s", "PASV", buf); + } + // Response is "227 garbageN1,N2,N3,N4,P1,P2[)garbage] + // Server's IP is N1.N2.N3.N4 (we ignore it) + // Server's port for data connection is P1*256+P2 + s = strrchr(buf, ')'); + if (s) s[0] = '\0'; + s = strrchr(buf, ','); + if (!s) goto pasv_error; + port = xatou_range(s+1, 0, 255); + *s = '\0'; + s = strrchr(buf, ','); + if (!s) goto pasv_error; + port += xatou_range(s+1, 0, 255) * 256; + s_in.sin_port = htons(port); + dfp = open_socket(&s_in); + + if (beg_range) { + sprintf(buf, "REST %"OFF_FMT"d", beg_range); + if (ftpcmd(buf, NULL, sfp, buf) == 350) + content_len -= beg_range; + } + + if (ftpcmd("RETR ", target.path, sfp, buf) > 150) + bb_error_msg_and_die("bad response to RETR: %s", buf); + } + + + /* + * Retrieve file + */ + if (chunked) { + fgets(buf, sizeof(buf), dfp); + content_len = STRTOOFF(buf, NULL, 16); + /* FIXME: error check?? */ + } + + /* Do it before progressmeter (want to have nice error message) */ + if (output_fd < 0) + output_fd = xopen(fname_out, + O_WRONLY|O_CREAT|O_EXCL|O_TRUNC); + + if (!(opt & WGET_OPT_QUIET)) + progressmeter(-1); + + do { + while (content_len > 0 || !got_clen) { + unsigned rdsz = sizeof(buf); + if (content_len < sizeof(buf) && (chunked || got_clen)) + rdsz = (unsigned)content_len; + n = safe_fread(buf, 1, rdsz, dfp); + if (n <= 0) + break; + if (full_write(output_fd, buf, n) != n) { + bb_perror_msg_and_die(bb_msg_write_error); + } +#if ENABLE_FEATURE_WGET_STATUSBAR + transferred += n; +#endif + if (got_clen) { + content_len -= n; + } + } + + if (chunked) { + safe_fgets(buf, sizeof(buf), dfp); /* This is a newline */ + safe_fgets(buf, sizeof(buf), dfp); + content_len = STRTOOFF(buf, NULL, 16); + /* FIXME: error check? */ + if (content_len == 0) { + chunked = 0; /* all done! */ + } + } + + if (n == 0 && ferror(dfp)) { + bb_perror_msg_and_die(bb_msg_read_error); + } + } while (chunked); + + if (!(opt & WGET_OPT_QUIET)) + progressmeter(1); + + if ((use_proxy == 0) && target.is_ftp) { + fclose(dfp); + if (ftpcmd(NULL, NULL, sfp, buf) != 226) + bb_error_msg_and_die("ftp error: %s", buf+4); + ftpcmd("QUIT", NULL, sfp, buf); + } + exit(EXIT_SUCCESS); +} + + +static void parse_url(char *src_url, struct host_info *h) +{ + char *url, *p, *cp, *sp, *up, *pp; + + /* h->allocated = */ url = xstrdup(src_url); + + if (strncmp(url, "http://", 7) == 0) { + h->port = bb_lookup_port("http", "tcp", 80); + h->host = url + 7; + h->is_ftp = 0; + } else if (strncmp(url, "ftp://", 6) == 0) { + h->port = bb_lookup_port("ftp", "tcp", 21); + h->host = url + 6; + h->is_ftp = 1; + } else + bb_error_msg_and_die("not an http or ftp url: %s", url); + + // FYI: + // "Real" wget 'http://busybox.net?var=a/b' sends this request: + // 'GET /?var=a/b HTTP 1.0' + // and saves 'index.html?var=a%2Fb' (we save 'b') + // wget 'http://busybox.net?login=john@doe': + // request: 'GET /?login=john@doe HTTP/1.0' + // saves: 'index.html?login=john@doe' (we save '?login=john@doe') + // wget 'http://busybox.net#test/test': + // request: 'GET / HTTP/1.0' + // saves: 'index.html' (we save 'test') + // + // We also don't add unique .N suffix if file exists... + sp = strchr(h->host, '/'); + p = strchr(h->host, '?'); if (!sp || (p && sp > p)) sp = p; + p = strchr(h->host, '#'); if (!sp || (p && sp > p)) sp = p; + if (!sp) { + h->path = ""; + } else if (*sp == '/') { + *sp++ = '\0'; + h->path = sp; + } else { // '#' or '?' + // http://busybox.net?login=john@doe is a valid URL + // memmove converts to: + // http:/busybox.nett?login=john@doe... + memmove(h->host-1, h->host, sp - h->host); + h->host--; + sp[-1] = '\0'; + h->path = sp; + } + + up = strrchr(h->host, '@'); + if (up != NULL) { + h->user = h->host; + *up++ = '\0'; + h->host = up; + } else + h->user = NULL; + + pp = h->host; + +#if ENABLE_FEATURE_WGET_IP6_LITERAL + if (h->host[0] == '[') { + char *ep; + + ep = h->host + 1; + while (*ep == ':' || isxdigit(*ep)) + ep++; + if (*ep == ']') { + h->host++; + *ep = '\0'; + pp = ep + 1; + } + } +#endif + + cp = strchr(pp, ':'); + if (cp != NULL) { + *cp++ = '\0'; + h->port = htons(xatou16(cp)); + } +} + + +static FILE *open_socket(struct sockaddr_in *s_in) +{ + FILE *fp; + + /* glibc 2.4 seems to try seeking on it - ??! */ + /* hopefully it understands what ESPIPE means... */ + fp = fdopen(xconnect_tcp_v4(s_in), "r+"); + if (fp == NULL) + bb_perror_msg_and_die("fdopen"); + + return fp; +} + + +static char *gethdr(char *buf, size_t bufsiz, FILE *fp, int *istrunc) +{ + char *s, *hdrval; + int c; + + *istrunc = 0; + + /* retrieve header line */ + if (fgets(buf, bufsiz, fp) == NULL) + return NULL; + + /* see if we are at the end of the headers */ + for (s = buf; *s == '\r'; ++s) + ; + if (s[0] == '\n') + return NULL; + + /* convert the header name to lower case */ + for (s = buf; isalnum(*s) || *s == '-'; ++s) + *s = tolower(*s); + + /* verify we are at the end of the header name */ + if (*s != ':') + bb_error_msg_and_die("bad header line: %s", buf); + + /* locate the start of the header value */ + for (*s++ = '\0'; *s == ' ' || *s == '\t'; ++s) + ; + hdrval = s; + + /* locate the end of header */ + while (*s != '\0' && *s != '\r' && *s != '\n') + ++s; + + /* end of header found */ + if (*s != '\0') { + *s = '\0'; + return hdrval; + } + + /* Rats! The buffer isn't big enough to hold the entire header value. */ + while (c = getc(fp), c != EOF && c != '\n') + ; + *istrunc = 1; + return hdrval; +} + +static int ftpcmd(char *s1, char *s2, FILE *fp, char *buf) +{ + int result; + if (s1) { + if (!s2) s2 = ""; + fprintf(fp, "%s%s\r\n", s1, s2); + fflush(fp); + } + + do { + char *buf_ptr; + + if (fgets(buf, 510, fp) == NULL) { + bb_perror_msg_and_die("error getting response"); + } + buf_ptr = strstr(buf, "\r\n"); + if (buf_ptr) { + *buf_ptr = '\0'; + } + } while (!isdigit(buf[0]) || buf[3] != ' '); + + buf[3] = '\0'; + result = xatoi_u(buf); + buf[3] = ' '; + return result; +} + +#if ENABLE_FEATURE_WGET_STATUSBAR +/* Stuff below is from BSD rcp util.c, as added to openshh. + * Original copyright notice is retained at the end of this file. + */ +static int +getttywidth(void) +{ + int width; + get_terminal_width_height(0, &width, NULL); + return width; +} + +static void +updateprogressmeter(int ignore) +{ + int save_errno = errno; + + progressmeter(0); + errno = save_errno; +} + +static void alarmtimer(int iwait) +{ + struct itimerval itv; + + itv.it_value.tv_sec = iwait; + itv.it_value.tv_usec = 0; + itv.it_interval = itv.it_value; + setitimer(ITIMER_REAL, &itv, NULL); +} + + +static void +progressmeter(int flag) +{ + static struct timeval lastupdate; + static off_t lastsize, totalsize; + + struct timeval now, td, tvwait; + off_t abbrevsize; + int elapsed, ratio, barlength, i; + char buf[256]; + + if (flag == -1) { /* first call to progressmeter */ + gettimeofday(&start, (struct timezone *) 0); + lastupdate = start; + lastsize = 0; + totalsize = content_len + beg_range; /* as content_len changes.. */ + } + + gettimeofday(&now, (struct timezone *) 0); + ratio = 100; + if (totalsize != 0 && !chunked) { + /* long long helps to have working ETA even if !LFS */ + ratio = (int) (100 * (unsigned long long)(transferred+beg_range) / totalsize); + ratio = MIN(ratio, 100); + } + + fprintf(stderr, "\r%-20.20s%4d%% ", curfile, ratio); + + barlength = getttywidth() - 51; + if (barlength > 0 && barlength < sizeof(buf)) { + i = barlength * ratio / 100; + memset(buf, '*', i); + memset(buf + i, ' ', barlength - i); + buf[barlength] = '\0'; + fprintf(stderr, "|%s|", buf); + } + i = 0; + abbrevsize = transferred + beg_range; + while (abbrevsize >= 100000) { + i++; + abbrevsize >>= 10; + } + /* see http://en.wikipedia.org/wiki/Tera */ + fprintf(stderr, "%6d %c%c ", (int)abbrevsize, " KMGTPEZY"[i], i?'B':' '); + + timersub(&now, &lastupdate, &tvwait); + if (transferred > lastsize) { + lastupdate = now; + lastsize = transferred; + if (tvwait.tv_sec >= STALLTIME) + timeradd(&start, &tvwait, &start); + tvwait.tv_sec = 0; + } + timersub(&now, &start, &td); + elapsed = td.tv_sec; + + if (tvwait.tv_sec >= STALLTIME) { + fprintf(stderr, " - stalled -"); + } else { + off_t to_download = totalsize - beg_range; + if (transferred <= 0 || elapsed <= 0 || transferred > to_download || chunked) { + fprintf(stderr, "--:--:-- ETA"); + } else { + /* to_download / (transferred/elapsed) - elapsed: */ + int eta = (int) ((unsigned long long)to_download*elapsed/transferred - elapsed); + /* (long long helps to have working ETA even if !LFS) */ + i = eta % 3600; + fprintf(stderr, "%02d:%02d:%02d ETA", eta / 3600, i / 60, i % 60); + } + } + + if (flag == -1) { /* first call to progressmeter */ + struct sigaction sa; + sa.sa_handler = updateprogressmeter; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGALRM, &sa, NULL); + alarmtimer(1); + } else if (flag == 1) { /* last call to progressmeter */ + alarmtimer(0); + transferred = 0; + putc('\n', stderr); + } +} +#endif + +/* Original copyright notice which applies to the CONFIG_FEATURE_WGET_STATUSBAR stuff, + * much of which was blatantly stolen from openssh. */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. + * + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: wget.c,v 1.75 2004/10/08 08:27:40 andersen Exp $ + */ diff --git a/networking/zcip.c b/networking/zcip.c new file mode 100644 index 000000000..27e281c93 --- /dev/null +++ b/networking/zcip.c @@ -0,0 +1,546 @@ +/* vi: set sw=4 ts=4: */ +/* + * RFC3927 ZeroConf IPv4 Link-Local addressing + * (see ) + * + * Copyright (C) 2003 by Arthur van Hoff (avh@strangeberry.com) + * Copyright (C) 2004 by David Brownell + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +/* + * ZCIP just manages the 169.254.*.* addresses. That network is not + * routed at the IP level, though various proxies or bridges can + * certainly be used. Its naming is built over multicast DNS. + */ + +//#define DEBUG + +// TODO: +// - more real-world usage/testing, especially daemon mode +// - kernel packet filters to reduce scheduling noise +// - avoid silent script failures, especially under load... +// - link status monitoring (restart on link-up; stop on link-down) + +#include "busybox.h" +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +struct arp_packet { + struct ether_header hdr; + struct ether_arp arp; +} ATTRIBUTE_PACKED; + +enum { +/* 169.254.0.0 */ + LINKLOCAL_ADDR = 0xa9fe0000, + +/* protocol timeout parameters, specified in seconds */ + PROBE_WAIT = 1, + PROBE_MIN = 1, + PROBE_MAX = 2, + PROBE_NUM = 3, + MAX_CONFLICTS = 10, + RATE_LIMIT_INTERVAL = 60, + ANNOUNCE_WAIT = 2, + ANNOUNCE_NUM = 2, + ANNOUNCE_INTERVAL = 2, + DEFEND_INTERVAL = 10 +}; + +/* States during the configuration process. */ +enum { + PROBE = 0, + RATE_LIMIT_PROBE, + ANNOUNCE, + MONITOR, + DEFEND +}; + +#define VDBG(fmt,args...) \ + do { } while (0) + +static unsigned opts; +#define FOREGROUND (opts & 1) +#define QUIT (opts & 2) + +/** + * Pick a random link local IP address on 169.254/16, except that + * the first and last 256 addresses are reserved. + */ +static void pick(struct in_addr *ip) +{ + unsigned tmp; + + /* use cheaper math than lrand48() mod N */ + do { + tmp = (lrand48() >> 16) & IN_CLASSB_HOST; + } while (tmp > (IN_CLASSB_HOST - 0x0200)); + ip->s_addr = htonl((LINKLOCAL_ADDR + 0x0100) + tmp); +} + +/* TODO: we need a flag to direct bb_[p]error_msg output to stderr. */ + +/** + * Broadcast an ARP packet. + */ +static void arp(int fd, struct sockaddr *saddr, int op, + const struct ether_addr *source_addr, struct in_addr source_ip, + const struct ether_addr *target_addr, struct in_addr target_ip) +{ + struct arp_packet p; + memset(&p, 0, sizeof(p)); + + // ether header + p.hdr.ether_type = htons(ETHERTYPE_ARP); + memcpy(p.hdr.ether_shost, source_addr, ETH_ALEN); + memset(p.hdr.ether_dhost, 0xff, ETH_ALEN); + + // arp request + p.arp.arp_hrd = htons(ARPHRD_ETHER); + p.arp.arp_pro = htons(ETHERTYPE_IP); + p.arp.arp_hln = ETH_ALEN; + p.arp.arp_pln = 4; + p.arp.arp_op = htons(op); + memcpy(&p.arp.arp_sha, source_addr, ETH_ALEN); + memcpy(&p.arp.arp_spa, &source_ip, sizeof (p.arp.arp_spa)); + memcpy(&p.arp.arp_tha, target_addr, ETH_ALEN); + memcpy(&p.arp.arp_tpa, &target_ip, sizeof (p.arp.arp_tpa)); + + // send it + if (sendto(fd, &p, sizeof (p), 0, saddr, sizeof (*saddr)) < 0) { + bb_perror_msg("sendto"); + //return -errno; + } + // Currently all callers ignore errors, that's why returns are + // commented out... + //return 0; +} + +/** + * Run a script. + */ +static int run(char *script, char *arg, char *intf, struct in_addr *ip) +{ + int pid, status; + char *why; + + if(1) { //always true: if (script != NULL) + VDBG("%s run %s %s\n", intf, script, arg); + if (ip != NULL) { + char *addr = inet_ntoa(*ip); + setenv("ip", addr, 1); + bb_info_msg("%s %s %s", arg, intf, addr); + } + + pid = vfork(); + if (pid < 0) { // error + why = "vfork"; + goto bad; + } else if (pid == 0) { // child + execl(script, script, arg, NULL); + bb_perror_msg("execl"); + _exit(EXIT_FAILURE); + } + + if (waitpid(pid, &status, 0) <= 0) { + why = "waitpid"; + goto bad; + } + if (WEXITSTATUS(status) != 0) { + bb_error_msg("script %s failed, exit=%d", + script, WEXITSTATUS(status)); + return -errno; + } + } + return 0; +bad: + status = -errno; + bb_perror_msg("%s %s, %s", arg, intf, why); + return status; +} + + +/** + * Return milliseconds of random delay, up to "secs" seconds. + */ +static unsigned ATTRIBUTE_ALWAYS_INLINE ms_rdelay(unsigned secs) +{ + return lrand48() % (secs * 1000); +} + +/** + * main program + */ + +/* Used to be auto variables on main() stack, but + * most of them were zero-inited. Moving them to bss + * is more space-efficient. + */ +static const struct in_addr null_ip; // = { 0 }; +static const struct ether_addr null_addr; // = { {0, 0, 0, 0, 0, 0} }; + +static struct sockaddr saddr; // memset(0); +static struct in_addr ip; // = { 0 }; +static struct ifreq ifr; //memset(0); + +static char *intf; // = NULL; +static char *script; // = NULL; +static suseconds_t timeout; // = 0; // milliseconds +static unsigned conflicts; // = 0; +static unsigned nprobes; // = 0; +static unsigned nclaims; // = 0; +static int ready; // = 0; +static int verbose; // = 0; +static int state = PROBE; + +int zcip_main(int argc, char *argv[]) +{ + struct ether_addr eth_addr; + char *why; + int fd; + + // parse commandline: prog [options] ifname script + char *r_opt; + opt_complementary = "vv:vf"; // -v accumulates and implies -f + opts = getopt32(argc, argv, "fqr:v", &r_opt, &verbose); + if (!FOREGROUND) { + /* Do it early, before all bb_xx_msg calls */ + logmode = LOGMODE_SYSLOG; + openlog(applet_name, 0, LOG_DAEMON); + } + if (opts & 4) { // -r n.n.n.n + if (inet_aton(r_opt, &ip) == 0 + || (ntohl(ip.s_addr) & IN_CLASSB_NET) != LINKLOCAL_ADDR) { + bb_error_msg_and_die("invalid link address"); + } + } + argc -= optind; + argv += optind; + if (argc != 2) + bb_show_usage(); + intf = argv[0]; + script = argv[1]; + setenv("interface", intf, 1); + + // initialize the interface (modprobe, ifup, etc) + if (run(script, "init", intf, NULL) < 0) + return EXIT_FAILURE; + + // initialize saddr + //memset(&saddr, 0, sizeof (saddr)); + safe_strncpy(saddr.sa_data, intf, sizeof (saddr.sa_data)); + + // open an ARP socket + fd = xsocket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ARP)); + // bind to the interface's ARP socket + xbind(fd, &saddr, sizeof (saddr)); + + // get the interface's ethernet address + //memset(&ifr, 0, sizeof (ifr)); + strncpy(ifr.ifr_name, intf, sizeof (ifr.ifr_name)); + if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) { + bb_perror_msg_and_die("get ethernet address"); + } + memcpy(ð_addr, &ifr.ifr_hwaddr.sa_data, ETH_ALEN); + + // start with some stable ip address, either a function of + // the hardware address or else the last address we used. + // NOTE: the sequence of addresses we try changes only + // depending on when we detect conflicts. + // (SVID 3 bogon: who says that "short" is always 16 bits?) + seed48( (unsigned short*)&ifr.ifr_hwaddr.sa_data ); + if (ip.s_addr == 0) + pick(&ip); + + // FIXME cases to handle: + // - zcip already running! + // - link already has local address... just defend/update + + // daemonize now; don't delay system startup + if (!FOREGROUND) { + setsid(); + xdaemon(0, 0); + bb_info_msg("start, interface %s", intf); + } + + // run the dynamic address negotiation protocol, + // restarting after address conflicts: + // - start with some address we want to try + // - short random delay + // - arp probes to see if another host else uses it + // - arp announcements that we're claiming it + // - use it + // - defend it, within limits + while (1) { + struct pollfd fds[1]; + struct timeval tv1; + struct arp_packet p; + + int source_ip_conflict = 0; + int target_ip_conflict = 0; + + fds[0].fd = fd; + fds[0].events = POLLIN; + fds[0].revents = 0; + + // poll, being ready to adjust current timeout + if (!timeout) { + timeout = ms_rdelay(PROBE_WAIT); + // FIXME setsockopt(fd, SO_ATTACH_FILTER, ...) to + // make the kernel filter out all packets except + // ones we'd care about. + } + // set tv1 to the point in time when we timeout + gettimeofday(&tv1, NULL); + tv1.tv_usec += (timeout % 1000) * 1000; + while (tv1.tv_usec > 1000000) { + tv1.tv_usec -= 1000000; + tv1.tv_sec++; + } + tv1.tv_sec += timeout / 1000; + + VDBG("...wait %ld %s nprobes=%d, nclaims=%d\n", + timeout, intf, nprobes, nclaims); + switch (poll(fds, 1, timeout)) { + + // timeout + case 0: + VDBG("state = %d\n", state); + switch (state) { + case PROBE: + // timeouts in the PROBE state mean no conflicting ARP packets + // have been received, so we can progress through the states + if (nprobes < PROBE_NUM) { + nprobes++; + VDBG("probe/%d %s@%s\n", + nprobes, intf, inet_ntoa(ip)); + arp(fd, &saddr, ARPOP_REQUEST, + ð_addr, null_ip, + &null_addr, ip); + timeout = PROBE_MIN * 1000; + timeout += ms_rdelay(PROBE_MAX + - PROBE_MIN); + } + else { + // Switch to announce state. + state = ANNOUNCE; + nclaims = 0; + VDBG("announce/%d %s@%s\n", + nclaims, intf, inet_ntoa(ip)); + arp(fd, &saddr, ARPOP_REQUEST, + ð_addr, ip, + ð_addr, ip); + timeout = ANNOUNCE_INTERVAL * 1000; + } + break; + case RATE_LIMIT_PROBE: + // timeouts in the RATE_LIMIT_PROBE state mean no conflicting ARP packets + // have been received, so we can move immediately to the announce state + state = ANNOUNCE; + nclaims = 0; + VDBG("announce/%d %s@%s\n", + nclaims, intf, inet_ntoa(ip)); + arp(fd, &saddr, ARPOP_REQUEST, + ð_addr, ip, + ð_addr, ip); + timeout = ANNOUNCE_INTERVAL * 1000; + break; + case ANNOUNCE: + // timeouts in the ANNOUNCE state mean no conflicting ARP packets + // have been received, so we can progress through the states + if (nclaims < ANNOUNCE_NUM) { + nclaims++; + VDBG("announce/%d %s@%s\n", + nclaims, intf, inet_ntoa(ip)); + arp(fd, &saddr, ARPOP_REQUEST, + ð_addr, ip, + ð_addr, ip); + timeout = ANNOUNCE_INTERVAL * 1000; + } + else { + // Switch to monitor state. + state = MONITOR; + // link is ok to use earlier + // FIXME update filters + run(script, "config", intf, &ip); + ready = 1; + conflicts = 0; + timeout = -1; // Never timeout in the monitor state. + + // NOTE: all other exit paths + // should deconfig ... + if (QUIT) + return EXIT_SUCCESS; + } + break; + case DEFEND: + // We won! No ARP replies, so just go back to monitor. + state = MONITOR; + timeout = -1; + conflicts = 0; + break; + default: + // Invalid, should never happen. Restart the whole protocol. + state = PROBE; + pick(&ip); + timeout = 0; + nprobes = 0; + nclaims = 0; + break; + } // switch (state) + break; // case 0 (timeout) + // packets arriving + case 1: + // We need to adjust the timeout in case we didn't receive + // a conflicting packet. + if (timeout > 0) { + struct timeval tv2; + + gettimeofday(&tv2, NULL); + if (timercmp(&tv1, &tv2, <)) { + // Current time is greater than the expected timeout time. + // Should never happen. + VDBG("missed an expected timeout\n"); + timeout = 0; + } else { + VDBG("adjusting timeout\n"); + timersub(&tv1, &tv2, &tv1); + timeout = 1000 * tv1.tv_sec + + tv1.tv_usec / 1000; + } + } + + if ((fds[0].revents & POLLIN) == 0) { + if (fds[0].revents & POLLERR) { + // FIXME: links routinely go down; + // this shouldn't necessarily exit. + bb_error_msg("%s: poll error", intf); + if (ready) { + run(script, "deconfig", + intf, &ip); + } + return EXIT_FAILURE; + } + continue; + } + + // read ARP packet + if (recv(fd, &p, sizeof (p), 0) < 0) { + why = "recv"; + goto bad; + } + if (p.hdr.ether_type != htons(ETHERTYPE_ARP)) + continue; + +#ifdef DEBUG + { + struct ether_addr * sha = (struct ether_addr *) p.arp.arp_sha; + struct ether_addr * tha = (struct ether_addr *) p.arp.arp_tha; + struct in_addr * spa = (struct in_addr *) p.arp.arp_spa; + struct in_addr * tpa = (struct in_addr *) p.arp.arp_tpa; + VDBG("%s recv arp type=%d, op=%d,\n", + intf, ntohs(p.hdr.ether_type), + ntohs(p.arp.arp_op)); + VDBG("\tsource=%s %s\n", + ether_ntoa(sha), + inet_ntoa(*spa)); + VDBG("\ttarget=%s %s\n", + ether_ntoa(tha), + inet_ntoa(*tpa)); + } +#endif + if (p.arp.arp_op != htons(ARPOP_REQUEST) + && p.arp.arp_op != htons(ARPOP_REPLY)) + continue; + + if (memcmp(p.arp.arp_spa, &ip.s_addr, sizeof(struct in_addr)) == 0 && + memcmp(ð_addr, &p.arp.arp_sha, ETH_ALEN) != 0) { + source_ip_conflict = 1; + } + if (memcmp(p.arp.arp_tpa, &ip.s_addr, sizeof(struct in_addr)) == 0 && + p.arp.arp_op == htons(ARPOP_REQUEST) && + memcmp(ð_addr, &p.arp.arp_tha, ETH_ALEN) != 0) { + target_ip_conflict = 1; + } + + VDBG("state = %d, source ip conflict = %d, target ip conflict = %d\n", + state, source_ip_conflict, target_ip_conflict); + switch (state) { + case PROBE: + case ANNOUNCE: + // When probing or announcing, check for source IP conflicts + // and other hosts doing ARP probes (target IP conflicts). + if (source_ip_conflict || target_ip_conflict) { + conflicts++; + if (conflicts >= MAX_CONFLICTS) { + VDBG("%s ratelimit\n", intf); + timeout = RATE_LIMIT_INTERVAL * 1000; + state = RATE_LIMIT_PROBE; + } + + // restart the whole protocol + pick(&ip); + timeout = 0; + nprobes = 0; + nclaims = 0; + } + break; + case MONITOR: + // If a conflict, we try to defend with a single ARP probe. + if (source_ip_conflict) { + VDBG("monitor conflict -- defending\n"); + state = DEFEND; + timeout = DEFEND_INTERVAL * 1000; + arp(fd, &saddr, + ARPOP_REQUEST, + ð_addr, ip, + ð_addr, ip); + } + break; + case DEFEND: + // Well, we tried. Start over (on conflict). + if (source_ip_conflict) { + state = PROBE; + VDBG("defend conflict -- starting over\n"); + ready = 0; + run(script, "deconfig", intf, &ip); + + // restart the whole protocol + pick(&ip); + timeout = 0; + nprobes = 0; + nclaims = 0; + } + break; + default: + // Invalid, should never happen. Restart the whole protocol. + VDBG("invalid state -- starting over\n"); + state = PROBE; + pick(&ip); + timeout = 0; + nprobes = 0; + nclaims = 0; + break; + } // switch state + + break; // case 1 (packets arriving) + default: + why = "poll"; + goto bad; + } // switch poll + } +bad: + bb_perror_msg("%s, %s", intf, why); + return EXIT_FAILURE; +} diff --git a/procps/Config.in b/procps/Config.in new file mode 100644 index 000000000..20d5f9bf2 --- /dev/null +++ b/procps/Config.in @@ -0,0 +1,121 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Process Utilities" + +config FREE + bool "free" + default n + help + free displays the total amount of free and used physical and swap + memory in the system, as well as the buffers used by the kernel. + The shared memory column should be ignored; it is obsolete. + +config FUSER + bool "fuser" + default n + help + fuser lists all PIDs (Process IDs) that currently have a given + file open. fuser can also list all PIDs that have a given network + (TCP or UDP) port open. + +config KILL + bool "kill" + default n + help + The command kill sends the specified signal to the specified + process or process group. If no signal is specified, the TERM + signal is sent. + +config KILLALL + bool "killall" + default n + depends on KILL + help + killall sends a signal to all processes running any of the + specified commands. If no signal name is specified, SIGTERM is + sent. + +config KILLALL5 + bool "killall5" + default n + depends on KILL + +config PIDOF + bool "pidof" + default n + help + Pidof finds the process id's (pids) of the named programs. It prints + those id's on the standard output. + +config FEATURE_PIDOF_SINGLE + bool "Enable argument for single shot (-s)" + default n + depends on PIDOF + help + Support argument '-s' for returning only the first pid found. + +config FEATURE_PIDOF_OMIT + bool "Enable argument for omitting pids (-o)" + default n + depends on PIDOF + help + Support argument '-o' for omitting the given pids in output. + The special pid %PPID can be used to name the parent process + of the pidof, in other words the calling shell or shell script. + +config PS + bool "ps" + default n + help + ps gives a snapshot of the current processes. + +config FEATURE_PS_WIDE + bool "Enable argument for wide output (-w)" + default n + depends on PS + help + Support argument 'w' for wide output. + If given once, 132 chars are printed and given more than + one, the length is unlimited. + +config RENICE + bool "renice" + default n + help + Renice alters the scheduling priority of one or more running + processes. + +config BB_SYSCTL + bool "sysctl" + default n + help + Configure kernel parameters at runtime. + +config TOP + bool "top" + default n + help + The top program provides a dynamic real-time view of a running + system. + +config FEATURE_TOP_CPU_USAGE_PERCENTAGE + bool "Support showing CPU usage percentage (add 2k bytes)" + default y + depends on TOP + help + Make top display CPU usage. + +config UPTIME + bool "uptime" + default n + help + uptime gives a one line display of the current time, how long + the system has been running, how many users are currently logged + on, and the system load averages for the past 1, 5, and 15 minutes. + + +endmenu + diff --git a/procps/Kbuild b/procps/Kbuild new file mode 100644 index 000000000..6a9a86637 --- /dev/null +++ b/procps/Kbuild @@ -0,0 +1,16 @@ +# 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_FREE) += free.o +lib-$(CONFIG_KILL) += kill.o +lib-$(CONFIG_PIDOF) += pidof.o +lib-$(CONFIG_PS) += ps.o +lib-$(CONFIG_RENICE) += renice.o +lib-$(CONFIG_BB_SYSCTL) += sysctl.o +lib-$(CONFIG_TOP) += top.o +lib-$(CONFIG_UPTIME) += uptime.o +lib-$(CONFIG_FUSER) += fuser.o diff --git a/procps/free.c b/procps/free.c new file mode 100644 index 000000000..84432e0d1 --- /dev/null +++ b/procps/free.c @@ -0,0 +1,68 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini free implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under the GPL version 2, see the file LICENSE in this tarball. + */ + +/* getopt not needed */ + +#include "busybox.h" + +int free_main(int argc, char **argv) +{ + struct sysinfo info; + sysinfo(&info); + + /* Kernels prior to 2.4.x will return info.mem_unit==0, so cope... */ + if (info.mem_unit==0) { + info.mem_unit=1; + } + if ( info.mem_unit == 1 ) { + info.mem_unit=1024; + + /* TODO: Make all this stuff not overflow when mem >= 4 Gib */ + info.totalram/=info.mem_unit; + info.freeram/=info.mem_unit; +#ifndef __uClinux__ + info.totalswap/=info.mem_unit; + info.freeswap/=info.mem_unit; +#endif + info.sharedram/=info.mem_unit; + info.bufferram/=info.mem_unit; + } else { + info.mem_unit/=1024; + /* TODO: Make all this stuff not overflow when mem >= 4 Gib */ + info.totalram*=info.mem_unit; + info.freeram*=info.mem_unit; +#ifndef __uClinux__ + info.totalswap*=info.mem_unit; + info.freeswap*=info.mem_unit; +#endif + info.sharedram*=info.mem_unit; + info.bufferram*=info.mem_unit; + } + + if (argc > 1 && **(argv + 1) == '-') + bb_show_usage(); + + printf("%6s%13s%13s%13s%13s%13s\n", "", "total", "used", "free", + "shared", "buffers"); + + printf("%6s%13ld%13ld%13ld%13ld%13ld\n", "Mem:", info.totalram, + info.totalram-info.freeram, info.freeram, + info.sharedram, info.bufferram); + +#ifndef __uClinux__ + printf("%6s%13ld%13ld%13ld\n", "Swap:", info.totalswap, + info.totalswap-info.freeswap, info.freeswap); + + printf("%6s%13ld%13ld%13ld\n", "Total:", info.totalram+info.totalswap, + (info.totalram-info.freeram)+(info.totalswap-info.freeswap), + info.freeram+info.freeswap); +#endif + return EXIT_SUCCESS; +} + diff --git a/procps/fuser.c b/procps/fuser.c new file mode 100644 index 000000000..4628cdf5c --- /dev/null +++ b/procps/fuser.c @@ -0,0 +1,366 @@ +/* vi: set sw=4 ts=4: */ +/* + * tiny fuser implementation + * + * Copyright 2004 Tony J. White + * + * May be distributed under the conditions of the + * GNU Library General Public License + */ + +#include "busybox.h" + +#define FUSER_PROC_DIR "/proc" +#define FUSER_MAX_LINE 255 + +#define FUSER_OPT_MOUNT 1 +#define FUSER_OPT_KILL 2 +#define FUSER_OPT_SILENT 4 +#define FUSER_OPT_IP6 8 +#define FUSER_OPT_IP4 16 + +typedef struct inode_list { + ino_t inode; + dev_t dev; + struct inode_list *next; +} inode_list; + +typedef struct pid_list { + pid_t pid; + struct pid_list *next; +} pid_list; + +static int fuser_option(char *option) +{ + int opt = 0; + + if(!(strlen(option))) return 0; + if(option[0] != '-') return 0; + ++option; + while(*option != '\0') { + if(*option == 'm') opt |= FUSER_OPT_MOUNT; + else if(*option == 'k') opt |= FUSER_OPT_KILL; + else if(*option == 's') opt |= FUSER_OPT_SILENT; + else if(*option == '6') opt |= FUSER_OPT_IP6; + else if(*option == '4') opt |= FUSER_OPT_IP4; + else { + bb_error_msg_and_die( + "Unsupported option '%c'", *option); + } + ++option; + } + return opt; +} + +static int fuser_file_to_dev_inode(const char *filename, + dev_t *dev, ino_t *inode) +{ + struct stat f_stat; + if((stat(filename, &f_stat)) < 0) return 0; + *inode = f_stat.st_ino; + *dev = f_stat.st_dev; + return 1; +} + +static int fuser_find_socket_dev(dev_t *dev) +{ + int fd = socket(PF_INET, SOCK_DGRAM,0); + struct stat buf; + + if (fd >= 0 && (fstat(fd, &buf)) == 0) { + *dev = buf.st_dev; + close(fd); + return 1; + } + return 0; +} + +static int fuser_parse_net_arg(const char *filename, + const char **proto, int *port) +{ + char path[sizeof(FUSER_PROC_DIR)+12], tproto[5]; + + if((sscanf(filename, "%d/%4s", port, tproto)) != 2) return 0; + sprintf(path, "%s/net/%s", FUSER_PROC_DIR, tproto); + if((access(path, R_OK)) != 0) return 0; + *proto = xstrdup(tproto); + return 1; +} + +static int fuser_add_pid(pid_list *plist, pid_t pid) +{ + pid_list *curr = NULL, *last = NULL; + + if(plist->pid == 0) plist->pid = pid; + curr = plist; + while(curr != NULL) { + if(curr->pid == pid) return 1; + last = curr; + curr = curr->next; + } + curr = xmalloc(sizeof(pid_list)); + last->next = curr; + curr->pid = pid; + curr->next = NULL; + return 1; +} + +static int fuser_add_inode(inode_list *ilist, dev_t dev, ino_t inode) +{ + inode_list *curr = NULL, *last = NULL; + + if(!ilist->inode && !ilist->dev) { + ilist->dev = dev; + ilist->inode = inode; + } + curr = ilist; + while(curr != NULL) { + if(curr->inode == inode && curr->dev == dev) return 1; + last = curr; + curr = curr->next; + } + curr = xmalloc(sizeof(inode_list)); + last->next = curr; + curr->dev = dev; + curr->inode = inode; + curr->next = NULL; + return 1; +} + +static int fuser_scan_proc_net(int opts, const char *proto, + int port, inode_list *ilist) +{ + char path[sizeof(FUSER_PROC_DIR)+12], line[FUSER_MAX_LINE+1]; + char addr[128]; + ino_t tmp_inode; + dev_t tmp_dev; + long long uint64_inode; + int tmp_port; + FILE *f; + + if(!fuser_find_socket_dev(&tmp_dev)) tmp_dev = 0; + sprintf(path, "%s/net/%s", FUSER_PROC_DIR, proto); + + if (!(f = fopen(path, "r"))) return 0; + while(fgets(line, FUSER_MAX_LINE, f)) { + if(sscanf(line, + "%*d: %64[0-9A-Fa-f]:%x %*x:%*x %*x %*x:%*x " + "%*x:%*x %*x %*d %*d %llu", + addr, &tmp_port, &uint64_inode) == 3) { + if((strlen(addr) == 8) && + (opts & FUSER_OPT_IP6)) continue; + else if((strlen(addr) > 8) && + (opts & FUSER_OPT_IP4)) continue; + if(tmp_port == port) { + tmp_inode = uint64_inode; + fuser_add_inode(ilist, tmp_dev, tmp_inode); + } + } + + } + fclose(f); + return 1; +} + +static int fuser_search_dev_inode(int opts, inode_list *ilist, + dev_t dev, ino_t inode) +{ + inode_list *curr; + curr = ilist; + + while(curr) { + if((opts & FUSER_OPT_MOUNT) && curr->dev == dev) + return 1; + if(curr->inode == inode && curr->dev == dev) + return 1; + curr = curr->next; + } + return 0; +} + +static int fuser_scan_pid_maps(int opts, const char *fname, pid_t pid, + inode_list *ilist, pid_list *plist) +{ + FILE *file; + char line[FUSER_MAX_LINE + 1]; + int major, minor; + ino_t inode; + long long uint64_inode; + dev_t dev; + + if (!(file = fopen(fname, "r"))) return 0; + while (fgets(line, FUSER_MAX_LINE, file)) { + if(sscanf(line, "%*s %*s %*s %x:%x %llu", + &major, &minor, &uint64_inode) != 3) continue; + inode = uint64_inode; + if(major == 0 && minor == 0 && inode == 0) continue; + dev = makedev(major, minor); + if(fuser_search_dev_inode(opts, ilist, dev, inode)) { + fuser_add_pid(plist, pid); + } + + } + fclose(file); + return 1; +} + +static int fuser_scan_link(int opts, const char *lname, pid_t pid, + inode_list *ilist, pid_list *plist) +{ + ino_t inode; + dev_t dev; + + if(!fuser_file_to_dev_inode(lname, &dev, &inode)) return 0; + if(fuser_search_dev_inode(opts, ilist, dev, inode)) + fuser_add_pid(plist, pid); + return 1; +} + +static int fuser_scan_dir_links(int opts, const char *dname, pid_t pid, + inode_list *ilist, pid_list *plist) +{ + DIR *d; + struct dirent *de; + char *lname; + + if((d = opendir(dname))) { + while((de = readdir(d)) != NULL) { + lname = concat_subpath_file(dname, de->d_name); + if(lname == NULL) + continue; + fuser_scan_link(opts, lname, pid, ilist, plist); + free(lname); + } + closedir(d); + } + else return 0; + return 1; + +} + +static int fuser_scan_proc_pids(int opts, inode_list *ilist, pid_list *plist) +{ + DIR *d; + struct dirent *de; + pid_t pid; + char *dname; + + if(!(d = opendir(FUSER_PROC_DIR))) return 0; + while((de = readdir(d)) != NULL) { + pid = (pid_t)atoi(de->d_name); + if(!pid) continue; + dname = concat_subpath_file(FUSER_PROC_DIR, de->d_name); + if(chdir(dname) < 0) { + free(dname); + continue; + } + free(dname); + fuser_scan_link(opts, "cwd", pid, ilist, plist); + fuser_scan_link(opts, "exe", pid, ilist, plist); + fuser_scan_link(opts, "root", pid, ilist, plist); + fuser_scan_dir_links(opts, "fd", pid, ilist, plist); + fuser_scan_dir_links(opts, "lib", pid, ilist, plist); + fuser_scan_dir_links(opts, "mmap", pid, ilist, plist); + fuser_scan_pid_maps(opts, "maps", pid, ilist, plist); + chdir(".."); + } + closedir(d); + return 1; +} + +static int fuser_print_pid_list(pid_list *plist) +{ + pid_list *curr = plist; + + if(plist == NULL) return 0; + while(curr != NULL) { + if(curr->pid > 0) printf("%d ", curr->pid); + curr = curr->next; + } + puts(""); + return 1; +} + +static int fuser_kill_pid_list(pid_list *plist, int sig) +{ + pid_list *curr = plist; + pid_t mypid = getpid(); + int success = 1; + + if(plist == NULL) return 0; + while(curr != NULL) { + if(curr->pid > 0 && curr->pid != mypid) { + if (kill(curr->pid, sig) != 0) { + bb_perror_msg( + "cannot kill pid '%d'", curr->pid); + success = 0; + } + } + curr = curr->next; + } + return success; +} + +int fuser_main(int argc, char **argv) +{ + int port, i, optn; + int* fni; /* file name indexes of argv */ + int fnic = 0; /* file name index count */ + const char *proto; + static int opt = 0; /* FUSER_OPT_ */ + dev_t dev; + ino_t inode; + pid_list *pids; + inode_list *inodes; + int killsig = SIGTERM; + int success = 1; + + if (argc < 2) + bb_show_usage(); + + fni = xmalloc(sizeof(int)); + for(i=1;i(killsig = get_signum(argv[i]+1))) + killsig = SIGTERM; + } + else { + fni = xrealloc(fni, sizeof(int) * (fnic+2)); + fni[fnic++] = i; + } + } + if(!fnic) return 1; + + inodes = xmalloc(sizeof(inode_list)); + for(i=0;ipid == 0) success = 0; + if(success) { + if(opt & FUSER_OPT_KILL) { + success = fuser_kill_pid_list(pids, killsig); + } + else if(!(opt & FUSER_OPT_SILENT)) { + success = fuser_print_pid_list(pids); + } + } + free(pids); + free(inodes); + /* return 0 on (success == 1) 1 otherwise */ + return (success != 1); +} diff --git a/procps/kill.c b/procps/kill.c new file mode 100644 index 000000000..18121f06f --- /dev/null +++ b/procps/kill.c @@ -0,0 +1,155 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini kill/killall implementation for busybox + * + * Copyright (C) 1995, 1996 by Bruce Perens . + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" + +int kill_main(int argc, char **argv) +{ + char *arg; + pid_t pid; + int signo = SIGTERM, errors = 0, quiet = 0; + const int killall = (ENABLE_KILLALL && applet_name[4] == 'a' + && (!ENABLE_KILLALL5 || applet_name[7] != '5')); + const int killall5 = (ENABLE_KILLALL5 && applet_name[4] == 'a' + && (!ENABLE_KILLALL || applet_name[7] == '5')); + + /* Parse any options */ + argc--; + arg = *++argv; + + if (argc < 1 || arg[0] != '-') { + goto do_it_now; + } + + /* The -l option, which prints out signal names. */ + if (arg[1] == 'l' && arg[2] == '\0') { + const char *name; + if (argc == 1) { + /* Print the whole signal list */ + int col = 0; + for (signo = 1; signo < 32; signo++) { + name = get_signame(signo); + if (isdigit(name[0])) continue; + if (col > 66) { + puts(""); + col = 0; + } + col += printf("%2d) %-6s", signo, name); + } + puts(""); + } else { /* -l */ + while ((arg = *++argv)) { + if (isdigit(arg[0])) { + signo = xatoi_u(arg); + name = get_signame(signo); + } else { + signo = get_signum(arg); + if (signo < 0) + bb_error_msg_and_die("unknown signal '%s'", arg); + name = get_signame(signo); + } + printf("%2d) %s\n", signo, name); + } + } + /* If they specified -l, we are all done */ + return EXIT_SUCCESS; + } + + /* The -q quiet option */ + if (killall && arg[1] == 'q' && arg[2] == '\0') { + quiet = 1; + arg = *++argv; + argc--; + if (argc < 1) bb_show_usage(); + if (arg[0] != '-') goto do_it_now; + } + + /* -SIG */ + signo = get_signum(&arg[1]); + if (signo < 0) + bb_error_msg_and_die("bad signal name '%s'", &arg[1]); + arg = *++argv; + argc--; + +do_it_now: + + if (killall5) { + pid_t sid; + procps_status_t* p = NULL; + +// Cannot happen anyway? We don't TERM ourself, we STOP +// /* kill(-1, sig) on Linux (at least 2.1.x) +// * might send signal to the calling process too */ +// signal(SIGTERM, SIG_IGN); + /* Now stop all processes */ + kill(-1, SIGSTOP); + /* Find out our own session id */ + pid = getpid(); + sid = getsid(pid); + /* Now kill all processes except our session */ + while ((p = procps_scan(p, PSSCAN_PID|PSSCAN_SID))) { + if (p->sid != sid && p->pid != pid && p->pid != 1) + kill(p->pid, signo); + } + /* And let them continue */ + kill(-1, SIGCONT); + return 0; + } + + /* Pid or name required for kill/killall */ + if (argc < 1) + bb_show_usage(); + + if (killall) { + /* Looks like they want to do a killall. Do that */ + pid = getpid(); + while (arg) { + pid_t* pidList; + + pidList = find_pid_by_name(arg); + if (*pidList == 0) { + errors++; + if (!quiet) + bb_error_msg("%s: no process killed", arg); + } else { + pid_t *pl; + + for (pl = pidList; *pl; pl++) { + if (*pl == pid) + continue; + if (kill(*pl, signo) == 0) + continue; + errors++; + if (!quiet) + bb_perror_msg("cannot kill pid %u", (unsigned)*pl); + } + } + free(pidList); + arg = *++argv; + } + return errors; + } + + /* Looks like they want to do a kill. Do that */ + while (arg) { + /* Huh? + if (!isdigit(arg[0]) && arg[0] != '-') + bb_error_msg_and_die("bad pid '%s'", arg); + */ + pid = xatou(arg); + /* FIXME: better overflow check? */ + if (kill(pid, signo) != 0) { + bb_perror_msg("cannot kill pid %u", (unsigned)pid); + errors++; + } + arg = *++argv; + } + return errors; +} diff --git a/procps/pidof.c b/procps/pidof.c new file mode 100644 index 000000000..1d9718945 --- /dev/null +++ b/procps/pidof.c @@ -0,0 +1,90 @@ +/* vi: set sw=4 ts=4: */ +/* + * pidof implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under the GPL version 2, see the file LICENSE in this tarball. + */ + +#include "busybox.h" + +enum { + USE_FEATURE_PIDOF_SINGLE(OPTBIT_SINGLE,) + USE_FEATURE_PIDOF_OMIT( OPTBIT_OMIT ,) + OPT_SINGLE = USE_FEATURE_PIDOF_SINGLE((1<data, "%PPID", 5)) { + llist_pop(&omits_p); + snprintf(getppid_str, sizeof(getppid_str), "%u", (unsigned)getppid()); + llist_add_to(&omits_p, getppid_str); + } + omits_p = omits_p->link; + } + } +#endif + /* Looks like everything is set to go. */ + while (optind < argc) { + pid_t *pidList; + pid_t *pl; + + /* reverse the pidlist like GNU pidof does. */ + pidList = pidlist_reverse(find_pid_by_name(argv[optind])); + for (pl = pidList; *pl; pl++) { + SKIP_FEATURE_PIDOF_OMIT(const) unsigned omitted = 0; +#if ENABLE_FEATURE_PIDOF_OMIT + if (opt & OPT_OMIT) { + llist_t *omits_p = omits; + while (omits_p) { + if (xatoul(omits_p->data) == *pl) { + omitted = 1; + break; + } else + omits_p = omits_p->link; + } + } +#endif + if (!omitted) { + printf(" %u" + first, (unsigned)*pl); + first = 0; + } + fail = (!ENABLE_FEATURE_PIDOF_OMIT && omitted); + + if (ENABLE_FEATURE_PIDOF_SINGLE && (opt & OPT_SINGLE)) + break; + } + free(pidList); + optind++; + } + putchar('\n'); + +#if ENABLE_FEATURE_PIDOF_OMIT + if (ENABLE_FEATURE_CLEAN_UP) + llist_free(omits, NULL); +#endif + return fail ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/procps/ps.c b/procps/ps.c new file mode 100644 index 000000000..e18bd2a58 --- /dev/null +++ b/procps/ps.c @@ -0,0 +1,388 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini ps implementation(s) for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under the GPL version 2, see the file LICENSE in this tarball. + */ + +#include "busybox.h" + +#if ENABLE_DESKTOP + +/* Print value to buf, max size+1 chars (including trailing '\0') */ + +void func_user(char *buf, int size, const procps_status_t *ps) +{ + safe_strncpy(buf, get_cached_username(ps->uid), size+1); +} + +void func_comm(char *buf, int size, const procps_status_t *ps) +{ + safe_strncpy(buf, ps->comm, size+1); +} + +void func_args(char *buf, int size, const procps_status_t *ps) +{ + buf[0] = '\0'; + if (ps->cmd) + safe_strncpy(buf, ps->cmd, size+1); + else if (size >= 2) + snprintf(buf, size+1, "[%.*s]", size-2, ps->comm); +} + +void func_pid(char *buf, int size, const procps_status_t *ps) +{ + snprintf(buf, size+1, "%*u", size, ps->pid); +} + +void func_ppid(char *buf, int size, const procps_status_t *ps) +{ + snprintf(buf, size+1, "%*u", size, ps->ppid); +} + +void func_pgid(char *buf, int size, const procps_status_t *ps) +{ + snprintf(buf, size+1, "%*u", size, ps->pgid); +} + +void func_rss(char *buf, int size, const procps_status_t *ps) +{ + char buf5[5]; + smart_ulltoa5( ((unsigned long long)ps->rss) << 10, buf5); + snprintf(buf, size+1, "%.*s", size, buf5); +} + +/* +void func_nice(char *buf, int size, const procps_status_t *ps) +{ + ps->??? +} + +void func_etime(char *buf, int size, const procps_status_t *ps) +{ + elapled time [[dd-]hh:]mm:ss +} + +void func_time(char *buf, int size, const procps_status_t *ps) +{ + cumulative time [[dd-]hh:]mm:ss +} + +void func_pcpu(char *buf, int size, const procps_status_t *ps) +{ +} + +void func_tty(char *buf, int size, const procps_status_t *ps) +{ +} +*/ + +typedef struct { + char name[8]; + const char *header; + void (*f)(char *buf, int size, const procps_status_t *ps); + int ps_flags; + int width; +} ps_out_t; + +static const ps_out_t out_spec[] = { +// Mandated by POSIX: + { "user" ,"USER" ,func_user ,PSSCAN_UIDGID,8 }, + { "comm" ,"COMMAND",func_comm ,PSSCAN_COMM ,16 }, + { "args" ,"COMMAND",func_args ,PSSCAN_CMD|PSSCAN_COMM,256 }, + { "pid" ,"PID" ,func_pid ,PSSCAN_PID ,5 }, + { "ppid" ,"PPID" ,func_ppid ,PSSCAN_PPID ,5 }, + { "pgid" ,"PGID" ,func_pgid ,PSSCAN_PGID ,5 }, +// { "etime" ,"ELAPSED",func_etime ,PSSCAN_ ,sizeof("ELAPSED")-1 }, +// { "group" ,"GROUP" ,func_group ,PSSCAN_UIDGID,sizeof("GROUP" )-1 }, +// { "nice" ,"NI" ,func_nice ,PSSCAN_ ,sizeof("NI" )-1 }, +// { "pcpu" ,"%CPU" ,func_pcpu ,PSSCAN_ ,sizeof("%CPU" )-1 }, +// { "rgroup","RGROUP" ,func_rgroup,PSSCAN_UIDGID,sizeof("RGROUP" )-1 }, +// { "ruser" ,"RUSER" ,func_ruser ,PSSCAN_UIDGID,sizeof("RUSER" )-1 }, +// { "time" ,"TIME" ,func_time ,PSSCAN_ ,sizeof("TIME" )-1 }, +// { "tty" ,"TT" ,func_tty ,PSSCAN_ ,sizeof("TT" )-1 }, +// { "vsz" ,"VSZ" ,func_vsz ,PSSCAN_VSZ ,4 }, +// Not mandated by POSIX: + { "rss" ,"RSS" ,func_rss ,PSSCAN_RSS ,4 }, +}; + +#define VEC_SIZE(v) ( sizeof(v) / sizeof((v)[0]) ) + +static ps_out_t* out; +static int out_cnt; +static int print_header; +static int ps_flags; +static char *buffer; +static unsigned terminal_width; + + +static ps_out_t* new_out_t(void) +{ + int i = out_cnt++; + out = xrealloc(out, out_cnt * sizeof(*out)); + return &out[i]; +} + +static const ps_out_t* find_out_spec(const char *name) +{ + int i; + for (i = 0; i < VEC_SIZE(out_spec); i++) { + if (!strcmp(name, out_spec[i].name)) + return &out_spec[i]; + } + bb_error_msg_and_die("bad -o argument '%s'", name); +} + +static void parse_o(char* opt) +{ + ps_out_t* new; + // POSIX: "-o is blank- or comma-separated list" (FIXME) + char *comma, *equal; + while (1) { + comma = strchr(opt, ','); + equal = strchr(opt, '='); + if (comma && (!equal || equal > comma)) { + *comma = '\0'; + *new_out_t() = *find_out_spec(opt); + *comma = ','; + opt = comma + 1; + continue; + } + break; + } + new = new_out_t(); + if (equal) + *equal = '\0'; + *new = *find_out_spec(opt); + if (equal) { + *equal = '='; + new->header = equal + 1; + // POSIX: the field widths shall be ... at least as wide as + // the header text (default or overridden value). + // If the header text is null, such as -o user=, + // the field width shall be at least as wide as the + // default header text + if (new->header[0]) { + new->width = strlen(new->header); + print_header = 1; + } + } else + print_header = 1; +} + +static void post_process(void) +{ + int i; + int width = 0; + for (i = 0; i < out_cnt; i++) { + ps_flags |= out[i].ps_flags; + if (out[i].header[0]) { + print_header = 1; + } + width += out[i].width + 1; /* "FIELD " */ + } + buffer = xmalloc(width + 1); /* for trailing \0 */ +} + +static void format_header(void) +{ + int i; + ps_out_t* op; + char *p = buffer; + if (!print_header) + return; + i = 0; + if (out_cnt) { + while (1) { + op = &out[i]; + if (++i == out_cnt) /* do not pad last field */ + break; + p += sprintf(p, "%-*s ", op->width, op->header); + } + strcpy(p, op->header); + } + printf("%.*s\n", terminal_width, buffer); +} + +static void format_process(const procps_status_t *ps) +{ + int i, len; + char *p = buffer; + i = 0; + if (out_cnt) while (1) { + out[i].f(p, out[i].width, ps); + // POSIX: Any field need not be meaningful in all + // implementations. In such a case a hyphen ( '-' ) + // should be output in place of the field value. + if (!*p) { + *p++ = '-'; + *p = '\0'; + } + len = strlen(p); + p += len; + len = out[i].width - len + 1; + if (++i == out_cnt) /* do not pad last field */ + break; + while (len--) + *p++ = ' '; + *p = '\0'; + } + printf("%.*s\n", terminal_width, buffer); +} + +/* Cannot be const: parse_o() will choke */ +static char default_o[] = "pid,user" /* TODO: ,vsz,stat */ ",args"; + +int ps_main(int argc, char **argv) +{ + procps_status_t *p; + llist_t* opt_o = NULL; + + // POSIX: + // -a Write information for all processes associated with terminals + // Implementations may omit session leaders from this list + // -A Write information for all processes + // -d Write information for all processes, except session leaders + // -e Write information for all processes (equivalent to -A.) + // -f Generate a full listing + // -l Generate a long listing + // -o col1,col2,col3=header + // Select which columns to distplay + /* We allow (and ignore) most of the above. FIXME */ + opt_complementary = "o::"; + getopt32(argc, argv, "o:aAdefl", &opt_o); + if (opt_o) { + opt_o = rev_llist(opt_o); + do { + parse_o(opt_o->data); + opt_o = opt_o->link; + } while (opt_o); + } else + parse_o(default_o); + post_process(); + + terminal_width = INT_MAX; + if (isatty(1)) { + get_terminal_width_height(1, &terminal_width, NULL); + terminal_width--; + } + format_header(); + + p = NULL; + while ((p = procps_scan(p, ps_flags))) { + format_process(p); + } + + return EXIT_SUCCESS; +} + + +#else /* !ENABLE_DESKTOP */ + + +int ps_main(int argc, char **argv) +{ + procps_status_t *p = NULL; + int i, len; + SKIP_SELINUX(const) int use_selinux = 0; + USE_SELINUX(security_context_t sid = NULL;) +#if !ENABLE_FEATURE_PS_WIDE + enum { terminal_width = 79 }; +#else + int terminal_width; + int w_count = 0; +#endif + +#if ENABLE_FEATURE_PS_WIDE || ENABLE_SELINUX +#if ENABLE_FEATURE_PS_WIDE + opt_complementary = "-:ww"; + USE_SELINUX(i =) getopt32(argc, argv, USE_SELINUX("c") "w", &w_count); + /* if w is given once, GNU ps sets the width to 132, + * if w is given more than once, it is "unlimited" + */ + if (w_count) { + terminal_width = (w_count==1) ? 132 : INT_MAX; + } else { + get_terminal_width_height(1, &terminal_width, NULL); + /* Go one less... */ + terminal_width--; + } +#else /* only ENABLE_SELINUX */ + i = getopt32(argc, argv, "c"); +#endif +#if ENABLE_SELINUX + if ((i & 1) && is_selinux_enabled()) + use_selinux = 1; +#endif +#endif /* ENABLE_FEATURE_PS_WIDE || ENABLE_SELINUX */ + + if (use_selinux) + puts(" PID Context Stat Command"); + else + puts(" PID Uid VmSize Stat Command"); + + while ((p = procps_scan(p, 0 + | PSSCAN_PID + | PSSCAN_UIDGID + | PSSCAN_STATE + | PSSCAN_RSS + | PSSCAN_CMD + ))) { + char *namecmd = p->cmd; +#if ENABLE_SELINUX + if (use_selinux) { + char sbuf[128]; + len = sizeof(sbuf); + + if (is_selinux_enabled()) { + if (getpidcon(p->pid, &sid) < 0) + sid = NULL; + } + + if (sid) { + /* I assume sid initialized with NULL */ + len = strlen(sid) + 1; + safe_strncpy(sbuf, sid, len); + freecon(sid); + sid = NULL; + } else { + safe_strncpy(sbuf, "unknown", 7); + } + len = printf("%5u %-32s %s ", p->pid, sbuf, p->state); + } else +#endif + { + const char *user = get_cached_username(p->uid); + if (p->rss == 0) + len = printf("%5u %-8s %s ", + p->pid, user, p->state); + else + len = printf("%5u %-8s %6ld %s ", + p->pid, user, p->rss, p->state); + } + + i = terminal_width-len; + + if (namecmd && namecmd[0]) { + if (i < 0) + i = 0; + if (strlen(namecmd) > (size_t)i) + namecmd[i] = 0; + puts(namecmd); + } else { + namecmd = p->comm; + if (i < 2) + i = 2; + if (strlen(namecmd) > ((size_t)i-2)) + namecmd[i-2] = 0; + printf("[%s]\n", namecmd); + } + } + if (ENABLE_FEATURE_CLEAN_UP) + clear_username_cache(); + return EXIT_SUCCESS; +} + +#endif /* ENABLE_DESKTOP */ diff --git a/procps/ps.posix b/procps/ps.posix new file mode 100644 index 000000000..57f4fa8af --- /dev/null +++ b/procps/ps.posix @@ -0,0 +1,175 @@ +This is what POSIX 2003 says about ps: + +By default, ps shall select all processes with the same effective user +ID as the current user and the same controlling terminal as the invoker + +ps [-aA][-defl][-G grouplist][-o format]...[-p proclist][-t termlist] +[-U userlist][-g grouplist][-n namelist][-u userlist] + +-a Write information for all processes associated with terminals. + Implementations may omit session leaders from this list. + +-A Write information for all processes. + +-d Write information for all processes, except session leaders. + +-e Write information for all processes. (Equivalent to -A.) + +-f Generate a full listing. (See the STDOUT section for the con- + tents of a full listing.) + +-g grouplist + Write information for processes whose session leaders are given + in grouplist. The application shall ensure that the grouplist is + a single argument in the form of a or comma-separated + list. + +-G grouplist + Write information for processes whose real group ID numbers are + given in grouplist. The application shall ensure that the grou- + plist is a single argument in the form of a or comma- + separated list. + +-l Generate a long listing. (See STDOUT for the contents of a long + listing.) + +-n namelist + Specify the name of an alternative system namelist file in place + of the default. The name of the default file and the format of a + namelist file are unspecified. + +-o format + Write information according to the format specification given in + format. Multiple -o options can be specified; the format speci- + fication shall be interpreted as the -separated concate- + nation of all the format option-arguments. + +-p proclist + Write information for processes whose process ID numbers are + given in proclist. The application shall ensure that the pro- + clist is a single argument in the form of a or comma- + separated list. + +-t termlist + Write information for processes associated with terminals given + in termlist. The application shall ensure that the termlist is a + single argument in the form of a or comma-separated + list. Terminal identifiers shall be given in an implementation- + defined format. On XSI-conformant systems, they shall be + given in one of two forms: the device's filename (for example, + tty04) or, if the device's filename starts with tty, just the + identifier following the characters tty (for example, "04" ). + +-u userlist + Write information for processes whose user ID numbers or login + names are given in userlist. The application shall ensure that + the userlist is a single argument in the form of a or + comma-separated list. In the listing, the numerical user ID + shall be written unless the -f option is used, in which case the + login name shall be written. + +-U userlist + Write information for processes whose real user ID numbers or + login names are given in userlist. The application shall ensure + that the userlist is a single argument in the form of a + or comma-separated list. + +With the exception of -o format, all of the options shown are used to +select processes. If any are specified, the default list shall be +ignored and ps shall select the processes represented by the inclusive +OR of all the selection-criteria options. + +The -o option allows the output format to be specified under user con- +trol. + +The application shall ensure that the format specification is a list of +names presented as a single argument, or comma-separated. Each +variable has a default header. The default header can be overridden by +appending an equals sign and the new text of the header. The rest of +the characters in the argument shall be used as the header text. The +fields specified shall be written in the order specified on the command +line, and should be arranged in columns in the output. The field widths +shall be selected by the system to be at least as wide as the header +text (default or overridden value). If the header text is null, such as +-o user=, the field width shall be at least as wide as the default +header text. If all header text fields are null, no header line shall +be written. + +ruser The real user ID of the process. This shall be the textual user + ID, if it can be obtained and the field width permits, or a dec- + imal representation otherwise. + +user The effective user ID of the process. This shall be the textual + user ID, if it can be obtained and the field width permits, or a + decimal representation otherwise. + +rgroup The real group ID of the process. This shall be the textual + group ID, if it can be obtained and the field width permits, or + a decimal representation otherwise. + +group The effective group ID of the process. This shall be the textual + group ID, if it can be obtained and the field width permits, or + a decimal representation otherwise. + +pid The decimal value of the process ID. + +ppid The decimal value of the parent process ID. + +pgid The decimal value of the process group ID. + +pcpu The ratio of CPU time used recently to CPU time available in the + same period, expressed as a percentage. The meaning of + "recently" in this context is unspecified. The CPU time avail- + able is determined in an unspecified manner. + +vsz The size of the process in (virtual) memory in 1024 byte units + as a decimal integer. + +nice The decimal value of the nice value of the process; see nice() . + +etime In the POSIX locale, the elapsed time since the process was + started, in the form: [[dd-]hh:]mm:ss + +time In the POSIX locale, the cumulative CPU time of the process in + the form: [dd-]hh:mm:ss + +tty The name of the controlling terminal of the process (if any) in + the same format used by the who utility. + +comm The name of the command being executed ( argv[0] value) as a + string. + +args The command with all its arguments as a string. The implementa- + tion may truncate this value to the field width; it is implemen- + tation-defined whether any further truncation occurs. It is + unspecified whether the string represented is a version of the + argument list as it was passed to the command when it started, + or is a version of the arguments as they may have been modified + by the application. Applications cannot depend on being able to + modify their argument list and having that modification be + reflected in the output of ps. + +Any field need not be meaningful in all implementations. In such a case +a hyphen ( '-' ) should be output in place of the field value. + +Only comm and args shall be allowed to contain s; all others +shall not. + +The following table specifies the default header to be used in the +POSIX locale corresponding to each format specifier. + + Format Specifier Default Header Format Specifier Default Header + args COMMAND ppid PPID + comm COMMAND rgroup RGROUP + etime ELAPSED ruser RUSER + group GROUP time TIME + nice NI tty TT + pcpu %CPU user USER + pgid PGID vsz VSZ + pid PID + +There is no special quoting mechanism for header text. The header text +is the rest of the argument. If multiple header changes are needed, +multiple -o options can be used, such as: + + ps -o "user=User Name" -o pid=Process\ ID diff --git a/procps/renice.c b/procps/renice.c new file mode 100644 index 000000000..08e0dc264 --- /dev/null +++ b/procps/renice.c @@ -0,0 +1,126 @@ +/* vi: set sw=4 ts=4: */ +/* + * renice implementation for busybox + * + * Copyright (C) 2005 Manuel Novoa III + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +/* Notes: + * Setting an absolute priority was obsoleted in SUSv2 and removed + * in SUSv3. However, the common linux version of renice does + * absolute and not relative. So we'll continue supporting absolute, + * although the stdout logging has been removed since both SUSv2 and + * SUSv3 specify that stdout isn't used. + * + * This version is lenient in that it doesn't require any IDs. The + * options -p, -g, and -u are treated as mode switches for the + * following IDs (if any). Multiple switches are allowed. + */ + +#include "busybox.h" +#include + +void BUG_bad_PRIO_PROCESS(void); +void BUG_bad_PRIO_PGRP(void); +void BUG_bad_PRIO_USER(void); + +int renice_main(int argc, char **argv) +{ + static const char Xetpriority_msg[] = "%cetpriority"; + + int retval = EXIT_SUCCESS; + int which = PRIO_PROCESS; /* Default 'which' value. */ + int use_relative = 0; + int adjustment, new_priority; + unsigned who; + char *arg; + + /* Yes, they are not #defines in glibc 2.4! #if won't work */ + if (PRIO_PROCESS < CHAR_MIN || PRIO_PROCESS > CHAR_MAX) + BUG_bad_PRIO_PROCESS(); + if (PRIO_PGRP < CHAR_MIN || PRIO_PGRP > CHAR_MAX) + BUG_bad_PRIO_PGRP(); + if (PRIO_USER < CHAR_MIN || PRIO_USER > CHAR_MAX) + BUG_bad_PRIO_USER(); + + arg = *++argv; + + /* Check if we are using a relative adjustment. */ + if (arg && arg[0] == '-' && arg[1] == 'n') { + use_relative = 1; + if (!arg[2]) + arg = *++argv; + else + arg += 2; + } + + if (!arg) { /* No args? Then show usage. */ + bb_show_usage(); + } + + /* Get the priority adjustment (absolute or relative). */ + adjustment = xatoi_range(arg, INT_MIN/2, INT_MAX/2); + + while ((arg = *++argv) != NULL) { + /* Check for a mode switch. */ + if (arg[0] == '-' && arg[1]) { + static const char opts[] + = { 'p', 'g', 'u', 0, PRIO_PROCESS, PRIO_PGRP, PRIO_USER }; + const char *p = strchr(opts, arg[1]); + if (p) { + which = p[4]; + if (!arg[2]) + continue; + arg += 2; + } + } + + /* Process an ID arg. */ + if (which == PRIO_USER) { + struct passwd *p; + p = getpwnam(arg); + if (!p) { + bb_error_msg("unknown user: %s", arg); + goto HAD_ERROR; + } + who = p->pw_uid; + } else { + who = bb_strtou(arg, NULL, 10); + if (errno) { + bb_error_msg("bad value: %s", arg); + goto HAD_ERROR; + } + } + + /* Get priority to use, and set it. */ + if (use_relative) { + int old_priority; + + errno = 0; /* Needed for getpriority error detection. */ + old_priority = getpriority(which, who); + if (errno) { + bb_perror_msg(Xetpriority_msg, 'g'); + goto HAD_ERROR; + } + + new_priority = old_priority + adjustment; + } else { + new_priority = adjustment; + } + + if (setpriority(which, who, new_priority) == 0) { + continue; + } + + bb_perror_msg(Xetpriority_msg, 's'); + HAD_ERROR: + retval = EXIT_FAILURE; + } + + /* No need to check for errors outputing to stderr since, if it + * was used, the HAD_ERROR label was reached and retval was set. */ + + return retval; +} diff --git a/procps/sysctl.c b/procps/sysctl.c new file mode 100644 index 000000000..49754489b --- /dev/null +++ b/procps/sysctl.c @@ -0,0 +1,326 @@ +/* vi: set sw=4 ts=4: */ +/* + * Sysctl 1.01 - A utility to read and manipulate the sysctl parameters + * + * Copyright 1999 George Staikos + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * Changelog: + * v1.01: + * - added -p to preload values from a file + * v1.01.1 + * - busybox applet aware by + * + */ + +#include "busybox.h" + +/* + * Function Prototypes + */ +static int sysctl_read_setting(const char *setting, int output); +static int sysctl_write_setting(const char *setting, int output); +static int sysctl_preload_file(const char *filename, int output); +static int sysctl_display_all(const char *path, int output, int show_table); + +/* + * Globals... + */ +static const char PROC_PATH[] = "/proc/sys/"; +static const char DEFAULT_PRELOAD[] = "/etc/sysctl.conf"; + +/* error messages */ +static const char ERR_UNKNOWN_PARAMETER[] = "error: Unknown parameter '%s'\n"; +static const char ERR_MALFORMED_SETTING[] = "error: Malformed setting '%s'\n"; +static const char ERR_NO_EQUALS[] = + "error: '%s' must be of the form name=value\n"; +static const char ERR_INVALID_KEY[] = "error: '%s' is an unknown key\n"; +static const char ERR_UNKNOWN_WRITING[] = + "error: unknown error %d setting key '%s'\n"; +static const char ERR_UNKNOWN_READING[] = + "error: unknown error %d reading key '%s'\n"; +static const char ERR_PERMISSION_DENIED[] = + "error: permission denied on key '%s'\n"; +static const char ERR_PRELOAD_FILE[] = + "error: cannot open preload file '%s'\n"; +static const char WARN_BAD_LINE[] = + "warning: %s(%d): invalid syntax, continuing...\n"; + + +static void dwrite_str(int fd, const char *buf) +{ + write(fd, buf, strlen(buf)); +} + +/* + * sysctl_main()... + */ +int sysctl_main(int argc, char **argv) +{ + int retval = 0; + int output = 1; + int write_mode = 0; + int switches_allowed = 1; + + if (argc < 2) + bb_show_usage(); + + argv++; + + for (; argv && *argv && **argv; argv++) { + if (switches_allowed && **argv == '-') { /* we have a switch */ + switch ((*argv)[1]) { + case 'n': + output = 0; + break; + case 'w': + write_mode = 1; + switches_allowed = 0; + break; + case 'p': + argv++; + return + sysctl_preload_file(((argv && *argv + && **argv) ? *argv : + DEFAULT_PRELOAD), output); + case 'a': + case 'A': + switches_allowed = 0; + return sysctl_display_all(PROC_PATH, output, + ((*argv)[1] == 'a') ? 0 : 1); + case 'h': + case '?': + bb_show_usage(); + default: + bb_error_msg(ERR_UNKNOWN_PARAMETER, *argv); + bb_show_usage(); + } + } else { + switches_allowed = 0; + if (write_mode) + retval = sysctl_write_setting(*argv, output); + else + sysctl_read_setting(*argv, output); + } + } + return retval; +} /* end sysctl_main() */ + + + +/* + * sysctl_preload_file + * preload the sysctl's from a conf file + * - we parse the file and then reform it (strip out whitespace) + */ +#define PRELOAD_BUF 256 + +int sysctl_preload_file(const char *filename, int output) +{ + int lineno = 0; + char oneline[PRELOAD_BUF]; + char buffer[PRELOAD_BUF]; + char *name, *value, *ptr; + FILE *fp = NULL; + + if (!filename || ((fp = fopen(filename, "r")) == NULL)) { + bb_error_msg_and_die(ERR_PRELOAD_FILE, filename); + } + + while (fgets(oneline, sizeof(oneline) - 1, fp)) { + oneline[sizeof(oneline) - 1] = '\0'; + lineno++; + trim(oneline); + ptr = (char *) oneline; + + if (*ptr == '#' || *ptr == ';') + continue; + + if (strlen(ptr) < 2) + continue; + + name = strtok(ptr, "="); + if (!name || !*name) { + bb_error_msg(WARN_BAD_LINE, filename, lineno); + continue; + } + + trim(name); + + value = strtok(NULL, "\n\r"); + if (!value || !*value) { + bb_error_msg(WARN_BAD_LINE, filename, lineno); + continue; + } + + while ((*value == ' ' || *value == '\t') && *value != 0) + value++; + /* safe because sizeof(oneline) == sizeof(buffer) */ + sprintf(buffer, "%s=%s", name, value); + sysctl_write_setting(buffer, output); + } + fclose(fp); + return 0; +} /* end sysctl_preload_file() */ + + +/* + * Write a single sysctl setting + */ +int sysctl_write_setting(const char *setting, int output) +{ + int retval = 0; + const char *name = setting; + const char *value; + const char *equals; + char *tmpname, *outname, *cptr; + int fd = -1; + + if (!name) /* probably dont' want to display this err */ + return 0; + + if (!(equals = strchr(setting, '='))) { + bb_error_msg(ERR_NO_EQUALS, setting); + return -1; + } + + value = equals + sizeof(char); /* point to the value in name=value */ + + if (!*name || !*value || name == equals) { + bb_error_msg(ERR_MALFORMED_SETTING, setting); + return -2; + } + + tmpname = xasprintf("%s%.*s", PROC_PATH, (int)(equals - name), name); + outname = xstrdup(tmpname + strlen(PROC_PATH)); + + while ((cptr = strchr(tmpname, '.')) != NULL) + *cptr = '/'; + + while ((cptr = strchr(outname, '/')) != NULL) + *cptr = '.'; + + if ((fd = open(tmpname, O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0) { + switch (errno) { + case ENOENT: + bb_error_msg(ERR_INVALID_KEY, outname); + break; + case EACCES: + bb_perror_msg(ERR_PERMISSION_DENIED, outname); + break; + default: + bb_error_msg(ERR_UNKNOWN_WRITING, errno, outname); + break; + } + retval = -1; + } else { + dwrite_str(fd, value); + close(fd); + if (output) { + dwrite_str(STDOUT_FILENO, outname); + dwrite_str(STDOUT_FILENO, " = "); + } + dwrite_str(STDOUT_FILENO, value); + dwrite_str(STDOUT_FILENO, "\n"); + } + + /* cleanup */ + free(tmpname); + free(outname); + return retval; +} /* end sysctl_write_setting() */ + + +/* + * Read a sysctl setting + * + */ +int sysctl_read_setting(const char *setting, int output) +{ + int retval = 0; + char *tmpname, *outname, *cptr; + char inbuf[1025]; + const char *name = setting; + FILE *fp; + + if (!setting || !*setting) + bb_error_msg(ERR_INVALID_KEY, setting); + + tmpname = concat_path_file(PROC_PATH, name); + outname = xstrdup(tmpname + strlen(PROC_PATH)); + + while ((cptr = strchr(tmpname, '.')) != NULL) + *cptr = '/'; + while ((cptr = strchr(outname, '/')) != NULL) + *cptr = '.'; + + if ((fp = fopen(tmpname, "r")) == NULL) { + switch (errno) { + case ENOENT: + bb_error_msg(ERR_INVALID_KEY, outname); + break; + case EACCES: + bb_error_msg(ERR_PERMISSION_DENIED, outname); + break; + default: + bb_error_msg(ERR_UNKNOWN_READING, errno, outname); + break; + } + retval = -1; + } else { + while (fgets(inbuf, sizeof(inbuf) - 1, fp)) { + if (output) { + dwrite_str(STDOUT_FILENO, outname); + dwrite_str(STDOUT_FILENO, " = "); + } + dwrite_str(STDOUT_FILENO, inbuf); + } + fclose(fp); + } + + free(tmpname); + free(outname); + return retval; +} /* end sysctl_read_setting() */ + + + +/* + * Display all the sysctl settings + * + */ +int sysctl_display_all(const char *path, int output, int show_table) +{ + int retval = 0; + int retval2; + DIR *dp; + struct dirent *de; + char *tmpdir; + struct stat ts; + + if (!(dp = opendir(path))) { + retval = -1; + } else { + while ((de = readdir(dp)) != NULL) { + tmpdir = concat_subpath_file(path, de->d_name); + if(tmpdir == NULL) + continue; + if ((retval2 = stat(tmpdir, &ts)) != 0) + bb_perror_msg(tmpdir); + else { + if (S_ISDIR(ts.st_mode)) { + sysctl_display_all(tmpdir, output, show_table); + } else + retval |= + sysctl_read_setting(tmpdir + strlen(PROC_PATH), + output); + + } + free(tmpdir); + } /* end while */ + closedir(dp); + } + + return retval; +} /* end sysctl_display_all() */ diff --git a/procps/top.c b/procps/top.c new file mode 100644 index 000000000..8d732d4b2 --- /dev/null +++ b/procps/top.c @@ -0,0 +1,566 @@ +/* vi: set sw=4 ts=4: */ +/* + * A tiny 'top' utility. + * + * This is written specifically for the linux /proc//stat(m) + * files format. + + * This reads the PIDs of all processes and their status and shows + * the status of processes (first ones that fit to screen) at given + * intervals. + * + * NOTES: + * - At startup this changes to /proc, all the reads are then + * relative to that. + * + * (C) Eero Tamminen + * + * Rewritten by Vladimir Oleynik (C) 2002 + */ + +/* Original code Copyrights */ +/* + * Copyright (c) 1992 Branko Lankester + * Copyright (c) 1992 Roger Binns + * Copyright (C) 1994-1996 Charles L. Blake. + * Copyright (C) 1992-1998 Michael K. Johnson + * May be distributed under the conditions of the + * GNU Library General Public License + */ + +#include "busybox.h" + + +typedef struct { + unsigned long rss; +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + unsigned long ticks; + unsigned pcpu; /* delta of ticks */ +#endif + unsigned pid, ppid; + unsigned uid; + char state[4]; + char comm[COMM_LEN]; +} top_status_t; +static top_status_t *top; +static int ntop; +/* This structure stores some critical information from one frame to + the next. Used for finding deltas. */ +struct save_hist { + unsigned long ticks; + unsigned pid; +}; +static struct save_hist *prev_hist; +static int prev_hist_count; +/* static int hist_iterations; */ +static unsigned total_pcpu; +/* static unsigned long total_rss; */ + + +#define OPT_BATCH_MODE (option_mask32 & 0x4) + +#if ENABLE_FEATURE_USE_TERMIOS +static int pid_sort(top_status_t *P, top_status_t *Q) +{ + /* Buggy wrt pids with high bit set */ + /* (linux pids are in [1..2^15-1]) */ + return (Q->pid - P->pid); +} +#endif + +static int mem_sort(top_status_t *P, top_status_t *Q) +{ + /* We want to avoid unsigned->signed and truncation errors */ + if (Q->rss < P->rss) return -1; + return Q->rss != P->rss; /* 0 if ==, 1 if > */ +} + + +typedef int (*cmp_funcp)(top_status_t *P, top_status_t *Q); + +#if !ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + +static cmp_funcp sort_function; + +#else + +enum { SORT_DEPTH = 3 }; + +static cmp_funcp sort_function[SORT_DEPTH]; + +static int pcpu_sort(top_status_t *P, top_status_t *Q) +{ + /* Buggy wrt ticks with high bit set */ + /* Affects only processes for which ticks overflow */ + return (int)Q->pcpu - (int)P->pcpu; +} + +static int time_sort(top_status_t *P, top_status_t *Q) +{ + /* We want to avoid unsigned->signed and truncation errors */ + if (Q->ticks < P->ticks) return -1; + return Q->ticks != P->ticks; /* 0 if ==, 1 if > */ +} + +static int mult_lvl_cmp(void* a, void* b) { + int i, cmp_val; + + for (i = 0; i < SORT_DEPTH; i++) { + cmp_val = (*sort_function[i])(a, b); + if (cmp_val != 0) + return cmp_val; + } + return 0; +} + + +typedef struct { + unsigned long long usr,nic,sys,idle,iowait,irq,softirq,steal; + unsigned long long total; + unsigned long long busy; +} jiffy_counts_t; +static jiffy_counts_t jif, prev_jif; +static void get_jiffy_counts(void) +{ + FILE* fp = xfopen("stat", "r"); + prev_jif = jif; + if (fscanf(fp, "cpu %lld %lld %lld %lld %lld %lld %lld %lld", + &jif.usr,&jif.nic,&jif.sys,&jif.idle, + &jif.iowait,&jif.irq,&jif.softirq,&jif.steal) < 4) { + bb_error_msg_and_die("failed to read /proc/stat"); + } + fclose(fp); + jif.total = jif.usr + jif.nic + jif.sys + jif.idle + + jif.iowait + jif.irq + jif.softirq + jif.steal; + /* procps 2.x does not count iowait as busy time */ + jif.busy = jif.total - jif.idle - jif.iowait; +} + + +static void do_stats(void) +{ + top_status_t *cur; + pid_t pid; + int i, last_i, n; + struct save_hist *new_hist; + + get_jiffy_counts(); + total_pcpu = 0; + /* total_rss = 0; */ + new_hist = xmalloc(sizeof(struct save_hist)*ntop); + /* + * Make a pass through the data to get stats. + */ + /* hist_iterations = 0; */ + i = 0; + for (n = 0; n < ntop; n++) { + cur = top + n; + + /* + * Calculate time in cur process. Time is sum of user time + * and system time + */ + pid = cur->pid; + new_hist[n].ticks = cur->ticks; + new_hist[n].pid = pid; + + /* find matching entry from previous pass */ + cur->pcpu = 0; + /* do not start at index 0, continue at last used one + * (brought hist_iterations from ~14000 down to 172) */ + last_i = i; + if (prev_hist_count) do { + if (prev_hist[i].pid == pid) { + cur->pcpu = cur->ticks - prev_hist[i].ticks; + total_pcpu += cur->pcpu; + break; + } + i = (i+1) % prev_hist_count; + /* hist_iterations++; */ + } while (i != last_i); + /* total_rss += cur->rss; */ + } + + /* + * Save cur frame's information. + */ + free(prev_hist); + prev_hist = new_hist; + prev_hist_count = ntop; +} +#endif /* FEATURE_TOP_CPU_USAGE_PERCENTAGE */ + + +/* display generic info (meminfo / loadavg) */ +static unsigned long display_generic(int scr_width) +{ + FILE *fp; + char buf[80]; + char scrbuf[80]; + char *end; + unsigned long total, used, mfree, shared, buffers, cached; + unsigned int needs_conversion = 1; + + /* read memory info */ + fp = xfopen("meminfo", "r"); + + /* + * Old kernels (such as 2.4.x) had a nice summary of memory info that + * we could parse, however this is gone entirely in 2.6. Try parsing + * the old way first, and if that fails, parse each field manually. + * + * First, we read in the first line. Old kernels will have bogus + * strings we don't care about, whereas new kernels will start right + * out with MemTotal: + * -- PFM. + */ + if (fscanf(fp, "MemTotal: %lu %s\n", &total, buf) != 2) { + fgets(buf, sizeof(buf), fp); /* skip first line */ + + fscanf(fp, "Mem: %lu %lu %lu %lu %lu %lu", + &total, &used, &mfree, &shared, &buffers, &cached); + } else { + /* + * Revert to manual parsing, which incidentally already has the + * sizes in kilobytes. This should be safe for both 2.4 and + * 2.6. + */ + needs_conversion = 0; + + fscanf(fp, "MemFree: %lu %s\n", &mfree, buf); + + /* + * MemShared: is no longer present in 2.6. Report this as 0, + * to maintain consistent behavior with normal procps. + */ + if (fscanf(fp, "MemShared: %lu %s\n", &shared, buf) != 2) + shared = 0; + + fscanf(fp, "Buffers: %lu %s\n", &buffers, buf); + fscanf(fp, "Cached: %lu %s\n", &cached, buf); + + used = total - mfree; + } + fclose(fp); + + /* read load average as a string */ + buf[0] = '\0'; + open_read_close("loadavg", buf, sizeof(buf)); + end = strchr(buf, ' '); + if (end) end = strchr(end+1, ' '); + if (end) end = strchr(end+1, ' '); + if (end) *end = '\0'; + + if (needs_conversion) { + /* convert to kilobytes */ + used /= 1024; + mfree /= 1024; + shared /= 1024; + buffers /= 1024; + cached /= 1024; + total /= 1024; + } + + /* output memory info and load average */ + /* clear screen & go to top */ + if (scr_width > sizeof(scrbuf)) + scr_width = sizeof(scrbuf); + snprintf(scrbuf, scr_width, + "Mem: %ldK used, %ldK free, %ldK shrd, %ldK buff, %ldK cached", + used, mfree, shared, buffers, cached); + + printf(OPT_BATCH_MODE ? "%s\n" : "\e[H\e[J%s\n", scrbuf); + + snprintf(scrbuf, scr_width, "Load average: %s", buf); + printf("%s\n", scrbuf); + + return total; +} + + +/* display process statuses */ +static void display_status(int count, int scr_width) +{ + enum { + bits_per_int = sizeof(int)*8 + }; + + top_status_t *s = top; + char rss_str_buf[8]; + unsigned long total_memory = display_generic(scr_width); /* or use total_rss? */ + unsigned pmem_shift, pmem_scale; + +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + unsigned pcpu_shift, pcpu_scale; + unsigned busy_jifs; + + /* what info of the processes is shown */ + printf(OPT_BATCH_MODE ? "%.*s" : "\e[7m%.*s\e[0m", scr_width, + " PID USER STATUS RSS PPID %CPU %MEM COMMAND"); +#define MIN_WIDTH \ + sizeof( " PID USER STATUS RSS PPID %CPU %MEM C") +#else + printf(OPT_BATCH_MODE ? "%.*s" : "\e[7m%.*s\e[0m", scr_width, + " PID USER STATUS RSS PPID %MEM COMMAND"); +#define MIN_WIDTH \ + sizeof( " PID USER STATUS RSS PPID %MEM C") +#endif + + /* + * MEM% = s->rss/MemTotal + */ + pmem_shift = bits_per_int-11; + pmem_scale = 1000*(1U<<(bits_per_int-11)) / total_memory; + /* s->rss is in kb. we want (s->rss * pmem_scale) to never overflow */ + while (pmem_scale >= 512) { + pmem_scale /= 4; + pmem_shift -= 2; + } +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + busy_jifs = jif.busy - prev_jif.busy; + /* This happens if there were lots of short-lived processes + * between two top updates (e.g. compilation) */ + if (total_pcpu < busy_jifs) total_pcpu = busy_jifs; + + /* + * CPU% = s->pcpu/sum(s->pcpu) * busy_cpu_ticks/total_cpu_ticks + * (pcpu is delta of sys+user time between samples) + */ + /* (jif.xxx - prev_jif.xxx) and s->pcpu are + * in 0..~64000 range (HZ*update_interval). + * we assume that unsigned is at least 32-bit. + */ + pcpu_shift = 6; + pcpu_scale = (1000*64*(uint16_t)busy_jifs ? : 1); + while (pcpu_scale < (1U<<(bits_per_int-2))) { + pcpu_scale *= 4; + pcpu_shift += 2; + } + pcpu_scale /= ( (uint16_t)(jif.total-prev_jif.total)*total_pcpu ? : 1); + /* we want (s->pcpu * pcpu_scale) to never overflow */ + while (pcpu_scale >= 1024) { + pcpu_scale /= 4; + pcpu_shift -= 2; + } + /* printf(" pmem_scale=%u pcpu_scale=%u ", pmem_scale, pcpu_scale); */ +#endif + while (count-- > 0) { + div_t pmem = div((s->rss*pmem_scale) >> pmem_shift, 10); + int col = scr_width+1; + USE_FEATURE_TOP_CPU_USAGE_PERCENTAGE(div_t pcpu;) + + if (s->rss >= 100*1024) + sprintf(rss_str_buf, "%6ldM", s->rss/1024); + else + sprintf(rss_str_buf, "%7ld", s->rss); + USE_FEATURE_TOP_CPU_USAGE_PERCENTAGE( + pcpu = div((s->pcpu*pcpu_scale) >> pcpu_shift, 10); + ) + col -= printf("\n%5u %-8s %s " + "%s%6u" + USE_FEATURE_TOP_CPU_USAGE_PERCENTAGE("%3u.%c") + "%3u.%c ", + s->pid, get_cached_username(s->uid), s->state, + rss_str_buf, s->ppid, + USE_FEATURE_TOP_CPU_USAGE_PERCENTAGE(pcpu.quot, '0'+pcpu.rem,) + pmem.quot, '0'+pmem.rem); + if (col > 0) + printf("%.*s", col, s->comm); + /* printf(" %d/%d %lld/%lld", s->pcpu, total_pcpu, + jif.busy - prev_jif.busy, jif.total - prev_jif.total); */ + s++; + } + /* printf(" %d", hist_iterations); */ + putchar(OPT_BATCH_MODE ? '\n' : '\r'); + fflush(stdout); +} + + +static void clearmems(void) +{ + clear_username_cache(); + free(top); + top = 0; + ntop = 0; +} + + +#if ENABLE_FEATURE_USE_TERMIOS +#include +#include + +static struct termios initial_settings; + +static void reset_term(void) +{ + tcsetattr(0, TCSANOW, (void *) &initial_settings); +#if ENABLE_FEATURE_CLEAN_UP + clearmems(); +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + free(prev_hist); +#endif +#endif /* FEATURE_CLEAN_UP */ +} + +static void sig_catcher(int sig ATTRIBUTE_UNUSED) +{ + reset_term(); + exit(1); +} +#endif /* FEATURE_USE_TERMIOS */ + + +int top_main(int argc, char **argv) +{ + int count, lines, col; + unsigned interval = 5; /* default update rate is 5 seconds */ + unsigned iterations = UINT_MAX; /* 2^32 iterations by default :) */ + char *sinterval, *siterations; +#if ENABLE_FEATURE_USE_TERMIOS + struct termios new_settings; + struct timeval tv; + fd_set readfds; + unsigned char c; +#endif /* FEATURE_USE_TERMIOS */ + + /* do normal option parsing */ + interval = 5; + opt_complementary = "-"; + getopt32(argc, argv, "d:n:b", &sinterval, &siterations); + if (option_mask32 & 0x1) interval = xatou(sinterval); // -d + if (option_mask32 & 0x2) iterations = xatou(siterations); // -n + //if (option_mask32 & 0x4) // -b + + /* change to /proc */ + xchdir("/proc"); +#if ENABLE_FEATURE_USE_TERMIOS + tcgetattr(0, (void *) &initial_settings); + memcpy(&new_settings, &initial_settings, sizeof(struct termios)); + /* unbuffered input, turn off echo */ + new_settings.c_lflag &= ~(ISIG | ICANON | ECHO | ECHONL); + + signal(SIGTERM, sig_catcher); + signal(SIGINT, sig_catcher); + tcsetattr(0, TCSANOW, (void *) &new_settings); + atexit(reset_term); +#endif /* FEATURE_USE_TERMIOS */ + +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + sort_function[0] = pcpu_sort; + sort_function[1] = mem_sort; + sort_function[2] = time_sort; +#else + sort_function = mem_sort; +#endif /* FEATURE_TOP_CPU_USAGE_PERCENTAGE */ + + while (1) { + procps_status_t *p = NULL; + + /* Default to 25 lines - 5 lines for status */ + lines = 24 - 3; + col = 79; +#if ENABLE_FEATURE_USE_TERMIOS + get_terminal_width_height(0, &col, &lines); + if (lines < 5 || col < MIN_WIDTH) { + sleep(interval); + continue; + } + lines -= 3; +#endif /* FEATURE_USE_TERMIOS */ + + /* read process IDs & status for all the processes */ + while ((p = procps_scan(p, 0 + | PSSCAN_PID + | PSSCAN_PPID + | PSSCAN_RSS + | PSSCAN_STIME + | PSSCAN_UTIME + | PSSCAN_STATE + | PSSCAN_COMM + | PSSCAN_SID + | PSSCAN_UIDGID + ))) { + int n = ntop; + top = xrealloc(top, (++ntop)*sizeof(top_status_t)); + top[n].pid = p->pid; + top[n].ppid = p->ppid; + top[n].rss = p->rss; + top[n].ticks = p->stime + p->utime; + top[n].uid = p->uid; + strcpy(top[n].state, p->state); + strcpy(top[n].comm, p->comm); + } + if (ntop == 0) { + bb_error_msg_and_die("can't find process info in /proc"); + } +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + if (!prev_hist_count) { + do_stats(); + sleep(1); + clearmems(); + continue; + } + do_stats(); + qsort(top, ntop, sizeof(top_status_t), (void*)mult_lvl_cmp); +#else + qsort(top, ntop, sizeof(top_status_t), (void*)sort_function); +#endif /* FEATURE_TOP_CPU_USAGE_PERCENTAGE */ + count = lines; + if (OPT_BATCH_MODE || count > ntop) { + count = ntop; + } + /* show status for each of the processes */ + display_status(count, col); +#if ENABLE_FEATURE_USE_TERMIOS + tv.tv_sec = interval; + tv.tv_usec = 0; + FD_ZERO(&readfds); + FD_SET(0, &readfds); + select(1, &readfds, NULL, NULL, &tv); + if (FD_ISSET(0, &readfds)) { + if (read(0, &c, 1) <= 0) { /* signal */ + return EXIT_FAILURE; + } + if (c == 'q' || c == initial_settings.c_cc[VINTR]) + break; + if (c == 'M') { +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + sort_function[0] = mem_sort; + sort_function[1] = pcpu_sort; + sort_function[2] = time_sort; +#else + sort_function = mem_sort; +#endif + } +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + if (c == 'P') { + sort_function[0] = pcpu_sort; + sort_function[1] = mem_sort; + sort_function[2] = time_sort; + } + if (c == 'T') { + sort_function[0] = time_sort; + sort_function[1] = mem_sort; + sort_function[2] = pcpu_sort; + } +#endif + if (c == 'N') { +#if ENABLE_FEATURE_TOP_CPU_USAGE_PERCENTAGE + sort_function[0] = pid_sort; +#else + sort_function = pid_sort; +#endif + } + } + if (!--iterations) + break; +#else + sleep(interval); +#endif /* FEATURE_USE_TERMIOS */ + clearmems(); + } + if (ENABLE_FEATURE_CLEAN_UP) + clearmems(); + putchar('\n'); + return EXIT_SUCCESS; +} diff --git a/procps/uptime.c b/procps/uptime.c new file mode 100644 index 000000000..37c9d449a --- /dev/null +++ b/procps/uptime.c @@ -0,0 +1,59 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini uptime implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen + * + * Licensed under the GPL version 2, see the file LICENSE in this tarball. + */ + +/* This version of uptime doesn't display the number of users on the system, + * since busybox init doesn't mess with utmp. For folks using utmp that are + * just dying to have # of users reported, feel free to write it as some type + * of CONFIG_FEATURE_UTMP_SUPPORT #define + */ + +/* getopt not needed */ + +#include "busybox.h" + +#ifndef FSHIFT +# define FSHIFT 16 /* nr of bits of precision */ +#endif +#define FIXED_1 (1<> FSHIFT) +#define LOAD_FRAC(x) LOAD_INT(((x) & (FIXED_1-1)) * 100) + + +int uptime_main(int argc, char **argv) +{ + int updays, uphours, upminutes; + struct sysinfo info; + struct tm *current_time; + time_t current_secs; + + time(¤t_secs); + current_time = localtime(¤t_secs); + + sysinfo(&info); + + printf(" %02d:%02d:%02d up ", + current_time->tm_hour, current_time->tm_min, current_time->tm_sec); + updays = (int) info.uptime / (60*60*24); + if (updays) + printf("%d day%s, ", updays, (updays != 1) ? "s" : ""); + upminutes = (int) info.uptime / 60; + uphours = (upminutes / 60) % 24; + upminutes %= 60; + if(uphours) + printf("%2d:%02d, ", uphours, upminutes); + else + printf("%d min, ", upminutes); + + printf("load average: %ld.%02ld, %ld.%02ld, %ld.%02ld\n", + LOAD_INT(info.loads[0]), LOAD_FRAC(info.loads[0]), + LOAD_INT(info.loads[1]), LOAD_FRAC(info.loads[1]), + LOAD_INT(info.loads[2]), LOAD_FRAC(info.loads[2])); + + return EXIT_SUCCESS; +} diff --git a/runit/Config.in b/runit/Config.in new file mode 100644 index 000000000..8a7deea72 --- /dev/null +++ b/runit/Config.in @@ -0,0 +1,66 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Runit Utilities" + +config RUNSV + bool "runsv" + default n + help + runsv starts and monitors a service and optionally an appendant log + service. + +config RUNSVDIR + bool "runsvdir" + default n + help + runsvdir starts a runsv process for each subdirectory, or symlink to + a directory, in the services directory dir, up to a limit of 1000 + subdirectories, and restarts a runsv process if it terminates. + +config SV + bool "sv" + default n + help + sv reports the current status and controls the state of services + monitored by the runsv supervisor. + +config SVLOGD + bool "svlogd" + default n + help + svlogd continuously reads log data from its standard input, optionally + filters log messages, and writes the data to one or more automatically + rotated logs. + +config CHPST + bool "chpst" + default n + help + chpst changes the process state according to the given options, and + execs specified program. + +config SETUIDGID + bool "setuidgid" + help + Sets soft resource limits as specified by options + +config ENVUIDGID + bool "envuidgid" + help + Sets $UID to account's uid and $GID to account's gid + +config ENVDIR + bool "envdir" + help + Sets various environment variables as specified by files + in the given directory + +config SOFTLIMIT + bool "softlimit" + help + Sets soft resource limits as specified by options + +endmenu diff --git a/runit/Kbuild b/runit/Kbuild new file mode 100644 index 000000000..ad1706cb6 --- /dev/null +++ b/runit/Kbuild @@ -0,0 +1,12 @@ +# 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_RUNSV) += runsv.o runit_lib.o +lib-$(CONFIG_RUNSVDIR) += runsvdir.o runit_lib.o +lib-$(CONFIG_SV) += sv.o runit_lib.o +lib-$(CONFIG_SVLOGD) += svlogd.o runit_lib.o +lib-$(CONFIG_CHPST) += chpst.o diff --git a/runit/chpst.c b/runit/chpst.c new file mode 100644 index 000000000..f8e63031f --- /dev/null +++ b/runit/chpst.c @@ -0,0 +1,373 @@ +/* +Copyright (c) 2001-2006, Gerrit Pape +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* Busyboxed by Denis Vlasenko */ +/* Dependencies on runit_lib.c removed */ + +#include "busybox.h" + +#include + +// Must match constants in chpst_main! +#define OPT_verbose (option_mask32 & 0x2000) +#define OPT_pgrp (option_mask32 & 0x4000) +#define OPT_nostdin (option_mask32 & 0x8000) +#define OPT_nostdout (option_mask32 & 0x10000) +#define OPT_nostderr (option_mask32 & 0x20000) + +static char *set_user; +static char *env_user; +static const char *env_dir; +static long limitd = -2; +static long limits = -2; +static long limitl = -2; +static long limita = -2; +static long limito = -2; +static long limitp = -2; +static long limitf = -2; +static long limitc = -2; +static long limitr = -2; +static long limitt = -2; +static int nicelvl; +static const char *root; + +static void suidgid(char *user) +{ + struct bb_uidgid_t ugid; + + if (!uidgid_get(&ugid, user)) { + bb_error_msg_and_die("unknown user/group: %s", user); + } + if (setgroups(1, &ugid.gid) == -1) + bb_perror_msg_and_die("setgroups"); + xsetgid(ugid.gid); + xsetuid(ugid.uid); +} + +static void euidgid(char *user) +{ + struct bb_uidgid_t ugid; + + if (!uidgid_get(&ugid, user)) { + bb_error_msg_and_die("unknown user/group: %s", user); + } + xsetenv("GID", utoa(ugid.gid)); + xsetenv("UID", utoa(ugid.uid)); +} + +static void edir(const char *directory_name) +{ + int wdir; + DIR *dir; + struct dirent *d; + int fd; + + wdir = xopen(".", O_RDONLY | O_NDELAY); + xchdir(directory_name); + dir = opendir("."); + if (!dir) + bb_perror_msg_and_die("opendir %s", directory_name); + for (;;) { + errno = 0; + d = readdir(dir); + if (!d) { + if (errno) + bb_perror_msg_and_die("readdir %s", + directory_name); + break; + } + if (d->d_name[0] == '.') continue; + fd = open(d->d_name, O_RDONLY | O_NDELAY); + if (fd < 0) { + if ((errno == EISDIR) && env_dir) { + if (OPT_verbose) + bb_perror_msg("warning: %s/%s is a directory", + directory_name, d->d_name); + continue; + } else + bb_perror_msg_and_die("open %s/%s", + directory_name, d->d_name); + } + if (fd >= 0) { + char buf[256]; + char *tail; + int size; + + size = safe_read(fd, buf, sizeof(buf)-1); + if (size < 0) + bb_perror_msg_and_die("read %s/%s", + directory_name, d->d_name); + if (size == 0) { + unsetenv(d->d_name); + continue; + } + buf[size] = '\n'; + tail = memchr(buf, '\n', sizeof(buf)); + /* skip trailing whitespace */; + while (1) { + if (tail[0]==' ') tail[0] = '\0'; + if (tail[0]=='\t') tail[0] = '\0'; + if (tail[0]=='\n') tail[0] = '\0'; + if (tail == buf) break; + tail--; + } + xsetenv(d->d_name, buf); + } + } + closedir(dir); + if (fchdir(wdir) == -1) bb_perror_msg_and_die("fchdir"); + close(wdir); +} + +static void limit(int what, long l) +{ + struct rlimit r; + + if (getrlimit(what, &r) == -1) bb_perror_msg_and_die("getrlimit"); + if ((l < 0) || (l > r.rlim_max)) + r.rlim_cur = r.rlim_max; + else + r.rlim_cur = l; + if (setrlimit(what, &r) == -1) bb_perror_msg_and_die("setrlimit"); +} + +static void slimit(void) +{ + if (limitd >= -1) { +#ifdef RLIMIT_DATA + limit(RLIMIT_DATA, limitd); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_DATA"); +#endif + } + if (limits >= -1) { +#ifdef RLIMIT_STACK + limit(RLIMIT_STACK, limits); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_STACK"); +#endif + } + if (limitl >= -1) { +#ifdef RLIMIT_MEMLOCK + limit(RLIMIT_MEMLOCK, limitl); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_MEMLOCK"); +#endif + } + if (limita >= -1) { +#ifdef RLIMIT_VMEM + limit(RLIMIT_VMEM, limita); +#else +#ifdef RLIMIT_AS + limit(RLIMIT_AS, limita); +#else + if (OPT_verbose) + bb_error_msg("system does not support %s", + "RLIMIT_VMEM"); +#endif +#endif + } + if (limito >= -1) { +#ifdef RLIMIT_NOFILE + limit(RLIMIT_NOFILE, limito); +#else +#ifdef RLIMIT_OFILE + limit(RLIMIT_OFILE, limito); +#else + if (OPT_verbose) + bb_error_msg("system does not support %s", + "RLIMIT_NOFILE"); +#endif +#endif + } + if (limitp >= -1) { +#ifdef RLIMIT_NPROC + limit(RLIMIT_NPROC, limitp); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_NPROC"); +#endif + } + if (limitf >= -1) { +#ifdef RLIMIT_FSIZE + limit(RLIMIT_FSIZE, limitf); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_FSIZE"); +#endif + } + if (limitc >= -1) { +#ifdef RLIMIT_CORE + limit(RLIMIT_CORE, limitc); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_CORE"); +#endif + } + if (limitr >= -1) { +#ifdef RLIMIT_RSS + limit(RLIMIT_RSS, limitr); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_RSS"); +#endif + } + if (limitt >= -1) { +#ifdef RLIMIT_CPU + limit(RLIMIT_CPU, limitt); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_CPU"); +#endif + } +} + +/* argv[0] */ +static void setuidgid(int, char **); +static void envuidgid(int, char **); +static void envdir(int, char **); +static void softlimit(int, char **); + +int chpst_main(int argc, char **argv) +{ + if (applet_name[3] == 'd') envdir(argc, argv); + if (applet_name[1] == 'o') softlimit(argc, argv); + if (applet_name[0] == 's') setuidgid(argc, argv); + if (applet_name[0] == 'e') envuidgid(argc, argv); + // otherwise we are chpst + + { + char *m,*d,*o,*p,*f,*c,*r,*t,*n; + getopt32(argc, argv, "+u:U:e:m:d:o:p:f:c:r:t:/:n:vP012", + &set_user,&env_user,&env_dir, + &m,&d,&o,&p,&f,&c,&r,&t,&root,&n); + // if (option_mask32 & 0x1) // -u + // if (option_mask32 & 0x2) // -U + // if (option_mask32 & 0x4) // -e + if (option_mask32 & 0x8) limits = limitl = limita = limitd = xatoul(m); // -m + if (option_mask32 & 0x10) limitd = xatoul(d); // -d + if (option_mask32 & 0x20) limito = xatoul(o); // -o + if (option_mask32 & 0x40) limitp = xatoul(p); // -p + if (option_mask32 & 0x80) limitf = xatoul(f); // -f + if (option_mask32 & 0x100) limitc = xatoul(c); // -c + if (option_mask32 & 0x200) limitr = xatoul(r); // -r + if (option_mask32 & 0x400) limitt = xatoul(t); // -t + // if (option_mask32 & 0x800) // -/ + if (option_mask32 & 0x1000) nicelvl = xatoi(n); // -n + // The below consts should match #defines at top! + //if (option_mask32 & 0x2000) OPT_verbose = 1; // -v + //if (option_mask32 & 0x4000) OPT_pgrp = 1; // -P + //if (option_mask32 & 0x8000) OPT_nostdin = 1; // -0 + //if (option_mask32 & 0x10000) OPT_nostdout = 1; // -1 + //if (option_mask32 & 0x20000) OPT_nostderr = 1; // -2 + } + argv += optind; + if (!argv || !*argv) bb_show_usage(); + + if (OPT_pgrp) setsid(); + if (env_dir) edir(env_dir); + if (root) { + xchdir(root); + if (chroot(".") == -1) + bb_perror_msg_and_die("chroot"); + } + slimit(); + if (nicelvl) { + errno = 0; + if (nice(nicelvl) == -1) + bb_perror_msg_and_die("nice"); + } + if (env_user) euidgid(env_user); + if (set_user) suidgid(set_user); + if (OPT_nostdin) close(0); + if (OPT_nostdout) close(1); + if (OPT_nostderr) close(2); + execvp(argv[0], argv); + bb_perror_msg_and_die("exec %s", argv[0]); +} + +static void setuidgid(int argc, char **argv) +{ + const char *account; + + account = *++argv; + if (!account) bb_show_usage(); + if (!*++argv) bb_show_usage(); + suidgid((char*)account); + execvp(argv[0], argv); + bb_perror_msg_and_die("exec %s", argv[0]); +} + +static void envuidgid(int argc, char **argv) +{ + const char *account; + + account = *++argv; + if (!account) bb_show_usage(); + if (!*++argv) bb_show_usage(); + euidgid((char*)account); + execvp(argv[0], argv); + bb_perror_msg_and_die("exec %s", argv[0]); +} + +static void envdir(int argc, char **argv) +{ + const char *dir; + + dir = *++argv; + if (!dir) bb_show_usage(); + if (!*++argv) bb_show_usage(); + edir(dir); + execvp(argv[0], argv); + bb_perror_msg_and_die("exec %s", argv[0]); +} + +static void softlimit(int argc, char **argv) +{ + char *a,*c,*d,*f,*l,*m,*o,*p,*r,*s,*t; + getopt32(argc, argv, "+a:c:d:f:l:m:o:p:r:s:t:", + &a,&c,&d,&f,&l,&m,&o,&p,&r,&s,&t); + if (option_mask32 & 0x001) limita = xatoul(a); // -a + if (option_mask32 & 0x002) limitc = xatoul(c); // -c + if (option_mask32 & 0x004) limitd = xatoul(d); // -d + if (option_mask32 & 0x008) limitf = xatoul(f); // -f + if (option_mask32 & 0x010) limitl = xatoul(l); // -l + if (option_mask32 & 0x020) limits = limitl = limita = limitd = xatoul(m); // -m + if (option_mask32 & 0x040) limito = xatoul(o); // -o + if (option_mask32 & 0x080) limitp = xatoul(p); // -p + if (option_mask32 & 0x100) limitr = xatoul(r); // -r + if (option_mask32 & 0x200) limits = xatoul(s); // -s + if (option_mask32 & 0x400) limitt = xatoul(t); // -t + argv += optind; + if (!argv[0]) bb_show_usage(); + slimit(); + execvp(argv[0], argv); + bb_perror_msg_and_die("exec %s", argv[0]); +} diff --git a/runit/runit_lib.c b/runit/runit_lib.c new file mode 100644 index 000000000..5ebbc5840 --- /dev/null +++ b/runit/runit_lib.c @@ -0,0 +1,982 @@ +/* +Copyright (c) 2001-2006, Gerrit Pape +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* Busyboxed by Denis Vlasenko */ +/* Collected into one file from runit's many tiny files */ +/* TODO: review, eliminate unneeded stuff, move good stuff to libbb */ + +#include +#include +#include "libbb.h" +#include "runit_lib.h" + +/*** buffer.c ***/ + +void buffer_init(buffer *s,int (*op)(int fd,char *buf,unsigned len),int fd,char *buf,unsigned len) +{ + s->x = buf; + s->fd = fd; + s->op = op; + s->p = 0; + s->n = len; +} + + +/*** buffer_get.c ***/ + +static int oneread(int (*op)(int fd,char *buf,unsigned len),int fd,char *buf,unsigned len) +{ + int r; + + for (;;) { + r = op(fd,buf,len); + if (r == -1) if (errno == EINTR) continue; + return r; + } +} + +static int getthis(buffer *s,char *buf,unsigned len) +{ + if (len > s->p) len = s->p; + s->p -= len; + memcpy(buf,s->x + s->n,len); + s->n += len; + return len; +} + +int buffer_feed(buffer *s) +{ + int r; + + if (s->p) return s->p; + r = oneread(s->op,s->fd,s->x,s->n); + if (r <= 0) return r; + s->p = r; + s->n -= r; + if (s->n > 0) memmove(s->x + s->n,s->x,r); + return r; +} + +int buffer_bget(buffer *s,char *buf,unsigned len) +{ + int r; + + if (s->p > 0) return getthis(s,buf,len); + if (s->n <= len) return oneread(s->op,s->fd,buf,s->n); + r = buffer_feed(s); if (r <= 0) return r; + return getthis(s,buf,len); +} + +int buffer_get(buffer *s,char *buf,unsigned len) +{ + int r; + + if (s->p > 0) return getthis(s,buf,len); + if (s->n <= len) return oneread(s->op,s->fd,buf,len); + r = buffer_feed(s); if (r <= 0) return r; + return getthis(s,buf,len); +} + +char *buffer_peek(buffer *s) +{ + return s->x + s->n; +} + +void buffer_seek(buffer *s,unsigned len) +{ + s->n += len; + s->p -= len; +} + + +/*** buffer_put.c ***/ + +static int allwrite(int (*op)(int fd,char *buf,unsigned len),int fd,const char *buf,unsigned len) +{ + int w; + + while (len) { + w = op(fd,(char*)buf,len); + if (w == -1) { + if (errno == EINTR) continue; + return -1; /* note that some data may have been written */ + } + if (w == 0) ; /* luser's fault */ + buf += w; + len -= w; + } + return 0; +} + +int buffer_flush(buffer *s) +{ + int p; + + p = s->p; + if (!p) return 0; + s->p = 0; + return allwrite(s->op,s->fd,s->x,p); +} + +int buffer_putalign(buffer *s,const char *buf,unsigned len) +{ + unsigned n; + + while (len > (n = s->n - s->p)) { + memcpy(s->x + s->p,buf,n); + s->p += n; + buf += n; + len -= n; + if (buffer_flush(s) == -1) return -1; + } + /* now len <= s->n - s->p */ + memcpy(s->x + s->p,buf,len); + s->p += len; + return 0; +} + +int buffer_put(buffer *s,const char *buf,unsigned len) +{ + unsigned n; + + n = s->n; + if (len > n - s->p) { + if (buffer_flush(s) == -1) return -1; + /* now s->p == 0 */ + if (n < BUFFER_OUTSIZE) n = BUFFER_OUTSIZE; + while (len > s->n) { + if (n > len) n = len; + if (allwrite(s->op,s->fd,buf,n) == -1) return -1; + buf += n; + len -= n; + } + } + /* now len <= s->n - s->p */ + memcpy(s->x + s->p,buf,len); + s->p += len; + return 0; +} + +int buffer_putflush(buffer *s,const char *buf,unsigned len) +{ + if (buffer_flush(s) == -1) return -1; + return allwrite(s->op,s->fd,buf,len); +} + +int buffer_putsalign(buffer *s,const char *buf) +{ + return buffer_putalign(s,buf,strlen(buf)); +} + +int buffer_puts(buffer *s,const char *buf) +{ + return buffer_put(s,buf,strlen(buf)); +} + +int buffer_putsflush(buffer *s,const char *buf) +{ + return buffer_putflush(s,buf,strlen(buf)); +} + + +/*** buffer_read.c ***/ + +int buffer_unixread(int fd,char *buf,unsigned len) +{ + return read(fd,buf,len); +} + + +/*** buffer_write.c ***/ + +int buffer_unixwrite(int fd,char *buf,unsigned len) +{ + return write(fd,buf,len); +} + + +/*** byte_chr.c ***/ + +unsigned byte_chr(char *s,unsigned n,int c) +{ + char ch; + char *t; + + ch = c; + t = s; + for (;;) { + if (!n) break; if (*t == ch) break; ++t; --n; + if (!n) break; if (*t == ch) break; ++t; --n; + if (!n) break; if (*t == ch) break; ++t; --n; + if (!n) break; if (*t == ch) break; ++t; --n; + } + return t - s; +} + + +/*** coe.c ***/ + +int coe(int fd) +{ + return fcntl(fd,F_SETFD,FD_CLOEXEC); +} + + +/*** fd_copy.c ***/ + +int fd_copy(int to,int from) +{ + if (to == from) return 0; + if (fcntl(from,F_GETFL,0) == -1) return -1; + close(to); + if (fcntl(from,F_DUPFD,to) == -1) return -1; + return 0; +} + + +/*** fd_move.c ***/ + +int fd_move(int to,int from) +{ + if (to == from) return 0; + if (fd_copy(to,from) == -1) return -1; + close(from); + return 0; +} + + +/*** fifo.c ***/ + +int fifo_make(const char *fn,int mode) { return mkfifo(fn,mode); } + + +/*** fmt_ptime.c ***/ + +unsigned fmt_ptime(char *s, struct taia *ta) { + struct tm *t; + unsigned long u; + + if (ta->sec.x < 4611686018427387914ULL) return 0; /* impossible? */ + u = ta->sec.x -4611686018427387914ULL; + if (!(t = gmtime((time_t*)&u))) return 0; + fmt_ulong(s, 1900 + t->tm_year); + s[4] = '-'; fmt_uint0(&s[5], t->tm_mon+1, 2); + s[7] = '-'; fmt_uint0(&s[8], t->tm_mday, 2); + s[10] = '_'; fmt_uint0(&s[11], t->tm_hour, 2); + s[13] = ':'; fmt_uint0(&s[14], t->tm_min, 2); + s[16] = ':'; fmt_uint0(&s[17], t->tm_sec, 2); + s[19] = '.'; fmt_uint0(&s[20], ta->nano, 9); + return 25; +} + +unsigned fmt_taia(char *s, struct taia *t) { + static char hex[16] = "0123456789abcdef"; + static char pack[TAIA_PACK]; + int i; + + taia_pack(pack, t); + s[0] = '@'; + for (i = 0; i < 12; ++i) { + s[i*2+1] = hex[(pack[i] >> 4) &15]; + s[i*2+2] = hex[pack[i] &15]; + } + return 25; +} + + +/*** fmt_uint.c ***/ + +unsigned fmt_uint(char *s,unsigned u) +{ + return fmt_ulong(s,u); +} + + +/*** fmt_uint0.c ***/ + +unsigned fmt_uint0(char *s,unsigned u,unsigned n) +{ + unsigned len; + len = fmt_uint(FMT_LEN,u); + while (len < n) { if (s) *s++ = '0'; ++len; } + if (s) fmt_uint(s,u); + return len; +} + + +/*** fmt_ulong.c ***/ + +unsigned fmt_ulong(char *s,unsigned long u) +{ + unsigned len; unsigned long q; + len = 1; q = u; + while (q > 9) { ++len; q /= 10; } + if (s) { + s += len; + do { *--s = '0' + (u % 10); u /= 10; } while(u); /* handles u == 0 */ + } + return len; +} + + +/*** tai_now.c ***/ + +void tai_now(struct tai *t) +{ + tai_unix(t,time((time_t *) 0)); +} + + +/*** tai_pack.c ***/ + +void tai_pack(char *s,const struct tai *t) +{ + uint64_t x; + + x = t->x; + s[7] = x & 255; x >>= 8; + s[6] = x & 255; x >>= 8; + s[5] = x & 255; x >>= 8; + s[4] = x & 255; x >>= 8; + s[3] = x & 255; x >>= 8; + s[2] = x & 255; x >>= 8; + s[1] = x & 255; x >>= 8; + s[0] = x; +} + + +/*** tai_sub.c ***/ + +void tai_sub(struct tai *t,const struct tai *u,const struct tai *v) +{ + t->x = u->x - v->x; +} + + +/*** tai_unpack.c ***/ + +void tai_unpack(const char *s,struct tai *t) +{ + uint64_t x; + + x = (unsigned char) s[0]; + x <<= 8; x += (unsigned char) s[1]; + x <<= 8; x += (unsigned char) s[2]; + x <<= 8; x += (unsigned char) s[3]; + x <<= 8; x += (unsigned char) s[4]; + x <<= 8; x += (unsigned char) s[5]; + x <<= 8; x += (unsigned char) s[6]; + x <<= 8; x += (unsigned char) s[7]; + t->x = x; +} + + +/*** taia_add.c ***/ + +/* XXX: breaks tai encapsulation */ + +void taia_add(struct taia *t,const struct taia *u,const struct taia *v) +{ + t->sec.x = u->sec.x + v->sec.x; + t->nano = u->nano + v->nano; + t->atto = u->atto + v->atto; + if (t->atto > 999999999UL) { + t->atto -= 1000000000UL; + ++t->nano; + } + if (t->nano > 999999999UL) { + t->nano -= 1000000000UL; + ++t->sec.x; + } +} + + +/*** taia_approx.c ***/ + +double taia_approx(const struct taia *t) +{ + return tai_approx(&t->sec) + taia_frac(t); +} + + +/*** taia_frac.c ***/ + +double taia_frac(const struct taia *t) +{ + return (t->atto * 0.000000001 + t->nano) * 0.000000001; +} + + +/*** taia_less.c ***/ + +/* XXX: breaks tai encapsulation */ + +int taia_less(const struct taia *t,const struct taia *u) +{ + if (t->sec.x < u->sec.x) return 1; + if (t->sec.x > u->sec.x) return 0; + if (t->nano < u->nano) return 1; + if (t->nano > u->nano) return 0; + return t->atto < u->atto; +} + + +/*** taia_now.c ***/ + +void taia_now(struct taia *t) +{ + struct timeval now; + gettimeofday(&now,(struct timezone *) 0); + tai_unix(&t->sec,now.tv_sec); + t->nano = 1000 * now.tv_usec + 500; + t->atto = 0; +} + + +/*** taia_pack.c ***/ + +void taia_pack(char *s,const struct taia *t) +{ + unsigned long x; + + tai_pack(s,&t->sec); + s += 8; + + x = t->atto; + s[7] = x & 255; x >>= 8; + s[6] = x & 255; x >>= 8; + s[5] = x & 255; x >>= 8; + s[4] = x; + x = t->nano; + s[3] = x & 255; x >>= 8; + s[2] = x & 255; x >>= 8; + s[1] = x & 255; x >>= 8; + s[0] = x; +} + + +/*** taia_sub.c ***/ + +/* XXX: breaks tai encapsulation */ + +void taia_sub(struct taia *t,const struct taia *u,const struct taia *v) +{ + unsigned long unano = u->nano; + unsigned long uatto = u->atto; + + t->sec.x = u->sec.x - v->sec.x; + t->nano = unano - v->nano; + t->atto = uatto - v->atto; + if (t->atto > uatto) { + t->atto += 1000000000UL; + --t->nano; + } + if (t->nano > unano) { + t->nano += 1000000000UL; + --t->sec.x; + } +} + + +/*** taia_uint.c ***/ + +/* XXX: breaks tai encapsulation */ + +void taia_uint(struct taia *t,unsigned s) +{ + t->sec.x = s; + t->nano = 0; + t->atto = 0; +} + + +/*** stralloc_cat.c ***/ +#if 0 + +int stralloc_cat(stralloc *sato,const stralloc *safrom) +{ + return stralloc_catb(sato,safrom->s,safrom->len); +} + + +/*** stralloc_catb.c ***/ + +int stralloc_catb(stralloc *sa,const char *s,unsigned n) +{ + if (!sa->s) return stralloc_copyb(sa,s,n); + if (!stralloc_readyplus(sa,n + 1)) return 0; + memcpy(sa->s + sa->len,s,n); + sa->len += n; + sa->s[sa->len] = 'Z'; /* ``offensive programming'' */ + return 1; +} + + +/*** stralloc_cats.c ***/ + +int stralloc_cats(stralloc *sa,const char *s) +{ + return stralloc_catb(sa,s,strlen(s)); +} + + +/*** stralloc_eady.c ***/ + +GEN_ALLOC_ready(stralloc,char,s,len,a,i,n,x,30,stralloc_ready) +GEN_ALLOC_readyplus(stralloc,char,s,len,a,i,n,x,30,stralloc_readyplus) + + +/*** stralloc_opyb.c ***/ + +int stralloc_copyb(stralloc *sa,const char *s,unsigned n) +{ + if (!stralloc_ready(sa,n + 1)) return 0; + memcpy(sa->s,s,n); + sa->len = n; + sa->s[n] = 'Z'; /* ``offensive programming'' */ + return 1; +} + + +/*** stralloc_opys.c ***/ + +int stralloc_copys(stralloc *sa,const char *s) +{ + return stralloc_copyb(sa,s,strlen(s)); +} + + +/*** stralloc_pend.c ***/ + +GEN_ALLOC_append(stralloc,char,s,len,a,i,n,x,30,stralloc_readyplus,stralloc_append) + +#endif /* stralloc */ + +/*** iopause.c ***/ + +void iopause(iopause_fd *x,unsigned len,struct taia *deadline,struct taia *stamp) +{ + struct taia t; + int millisecs; + double d; + int i; + + if (taia_less(deadline,stamp)) + millisecs = 0; + else { + t = *stamp; + taia_sub(&t,deadline,&t); + d = taia_approx(&t); + if (d > 1000.0) d = 1000.0; + millisecs = d * 1000.0 + 20.0; + } + + for (i = 0;i < len;++i) + x[i].revents = 0; + + poll(x,len,millisecs); + /* XXX: some kernels apparently need x[0] even if len is 0 */ + /* XXX: how to handle EAGAIN? are kernels really this dumb? */ + /* XXX: how to handle EINVAL? when exactly can this happen? */ +} + + +/*** lock_ex.c ***/ + +int lock_ex(int fd) +{ + return flock(fd,LOCK_EX); +} + + +/*** lock_exnb.c ***/ + +int lock_exnb(int fd) +{ + return flock(fd,LOCK_EX | LOCK_NB); +} + + +/*** open_append.c ***/ + +int open_append(const char *fn) +{ + return open(fn, O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600); +} + + +/*** open_read.c ***/ + +int open_read(const char *fn) +{ + return open(fn, O_RDONLY|O_NDELAY); +} + + +/*** open_trunc.c ***/ + +int open_trunc(const char *fn) +{ + return open(fn,O_WRONLY | O_NDELAY | O_TRUNC | O_CREAT,0644); +} + + +/*** open_write.c ***/ + +int open_write(const char *fn) +{ + return open(fn, O_WRONLY|O_NDELAY); +} + + +/*** openreadclose.c ***/ +#if 0 +int openreadclose(const char *fn,stralloc *sa,unsigned bufsize) +{ + int fd; + fd = open_read(fn); + if (fd == -1) { + if (errno == ENOENT) return 0; + return -1; + } + if (readclose(fd,sa,bufsize) == -1) return -1; + return 1; +} +#endif + + +/*** pathexec_env.c ***/ +#if 0 +static stralloc plus; +static stralloc tmp; + +int pathexec_env(const char *s,const char *t) +{ + if (!s) return 1; + if (!stralloc_copys(&tmp,s)) return 0; + if (t) { + if (!stralloc_cats(&tmp,"=")) return 0; + if (!stralloc_cats(&tmp,t)) return 0; + } + if (!stralloc_0(&tmp)) return 0; + return stralloc_cat(&plus,&tmp); +} + +void pathexec(char **argv) +{ + char **e; + unsigned elen; + unsigned i; + unsigned j; + unsigned split; + unsigned t; + + if (!stralloc_cats(&plus,"")) return; + + elen = 0; + for (i = 0;environ[i];++i) + ++elen; + for (i = 0;i < plus.len;++i) + if (!plus.s[i]) + ++elen; + + e = malloc((elen + 1) * sizeof(char *)); + if (!e) return; + + elen = 0; + for (i = 0;environ[i];++i) + e[elen++] = environ[i]; + + j = 0; + for (i = 0;i < plus.len;++i) + if (!plus.s[i]) { + split = str_chr(plus.s + j,'='); + for (t = 0;t < elen;++t) + if (memcmp(plus.s + j,e[t],split) == 0) + if (e[t][split] == '=') { + --elen; + e[t] = e[elen]; + break; + } + if (plus.s[j + split]) + e[elen++] = plus.s + j; + j = i + 1; + } + e[elen] = 0; + + pathexec_run(*argv,argv,e); + free(e); +} +#endif + +/*** pathexec_run.c ***/ +#if 0 +static stralloc tmp; + +void pathexec_run(const char *file,char *const *argv,char *const *envp) +{ + const char *path; + unsigned split; + int savederrno; + + if (file[str_chr(file,'/')]) { + execve(file,argv,envp); + return; + } + + path = getenv("PATH"); + if (!path) path = "/bin:/usr/bin"; + + savederrno = 0; + for (;;) { + split = str_chr(path,':'); + if (!stralloc_copyb(&tmp,path,split)) return; + if (!split) + if (!stralloc_cats(&tmp,".")) return; + if (!stralloc_cats(&tmp,"/")) return; + if (!stralloc_cats(&tmp,file)) return; + if (!stralloc_0(&tmp)) return; + + execve(tmp.s,argv,envp); + if (errno != ENOENT) { + savederrno = errno; + if ((errno != EACCES) && (errno != EPERM) && (errno != EISDIR)) return; + } + + if (!path[split]) { + if (savederrno) errno = savederrno; + return; + } + path += split; + path += 1; + } +} +#endif + +/*** pmatch.c ***/ + +unsigned pmatch(const char *p, const char *s, unsigned len) { + for (;;) { + char c = *p++; + if (!c) return !len; + switch (c) { + case '*': + if (!(c = *p)) return 1; + for (;;) { + if (!len) return 0; + if (*s == c) break; + ++s; --len; + } + continue; + case '+': + if ((c = *p++) != *s) return 0; + for (;;) { + if (!len) return 1; + if (*s != c) break; + ++s; --len; + } + continue; + /* + case '?': + if (*p == '?') { + if (*s != '?') return 0; + ++p; + } + ++s; --len; + continue; + */ + default: + if (!len) return 0; + if (*s != c) return 0; + ++s; --len; + continue; + } + } + return 0; +} + + +/*** prot.c ***/ + +int prot_gid(int gid) +{ + gid_t x = gid; + if (setgroups(1,&x) == -1) return -1; + return setgid(gid); /* _should_ be redundant, but on some systems it isn't */ +} + +int prot_uid(int uid) +{ + return setuid(uid); +} + + +/*** readclose.c ***/ +#if 0 +int readclose_append(int fd,stralloc *sa,unsigned bufsize) +{ + int r; + for (;;) { + if (!stralloc_readyplus(sa,bufsize)) { close(fd); return -1; } + r = read(fd,sa->s + sa->len,bufsize); + if (r == -1) if (errno == EINTR) continue; + if (r <= 0) { close(fd); return r; } + sa->len += r; + } +} + +int readclose(int fd,stralloc *sa,unsigned bufsize) +{ + if (!stralloc_copys(sa,"")) { close(fd); return -1; } + return readclose_append(fd,sa,bufsize); +} +#endif + +/*** scan_ulong.c ***/ + +unsigned scan_ulong(const char *s,unsigned long *u) +{ + unsigned pos = 0; + unsigned long result = 0; + unsigned long c; + while ((c = (unsigned long) (unsigned char) (s[pos] - '0')) < 10) { + result = result * 10 + c; + ++pos; + } + *u = result; + return pos; +} + + +/*** seek_set.c ***/ + +int seek_set(int fd,seek_pos pos) +{ + if (lseek(fd,(off_t) pos,SEEK_SET) == -1) return -1; return 0; +} + + +/*** sig.c ***/ + +int sig_alarm = SIGALRM; +int sig_child = SIGCHLD; +int sig_cont = SIGCONT; +int sig_hangup = SIGHUP; +int sig_int = SIGINT; +int sig_pipe = SIGPIPE; +int sig_term = SIGTERM; + +void (*sig_defaulthandler)(int) = SIG_DFL; +void (*sig_ignorehandler)(int) = SIG_IGN; + + +/*** sig_block.c ***/ + +void sig_block(int sig) +{ + sigset_t ss; + sigemptyset(&ss); + sigaddset(&ss,sig); + sigprocmask(SIG_BLOCK,&ss,(sigset_t *) 0); +} + +void sig_unblock(int sig) +{ + sigset_t ss; + sigemptyset(&ss); + sigaddset(&ss,sig); + sigprocmask(SIG_UNBLOCK,&ss,(sigset_t *) 0); +} + +void sig_blocknone(void) +{ + sigset_t ss; + sigemptyset(&ss); + sigprocmask(SIG_SETMASK,&ss,(sigset_t *) 0); +} + + +/*** sig_catch.c ***/ + +void sig_catch(int sig,void (*f)(int)) +{ + struct sigaction sa; + sa.sa_handler = f; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(sig,&sa,(struct sigaction *) 0); +} + + +/*** sig_pause.c ***/ + +void sig_pause(void) +{ + sigset_t ss; + sigemptyset(&ss); + sigsuspend(&ss); +} + + +/*** str_chr.c ***/ + +unsigned str_chr(const char *s,int c) +{ + char ch; + const char *t; + + ch = c; + t = s; + for (;;) { + if (!*t) break; if (*t == ch) break; ++t; + if (!*t) break; if (*t == ch) break; ++t; + if (!*t) break; if (*t == ch) break; ++t; + if (!*t) break; if (*t == ch) break; ++t; + } + return t - s; +} + + +/*** wait_nohang.c ***/ + +int wait_nohang(int *wstat) +{ + return waitpid(-1,wstat,WNOHANG); +} + + +/*** wait_pid.c ***/ + +int wait_pid(int *wstat, int pid) +{ + int r; + + do + r = waitpid(pid,wstat,0); + while ((r == -1) && (errno == EINTR)); + return r; +} diff --git a/runit/runit_lib.h b/runit/runit_lib.h new file mode 100644 index 000000000..1f911919d --- /dev/null +++ b/runit/runit_lib.h @@ -0,0 +1,403 @@ +/* +Copyright (c) 2001-2006, Gerrit Pape +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/*** buffer.h ***/ + +typedef struct buffer { + char *x; + unsigned p; + unsigned n; + int fd; + int (*op)(int fd,char *buf,unsigned len); +} buffer; + +#define BUFFER_INIT(op,fd,buf,len) { (buf), 0, (len), (fd), (op) } +#define BUFFER_INSIZE 8192 +#define BUFFER_OUTSIZE 8192 + +extern void buffer_init(buffer *,int (*)(int fd,char *buf,unsigned len),int,char *,unsigned); + +extern int buffer_flush(buffer *); +extern int buffer_put(buffer *,const char *,unsigned); +extern int buffer_putalign(buffer *,const char *,unsigned); +extern int buffer_putflush(buffer *,const char *,unsigned); +extern int buffer_puts(buffer *,const char *); +extern int buffer_putsalign(buffer *,const char *); +extern int buffer_putsflush(buffer *,const char *); + +#define buffer_PUTC(s,c) \ + ( ((s)->n != (s)->p) \ + ? ( (s)->x[(s)->p++] = (c), 0 ) \ + : buffer_put((s),&(c),1) \ + ) + +extern int buffer_get(buffer *,char *,unsigned); +extern int buffer_bget(buffer *,char *,unsigned); +extern int buffer_feed(buffer *); + +extern char *buffer_peek(buffer *); +extern void buffer_seek(buffer *,unsigned); + +#define buffer_PEEK(s) ( (s)->x + (s)->n ) +#define buffer_SEEK(s,len) ( ( (s)->p -= (len) ) , ( (s)->n += (len) ) ) + +#define buffer_GETC(s,c) \ + ( ((s)->p > 0) \ + ? ( *(c) = (s)->x[(s)->n], buffer_SEEK((s),1), 1 ) \ + : buffer_get((s),(c),1) \ + ) + +extern int buffer_copy(buffer *,buffer *); + +extern int buffer_unixread(int,char *,unsigned); +/* Actually, int buffer_unixwrite(int,const char *,unsigned), + but that 'const' will produce warnings... oh well */ +extern int buffer_unixwrite(int,char *,unsigned); + + +/*** byte.h ***/ + +extern unsigned byte_chr(char *s,unsigned n,int c); + + +/*** coe.h ***/ + +extern int coe(int); + + +/*** direntry.h ***/ + +#define direntry struct dirent + + +/*** fd.h ***/ + +extern int fd_copy(int,int); +extern int fd_move(int,int); + + +/*** fifo.h ***/ + +extern int fifo_make(const char *,int); + + +/*** fmt.h ***/ + +#define FMT_ULONG 40 /* enough space to hold 2^128 - 1 in decimal, plus \0 */ +#define FMT_LEN ((char *) 0) /* convenient abbreviation */ + +extern unsigned fmt_uint(char *,unsigned); +extern unsigned fmt_uint0(char *,unsigned,unsigned); +extern unsigned fmt_xint(char *,unsigned); +extern unsigned fmt_nbbint(char *,unsigned,unsigned,unsigned,unsigned); +extern unsigned fmt_ushort(char *,unsigned short); +extern unsigned fmt_xshort(char *,unsigned short); +extern unsigned fmt_nbbshort(char *,unsigned,unsigned,unsigned,unsigned short); +extern unsigned fmt_ulong(char *,unsigned long); +extern unsigned fmt_xlong(char *,unsigned long); +extern unsigned fmt_nbblong(char *,unsigned,unsigned,unsigned,unsigned long); + +extern unsigned fmt_plusminus(char *,int); +extern unsigned fmt_minus(char *,int); +extern unsigned fmt_0x(char *,int); + +extern unsigned fmt_str(char *,const char *); +extern unsigned fmt_strn(char *,const char *,unsigned); + + +/*** tai.h ***/ + +struct tai { + uint64_t x; +} ; + +#define tai_unix(t,u) ((void) ((t)->x = 4611686018427387914ULL + (uint64_t) (u))) + +extern void tai_now(struct tai *); + +#define tai_approx(t) ((double) ((t)->x)) + +extern void tai_add(struct tai *,const struct tai *,const struct tai *); +extern void tai_sub(struct tai *,const struct tai *,const struct tai *); +#define tai_less(t,u) ((t)->x < (u)->x) + +#define TAI_PACK 8 +extern void tai_pack(char *,const struct tai *); +extern void tai_unpack(const char *,struct tai *); + +extern void tai_uint(struct tai *,unsigned); + + +/*** taia.h ***/ + +struct taia { + struct tai sec; + unsigned long nano; /* 0...999999999 */ + unsigned long atto; /* 0...999999999 */ +} ; + +extern void taia_tai(const struct taia *,struct tai *); + +extern void taia_now(struct taia *); + +extern double taia_approx(const struct taia *); +extern double taia_frac(const struct taia *); + +extern void taia_add(struct taia *,const struct taia *,const struct taia *); +extern void taia_addsec(struct taia *,const struct taia *,int); +extern void taia_sub(struct taia *,const struct taia *,const struct taia *); +extern void taia_half(struct taia *,const struct taia *); +extern int taia_less(const struct taia *,const struct taia *); + +#define TAIA_PACK 16 +extern void taia_pack(char *,const struct taia *); +extern void taia_unpack(const char *,struct taia *); + +#define TAIA_FMTFRAC 19 +extern unsigned taia_fmtfrac(char *,const struct taia *); + +extern void taia_uint(struct taia *,unsigned); + + +/*** fmt_ptime.h ***/ + +#define FMT_PTIME 30 + +extern unsigned fmt_ptime(char *, struct taia *); +extern unsigned fmt_taia(char *, struct taia *); + + +/*** gen_alloc.h ***/ + +#define GEN_ALLOC_typedef(ta,type,field,len,a) \ + typedef struct ta { type *field; unsigned len; unsigned a; } ta; + + +/*** gen_allocdefs.h ***/ + +#define GEN_ALLOC_ready(ta,type,field,len,a,i,n,x,base,ta_ready) \ +int ta_ready(ta *x,unsigned n) \ +{ unsigned i; \ + if (x->field) { \ + i = x->a; \ + if (n > i) { \ + x->a = base + n + (n >> 3); \ + x->field = realloc(x->field,x->a * sizeof(type)); \ + if (x->field) return 1; \ + x->a = i; return 0; } \ + return 1; } \ + x->len = 0; \ + return !!(x->field = malloc((x->a = n) * sizeof(type))); } + +#define GEN_ALLOC_readyplus(ta,type,field,len,a,i,n,x,base,ta_rplus) \ +int ta_rplus(ta *x,unsigned n) \ +{ unsigned i; \ + if (x->field) { \ + i = x->a; n += x->len; \ + if (n > i) { \ + x->a = base + n + (n >> 3); \ + x->field = realloc(x->field,x->a * sizeof(type)); \ + if (x->field) return 1; \ + x->a = i; return 0; } \ + return 1; } \ + x->len = 0; \ + return !!(x->field = malloc((x->a = n) * sizeof(type))); } + +#define GEN_ALLOC_append(ta,type,field,len,a,i,n,x,base,ta_rplus,ta_append) \ +int ta_append(ta *x,const type *i) \ +{ if (!ta_rplus(x,1)) return 0; x->field[x->len++] = *i; return 1; } + + +/*** stralloc.h ***/ +#if 0 +GEN_ALLOC_typedef(stralloc,char,s,len,a) + +extern int stralloc_ready(stralloc *,unsigned); +extern int stralloc_readyplus(stralloc *,unsigned); +extern int stralloc_copy(stralloc *,const stralloc *); +extern int stralloc_cat(stralloc *,const stralloc *); +extern int stralloc_copys(stralloc *,const char *); +extern int stralloc_cats(stralloc *,const char *); +extern int stralloc_copyb(stralloc *,const char *,unsigned); +extern int stralloc_catb(stralloc *,const char *,unsigned); +extern int stralloc_append(stralloc *,const char *); /* beware: this takes a pointer to 1 char */ +extern int stralloc_starts(stralloc *,const char *); + +#define stralloc_0(sa) stralloc_append(sa,"") + +extern int stralloc_catulong0(stralloc *,unsigned long,unsigned); +extern int stralloc_catlong0(stralloc *,long,unsigned); + +#define stralloc_catlong(sa,l) (stralloc_catlong0((sa),(l),0)) +#define stralloc_catuint0(sa,i,n) (stralloc_catulong0((sa),(i),(n))) +#define stralloc_catint0(sa,i,n) (stralloc_catlong0((sa),(i),(n))) +#define stralloc_catint(sa,i) (stralloc_catlong0((sa),(i),0)) +#endif + +/*** iopause.h ***/ + +typedef struct pollfd iopause_fd; +#define IOPAUSE_READ POLLIN +#define IOPAUSE_WRITE POLLOUT + +extern void iopause(iopause_fd *,unsigned,struct taia *,struct taia *); + + +/*** lock.h ***/ + +extern int lock_ex(int); +extern int lock_un(int); +extern int lock_exnb(int); + + +/*** ndelay.h ***/ + +extern int ndelay_on(int); +extern int ndelay_off(int); + + +/*** open.h ***/ + +extern int open_read(const char *); +extern int open_excl(const char *); +extern int open_append(const char *); +extern int open_trunc(const char *); +extern int open_write(const char *); + + +/*** openreadclose.h ***/ +#if 0 +extern int openreadclose(const char *,stralloc *,unsigned); +#endif + +/*** pathexec.h ***/ + +extern void pathexec_run(const char *,char *const *,char *const *); +extern int pathexec_env(const char *,const char *); +extern void pathexec(char **); + + +/*** pmatch.h ***/ + +extern unsigned pmatch(const char *, const char *, unsigned); + + +/*** prot.h ***/ + +extern int prot_gid(int); +extern int prot_uid(int); + + +/*** readclose.h ***/ +#if 0 +extern int readclose_append(int,stralloc *,unsigned); +extern int readclose(int,stralloc *,unsigned); +#endif + +/*** scan.h ***/ + +extern unsigned scan_uint(const char *,unsigned *); +extern unsigned scan_xint(const char *,unsigned *); +extern unsigned scan_nbbint(const char *,unsigned,unsigned,unsigned,unsigned *); +extern unsigned scan_ushort(const char *,unsigned short *); +extern unsigned scan_xshort(const char *,unsigned short *); +extern unsigned scan_nbbshort(const char *,unsigned,unsigned,unsigned,unsigned short *); +extern unsigned scan_ulong(const char *,unsigned long *); +extern unsigned scan_xlong(const char *,unsigned long *); +extern unsigned scan_nbblong(const char *,unsigned,unsigned,unsigned,unsigned long *); + +extern unsigned scan_plusminus(const char *,int *); +extern unsigned scan_0x(const char *,unsigned *); + +extern unsigned scan_whitenskip(const char *,unsigned); +extern unsigned scan_nonwhitenskip(const char *,unsigned); +extern unsigned scan_charsetnskip(const char *,const char *,unsigned); +extern unsigned scan_noncharsetnskip(const char *,const char *,unsigned); + +extern unsigned scan_strncmp(const char *,const char *,unsigned); +extern unsigned scan_memcmp(const char *,const char *,unsigned); + +extern unsigned scan_long(const char *,long *); +extern unsigned scan_8long(const char *,unsigned long *); + + +/*** seek.h ***/ + +typedef unsigned long seek_pos; + +extern seek_pos seek_cur(int); + +extern int seek_set(int,seek_pos); +extern int seek_end(int); + +extern int seek_trunc(int,seek_pos); + +#define seek_begin(fd) (seek_set((fd),(seek_pos) 0)) + + +/*** sig.h ***/ + +extern int sig_alarm; +extern int sig_child; +extern int sig_cont; +extern int sig_hangup; +extern int sig_int; +extern int sig_pipe; +extern int sig_term; + +extern void (*sig_defaulthandler)(int); +extern void (*sig_ignorehandler)(int); + +extern void sig_catch(int,void (*)(int)); +#define sig_ignore(s) (sig_catch((s),sig_ignorehandler)) +#define sig_uncatch(s) (sig_catch((s),sig_defaulthandler)) + +extern void sig_block(int); +extern void sig_unblock(int); +extern void sig_blocknone(void); +extern void sig_pause(void); + +extern void sig_dfl(int); + + +/*** str.h ***/ + +extern unsigned str_chr(const char *,int); /* never returns NULL */ + +#define str_diff(s,t) strcmp((s),(t)) +#define str_equal(s,t) (!strcmp((s),(t))) + + +/*** wait.h ***/ + +extern int wait_pid(int *wstat, int pid); +extern int wait_nohang(int *wstat); + +#define wait_crashed(w) ((w) & 127) +#define wait_exitcode(w) ((w) >> 8) +#define wait_stopsig(w) ((w) >> 8) +#define wait_stopped(w) (((w) & 127) == 127) diff --git a/runit/runsv.c b/runit/runsv.c new file mode 100644 index 000000000..e1b5459fb --- /dev/null +++ b/runit/runsv.c @@ -0,0 +1,613 @@ +/* Busyboxed by Denis Vlasenko */ +/* TODO: depends on runit_lib.c - review and reduce/eliminate */ + +#include +#include +#include "busybox.h" +#include "runit_lib.h" + +static int selfpipe[2]; + +/* state */ +#define S_DOWN 0 +#define S_RUN 1 +#define S_FINISH 2 +/* ctrl */ +#define C_NOOP 0 +#define C_TERM 1 +#define C_PAUSE 2 +/* want */ +#define W_UP 0 +#define W_DOWN 1 +#define W_EXIT 2 + +struct svdir { + int pid; + int state; + int ctrl; + int want; + struct taia start; + int fdlock; + int fdcontrol; + int fdcontrolwrite; + int islog; +}; +static struct svdir svd[2]; + +static int sigterm = 0; +static int haslog = 0; +static int pidchanged = 1; +static int logpipe[2]; +static char *dir; + +#define usage() bb_show_usage() + +static void fatal2_cannot(char *m1, char *m2) +{ + bb_perror_msg_and_die("%s: fatal: cannot %s%s", dir, m1, m2); + /* was exiting 111 */ +} +static void fatal_cannot(char *m) +{ + fatal2_cannot(m, ""); + /* was exiting 111 */ +} +static void fatal2x_cannot(char *m1, char *m2) +{ + bb_error_msg_and_die("%s: fatal: cannot %s%s", dir, m1, m2); + /* was exiting 111 */ +} +static void warn_cannot(char *m) +{ + bb_perror_msg("%s: warning: cannot %s", dir, m); +} +static void warnx_cannot(char *m) +{ + bb_error_msg("%s: warning: cannot %s", dir, m); +} + +static void stopservice(struct svdir *); + +static void s_child(int sig_no) +{ + write(selfpipe[1], "", 1); +} + +static void s_term(int sig_no) +{ + sigterm = 1; + write(selfpipe[1], "", 1); /* XXX */ +} + +static char *add_str(char *p, const char *to_add) +{ + while ((*p = *to_add) != '\0') { + p++; + to_add++; + } + return p; +} + +static int open_trunc_or_warn(const char *name) +{ + int fd = open_trunc(name); + if (fd < 0) + bb_perror_msg("%s: warning: cannot open %s", + dir, name); + return fd; +} + +static int rename_or_warn(const char *old, const char *new) +{ + if (rename(old, new) == -1) { + bb_perror_msg("%s: warning: cannot rename %s to %s", + dir, old, new); + return -1; + } + return 0; +} + +static void update_status(struct svdir *s) +{ + unsigned long l; + int fd; + char status[20]; + + /* pid */ + if (pidchanged) { + fd = open_trunc_or_warn("supervise/pid.new"); + if (fd < 0) + return; + if (s->pid) { + char spid[sizeof(s->pid)*3 + 2]; + int size = sprintf(spid, "%d\n", s->pid); + write(fd, spid, size); + } + close(fd); + if (s->islog) { + if (rename_or_warn("supervise/pid.new", "log/supervise/pid")) + return; + } else if (rename_or_warn("supervise/pid.new", "supervise/pid")) { + return; + } + pidchanged = 0; + } + + /* stat */ + fd = open_trunc_or_warn("supervise/stat.new"); + if (fd < -1) + return; + + { + char stat_buf[sizeof("finish, paused, got TERM, want down\n")]; + char *p = stat_buf; + switch (s->state) { + case S_DOWN: + p = add_str(p, "down"); + break; + case S_RUN: + p = add_str(p, "run"); + break; + case S_FINISH: + p = add_str(p, "finish"); + break; + } + if (s->ctrl & C_PAUSE) p = add_str(p, ", paused"); + if (s->ctrl & C_TERM) p = add_str(p, ", got TERM"); + if (s->state != S_DOWN) + switch(s->want) { + case W_DOWN: + p = add_str(p, ", want down"); + break; + case W_EXIT: + p = add_str(p, ", want exit"); + break; + } + *p++ = '\n'; + write(fd, stat_buf, p - stat_buf); + close(fd); + } + + if (s->islog) { + rename_or_warn("supervise/stat.new", "log/supervise/stat"); + } else { + rename_or_warn("supervise/stat.new", "log/supervise/stat"+4); + } + + /* supervise compatibility */ + taia_pack(status, &s->start); + l = (unsigned long)s->pid; + status[12] = l; l >>=8; + status[13] = l; l >>=8; + status[14] = l; l >>=8; + status[15] = l; + if (s->ctrl & C_PAUSE) + status[16] = 1; + else + status[16] = 0; + if (s->want == W_UP) + status[17] = 'u'; + else + status[17] = 'd'; + if (s->ctrl & C_TERM) + status[18] = 1; + else + status[18] = 0; + status[19] = s->state; + fd = open_trunc_or_warn("supervise/status.new"); + if (fd < 0) + return; + l = write(fd, status, sizeof status); + if (l < 0) { + warn_cannot("write supervise/status.new"); + close(fd); + unlink("supervise/status.new"); + return; + } + close(fd); + if (l < sizeof status) { + warnx_cannot("write supervise/status.new: partial write"); + return; + } + if (s->islog) { + rename_or_warn("supervise/status.new", "log/supervise/status"); + } else { + rename_or_warn("supervise/status.new", "log/supervise/status"+4); + } +} + +static unsigned custom(struct svdir *s, char c) +{ + int pid; + int w; + char a[10]; + struct stat st; + char *prog[2]; + + if (s->islog) return 0; + memcpy(a, "control/?", 10); + a[8] = c; + if (stat(a, &st) == 0) { + if (st.st_mode & S_IXUSR) { + pid = fork(); + if (pid == -1) { + warn_cannot("fork for control/?"); + return 0; + } + if (!pid) { + if (haslog && fd_copy(1, logpipe[1]) == -1) + warn_cannot("setup stdout for control/?"); + prog[0] = a; + prog[1] = 0; + execve(a, prog, environ); + fatal_cannot("run control/?"); + } + while (wait_pid(&w, pid) == -1) { + if (errno == EINTR) continue; + warn_cannot("wait for child control/?"); + return 0; + } + return !wait_exitcode(w); + } + } + else { + if (errno == ENOENT) return 0; + warn_cannot("stat control/?"); + } + return 0; +} + +static void stopservice(struct svdir *s) +{ + if (s->pid && ! custom(s, 't')) { + kill(s->pid, SIGTERM); + s->ctrl |=C_TERM; + update_status(s); + } + if (s->want == W_DOWN) { + kill(s->pid, SIGCONT); + custom(s, 'd'); return; + } + if (s->want == W_EXIT) { + kill(s->pid, SIGCONT); + custom(s, 'x'); + } +} + +static void startservice(struct svdir *s) +{ + int p; + char *run[2]; + + if (s->state == S_FINISH) + run[0] = "./finish"; + else { + run[0] = "./run"; + custom(s, 'u'); + } + run[1] = 0; + + if (s->pid != 0) stopservice(s); /* should never happen */ + while ((p = fork()) == -1) { + warn_cannot("fork, sleeping"); + sleep(5); + } + if (p == 0) { + /* child */ + if (haslog) { + if (s->islog) { + if (fd_copy(0, logpipe[0]) == -1) + fatal_cannot("setup filedescriptor for ./log/run"); + close(logpipe[1]); + if (chdir("./log") == -1) + fatal_cannot("change directory to ./log"); + } else { + if (fd_copy(1, logpipe[1]) == -1) + fatal_cannot("setup filedescriptor for ./run"); + close(logpipe[0]); + } + } + sig_uncatch(sig_child); + sig_unblock(sig_child); + sig_uncatch(sig_term); + sig_unblock(sig_term); + execve(*run, run, environ); + if (s->islog) + fatal2_cannot("start log/", *run); + else + fatal2_cannot("start ", *run); + } + if (s->state != S_FINISH) { + taia_now(&s->start); + s->state = S_RUN; + } + s->pid = p; + pidchanged = 1; + s->ctrl = C_NOOP; + update_status(s); +} + +static int ctrl(struct svdir *s, char c) +{ + switch(c) { + case 'd': /* down */ + s->want = W_DOWN; + update_status(s); + if (s->pid && s->state != S_FINISH) stopservice(s); + break; + case 'u': /* up */ + s->want = W_UP; + update_status(s); + if (s->pid == 0) startservice(s); + break; + case 'x': /* exit */ + if (s->islog) break; + s->want = W_EXIT; + update_status(s); + if (s->pid && s->state != S_FINISH) stopservice(s); + break; + case 't': /* sig term */ + if (s->pid && s->state != S_FINISH) stopservice(s); + break; + case 'k': /* sig kill */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGKILL); + s->state = S_DOWN; + break; + case 'p': /* sig pause */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGSTOP); + s->ctrl |=C_PAUSE; + update_status(s); + break; + case 'c': /* sig cont */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGCONT); + if (s->ctrl & C_PAUSE) s->ctrl &=~C_PAUSE; + update_status(s); + break; + case 'o': /* once */ + s->want = W_DOWN; + update_status(s); + if (!s->pid) startservice(s); + break; + case 'a': /* sig alarm */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGALRM); + break; + case 'h': /* sig hup */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGHUP); + break; + case 'i': /* sig int */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGINT); + break; + case 'q': /* sig quit */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGQUIT); + break; + case '1': /* sig usr1 */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGUSR1); + break; + case '2': /* sig usr2 */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGUSR2); + break; + } + return 1; +} + +int runsv_main(int argc, char **argv) +{ + struct stat s; + int fd; + int r; + char buf[256]; + + if (!argv[1] || argv[2]) usage(); + dir = argv[1]; + + if (pipe(selfpipe) == -1) fatal_cannot("create selfpipe"); + coe(selfpipe[0]); + coe(selfpipe[1]); + ndelay_on(selfpipe[0]); + ndelay_on(selfpipe[1]); + + sig_block(sig_child); + sig_catch(sig_child, s_child); + sig_block(sig_term); + sig_catch(sig_term, s_term); + + xchdir(dir); + svd[0].pid = 0; + svd[0].state = S_DOWN; + svd[0].ctrl = C_NOOP; + svd[0].want = W_UP; + svd[0].islog = 0; + svd[1].pid = 0; + taia_now(&svd[0].start); + if (stat("down", &s) != -1) svd[0].want = W_DOWN; + + if (stat("log", &s) == -1) { + if (errno != ENOENT) + warn_cannot("stat ./log"); + } else { + if (!S_ISDIR(s.st_mode)) + warnx_cannot("stat log/down: log is not a directory"); + else { + haslog = 1; + svd[1].state = S_DOWN; + svd[1].ctrl = C_NOOP; + svd[1].want = W_UP; + svd[1].islog = 1; + taia_now(&svd[1].start); + if (stat("log/down", &s) != -1) + svd[1].want = W_DOWN; + if (pipe(logpipe) == -1) + fatal_cannot("create log pipe"); + coe(logpipe[0]); + coe(logpipe[1]); + } + } + + if (mkdir("supervise", 0700) == -1) { + r = readlink("supervise", buf, 256); + if (r != -1) { + if (r == 256) + fatal2x_cannot("readlink ./supervise: ", "name too long"); + buf[r] = 0; + mkdir(buf, 0700); + } else { + if ((errno != ENOENT) && (errno != EINVAL)) + fatal_cannot("readlink ./supervise"); + } + } + svd[0].fdlock = xopen3("log/supervise/lock"+4, + O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600); + if (lock_exnb(svd[0].fdlock) == -1) + fatal_cannot("lock supervise/lock"); + coe(svd[0].fdlock); + if (haslog) { + if (mkdir("log/supervise", 0700) == -1) { + r = readlink("log/supervise", buf, 256); + if (r != -1) { + if (r == 256) + fatal2x_cannot("readlink ./log/supervise: ", "name too long"); + buf[r] = 0; + fd = xopen(".", O_RDONLY|O_NDELAY); + xchdir("./log"); + mkdir(buf, 0700); + if (fchdir(fd) == -1) + fatal_cannot("change back to service directory"); + close(fd); + } + else { + if ((errno != ENOENT) && (errno != EINVAL)) + fatal_cannot("readlink ./log/supervise"); + } + } + svd[1].fdlock = xopen3("log/supervise/lock", + O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600); + if (lock_ex(svd[1].fdlock) == -1) + fatal_cannot("lock log/supervise/lock"); + coe(svd[1].fdlock); + } + + fifo_make("log/supervise/control"+4, 0600); + svd[0].fdcontrol = xopen("log/supervise/control"+4, O_RDONLY|O_NDELAY); + coe(svd[0].fdcontrol); + svd[0].fdcontrolwrite = xopen("log/supervise/control"+4, O_WRONLY|O_NDELAY); + coe(svd[0].fdcontrolwrite); + update_status(&svd[0]); + if (haslog) { + fifo_make("log/supervise/control", 0600); + svd[1].fdcontrol = xopen("log/supervise/control", O_RDONLY|O_NDELAY); + coe(svd[1].fdcontrol); + svd[1].fdcontrolwrite = xopen("log/supervise/control", O_WRONLY|O_NDELAY); + coe(svd[1].fdcontrolwrite); + update_status(&svd[1]); + } + fifo_make("log/supervise/ok"+4, 0600); + fd = xopen("log/supervise/ok"+4, O_RDONLY|O_NDELAY); + coe(fd); + if (haslog) { + fifo_make("log/supervise/ok", 0600); + fd = xopen("log/supervise/ok", O_RDONLY|O_NDELAY); + coe(fd); + } + for (;;) { + iopause_fd x[3]; + struct taia deadline; + struct taia now; + char ch; + + if (haslog) + if (!svd[1].pid && svd[1].want == W_UP) + startservice(&svd[1]); + if (!svd[0].pid) + if (svd[0].want == W_UP || svd[0].state == S_FINISH) + startservice(&svd[0]); + + x[0].fd = selfpipe[0]; + x[0].events = IOPAUSE_READ; + x[1].fd = svd[0].fdcontrol; + x[1].events = IOPAUSE_READ; + if (haslog) { + x[2].fd = svd[1].fdcontrol; + x[2].events = IOPAUSE_READ; + } + taia_now(&now); + taia_uint(&deadline, 3600); + taia_add(&deadline, &now, &deadline); + + sig_unblock(sig_term); + sig_unblock(sig_child); + iopause(x, 2+haslog, &deadline, &now); + sig_block(sig_term); + sig_block(sig_child); + + while (read(selfpipe[0], &ch, 1) == 1) + ; + for (;;) { + int child; + int wstat; + + child = wait_nohang(&wstat); + if (!child) break; + if ((child == -1) && (errno != EINTR)) break; + if (child == svd[0].pid) { + svd[0].pid = 0; + pidchanged = 1; + svd[0].ctrl &=~C_TERM; + if (svd[0].state != S_FINISH) + fd = open_read("finish"); + if (fd != -1) { + close(fd); + svd[0].state = S_FINISH; + update_status(&svd[0]); + continue; + } + svd[0].state = S_DOWN; + taia_uint(&deadline, 1); + taia_add(&deadline, &svd[0].start, &deadline); + taia_now(&svd[0].start); + update_status(&svd[0]); + if (taia_less(&svd[0].start, &deadline)) sleep(1); + } + if (haslog) { + if (child == svd[1].pid) { + svd[1].pid = 0; + pidchanged = 1; + svd[1].state = S_DOWN; + svd[1].ctrl &=~C_TERM; + taia_uint(&deadline, 1); + taia_add(&deadline, &svd[1].start, &deadline); + taia_now(&svd[1].start); + update_status(&svd[1]); + if (taia_less(&svd[1].start, &deadline)) sleep(1); + } + } + } + if (read(svd[0].fdcontrol, &ch, 1) == 1) + ctrl(&svd[0], ch); + if (haslog) + if (read(svd[1].fdcontrol, &ch, 1) == 1) + ctrl(&svd[1], ch); + + if (sigterm) { + ctrl(&svd[0], 'x'); + sigterm = 0; + } + + if (svd[0].want == W_EXIT && svd[0].state == S_DOWN) { + if (svd[1].pid == 0) + _exit(0); + if (svd[1].want != W_EXIT) { + svd[1].want = W_EXIT; + /* stopservice(&svd[1]); */ + update_status(&svd[1]); + close(logpipe[1]); + close(logpipe[0]); + //if (close(logpipe[1]) == -1) + // warn_cannot("close logpipe[1]"); + //if (close(logpipe[0]) == -1) + // warn_cannot("close logpipe[0]"); + } + } + } + /* not reached */ + return 0; +} diff --git a/runit/runsvdir.c b/runit/runsvdir.c new file mode 100644 index 000000000..9238eec82 --- /dev/null +++ b/runit/runsvdir.c @@ -0,0 +1,306 @@ +/* Busyboxed by Denis Vlasenko */ +/* TODO: depends on runit_lib.c - review and reduce/eliminate */ + +#include +#include +#include "busybox.h" +#include "runit_lib.h" + +#define MAXSERVICES 1000 + +static char *svdir; +static unsigned long dev; +static unsigned long ino; +static struct service { + unsigned long dev; + unsigned long ino; + int pid; + int isgone; +} *sv; +static int svnum; +static int check = 1; +static char *rplog; +static int rploglen; +static int logpipe[2]; +static iopause_fd io[1]; +static struct taia stamplog; +static int exitsoon; +static int pgrp; + +#define usage() bb_show_usage() +static void fatal2_cannot(char *m1, char *m2) +{ + bb_perror_msg_and_die("%s: fatal: cannot %s%s", svdir, m1, m2); + /* was exiting 100 */ +} +static void warn3x(char *m1, char *m2, char *m3) +{ + bb_error_msg("%s: warning: %s%s%s", svdir, m1, m2, m3); +} +static void warn2_cannot(char *m1, char *m2) +{ + warn3x("cannot ", m1, m2); +} +static void warnx(char *m1) +{ + warn3x(m1, "", ""); +} + +static void s_term(int sig_no) +{ + exitsoon = 1; +} +static void s_hangup(int sig_no) +{ + exitsoon = 2; +} + +static void runsv(int no, char *name) +{ + int pid = fork(); + + if (pid == -1) { + warn2_cannot("fork for ", name); + return; + } + if (pid == 0) { + /* child */ + char *prog[3]; + + prog[0] = "runsv"; + prog[1] = name; + prog[2] = 0; + sig_uncatch(sig_hangup); + sig_uncatch(sig_term); + if (pgrp) setsid(); + execvp(prog[0], prog); + //pathexec_run(*prog, prog, (char* const*)environ); + fatal2_cannot("start runsv ", name); + } + sv[no].pid = pid; +} + +static void runsvdir(void) +{ + DIR *dir; + direntry *d; + int i; + struct stat s; + + dir = opendir("."); + if (!dir) { + warn2_cannot("open directory ", svdir); + return; + } + for (i = 0; i < svnum; i++) + sv[i].isgone = 1; + errno = 0; + while ((d = readdir(dir))) { + if (d->d_name[0] == '.') continue; + if (stat(d->d_name, &s) == -1) { + warn2_cannot("stat ", d->d_name); + errno = 0; + continue; + } + if (!S_ISDIR(s.st_mode)) continue; + for (i = 0; i < svnum; i++) { + if ((sv[i].ino == s.st_ino) && (sv[i].dev == s.st_dev)) { + sv[i].isgone = 0; + if (!sv[i].pid) + runsv(i, d->d_name); + break; + } + } + if (i == svnum) { + /* new service */ + struct service *svnew = realloc(sv, (i+1) * sizeof(*sv)); + if (!svnew) { + warn3x("cannot start runsv ", d->d_name, + " too many services"); + continue; + } + sv = svnew; + svnum++; + memset(&sv[i], 0, sizeof(sv[i])); + sv[i].ino = s.st_ino; + sv[i].dev = s.st_dev; + //sv[i].pid = 0; + //sv[i].isgone = 0; + runsv(i, d->d_name); + check = 1; + } + } + if (errno) { + warn2_cannot("read directory ", svdir); + closedir(dir); + check = 1; + return; + } + closedir(dir); + + /* SIGTERM removed runsv's */ + for (i = 0; i < svnum; i++) { + if (!sv[i].isgone) + continue; + if (sv[i].pid) + kill(sv[i].pid, SIGTERM); + sv[i] = sv[--svnum]; + check = 1; + } +} + +static int setup_log(void) +{ + rploglen = strlen(rplog); + if (rploglen < 7) { + warnx("log must have at least seven characters"); + return 0; + } + if (pipe(logpipe) == -1) { + warnx("cannot create pipe for log"); + return -1; + } + coe(logpipe[1]); + coe(logpipe[0]); + ndelay_on(logpipe[0]); + ndelay_on(logpipe[1]); + if (fd_copy(2, logpipe[1]) == -1) { + warnx("cannot set filedescriptor for log"); + return -1; + } + io[0].fd = logpipe[0]; + io[0].events = IOPAUSE_READ; + taia_now(&stamplog); + return 1; +} + +int runsvdir_main(int argc, char **argv) +{ + struct stat s; + time_t mtime = 0; + int wstat; + int curdir; + int pid; + struct taia deadline; + struct taia now; + struct taia stampcheck; + char ch; + int i; + + argv++; + if (!argv || !*argv) usage(); + if (**argv == '-') { + switch (*(*argv + 1)) { + case 'P': pgrp = 1; + case '-': ++argv; + } + if (!argv || !*argv) usage(); + } + + sig_catch(sig_term, s_term); + sig_catch(sig_hangup, s_hangup); + svdir = *argv++; + if (argv && *argv) { + rplog = *argv; + if (setup_log() != 1) { + rplog = 0; + warnx("log service disabled"); + } + } + curdir = open_read("."); + if (curdir == -1) + fatal2_cannot("open current directory", ""); + coe(curdir); + + taia_now(&stampcheck); + + for (;;) { + /* collect children */ + for (;;) { + pid = wait_nohang(&wstat); + if (pid <= 0) break; + for (i = 0; i < svnum; i++) { + if (pid == sv[i].pid) { + /* runsv has gone */ + sv[i].pid = 0; + check = 1; + break; + } + } + } + + taia_now(&now); + if (now.sec.x < (stampcheck.sec.x - 3)) { + /* time warp */ + warnx("time warp: resetting time stamp"); + taia_now(&stampcheck); + taia_now(&now); + if (rplog) taia_now(&stamplog); + } + if (taia_less(&now, &stampcheck) == 0) { + /* wait at least a second */ + taia_uint(&deadline, 1); + taia_add(&stampcheck, &now, &deadline); + + if (stat(svdir, &s) != -1) { + if (check || s.st_mtime != mtime + || s.st_ino != ino || s.st_dev != dev + ) { + /* svdir modified */ + if (chdir(svdir) != -1) { + mtime = s.st_mtime; + dev = s.st_dev; + ino = s.st_ino; + check = 0; + if (now.sec.x <= (4611686018427387914ULL + (uint64_t)mtime)) + sleep(1); + runsvdir(); + while (fchdir(curdir) == -1) { + warn2_cannot("change directory, pausing", ""); + sleep(5); + } + } else + warn2_cannot("change directory to ", svdir); + } + } else + warn2_cannot("stat ", svdir); + } + + if (rplog) { + if (taia_less(&now, &stamplog) == 0) { + write(logpipe[1], ".", 1); + taia_uint(&deadline, 900); + taia_add(&stamplog, &now, &deadline); + } + } + taia_uint(&deadline, check ? 1 : 5); + taia_add(&deadline, &now, &deadline); + + sig_block(sig_child); + if (rplog) + iopause(io, 1, &deadline, &now); + else + iopause(0, 0, &deadline, &now); + sig_unblock(sig_child); + + if (rplog && (io[0].revents | IOPAUSE_READ)) + while (read(logpipe[0], &ch, 1) > 0) + if (ch) { + for (i = 6; i < rploglen; i++) + rplog[i-1] = rplog[i]; + rplog[rploglen-1] = ch; + } + + switch (exitsoon) { + case 1: + _exit(0); + case 2: + for (i = 0; i < svnum; i++) + if (sv[i].pid) + kill(sv[i].pid, SIGTERM); + _exit(111); + } + } + /* not reached */ + return 0; +} diff --git a/runit/sv.c b/runit/sv.c new file mode 100644 index 000000000..819f31419 --- /dev/null +++ b/runit/sv.c @@ -0,0 +1,360 @@ +/* Busyboxed by Denis Vlasenko */ +/* TODO: depends on runit_lib.c - review and reduce/eliminate */ + +#include +#include +#include "busybox.h" +#include "runit_lib.h" + +static char *action; +static char *acts; +static char *varservice = "/var/service/"; +static char **service; +static char **servicex; +static unsigned services; +static unsigned rc = 0; +static unsigned verbose = 0; +static unsigned long waitsec = 7; +static unsigned kll = 0; +static struct taia tstart, tnow, tdiff; +static struct tai tstatus; + +static int (*act)(char*) = 0; +static int (*cbk)(char*) = 0; + +static int curdir, fd, r; +static char svstatus[20]; + +#define usage() bb_show_usage() + +static void fatal_cannot(char *m1) +{ + bb_perror_msg("fatal: cannot %s", m1); + _exit(151); +} + +static void out(char *p, char *m1) +{ + printf("%s%s: %s", p, *service, m1); + if (errno) { + printf(": %s", strerror(errno)); + } + puts(""); /* will also flush the output */ +} + +#define FAIL "fail: " +#define WARN "warning: " +#define OK "ok: " +#define RUN "run: " +#define FINISH "finish: " +#define DOWN "down: " +#define TIMEOUT "timeout: " +#define KILL "kill: " + +static void fail(char *m1) { ++rc; out(FAIL, m1); } +static void failx(char *m1) { errno = 0; fail(m1); } +static void warn_cannot(char *m1) { ++rc; out("warning: cannot ", m1); } +static void warnx_cannot(char *m1) { errno = 0; warn_cannot(m1); } +static void ok(char *m1) { errno = 0; out(OK, m1); } + +static int svstatus_get(void) +{ + if ((fd = open_write("supervise/ok")) == -1) { + if (errno == ENODEV) { + *acts == 'x' ? ok("runsv not running") + : failx("runsv not running"); + return 0; + } + warn_cannot("open supervise/ok"); + return -1; + } + close(fd); + if ((fd = open_read("supervise/status")) == -1) { + warn_cannot("open supervise/status"); + return -1; + } + r = read(fd, svstatus, 20); + close(fd); + switch(r) { + case 20: break; + case -1: warn_cannot("read supervise/status"); return -1; + default: warnx_cannot("read supervise/status: bad format"); return -1; + } + return 1; +} + +static unsigned svstatus_print(char *m) +{ + int pid; + int normallyup = 0; + struct stat s; + + if (stat("down", &s) == -1) { + if (errno != ENOENT) { + bb_perror_msg(WARN"cannot stat %s/down", *service); + return 0; + } + normallyup = 1; + } + pid = (unsigned char) svstatus[15]; + pid <<= 8; pid += (unsigned char)svstatus[14]; + pid <<= 8; pid += (unsigned char)svstatus[13]; + pid <<= 8; pid += (unsigned char)svstatus[12]; + tai_unpack(svstatus, &tstatus); + if (pid) { + switch (svstatus[19]) { + case 1: printf(RUN); break; + case 2: printf(FINISH); break; + } + printf("%s: (pid %d) ", m, pid); + } + else { + printf(DOWN"%s: ", m); + } + printf("%lus", (unsigned long)(tnow.sec.x < tstatus.x ? 0 : tnow.sec.x-tstatus.x)); + if (pid && !normallyup) printf(", normally down"); + if (!pid && normallyup) printf(", normally up"); + if (pid && svstatus[16]) printf(", paused"); + if (!pid && (svstatus[17] == 'u')) printf(", want up"); + if (pid && (svstatus[17] == 'd')) printf(", want down"); + if (pid && svstatus[18]) printf(", got TERM"); + return pid ? 1 : 2; +} + +static int status(char *unused) +{ + r = svstatus_get(); + switch(r) { case -1: case 0: return 0; } + r = svstatus_print(*service); + if (chdir("log") == -1) { + if (errno != ENOENT) { + printf("; log: "WARN"cannot change to log service directory: %s", + strerror(errno)); + } + } else if (svstatus_get()) { + printf("; "); + svstatus_print("log"); + } + puts(""); /* will also flush the output */ + return r; +} + +static int checkscript(void) +{ + char *prog[2]; + struct stat s; + int pid, w; + + if (stat("check", &s) == -1) { + if (errno == ENOENT) return 1; + bb_perror_msg(WARN"cannot stat %s/check", *service); + return 0; + } + /* if (!(s.st_mode & S_IXUSR)) return 1; */ + if ((pid = fork()) == -1) { + bb_perror_msg(WARN"cannot fork for %s/check", *service); + return 0; + } + if (!pid) { + prog[0] = "./check"; + prog[1] = 0; + close(1); + execve("check", prog, environ); + bb_perror_msg(WARN"cannot run %s/check", *service); + _exit(0); + } + while (wait_pid(&w, pid) == -1) { + if (errno == EINTR) continue; + bb_perror_msg(WARN"cannot wait for child %s/check", *service); + return 0; + } + return !wait_exitcode(w); +} + +static int check(char *a) +{ + unsigned pid; + + if ((r = svstatus_get()) == -1) return -1; + if (r == 0) { if (*a == 'x') return 1; return -1; } + pid = (unsigned char)svstatus[15]; + pid <<= 8; pid += (unsigned char)svstatus[14]; + pid <<= 8; pid += (unsigned char)svstatus[13]; + pid <<= 8; pid += (unsigned char)svstatus[12]; + switch (*a) { + case 'x': return 0; + case 'u': + if (!pid || svstatus[19] != 1) return 0; + if (!checkscript()) return 0; + break; + case 'd': if (pid) return 0; break; + case 'c': if (pid) if (!checkscript()) return 0; break; + case 't': + if (!pid && svstatus[17] == 'd') break; + tai_unpack(svstatus, &tstatus); + if ((tstart.sec.x > tstatus.x) || !pid || svstatus[18] || !checkscript()) + return 0; + break; + case 'o': + tai_unpack(svstatus, &tstatus); + if ((!pid && tstart.sec.x > tstatus.x) || (pid && svstatus[17] != 'd')) + return 0; + } + printf(OK); svstatus_print(*service); puts(""); /* will also flush the output */ + return 1; +} + +static int control(char *a) +{ + if (svstatus_get() <= 0) return -1; + if (svstatus[17] == *a) return 0; + if ((fd = open_write("supervise/control")) == -1) { + if (errno != ENODEV) + warn_cannot("open supervise/control"); + else + *a == 'x' ? ok("runsv not running") : failx("runsv not running"); + return -1; + } + r = write(fd, a, strlen(a)); + close(fd); + if (r != strlen(a)) { + warn_cannot("write to supervise/control"); + return -1; + } + return 1; +} + +int sv_main(int argc, char **argv) +{ + unsigned opt; + unsigned i, want_exit; + char *x; + + for (i = strlen(*argv); i; --i) + if ((*argv)[i-1] == '/') + break; + *argv += i; + service = argv; + services = 1; + if ((x = getenv("SVDIR"))) varservice = x; + if ((x = getenv("SVWAIT"))) waitsec = xatoul(x); + /* TODO: V can be handled internally by getopt_ulflags */ + opt = getopt32(argc, argv, "w:vV", &x); + if (opt & 1) waitsec = xatoul(x); + if (opt & 2) verbose = 1; + if (opt & 4) usage(); + if (!(action = *argv++)) usage(); + --argc; + service = argv; services = argc; + if (!*service) usage(); + + taia_now(&tnow); tstart = tnow; + if ((curdir = open_read(".")) == -1) + fatal_cannot("open current directory"); + + act = &control; acts = "s"; + if (verbose) cbk = ✓ + switch (*action) { + case 'x': case 'e': + acts = "x"; break; + case 'X': case 'E': + acts = "x"; kll = 1; cbk = ✓ break; + case 'D': + acts = "d"; kll = 1; cbk = ✓ break; + case 'T': + acts = "tc"; kll = 1; cbk = ✓ break; + case 'c': + if (!str_diff(action, "check")) { + act = 0; + acts = "c"; + cbk = ✓ + break; + } + case 'u': case 'd': case 'o': case 't': case 'p': case 'h': + case 'a': case 'i': case 'k': case 'q': case '1': case '2': + action[1] = 0; acts = action; break; + case 's': + if (!str_diff(action, "shutdown")) { + acts = "x"; + cbk = ✓ + break; + } + if (!str_diff(action, "start")) { + acts = "u"; + cbk = ✓ + break; + } + if (!str_diff(action, "stop")) { + acts = "d"; + cbk = ✓ + break; + } + act = &status; cbk = 0; break; + case 'r': + if (!str_diff(action, "restart")) { + acts = "tcu"; + cbk = ✓ + break; + } + usage(); + case 'f': + if (!str_diff(action, "force-reload")) + { acts = "tc"; kll = 1; cbk = ✓ break; } + if (!str_diff(action, "force-restart")) + { acts = "tcu"; kll = 1; cbk = ✓ break; } + if (!str_diff(action, "force-shutdown")) + { acts = "x"; kll = 1; cbk = ✓ break; } + if (!str_diff(action, "force-stop")) + { acts = "d"; kll = 1; cbk = ✓ break; } + default: + usage(); + } + + servicex = service; + for (i = 0; i < services; ++i) { + if ((**service != '/') && (**service != '.')) { + if ((chdir(varservice) == -1) || (chdir(*service) == -1)) { + fail("cannot change to service directory"); + *service = 0; + } + } else if (chdir(*service) == -1) { + fail("cannot change to service directory"); + *service = 0; + } + if (*service) if (act && (act(acts) == -1)) *service = 0; + if (fchdir(curdir) == -1) fatal_cannot("change to original directory"); + service++; + } + + if (*cbk) + for (;;) { + taia_sub(&tdiff, &tnow, &tstart); + service = servicex; want_exit = 1; + for (i = 0; i < services; ++i, ++service) { + if (!*service) continue; + if ((**service != '/') && (**service != '.')) { + if ((chdir(varservice) == -1) || (chdir(*service) == -1)) { + fail("cannot change to service directory"); + *service = 0; + } + } else if (chdir(*service) == -1) { + fail("cannot change to service directory"); + *service = 0; + } + if (*service) { if (cbk(acts) != 0) *service = 0; else want_exit = 0; } + if (*service && taia_approx(&tdiff) > waitsec) { + kll ? printf(KILL) : printf(TIMEOUT); + if (svstatus_get() > 0) { svstatus_print(*service); ++rc; } + puts(""); /* will also flush the output */ + if (kll) control("k"); + *service = 0; + } + if (fchdir(curdir) == -1) + fatal_cannot("change to original directory"); + } + if (want_exit) break; + usleep(420000); + taia_now(&tnow); + } + return rc > 99 ? 99 : rc; +} diff --git a/runit/svlogd.c b/runit/svlogd.c new file mode 100644 index 000000000..7024c3db4 --- /dev/null +++ b/runit/svlogd.c @@ -0,0 +1,878 @@ +/* +Copyright (c) 2001-2006, Gerrit Pape +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* Busyboxed by Denis Vlasenko */ +/* TODO: depends on runit_lib.c - review and reduce/eliminate */ + +#include +#include +#include "busybox.h" +#include "runit_lib.h" + +static unsigned verbose; +static int linemax = 1000; +static int buflen = 1024; +static int linelen; + +static char **fndir; +static int fdwdir; +static int wstat; +static struct taia trotate; + +static char *line; +static unsigned exitasap; +static unsigned rotateasap; +static unsigned reopenasap; +static unsigned linecomplete = 1; +static unsigned tmaxflag; +static iopause_fd in; + +static const char *replace = ""; +static char repl; + +static struct logdir { + char *btmp; + /* pattern list to match, in "aa\0bb\0\cc\0\0" form */ + char *inst; + char *processor; + char *name; + unsigned size; + unsigned sizemax; + unsigned nmax; + unsigned nmin; + /* int (not long) because of taia_uint() usage: */ + unsigned tmax; + int ppid; + int fddir; + int fdcur; + int fdlock; + struct taia trotate; + char fnsave[FMT_PTIME]; + char match; + char matcherr; +} *dir; +static unsigned dirn = 0; + +#define FATAL "fatal: " +#define WARNING "warning: " +#define PAUSE "pausing: " +#define INFO "info: " + +#define usage() bb_show_usage() +static void fatalx(char *m0) +{ + bb_error_msg_and_die(FATAL"%s", m0); +} +static void warn(char *m0) { + bb_perror_msg(WARNING"%s", m0); +} +static void warn2(char *m0, char *m1) +{ + bb_perror_msg(WARNING"%s: %s", m0, m1); +} +static void warnx(char *m0, char *m1) +{ + bb_error_msg(WARNING"%s: %s", m0, m1); +} +static void pause_nomem(void) +{ + bb_error_msg(PAUSE"out of memory"); sleep(3); +} +static void pause1cannot(char *m0) +{ + bb_perror_msg(PAUSE"cannot %s", m0); sleep(3); +} +static void pause2cannot(char *m0, char *m1) +{ + bb_perror_msg(PAUSE"cannot %s %s", m0, m1); + sleep(3); +} + +static char* wstrdup(const char *str) +{ + char *s; + while (!(s = strdup(str))) pause_nomem(); + return s; +} + +static unsigned processorstart(struct logdir *ld) +{ + int pid; + + if (!ld->processor) return 0; + if (ld->ppid) { + warnx("processor already running", ld->name); + return 0; + } + while ((pid = fork()) == -1) + pause2cannot("fork for processor", ld->name); + if (!pid) { + char *prog[4]; + int fd; + + /* child */ + sig_uncatch(sig_term); + sig_uncatch(sig_alarm); + sig_uncatch(sig_hangup); + sig_unblock(sig_term); + sig_unblock(sig_alarm); + sig_unblock(sig_hangup); + + if (verbose) + bb_error_msg(INFO"processing: %s/%s", ld->name, ld->fnsave); + fd = xopen(ld->fnsave, O_RDONLY|O_NDELAY); + if (fd_move(0, fd) == -1) + bb_perror_msg_and_die(FATAL"cannot %s processor %s", "move filedescriptor for", ld->name); + ld->fnsave[26] = 't'; + fd = xopen(ld->fnsave, O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT); + if (fd_move(1, fd) == -1) + bb_perror_msg_and_die(FATAL"cannot %s processor %s", "move filedescriptor for", ld->name); + fd = open_read("state"); + if (fd == -1) { + if (errno != ENOENT) + bb_perror_msg_and_die(FATAL"cannot %s processor %s", "open state for", ld->name); + close(xopen("state", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT)); + fd = xopen("state", O_RDONLY|O_NDELAY); + } + if (fd_move(4, fd) == -1) + bb_perror_msg_and_die(FATAL"cannot %s processor %s", "move filedescriptor for", ld->name); + fd = xopen("newstate", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT); + if (fd_move(5, fd) == -1) + bb_perror_msg_and_die(FATAL"cannot %s processor %s", "move filedescriptor for", ld->name); + + prog[0] = "sh"; + prog[1] = "-c"; + prog[2] = ld->processor; + prog[3] = '\0'; + execve("/bin/sh", prog, environ); + bb_perror_msg_and_die(FATAL"cannot %s processor %s", "run", ld->name); + } + ld->ppid = pid; + return 1; +} + +static unsigned processorstop(struct logdir *ld) +{ + char f[28]; + + if (ld->ppid) { + sig_unblock(sig_hangup); + while (wait_pid(&wstat, ld->ppid) == -1) + pause2cannot("wait for processor", ld->name); + sig_block(sig_hangup); + ld->ppid = 0; + } + if (ld->fddir == -1) return 1; + while (fchdir(ld->fddir) == -1) + pause2cannot("change directory, want processor", ld->name); + if (wait_exitcode(wstat) != 0) { + warnx("processor failed, restart", ld->name); + ld->fnsave[26] = 't'; + unlink(ld->fnsave); + ld->fnsave[26] = 'u'; + processorstart(ld); + while (fchdir(fdwdir) == -1) + pause1cannot("change to initial working directory"); + return ld->processor ? 0 : 1; + } + ld->fnsave[26] = 't'; + memcpy(f, ld->fnsave, 26); + f[26] = 's'; + f[27] = '\0'; + while (rename(ld->fnsave, f) == -1) + pause2cannot("rename processed", ld->name); + while (chmod(f, 0744) == -1) + pause2cannot("set mode of processed", ld->name); + ld->fnsave[26] = 'u'; + if (unlink(ld->fnsave) == -1) + bb_error_msg(WARNING"cannot unlink: %s/%s", ld->name, ld->fnsave); + while (rename("newstate", "state") == -1) + pause2cannot("rename state", ld->name); + if (verbose) bb_error_msg(INFO"processed: %s/%s", ld->name, f); + while (fchdir(fdwdir) == -1) + pause1cannot("change to initial working directory"); + return 1; +} + +static void rmoldest(struct logdir *ld) +{ + DIR *d; + struct dirent *f; + char oldest[FMT_PTIME]; + int n = 0; + + oldest[0] = 'A'; oldest[1] = oldest[27] = 0; + while (!(d = opendir("."))) + pause2cannot("open directory, want rotate", ld->name); + errno = 0; + while ((f = readdir(d))) { + if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) { + if (f->d_name[26] == 't') { + if (unlink(f->d_name) == -1) + warn2("cannot unlink processor leftover", f->d_name); + } else { + ++n; + if (strcmp(f->d_name, oldest) < 0) + memcpy(oldest, f->d_name, 27); + } + errno = 0; + } + } + if (errno) warn2("cannot read directory", ld->name); + closedir(d); + + if (ld->nmax && (n > ld->nmax)) { + if (verbose) bb_error_msg(INFO"delete: %s/%s", ld->name, oldest); + if ((*oldest == '@') && (unlink(oldest) == -1)) + warn2("cannot unlink oldest logfile", ld->name); + } +} + +static unsigned rotate(struct logdir *ld) +{ + struct stat st; + struct taia now; + + if (ld->fddir == -1) { + ld->tmax = 0; + return 0; + } + if (ld->ppid) + while(!processorstop(ld)) + /* wait */; + + while (fchdir(ld->fddir) == -1) + pause2cannot("change directory, want rotate", ld->name); + + /* create new filename */ + ld->fnsave[25] = '.'; + ld->fnsave[26] = 's'; + if (ld->processor) + ld->fnsave[26] = 'u'; + ld->fnsave[27] = '\0'; + do { + taia_now(&now); + fmt_taia(ld->fnsave, &now); + errno = 0; + } while ((stat(ld->fnsave, &st) != -1) || (errno != ENOENT)); + + if (ld->tmax && taia_less(&ld->trotate, &now)) { + taia_uint(&ld->trotate, ld->tmax); + taia_add(&ld->trotate, &now, &ld->trotate); + if (taia_less(&ld->trotate, &trotate)) + trotate = ld->trotate; + } + + if (ld->size > 0) { + while (fsync(ld->fdcur) == -1) + pause2cannot("fsync current logfile", ld->name); + while (fchmod(ld->fdcur, 0744) == -1) + pause2cannot("set mode of current", ld->name); + close(ld->fdcur); + if (verbose) { + bb_error_msg(INFO"rename: %s/current %s %u", ld->name, + ld->fnsave, ld->size); + } + while (rename("current", ld->fnsave) == -1) + pause2cannot("rename current", ld->name); + while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1) + pause2cannot("create new current", ld->name); + coe(ld->fdcur); + ld->size = 0; + while (fchmod(ld->fdcur, 0644) == -1) + pause2cannot("set mode of current", ld->name); + rmoldest(ld); + processorstart(ld); + } + + while (fchdir(fdwdir) == -1) + pause1cannot("change to initial working directory"); + return 1; +} + +static int buffer_pwrite(int n, char *s, unsigned len) +{ + int i; + struct logdir *ld = &dir[n]; + + if (ld->sizemax) { + if (ld->size >= ld->sizemax) + rotate(ld); + if (len > (ld->sizemax - ld->size)) + len = ld->sizemax - ld->size; + } + while ((i = write(ld->fdcur, s, len)) == -1) { + if ((errno == ENOSPC) && (ld->nmin < ld->nmax)) { + DIR *d; + struct dirent *f; + char oldest[FMT_PTIME]; + int j = 0; + + while (fchdir(ld->fddir) == -1) + pause2cannot("change directory, want remove old logfile", + ld->name); + oldest[0] = 'A'; + oldest[1] = oldest[27] = '\0'; + while (!(d = opendir("."))) + pause2cannot("open directory, want remove old logfile", + ld->name); + errno = 0; + while ((f = readdir(d))) + if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) { + ++j; + if (strcmp(f->d_name, oldest) < 0) + memcpy(oldest, f->d_name, 27); + } + if (errno) warn2("cannot read directory, want remove old logfile", + ld->name); + closedir(d); + errno = ENOSPC; + if (j > ld->nmin) { + if (*oldest == '@') { + bb_error_msg(WARNING"out of disk space, delete: %s/%s", + ld->name, oldest); + errno = 0; + if (unlink(oldest) == -1) { + warn2("cannot unlink oldest logfile", ld->name); + errno = ENOSPC; + } + while (fchdir(fdwdir) == -1) + pause1cannot("change to initial working directory"); + } + } + } + if (errno) pause2cannot("write to current", ld->name); + } + + ld->size += i; + if (ld->sizemax) + if (s[i-1] == '\n') + if (ld->size >= (ld->sizemax - linemax)) + rotate(ld); + return i; +} + +static void logdir_close(struct logdir *ld) +{ + if (ld->fddir == -1) + return; + if (verbose) + bb_error_msg(INFO"close: %s", ld->name); + close(ld->fddir); + ld->fddir = -1; + if (ld->fdcur == -1) + return; /* impossible */ + while (fsync(ld->fdcur) == -1) + pause2cannot("fsync current logfile", ld->name); + while (fchmod(ld->fdcur, 0744) == -1) + pause2cannot("set mode of current", ld->name); + close(ld->fdcur); + ld->fdcur = -1; + if (ld->fdlock == -1) + return; /* impossible */ + close(ld->fdlock); + ld->fdlock = -1; + free(ld->processor); + ld->processor = NULL; +} + +static unsigned logdir_open(struct logdir *ld, const char *fn) +{ + char buf[128]; + struct taia now; + char *new, *s, *np; + int i; + struct stat st; + + ld->fddir = open(fn, O_RDONLY|O_NDELAY); + if (ld->fddir == -1) { + warn2("cannot open log directory", (char*)fn); + return 0; + } + coe(ld->fddir); + if (fchdir(ld->fddir) == -1) { + logdir_close(ld); + warn2("cannot change directory", (char*)fn); + return 0; + } + ld->fdlock = open("lock", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600); + if ((ld->fdlock == -1) + || (lock_exnb(ld->fdlock) == -1) + ) { + logdir_close(ld); + warn2("cannot lock directory", (char*)fn); + while (fchdir(fdwdir) == -1) + pause1cannot("change to initial working directory"); + return 0; + } + coe(ld->fdlock); + + ld->size = 0; + ld->sizemax = 1000000; + ld->nmax = ld->nmin = 10; + ld->tmax = 0; + ld->name = (char*)fn; + ld->ppid = 0; + ld->match = '+'; + free(ld->inst); ld->inst = NULL; + free(ld->processor); ld->processor = NULL; + + /* read config */ + i = open_read_close("config", buf, sizeof(buf)); + if (i < 0) + warn2("cannot read config", ld->name); + if (i > 0) { + if (verbose) bb_error_msg(INFO"read: %s/config", ld->name); + s = buf; + while (s) { + np = strchr(s, '\n'); + if (np) *np++ = '\0'; + switch (s[0]) { + case '+': + case '-': + case 'e': + case 'E': + while (1) { + int l = asprintf(&new, "%s%s\n", ld->inst?:"", s); + if (l >= 0 && new) break; + pause_nomem(); + } + free(ld->inst); + ld->inst = new; + break; + case 's': { + static const struct suffix_mult km_suffixes[] = { + { "k", 1024 }, + { "m", 1024*1024 }, + { NULL, 0 } + }; + ld->sizemax = xatou_sfx(&s[1], km_suffixes); + break; + } + case 'n': + ld->nmax = xatoi_u(&s[1]); + break; + case 'N': + ld->nmin = xatoi_u(&s[1]); + break; + case 't': { + static const struct suffix_mult mh_suffixes[] = { + { "m", 60 }, + { "h", 60*60 }, + /*{ "d", 24*60*60 },*/ + { NULL, 0 } + }; + ld->tmax = xatou_sfx(&s[1], mh_suffixes); + if (ld->tmax) { + taia_uint(&ld->trotate, ld->tmax); + taia_add(&ld->trotate, &now, &ld->trotate); + if (!tmaxflag || taia_less(&ld->trotate, &trotate)) + trotate = ld->trotate; + tmaxflag = 1; + } + break; + } + case '!': + if (s[1]) { + free(ld->processor); + ld->processor = wstrdup(s); + } + break; + } + s = np; + } + /* Convert "aa\nbb\ncc\n\0" to "aa\0bb\0cc\0\0" */ + s = ld->inst; + while (s) { + np = strchr(s, '\n'); + if (np) *np++ = '\0'; + s = np; + } + } + + /* open current */ + i = stat("current", &st); + if (i != -1) { + if (st.st_size && ! (st.st_mode & S_IXUSR)) { + ld->fnsave[25] = '.'; + ld->fnsave[26] = 'u'; + ld->fnsave[27] = '\0'; + do { + taia_now(&now); + fmt_taia(ld->fnsave, &now); + errno = 0; + } while ((stat(ld->fnsave, &st) != -1) || (errno != ENOENT)); + while (rename("current", ld->fnsave) == -1) + pause2cannot("rename current", ld->name); + rmoldest(ld); + i = -1; + } else { + /* Be paranoid: st.st_size can be not just bigger, but WIDER! */ + /* (bug in original svlogd. remove this comment when fixed there) */ + ld->size = (st.st_size > ld->sizemax) ? ld->sizemax : st.st_size; + } + } else { + if (errno != ENOENT) { + logdir_close(ld); + warn2("cannot stat current", ld->name); + while (fchdir(fdwdir) == -1) + pause1cannot("change to initial working directory"); + return 0; + } + } + while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1) + pause2cannot("open current", ld->name); + coe(ld->fdcur); + while (fchmod(ld->fdcur, 0644) == -1) + pause2cannot("set mode of current", ld->name); + + if (verbose) { + if (i == 0) bb_error_msg(INFO"append: %s/current", ld->name); + else bb_error_msg(INFO"new: %s/current", ld->name); + } + + while (fchdir(fdwdir) == -1) + pause1cannot("change to initial working directory"); + return 1; +} + +static void logdirs_reopen(void) +{ + struct taia now; + int l; + int ok = 0; + + tmaxflag = 0; + taia_now(&now); + for (l = 0; l < dirn; ++l) { + logdir_close(&dir[l]); + if (logdir_open(&dir[l], fndir[l])) ok = 1; + } + if (!ok) fatalx("no functional log directories"); +} + +/* Used for reading stdin */ +static int buffer_pread(int fd, char *s, unsigned len) +{ + struct taia now; + int i; + + if (rotateasap) { + for (i = 0; i < dirn; ++i) + rotate(dir+i); + rotateasap = 0; + } + if (exitasap) { + if (linecomplete) + return 0; + len = 1; + } + if (reopenasap) { + logdirs_reopen(); + reopenasap = 0; + } + taia_now(&now); + taia_uint(&trotate, 2744); + taia_add(&trotate, &now, &trotate); + for (i = 0; i < dirn; ++i) + if (dir[i].tmax) { + if (taia_less(&dir[i].trotate, &now)) + rotate(dir+i); + if (taia_less(&dir[i].trotate, &trotate)) + trotate = dir[i].trotate; + } + + while (1) { + /* Comment? */ + sig_unblock(sig_term); + sig_unblock(sig_child); + sig_unblock(sig_alarm); + sig_unblock(sig_hangup); + iopause(&in, 1, &trotate, &now); + sig_block(sig_term); + sig_block(sig_child); + sig_block(sig_alarm); + sig_block(sig_hangup); + i = safe_read(fd, s, len); + if (i >= 0) break; + if (errno != EAGAIN) { + warn("cannot read standard input"); + break; + } + /* else: EAGAIN - normal, repeat silently */ + } + + if (i > 0) { + int cnt; + linecomplete = (s[i-1] == '\n'); + if (!repl) return i; + + cnt = i; + while (--cnt >= 0) { + char ch = *s; + if (ch != '\n') { + if (ch < 32 || ch > 126) + *s = repl; + else { + int j; + for (j = 0; replace[j]; ++j) { + if (ch == replace[j]) { + *s = repl; + break; + } + } + } + } + s++; + } + } + return i; +} + + +static void sig_term_handler(int sig_no) +{ + if (verbose) + bb_error_msg(INFO"sig%s received", "term"); + exitasap = 1; +} + +static void sig_child_handler(int sig_no) +{ + int pid, l; + + if (verbose) + bb_error_msg(INFO"sig%s received", "child"); + while ((pid = wait_nohang(&wstat)) > 0) + for (l = 0; l < dirn; ++l) + if (dir[l].ppid == pid) { + dir[l].ppid = 0; + processorstop(&dir[l]); + break; + } +} + +static void sig_alarm_handler(int sig_no) +{ + if (verbose) + bb_error_msg(INFO"sig%s received", "alarm"); + rotateasap = 1; +} + +static void sig_hangup_handler(int sig_no) +{ + if (verbose) + bb_error_msg(INFO"sig%s received", "hangup"); + reopenasap = 1; +} + +static void logmatch(struct logdir *ld) +{ + char *s; + + ld->match = '+'; + ld->matcherr = 'E'; + s = ld->inst; + while (s && s[0]) { + switch (s[0]) { + case '+': + case '-': + if (pmatch(s+1, line, linelen)) + ld->match = s[0]; + break; + case 'e': + case 'E': + if (pmatch(s+1, line, linelen)) + ld->matcherr = s[0]; + break; + } + s += strlen(s) + 1; + } +} + +int svlogd_main(int argc, char **argv) +{ + struct taia now; + char *r,*l,*b; + ssize_t stdin_cnt = 0; + int i; + unsigned opt; + unsigned timestamp = 0; + + opt_complementary = "tt:vv"; + opt = getopt32(argc, argv, "r:R:l:b:tv", + &r, &replace, &l, &b, ×tamp, &verbose); + if (opt & 1) { // -r + repl = r[0]; + if (!repl || r[1]) usage(); + } + if (opt & 2) if (!repl) repl = '_'; // -R + if (opt & 4) { // -l + linemax = xatou_range(l, 0, 1000); + if (linemax == 0) linemax = 1000; + if (linemax < 256) linemax = 256; + } + if (opt & 8) { // -b + buflen = xatoi_u(b); + if (buflen == 0) buflen = 1024; + } + //if (opt & 0x10) timestamp++; // -t + //if (opt & 0x20) verbose++; // -v + if (timestamp > 2) timestamp = 2; + argv += optind; + argc -= optind; + + dirn = argc; + if (dirn <= 0) usage(); + if (buflen <= linemax) usage(); + fdwdir = xopen(".", O_RDONLY|O_NDELAY); + coe(fdwdir); + dir = xmalloc(dirn * sizeof(struct logdir)); + for (i = 0; i < dirn; ++i) { + dir[i].fddir = -1; + dir[i].fdcur = -1; + dir[i].btmp = xmalloc(buflen); + dir[i].ppid = 0; + } + line = xmalloc(linemax + (timestamp ? 26 : 0)); + fndir = argv; + in.fd = 0; + in.events = IOPAUSE_READ; + ndelay_on(in.fd); + + sig_block(sig_term); + sig_block(sig_child); + sig_block(sig_alarm); + sig_block(sig_hangup); + sig_catch(sig_term, sig_term_handler); + sig_catch(sig_child, sig_child_handler); + sig_catch(sig_alarm, sig_alarm_handler); + sig_catch(sig_hangup, sig_hangup_handler); + + logdirs_reopen(); + + /* Each iteration processes one line */ + while (1) { + int printlen; + char *lineptr = line; + char *np; + char ch; + + /* Prepare timestamp if needed */ + if (timestamp) { + char stamp[FMT_PTIME]; + taia_now(&now); + switch (timestamp) { + case 1: + fmt_taia(stamp, &now); + break; + default: /* case 2: */ + fmt_ptime(stamp, &now); + break; + } + memcpy(line, stamp, 25); + line[25] = ' '; + lineptr += 26; + } + + /* lineptr[0..linemax-1] - buffer for stdin */ + /* (possibly has some unprocessed data from prev loop) */ + + /* Refill the buffer if needed */ + np = memchr(lineptr, '\n', stdin_cnt); + i = linemax - stdin_cnt; /* avail. bytes at tail */ + if (i >= 128 && !exitasap && !np) { + int sz = buffer_pread(0, lineptr + stdin_cnt, i); + if (sz <= 0) /* EOF or error on stdin */ + exitasap = 1; + else { + stdin_cnt += sz; + np = memchr(lineptr, '\n', stdin_cnt); + } + } + if (stdin_cnt <= 0 && exitasap) + break; + + /* Search for '\n' (in fact, np already holds the result) */ + linelen = stdin_cnt; + if (np) linelen = np - lineptr + 1; + /* linelen == no of chars incl. '\n' (or == stdin_cnt) */ + ch = lineptr[linelen-1]; + + printlen = linelen + (timestamp ? 26 : 0); + /* write out line[0..printlen-1] to each log destination */ + for (i = 0; i < dirn; ++i) { + struct logdir *ld = &dir[i]; + if (ld->fddir == -1) continue; + if (ld->inst) + logmatch(ld); + if (ld->matcherr == 'e') + full_write(2, line, printlen); + if (ld->match != '+') continue; + buffer_pwrite(i, line, printlen); + } + + /* If we didn't see '\n' (long input line), */ + /* read/write repeatedly until we see it */ + while (ch != '\n') { + /* lineptr is emptied now, safe to use as buffer */ + stdin_cnt = exitasap ? -1 : buffer_pread(0, lineptr, linemax); + if (stdin_cnt <= 0) { /* EOF or error on stdin */ + lineptr[0] = ch = '\n'; + linelen = 1; + exitasap = 1; + stdin_cnt = 1; + } else { + linelen = stdin_cnt; + np = memchr(lineptr, '\n', stdin_cnt); + if (np) linelen = np - lineptr + 1; + ch = lineptr[linelen-1]; + } + /* linelen == no of chars incl. '\n' (or == stdin_cnt) */ + for (i = 0; i < dirn; ++i) { + if (dir[i].fddir == -1) continue; + if (dir[i].matcherr == 'e') + full_write(2, lineptr, linelen); + if (dir[i].match != '+') continue; + buffer_pwrite(i, lineptr, linelen); + } + } + + /* Move unprocessed data to the front of line */ + stdin_cnt -= linelen; + if (stdin_cnt > 0) /* TODO: slow if buffer is big */ + memmove(lineptr, &lineptr[linelen], stdin_cnt); + } + + for (i = 0; i < dirn; ++i) { + if (dir[i].ppid) + while (!processorstop(&dir[i])) + /* repeat */; + logdir_close(&dir[i]); + } + _exit(0); +} diff --git a/scripts/Kbuild b/scripts/Kbuild new file mode 100644 index 000000000..83b423253 --- /dev/null +++ b/scripts/Kbuild @@ -0,0 +1,7 @@ +### +# scripts contains sources for various helper programs used throughout +# the kernel for the build process. +# --------------------------------------------------------------------------- + +# Let clean descend into subdirs +subdir- += basic kconfig diff --git a/scripts/Kbuild.include b/scripts/Kbuild.include new file mode 100644 index 000000000..b0d067be7 --- /dev/null +++ b/scripts/Kbuild.include @@ -0,0 +1,148 @@ +#### +# kbuild: Generic definitions + +# Convinient variables +comma := , +squote := ' +empty := +space := $(empty) $(empty) + +### +# The temporary file to save gcc -MD generated dependencies must not +# contain a comma +depfile = $(subst $(comma),_,$(@D)/.$(@F).d) + +### +# Escape single quote for use in echo statements +escsq = $(subst $(squote),'\$(squote)',$1) + +### +# filechk is used to check if the content of a generated file is updated. +# Sample usage: +# define filechk_sample +# echo $KERNELRELEASE +# endef +# version.h : Makefile +# $(call filechk,sample) +# The rule defined shall write to stdout the content of the new file. +# The existing file will be compared with the new one. +# - If no file exist it is created +# - If the content differ the new file is used +# - If they are equal no change, and no timestamp update +# - stdin is piped in from the first prerequisite ($<) so one has +# to specify a valid file as first prerequisite (often the kbuild file) +define filechk + $(Q)set -e; \ + echo ' CHK $@'; \ + mkdir -p $(dir $@); \ + $(filechk_$(1)) < $< > $@.tmp; \ + if [ -r $@ ] && cmp -s $@ $@.tmp; then \ + rm -f $@.tmp; \ + else \ + echo ' UPD $@'; \ + mv -f $@.tmp $@; \ + fi +endef + +###### +# gcc support functions +# See documentation in Documentation/kbuild/makefiles.txt + +# as-option +# Usage: cflags-y += $(call as-option, -Wa$(comma)-isa=foo,) + +as-option = $(shell if $(CC) $(CFLAGS) $(1) -Wa,-Z -c -o /dev/null \ + -xassembler /dev/null > /dev/null 2>&1; then echo "$(1)"; \ + else echo "$(2)"; fi ;) + +# cc-option +# Usage: cflags-y += $(call cc-option, -march=winchip-c6, -march=i586) + +cc-option = $(shell if $(CC) $(CFLAGS) $(1) -S -o /dev/null -xc /dev/null \ + > /dev/null 2>&1; then echo "$(1)"; else echo "$(2)"; fi ;) + +# cc-option-yn +# Usage: flag := $(call cc-option-yn, -march=winchip-c6) +cc-option-yn = $(shell if $(CC) $(CFLAGS) $(1) -S -o /dev/null -xc /dev/null \ + > /dev/null 2>&1; then echo "y"; else echo "n"; fi;) + +# cc-option-align +# Prefix align with either -falign or -malign +cc-option-align = $(subst -functions=0,,\ + $(call cc-option,-falign-functions=0,-malign-functions=0)) + +# cc-version +# Usage gcc-ver := $(call cc-version, $(CC)) +cc-version = $(shell $(CONFIG_SHELL) $(srctree)/scripts/gcc-version.sh \ + $(if $(1), $(1), $(CC))) + +# cc-ifversion +# Usage: EXTRA_CFLAGS += $(call cc-ifversion, -lt, 0402, -O1) +cc-ifversion = $(shell if [ $(call cc-version, $(CC)) $(1) $(2) ]; then \ + echo $(3); fi;) + +### +# Shorthand for $(Q)$(MAKE) -f scripts/Makefile.build obj= +# Usage: +# $(Q)$(MAKE) $(build)=dir +build := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.build obj + +# Prefix -I with $(srctree) if it is not an absolute path +addtree = $(if $(filter-out -I/%,$(1)),$(patsubst -I%,-I$(srctree)/%,$(1))) $(1) +# Find all -I options and call addtree +flags = $(foreach o,$($(1)),$(if $(filter -I%,$(o)),$(call addtree,$(o)),$(o))) + +# If quiet is set, only print short version of command +cmd = @$(echo-cmd) $(cmd_$(1)) + +# Add $(obj)/ for paths that is not absolute +objectify = $(foreach o,$(1),$(if $(filter /%,$(o)),$(o),$(obj)/$(o))) + +### +# if_changed - execute command if any prerequisite is newer than +# target, or command line has changed +# if_changed_dep - as if_changed, but uses fixdep to reveal dependencies +# including used config symbols +# if_changed_rule - as if_changed but execute rule instead +# See Documentation/kbuild/makefiles.txt for more info + +ifneq ($(KBUILD_NOCMDDEP),1) +# Check if both arguments has same arguments. Result in empty string if equal +# User may override this check using make KBUILD_NOCMDDEP=1 +arg-check = $(strip $(filter-out $(1), $(2)) $(filter-out $(2), $(1)) ) +endif + +# echo command. Short version is $(quiet) equals quiet, otherwise full command +echo-cmd = $(if $($(quiet)cmd_$(1)), \ + echo ' $(call escsq,$($(quiet)cmd_$(1)))';) + +make-cmd = $(subst \#,\\\#,$(subst $$,$$$$,$(call escsq,$(cmd_$(1))))) + +# function to only execute the passed command if necessary +# >'< substitution is for echo to work, >$< substitution to preserve $ when reloading .cmd file +# note: when using inline perl scripts [perl -e '...$$t=1;...'] in $(cmd_xxx) double $$ your perl vars +# +if_changed = $(if $(strip $(filter-out $(PHONY),$?) \ + $(call arg-check, $(cmd_$(1)), $(cmd_$@)) ), \ + @set -e; \ + $(echo-cmd) $(cmd_$(1)); \ + echo 'cmd_$@ := $(make-cmd)' > $(@D)/.$(@F).cmd) + +# execute the command and also postprocess generated .d dependencies +# file +if_changed_dep = $(if $(strip $(filter-out $(PHONY),$?) \ + $(filter-out FORCE $(wildcard $^),$^) \ + $(call arg-check, $(cmd_$(1)), $(cmd_$@)) ), \ + @set -e; \ + $(echo-cmd) $(cmd_$(1)); \ + scripts/basic/fixdep $(depfile) $@ '$(make-cmd)' > $(@D)/.$(@F).tmp; \ + rm -f $(depfile); \ + mv -f $(@D)/.$(@F).tmp $(@D)/.$(@F).cmd) + +# Usage: $(call if_changed_rule,foo) +# will check if $(cmd_foo) changed, or any of the prequisites changed, +# and if so will execute $(rule_foo) +if_changed_rule = $(if $(strip $(filter-out $(PHONY),$?) \ + $(call arg-check, $(cmd_$(1)), $(cmd_$@)) ),\ + @set -e; \ + $(rule_$(1))) diff --git a/scripts/Makefile.build b/scripts/Makefile.build new file mode 100644 index 000000000..e48e60da3 --- /dev/null +++ b/scripts/Makefile.build @@ -0,0 +1,338 @@ +# ========================================================================== +# Building +# ========================================================================== + +src := $(obj) + +PHONY := __build +__build: + +# Read .config if it exist, otherwise ignore +-include .config + +include scripts/Kbuild.include + +# The filename Kbuild has precedence over Makefile +kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src)) +include $(if $(wildcard $(kbuild-dir)/Kbuild), $(kbuild-dir)/Kbuild, $(kbuild-dir)/Makefile) + +include scripts/Makefile.lib + +ifdef host-progs +ifneq ($(hostprogs-y),$(host-progs)) +$(warning kbuild: $(obj)/Makefile - Usage of host-progs is deprecated. Please replace with hostprogs-y!) +hostprogs-y += $(host-progs) +endif +endif + +# Do not include host rules unles needed +ifneq ($(hostprogs-y)$(hostprogs-m),) +include scripts/Makefile.host +endif + +ifneq ($(KBUILD_SRC),) +# Create output directory if not already present +_dummy := $(shell [ -d $(obj) ] || mkdir -p $(obj)) + +# Create directories for object files if directory does not exist +# Needed when obj-y := dir/file.o syntax is used +_dummy := $(foreach d,$(obj-dirs), $(shell [ -d $(d) ] || mkdir -p $(d))) +endif + + +ifdef EXTRA_TARGETS +$(warning kbuild: $(obj)/Makefile - Usage of EXTRA_TARGETS is obsolete in 2.6. Please fix!) +endif + +ifdef build-targets +$(warning kbuild: $(obj)/Makefile - Usage of build-targets is obsolete in 2.6. Please fix!) +endif + +ifdef export-objs +$(warning kbuild: $(obj)/Makefile - Usage of export-objs is obsolete in 2.6. Please fix!) +endif + +ifdef O_TARGET +$(warning kbuild: $(obj)/Makefile - Usage of O_TARGET := $(O_TARGET) is obsolete in 2.6. Please fix!) +endif + +ifdef L_TARGET +$(error kbuild: $(obj)/Makefile - Use of L_TARGET is replaced by lib-y in 2.6. Please fix!) +endif + +ifdef list-multi +$(warning kbuild: $(obj)/Makefile - list-multi := $(list-multi) is obsolete in 2.6. Please fix!) +endif + +ifndef obj +$(warning kbuild: Makefile.build is included improperly) +endif + +# =========================================================================== + +ifneq ($(strip $(lib-y) $(lib-m) $(lib-n) $(lib-)),) +lib-target := $(obj)/lib.a +endif + +ifneq ($(strip $(obj-y) $(obj-m) $(obj-n) $(obj-) $(lib-target)),) +builtin-target := $(obj)/built-in.o +endif + +# We keep a list of all modules in $(MODVERDIR) + +__build: $(if $(KBUILD_BUILTIN),$(builtin-target) $(lib-target) $(extra-y)) \ + $(if $(KBUILD_MODULES),$(obj-m)) \ + $(subdir-ym) $(always) + @: + +# Linus' kernel sanity checking tool +ifneq ($(KBUILD_CHECKSRC),0) + ifeq ($(KBUILD_CHECKSRC),2) + quiet_cmd_force_checksrc = CHECK $< + cmd_force_checksrc = $(CHECK) $(CHECKFLAGS) $(c_flags) $< ; + else + quiet_cmd_checksrc = CHECK $< + cmd_checksrc = $(CHECK) $(CHECKFLAGS) $(c_flags) $< ; + endif +endif + + +# Compile C sources (.c) +# --------------------------------------------------------------------------- + +# Default is built-in, unless we know otherwise +modkern_cflags := $(CFLAGS_KERNEL) +quiet_modtag := $(empty) $(empty) + +$(real-objs-m) : modkern_cflags := $(CFLAGS_MODULE) +$(real-objs-m:.o=.i) : modkern_cflags := $(CFLAGS_MODULE) +$(real-objs-m:.o=.s) : modkern_cflags := $(CFLAGS_MODULE) +$(real-objs-m:.o=.lst): modkern_cflags := $(CFLAGS_MODULE) + +$(real-objs-m) : quiet_modtag := [M] +$(real-objs-m:.o=.i) : quiet_modtag := [M] +$(real-objs-m:.o=.s) : quiet_modtag := [M] +$(real-objs-m:.o=.lst): quiet_modtag := [M] + +$(obj-m) : quiet_modtag := [M] + +# Default for not multi-part modules +modname = $(*F) + +$(multi-objs-m) : modname = $(modname-multi) +$(multi-objs-m:.o=.i) : modname = $(modname-multi) +$(multi-objs-m:.o=.s) : modname = $(modname-multi) +$(multi-objs-m:.o=.lst) : modname = $(modname-multi) +$(multi-objs-y) : modname = $(modname-multi) +$(multi-objs-y:.o=.i) : modname = $(modname-multi) +$(multi-objs-y:.o=.s) : modname = $(modname-multi) +$(multi-objs-y:.o=.lst) : modname = $(modname-multi) + +quiet_cmd_cc_s_c = CC $(quiet_modtag) $@ +cmd_cc_s_c = $(CC) $(c_flags) -fverbose-asm -S -o $@ $< + +%.s: %.c FORCE + $(call if_changed_dep,cc_s_c) + +quiet_cmd_cc_i_c = CPP $(quiet_modtag) $@ +cmd_cc_i_c = $(CPP) $(c_flags) -o $@ $< + +%.i: %.c FORCE + $(call if_changed_dep,cc_i_c) + +# C (.c) files +# The C file is compiled and updated dependency information is generated. +# (See cmd_cc_o_c + relevant part of rule_cc_o_c) + +quiet_cmd_cc_o_c = CC $(quiet_modtag) $@ + +ifndef CONFIG_MODVERSIONS +cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< + +else +# When module versioning is enabled the following steps are executed: +# o compile a .tmp_.o from .c +# o if .tmp_.o doesn't contain a __ksymtab version, i.e. does +# not export symbols, we just rename .tmp_.o to .o and +# are done. +# o otherwise, we calculate symbol versions using the good old +# genksyms on the preprocessed source and postprocess them in a way +# that they are usable as a linker script +# o generate .o from .tmp_.o using the linker to +# replace the unresolved symbols __crc_exported_symbol with +# the actual value of the checksum generated by genksyms + +cmd_cc_o_c = $(CC) $(c_flags) -c -o $(@D)/.tmp_$(@F) $< +cmd_modversions = \ + if $(OBJDUMP) -h $(@D)/.tmp_$(@F) | grep -q __ksymtab; then \ + $(CPP) -D__GENKSYMS__ $(c_flags) $< \ + | $(GENKSYMS) -a $(ARCH) \ + > $(@D)/.tmp_$(@F:.o=.ver); \ + \ + $(LD) $(LDFLAGS) -r -o $@ $(@D)/.tmp_$(@F) \ + -T $(@D)/.tmp_$(@F:.o=.ver); \ + rm -f $(@D)/.tmp_$(@F) $(@D)/.tmp_$(@F:.o=.ver); \ + else \ + mv -f $(@D)/.tmp_$(@F) $@; \ + fi; +endif + +define rule_cc_o_c + $(call echo-cmd,checksrc) $(cmd_checksrc) \ + $(call echo-cmd,cc_o_c) $(cmd_cc_o_c); \ + $(cmd_modversions) \ + scripts/basic/fixdep $(depfile) $@ '$(call make-cmd,cc_o_c)' > $(@D)/.$(@F).tmp; \ + rm -f $(depfile); \ + mv -f $(@D)/.$(@F).tmp $(@D)/.$(@F).cmd +endef + +# Built-in and composite module parts + +%.o: %.c FORCE + $(call cmd,force_checksrc) + $(call if_changed_rule,cc_o_c) + +# Single-part modules are special since we need to mark them in $(MODVERDIR) + +$(single-used-m): %.o: %.c FORCE + $(call cmd,force_checksrc) + $(call if_changed_rule,cc_o_c) + @{ echo $(@:.o=.ko); echo $@; } > $(MODVERDIR)/$(@F:.o=.mod) + +quiet_cmd_cc_lst_c = MKLST $@ + cmd_cc_lst_c = $(CC) $(c_flags) -g -c -o $*.o $< && \ + $(CONFIG_SHELL) $(srctree)/scripts/makelst $*.o \ + System.map $(OBJDUMP) > $@ + +%.lst: %.c FORCE + $(call if_changed_dep,cc_lst_c) + +# Compile assembler sources (.S) +# --------------------------------------------------------------------------- + +modkern_aflags := $(AFLAGS_KERNEL) + +$(real-objs-m) : modkern_aflags := $(AFLAGS_MODULE) +$(real-objs-m:.o=.s): modkern_aflags := $(AFLAGS_MODULE) + +quiet_cmd_as_s_S = CPP $(quiet_modtag) $@ +cmd_as_s_S = $(CPP) $(a_flags) -o $@ $< + +%.s: %.S FORCE + $(call if_changed_dep,as_s_S) + +quiet_cmd_as_o_S = AS $(quiet_modtag) $@ +cmd_as_o_S = $(CC) $(a_flags) -c -o $@ $< + +%.o: %.S FORCE + $(call if_changed_dep,as_o_S) + +targets += $(real-objs-y) $(real-objs-m) $(lib-y) +targets += $(extra-y) $(MAKECMDGOALS) $(always) + +# Linker scripts preprocessor (.lds.S -> .lds) +# --------------------------------------------------------------------------- +quiet_cmd_cpp_lds_S = LDS $@ + cmd_cpp_lds_S = $(CPP) $(cpp_flags) -D__ASSEMBLY__ -o $@ $< + +%.lds: %.lds.S FORCE + $(call if_changed_dep,cpp_lds_S) + +# Build the compiled-in targets +# --------------------------------------------------------------------------- + +# To build objects in subdirs, we need to descend into the directories +$(sort $(subdir-obj-y)): $(subdir-ym) ; + +# +# Rule to compile a set of .o files into one .o file +# +ifdef builtin-target +quiet_cmd_link_o_target = LD $@ +# If the list of objects to link is empty, just create an empty built-in.o +cmd_link_o_target = $(if $(strip $(obj-y)),\ + $(LD) $(ld_flags) -r -o $@ $(filter $(obj-y), $^),\ + rm -f $@; $(AR) rcs $@) + +$(builtin-target): $(obj-y) FORCE + $(call if_changed,link_o_target) + +targets += $(builtin-target) +endif # builtin-target + +# +# Rule to compile a set of .o files into one .a file +# +ifdef lib-target +quiet_cmd_link_l_target = AR $@ +cmd_link_l_target = rm -f $@; $(AR) $(EXTRA_ARFLAGS) rcs $@ $(lib-y) + +$(lib-target): $(lib-y) FORCE + $(call if_changed,link_l_target) + +targets += $(lib-target) +endif + +# +# Rule to link composite objects +# +# Composite objects are specified in kbuild makefile as follows: +# -objs := +# or +# -y := +link_multi_deps = \ +$(filter $(addprefix $(obj)/, \ +$($(subst $(obj)/,,$(@:.o=-objs))) \ +$($(subst $(obj)/,,$(@:.o=-y)))), $^) + +quiet_cmd_link_multi-y = LD $@ +cmd_link_multi-y = $(LD) $(ld_flags) -r -o $@ $(link_multi_deps) + +quiet_cmd_link_multi-m = LD [M] $@ +cmd_link_multi-m = $(LD) $(ld_flags) $(LDFLAGS_MODULE) -o $@ $(link_multi_deps) + +# We would rather have a list of rules like +# foo.o: $(foo-objs) +# but that's not so easy, so we rather make all composite objects depend +# on the set of all their parts +$(multi-used-y) : %.o: $(multi-objs-y) FORCE + $(call if_changed,link_multi-y) + +$(multi-used-m) : %.o: $(multi-objs-m) FORCE + $(call if_changed,link_multi-m) + @{ echo $(@:.o=.ko); echo $(link_multi_deps); } > $(MODVERDIR)/$(@F:.o=.mod) + +targets += $(multi-used-y) $(multi-used-m) + + +# Descending +# --------------------------------------------------------------------------- + +PHONY += $(subdir-ym) +$(subdir-ym): + $(Q)$(MAKE) $(build)=$@ + +# Add FORCE to the prequisites of a target to force it to be always rebuilt. +# --------------------------------------------------------------------------- + +PHONY += FORCE + +FORCE: + +# Read all saved command lines and dependencies for the $(targets) we +# may be building above, using $(if_changed{,_dep}). As an +# optimization, we don't need to read them if the target does not +# exist, we will rebuild anyway in that case. + +targets := $(wildcard $(sort $(targets))) +cmd_files := $(wildcard $(foreach f,$(targets),$(dir $(f)).$(notdir $(f)).cmd)) + +ifneq ($(cmd_files),) + include $(cmd_files) +endif + + +# Declare the contents of the .PHONY variable as phony. We keep that +# information in a variable se we can use it in if_changed and friends. + +.PHONY: $(PHONY) diff --git a/scripts/Makefile.clean b/scripts/Makefile.clean new file mode 100644 index 000000000..cff33498f --- /dev/null +++ b/scripts/Makefile.clean @@ -0,0 +1,102 @@ +# ========================================================================== +# Cleaning up +# ========================================================================== + +src := $(obj) + +PHONY := __clean +__clean: + +# Shorthand for $(Q)$(MAKE) scripts/Makefile.clean obj=dir +# Usage: +# $(Q)$(MAKE) $(clean)=dir +clean := -f $(if $(KBUILD_SRC),$(srctree)/)scripts/Makefile.clean obj + +# The filename Kbuild has precedence over Makefile +kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src)) +include $(if $(wildcard $(kbuild-dir)/Kbuild), $(kbuild-dir)/Kbuild, $(kbuild-dir)/Makefile) + +# Figure out what we need to build from the various variables +# ========================================================================== + +__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y))) +subdir-y += $(__subdir-y) +__subdir-m := $(patsubst %/,%,$(filter %/, $(obj-m))) +subdir-m += $(__subdir-m) +__subdir-n := $(patsubst %/,%,$(filter %/, $(obj-n))) +subdir-n += $(__subdir-n) +__subdir- := $(patsubst %/,%,$(filter %/, $(obj-))) +subdir- += $(__subdir-) + +# Subdirectories we need to descend into + +subdir-ym := $(sort $(subdir-y) $(subdir-m)) +subdir-ymn := $(sort $(subdir-ym) $(subdir-n) $(subdir-)) + +# Add subdir path + +subdir-ymn := $(addprefix $(obj)/,$(subdir-ymn)) + +# build a list of files to remove, usually releative to the current +# directory + +__clean-files := $(extra-y) $(EXTRA_TARGETS) $(always) \ + $(targets) $(clean-files) \ + $(host-progs) \ + $(hostprogs-y) $(hostprogs-m) $(hostprogs-) + +# as clean-files is given relative to the current directory, this adds +# a $(obj) prefix, except for absolute paths + +__clean-files := $(wildcard \ + $(addprefix $(obj)/, $(filter-out /%, $(__clean-files))) \ + $(filter /%, $(__clean-files))) + +# as clean-dirs is given relative to the current directory, this adds +# a $(obj) prefix, except for absolute paths + +__clean-dirs := $(wildcard \ + $(addprefix $(obj)/, $(filter-out /%, $(clean-dirs))) \ + $(filter /%, $(clean-dirs))) + +# ========================================================================== + +quiet_cmd_clean = CLEAN $(obj) + cmd_clean = rm -f $(__clean-files) +quiet_cmd_cleandir = CLEAN $(__clean-dirs) + cmd_cleandir = rm -rf $(__clean-dirs) + + +__clean: $(subdir-ymn) +ifneq ($(strip $(__clean-files)),) + +$(call cmd,clean) +endif +ifneq ($(strip $(__clean-dirs)),) + +$(call cmd,cleandir) +endif +ifneq ($(strip $(clean-rule)),) + +$(clean-rule) +endif + @: + + +# =========================================================================== +# Generic stuff +# =========================================================================== + +# Descending +# --------------------------------------------------------------------------- + +PHONY += $(subdir-ymn) +$(subdir-ymn): + $(Q)$(MAKE) $(clean)=$@ + +# If quiet is set, only print short version of command + +cmd = @$(if $($(quiet)cmd_$(1)),echo ' $($(quiet)cmd_$(1))' &&) $(cmd_$(1)) + + +# Declare the contents of the .PHONY variable as phony. We keep that +# information in a variable se we can use it in if_changed and friends. + +.PHONY: $(PHONY) diff --git a/scripts/Makefile.host b/scripts/Makefile.host new file mode 100644 index 000000000..2d519704b --- /dev/null +++ b/scripts/Makefile.host @@ -0,0 +1,156 @@ +# ========================================================================== +# Building binaries on the host system +# Binaries are used during the compilation of the kernel, for example +# to preprocess a data file. +# +# Both C and C++ is supported, but preferred language is C for such utilities. +# +# Samle syntax (see Documentation/kbuild/makefile.txt for reference) +# hostprogs-y := bin2hex +# Will compile bin2hex.c and create an executable named bin2hex +# +# hostprogs-y := lxdialog +# lxdialog-objs := checklist.o lxdialog.o +# Will compile lxdialog.c and checklist.c, and then link the executable +# lxdialog, based on checklist.o and lxdialog.o +# +# hostprogs-y := qconf +# qconf-cxxobjs := qconf.o +# qconf-objs := menu.o +# Will compile qconf as a C++ program, and menu as a C program. +# They are linked as C++ code to the executable qconf + +# hostprogs-y := conf +# conf-objs := conf.o libkconfig.so +# libkconfig-objs := expr.o type.o +# Will create a shared library named libkconfig.so that consist of +# expr.o and type.o (they are both compiled as C code and the object file +# are made as position independent code). +# conf.c is compiled as a c program, and conf.o is linked together with +# libkconfig.so as the executable conf. +# Note: Shared libraries consisting of C++ files are not supported + +__hostprogs := $(sort $(hostprogs-y)$(hostprogs-m)) + +# hostprogs-y := tools/build may have been specified. Retreive directory +obj-dirs += $(foreach f,$(__hostprogs), $(if $(dir $(f)),$(dir $(f)))) +obj-dirs := $(strip $(sort $(filter-out ./,$(obj-dirs)))) + + +# C code +# Executables compiled from a single .c file +host-csingle := $(foreach m,$(__hostprogs),$(if $($(m)-objs),,$(m))) + +# C executables linked based on several .o files +host-cmulti := $(foreach m,$(__hostprogs),\ + $(if $($(m)-cxxobjs),,$(if $($(m)-objs),$(m)))) + +# Object (.o) files compiled from .c files +host-cobjs := $(sort $(foreach m,$(__hostprogs),$($(m)-objs))) + +# C++ code +# C++ executables compiled from at least on .cc file +# and zero or more .c files +host-cxxmulti := $(foreach m,$(__hostprogs),$(if $($(m)-cxxobjs),$(m))) + +# C++ Object (.o) files compiled from .cc files +host-cxxobjs := $(sort $(foreach m,$(host-cxxmulti),$($(m)-cxxobjs))) + +# Shared libaries (only .c supported) +# Shared libraries (.so) - all .so files referenced in "xxx-objs" +host-cshlib := $(sort $(filter %.so, $(host-cobjs))) +# Remove .so files from "xxx-objs" +host-cobjs := $(filter-out %.so,$(host-cobjs)) + +#Object (.o) files used by the shared libaries +host-cshobjs := $(sort $(foreach m,$(host-cshlib),$($(m:.so=-objs)))) + +__hostprogs := $(addprefix $(obj)/,$(__hostprogs)) +host-csingle := $(addprefix $(obj)/,$(host-csingle)) +host-cmulti := $(addprefix $(obj)/,$(host-cmulti)) +host-cobjs := $(addprefix $(obj)/,$(host-cobjs)) +host-cxxmulti := $(addprefix $(obj)/,$(host-cxxmulti)) +host-cxxobjs := $(addprefix $(obj)/,$(host-cxxobjs)) +host-cshlib := $(addprefix $(obj)/,$(host-cshlib)) +host-cshobjs := $(addprefix $(obj)/,$(host-cshobjs)) +obj-dirs := $(addprefix $(obj)/,$(obj-dirs)) + +##### +# Handle options to gcc. Support building with separate output directory + +_hostc_flags = $(HOSTCFLAGS) $(HOST_EXTRACFLAGS) $(HOSTCFLAGS_$(*F).o) +_hostcxx_flags = $(HOSTCXXFLAGS) $(HOST_EXTRACXXFLAGS) $(HOSTCXXFLAGS_$(*F).o) + +ifeq ($(KBUILD_SRC),) +__hostc_flags = $(_hostc_flags) +__hostcxx_flags = $(_hostcxx_flags) +else +__hostc_flags = -I$(obj) $(call flags,_hostc_flags) +__hostcxx_flags = -I$(obj) $(call flags,_hostcxx_flags) +endif + +hostc_flags = -Wp,-MD,$(depfile) $(__hostc_flags) +hostcxx_flags = -Wp,-MD,$(depfile) $(__hostcxx_flags) + +##### +# Compile programs on the host + +# Create executable from a single .c file +# host-csingle -> Executable +quiet_cmd_host-csingle = HOSTCC $@ + cmd_host-csingle = $(HOSTCC) $(hostc_flags) -o $@ $< \ + $(HOST_LOADLIBES) $(HOSTLOADLIBES_$(@F)) +$(host-csingle): %: %.c FORCE + $(call if_changed_dep,host-csingle) + +# Link an executable based on list of .o files, all plain c +# host-cmulti -> executable +quiet_cmd_host-cmulti = HOSTLD $@ + cmd_host-cmulti = $(HOSTCC) $(HOSTLDFLAGS) -o $@ \ + $(addprefix $(obj)/,$($(@F)-objs)) \ + $(HOST_LOADLIBES) $(HOSTLOADLIBES_$(@F)) +$(host-cmulti): %: $(host-cobjs) $(host-cshlib) FORCE + $(call if_changed,host-cmulti) + +# Create .o file from a single .c file +# host-cobjs -> .o +quiet_cmd_host-cobjs = HOSTCC $@ + cmd_host-cobjs = $(HOSTCC) $(hostc_flags) -c -o $@ $< +$(host-cobjs): %.o: %.c FORCE + $(call if_changed_dep,host-cobjs) + +# Link an executable based on list of .o files, a mixture of .c and .cc +# host-cxxmulti -> executable +quiet_cmd_host-cxxmulti = HOSTLD $@ + cmd_host-cxxmulti = $(HOSTCXX) $(HOSTLDFLAGS) -o $@ \ + $(foreach o,objs cxxobjs,\ + $(addprefix $(obj)/,$($(@F)-$(o)))) \ + $(HOST_LOADLIBES) $(HOSTLOADLIBES_$(@F)) +$(host-cxxmulti): %: $(host-cobjs) $(host-cxxobjs) $(host-cshlib) FORCE + $(call if_changed,host-cxxmulti) + +# Create .o file from a single .cc (C++) file +quiet_cmd_host-cxxobjs = HOSTCXX $@ + cmd_host-cxxobjs = $(HOSTCXX) $(hostcxx_flags) -c -o $@ $< +$(host-cxxobjs): %.o: %.cc FORCE + $(call if_changed_dep,host-cxxobjs) + +# Compile .c file, create position independent .o file +# host-cshobjs -> .o +quiet_cmd_host-cshobjs = HOSTCC -fPIC $@ + cmd_host-cshobjs = $(HOSTCC) $(hostc_flags) -fPIC -c -o $@ $< +$(host-cshobjs): %.o: %.c FORCE + $(call if_changed_dep,host-cshobjs) + +# Link a shared library, based on position independent .o files +# *.o -> .so shared library (host-cshlib) +quiet_cmd_host-cshlib = HOSTLLD -shared $@ + cmd_host-cshlib = $(HOSTCC) $(HOSTLDFLAGS) -shared -o $@ \ + $(addprefix $(obj)/,$($(@F:.so=-objs))) \ + $(HOST_LOADLIBES) $(HOSTLOADLIBES_$(@F)) +$(host-cshlib): %: $(host-cshobjs) FORCE + $(call if_changed,host-cshlib) + +targets += $(host-csingle) $(host-cmulti) $(host-cobjs)\ + $(host-cxxmulti) $(host-cxxobjs) $(host-cshlib) $(host-cshobjs) + diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib new file mode 100644 index 000000000..2cb4935e8 --- /dev/null +++ b/scripts/Makefile.lib @@ -0,0 +1,165 @@ +# Backward compatibility - to be removed... +extra-y += $(EXTRA_TARGETS) +# Figure out what we need to build from the various variables +# =========================================================================== + +# When an object is listed to be built compiled-in and modular, +# only build the compiled-in version + +obj-m := $(filter-out $(obj-y),$(obj-m)) + +# Libraries are always collected in one lib file. +# Filter out objects already built-in + +lib-y := $(filter-out $(obj-y), $(sort $(lib-y) $(lib-m))) + + +# Handle objects in subdirs +# --------------------------------------------------------------------------- +# o if we encounter foo/ in $(obj-y), replace it by foo/built-in.o +# and add the directory to the list of dirs to descend into: $(subdir-y) +# o if we encounter foo/ in $(obj-m), remove it from $(obj-m) +# and add the directory to the list of dirs to descend into: $(subdir-m) + +__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y))) +subdir-y += $(__subdir-y) +__subdir-m := $(patsubst %/,%,$(filter %/, $(obj-m))) +subdir-m += $(__subdir-m) +obj-y := $(patsubst %/, %/built-in.o, $(obj-y)) +obj-m := $(filter-out %/, $(obj-m)) + +# Subdirectories we need to descend into + +subdir-ym := $(sort $(subdir-y) $(subdir-m)) + +# if $(foo-objs) exists, foo.o is a composite object +multi-used-y := $(sort $(foreach m,$(obj-y), $(if $(strip $($(m:.o=-objs)) $($(m:.o=-y))), $(m)))) +multi-used-m := $(sort $(foreach m,$(obj-m), $(if $(strip $($(m:.o=-objs)) $($(m:.o=-y))), $(m)))) +multi-used := $(multi-used-y) $(multi-used-m) +single-used-m := $(sort $(filter-out $(multi-used-m),$(obj-m))) + +# Build list of the parts of our composite objects, our composite +# objects depend on those (obviously) +multi-objs-y := $(foreach m, $(multi-used-y), $($(m:.o=-objs)) $($(m:.o=-y))) +multi-objs-m := $(foreach m, $(multi-used-m), $($(m:.o=-objs)) $($(m:.o=-y))) +multi-objs := $(multi-objs-y) $(multi-objs-m) + +# $(subdir-obj-y) is the list of objects in $(obj-y) which do not live +# in the local directory +subdir-obj-y := $(foreach o,$(obj-y),$(if $(filter-out $(o),$(notdir $(o))),$(o))) + +# $(obj-dirs) is a list of directories that contain object files +obj-dirs := $(dir $(multi-objs) $(subdir-obj-y)) + +# Replace multi-part objects by their individual parts, look at local dir only +real-objs-y := $(foreach m, $(filter-out $(subdir-obj-y), $(obj-y)), $(if $(strip $($(m:.o=-objs)) $($(m:.o=-y))),$($(m:.o=-objs)) $($(m:.o=-y)),$(m))) $(extra-y) +real-objs-m := $(foreach m, $(obj-m), $(if $(strip $($(m:.o=-objs)) $($(m:.o=-y))),$($(m:.o=-objs)) $($(m:.o=-y)),$(m))) + +# Add subdir path + +extra-y := $(addprefix $(obj)/,$(extra-y)) +always := $(addprefix $(obj)/,$(always)) +targets := $(addprefix $(obj)/,$(targets)) +obj-y := $(addprefix $(obj)/,$(obj-y)) +obj-m := $(addprefix $(obj)/,$(obj-m)) +lib-y := $(addprefix $(obj)/,$(lib-y)) +subdir-obj-y := $(addprefix $(obj)/,$(subdir-obj-y)) +real-objs-y := $(addprefix $(obj)/,$(real-objs-y)) +real-objs-m := $(addprefix $(obj)/,$(real-objs-m)) +single-used-m := $(addprefix $(obj)/,$(single-used-m)) +multi-used-y := $(addprefix $(obj)/,$(multi-used-y)) +multi-used-m := $(addprefix $(obj)/,$(multi-used-m)) +multi-objs-y := $(addprefix $(obj)/,$(multi-objs-y)) +multi-objs-m := $(addprefix $(obj)/,$(multi-objs-m)) +subdir-ym := $(addprefix $(obj)/,$(subdir-ym)) +obj-dirs := $(addprefix $(obj)/,$(obj-dirs)) + +# These flags are needed for modversions and compiling, so we define them here +# already +# $(modname_flags) #defines KBUILD_MODNAME as the name of the module it will +# end up in (or would, if it gets compiled in) +# Note: It's possible that one object gets potentially linked into more +# than one module. In that case KBUILD_MODNAME will be set to foo_bar, +# where foo and bar are the name of the modules. +name-fix = $(subst $(comma),_,$(subst -,_,$1)) +basename_flags = -D"KBUILD_BASENAME=KBUILD_STR($(call name-fix,$(*F)))" +modname_flags = $(if $(filter 1,$(words $(modname))),\ + -D"KBUILD_MODNAME=KBUILD_STR($(call name-fix,$(modname)))") + +_c_flags = $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$(*F).o) +_a_flags = $(AFLAGS) $(EXTRA_AFLAGS) $(AFLAGS_$(*F).o) +_cpp_flags = $(CPPFLAGS) $(EXTRA_CPPFLAGS) $(CPPFLAGS_$(@F)) + +# If building the kernel in a separate objtree expand all occurrences +# of -Idir to -I$(srctree)/dir except for absolute paths (starting with '/'). + +ifeq ($(KBUILD_SRC),) +__c_flags = $(_c_flags) +__a_flags = $(_a_flags) +__cpp_flags = $(_cpp_flags) +else + +# -I$(obj) locates generated .h files +# $(call addtree,-I$(obj)) locates .h files in srctree, from generated .c files +# and locates generated .h files +# FIXME: Replace both with specific CFLAGS* statements in the makefiles +__c_flags = $(call addtree,-I$(obj)) $(call flags,_c_flags) +__a_flags = $(call flags,_a_flags) +__cpp_flags = $(call flags,_cpp_flags) +endif + +c_flags = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(CPPFLAGS) \ + $(__c_flags) $(modkern_cflags) \ + -D"KBUILD_STR(s)=\#s" $(basename_flags) $(modname_flags) + +a_flags = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(CPPFLAGS) \ + $(__a_flags) $(modkern_aflags) + +cpp_flags = -Wp,-MD,$(depfile) $(NOSTDINC_FLAGS) $(__cpp_flags) + +ld_flags = $(LDFLAGS) $(EXTRA_LDFLAGS) + +# Finds the multi-part object the current object will be linked into +modname-multi = $(sort $(foreach m,$(multi-used),\ + $(if $(filter $(subst $(obj)/,,$*.o), $($(m:.o=-objs)) $($(m:.o=-y))),$(m:.o=)))) + +# Shipped files +# =========================================================================== + +quiet_cmd_shipped = SHIPPED $@ +cmd_shipped = cat $< > $@ + +$(obj)/%:: $(src)/%_shipped + $(call cmd,shipped) + +# Commands useful for building a boot image +# =========================================================================== +# +# Use as following: +# +# target: source(s) FORCE +# $(if_changed,ld/objcopy/gzip) +# +# and add target to EXTRA_TARGETS so that we know we have to +# read in the saved command line + +# Linking +# --------------------------------------------------------------------------- + +quiet_cmd_ld = LD $@ +cmd_ld = $(LD) $(LDFLAGS) $(EXTRA_LDFLAGS) $(LDFLAGS_$(@F)) \ + $(filter-out FORCE,$^) -o $@ + +# Objcopy +# --------------------------------------------------------------------------- + +quiet_cmd_objcopy = OBJCOPY $@ +cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@ + +# Gzip +# --------------------------------------------------------------------------- + +quiet_cmd_gzip = GZIP $@ +cmd_gzip = gzip -f -9 < $< > $@ + + diff --git a/scripts/basic/Makefile b/scripts/basic/Makefile new file mode 100644 index 000000000..119f079cf --- /dev/null +++ b/scripts/basic/Makefile @@ -0,0 +1,18 @@ +### +# Makefile.basic list the most basic programs used during the build process. +# The programs listed herein is what is needed to do the basic stuff, +# such as splitting .config and fix dependency file. +# This initial step is needed to avoid files to be recompiled +# when busybox configuration changes (which is what happens when +# .config is included by main Makefile. +# --------------------------------------------------------------------------- +# fixdep: Used to generate dependency information during build process +# split-include: Divide all config symbols up in a number of files in +# include/config/... +# docproc: Used in Documentation/docbook + +hostprogs-y := fixdep split-include docproc +always := $(hostprogs-y) + +# fixdep is needed to compile other host programs +$(addprefix $(obj)/,$(filter-out fixdep,$(always))): $(obj)/fixdep diff --git a/scripts/basic/docproc.c b/scripts/basic/docproc.c new file mode 100644 index 000000000..cb02baa63 --- /dev/null +++ b/scripts/basic/docproc.c @@ -0,0 +1,398 @@ +/* + * docproc is a simple preprocessor for the template files + * used as placeholders for the kernel internal documentation. + * docproc is used for documentation-frontend and + * dependency-generator. + * The two usages have in common that they require + * some knowledge of the .tmpl syntax, therefore they + * are kept together. + * + * documentation-frontend + * Scans the template file and call kernel-doc for + * all occurrences of ![EIF]file + * Beforehand each referenced file are scanned for + * any exported sympols "EXPORT_SYMBOL()" statements. + * This is used to create proper -function and + * -nofunction arguments in calls to kernel-doc. + * Usage: docproc doc file.tmpl + * + * dependency-generator: + * Scans the template file and list all files + * referenced in a format recognized by make. + * Usage: docproc depend file.tmpl + * Writes dependency information to stdout + * in the following format: + * file.tmpl src.c src2.c + * The filenames are obtained from the following constructs: + * !Efilename + * !Ifilename + * !Dfilename + * !Ffilename + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* exitstatus is used to keep track of any failing calls to kernel-doc, + * but execution continues. */ +int exitstatus = 0; + +typedef void DFL(char *); +DFL *defaultline; + +typedef void FILEONLY(char * file); +FILEONLY *internalfunctions; +FILEONLY *externalfunctions; +FILEONLY *symbolsonly; + +typedef void FILELINE(char * file, char * line); +FILELINE * singlefunctions; +FILELINE * entity_system; + +#define MAXLINESZ 2048 +#define MAXFILES 250 +#define KERNELDOCPATH "scripts/" +#define KERNELDOC "kernel-doc" +#define DOCBOOK "-docbook" +#define FUNCTION "-function" +#define NOFUNCTION "-nofunction" + +void usage (void) +{ + fprintf(stderr, "Usage: docproc {doc|depend} file\n"); + fprintf(stderr, "Input is read from file.tmpl. Output is sent to stdout\n"); + fprintf(stderr, "doc: frontend when generating kernel documentation\n"); + fprintf(stderr, "depend: generate list of files referenced within file\n"); +} + +/* + * Execute kernel-doc with parameters givin in svec + */ +void exec_kernel_doc(char **svec) +{ + pid_t pid; + int ret; + char real_filename[PATH_MAX + 1]; + /* Make sure output generated so far are flushed */ + fflush(stdout); + switch(pid=fork()) { + case -1: + perror("fork"); + exit(1); + case 0: + memset(real_filename, 0, sizeof(real_filename)); + strncat(real_filename, getenv("SRCTREE"), PATH_MAX); + strncat(real_filename, KERNELDOCPATH KERNELDOC, + PATH_MAX - strlen(real_filename)); + execvp(real_filename, svec); + fprintf(stderr, "exec "); + perror(real_filename); + exit(1); + default: + waitpid(pid, &ret ,0); + } + if (WIFEXITED(ret)) + exitstatus |= WEXITSTATUS(ret); + else + exitstatus = 0xff; +} + +/* Types used to create list of all exported symbols in a number of files */ +struct symbols +{ + char *name; +}; + +struct symfile +{ + char *filename; + struct symbols *symbollist; + int symbolcnt; +}; + +struct symfile symfilelist[MAXFILES]; +int symfilecnt = 0; + +void add_new_symbol(struct symfile *sym, char * symname) +{ + sym->symbollist = + realloc(sym->symbollist, (sym->symbolcnt + 1) * sizeof(char *)); + sym->symbollist[sym->symbolcnt++].name = strdup(symname); +} + +/* Add a filename to the list */ +struct symfile * add_new_file(char * filename) +{ + symfilelist[symfilecnt++].filename = strdup(filename); + return &symfilelist[symfilecnt - 1]; +} +/* Check if file already are present in the list */ +struct symfile * filename_exist(char * filename) +{ + int i; + for (i=0; i < symfilecnt; i++) + if (strcmp(symfilelist[i].filename, filename) == 0) + return &symfilelist[i]; + return NULL; +} + +/* + * List all files referenced within the template file. + * Files are separated by tabs. + */ +void adddep(char * file) { printf("\t%s", file); } +void adddep2(char * file, char * line) { line = line; adddep(file); } +void noaction(char * line) { line = line; } +void noaction2(char * file, char * line) { file = file; line = line; } + +/* Echo the line without further action */ +void printline(char * line) { printf("%s", line); } + +/* + * Find all symbols exported with EXPORT_SYMBOL and EXPORT_SYMBOL_GPL + * in filename. + * All symbols located are stored in symfilelist. + */ +void find_export_symbols(char * filename) +{ + FILE * fp; + struct symfile *sym; + char line[MAXLINESZ]; + if (filename_exist(filename) == NULL) { + char real_filename[PATH_MAX + 1]; + memset(real_filename, 0, sizeof(real_filename)); + strncat(real_filename, getenv("SRCTREE"), PATH_MAX); + strncat(real_filename, filename, + PATH_MAX - strlen(real_filename)); + sym = add_new_file(filename); + fp = fopen(real_filename, "r"); + if (fp == NULL) + { + fprintf(stderr, "docproc: "); + perror(real_filename); + } + while(fgets(line, MAXLINESZ, fp)) { + char *p; + char *e; + if (((p = strstr(line, "EXPORT_SYMBOL_GPL")) != 0) || + ((p = strstr(line, "EXPORT_SYMBOL")) != 0)) { + /* Skip EXPORT_SYMBOL{_GPL} */ + while (isalnum(*p) || *p == '_') + p++; + /* Remove paranteses and additional ws */ + while (isspace(*p)) + p++; + if (*p != '(') + continue; /* Syntax error? */ + else + p++; + while (isspace(*p)) + p++; + e = p; + while (isalnum(*e) || *e == '_') + e++; + *e = '\0'; + add_new_symbol(sym, p); + } + } + fclose(fp); + } +} + +/* + * Document all external or internal functions in a file. + * Call kernel-doc with following parameters: + * kernel-doc -docbook -nofunction function_name1 filename + * function names are obtained from all the the src files + * by find_export_symbols. + * intfunc uses -nofunction + * extfunc uses -function + */ +void docfunctions(char * filename, char * type) +{ + int i,j; + int symcnt = 0; + int idx = 0; + char **vec; + + for (i=0; i <= symfilecnt; i++) + symcnt += symfilelist[i].symbolcnt; + vec = malloc((2 + 2 * symcnt + 2) * sizeof(char*)); + if (vec == NULL) { + perror("docproc: "); + exit(1); + } + vec[idx++] = KERNELDOC; + vec[idx++] = DOCBOOK; + for (i=0; i < symfilecnt; i++) { + struct symfile * sym = &symfilelist[i]; + for (j=0; j < sym->symbolcnt; j++) { + vec[idx++] = type; + vec[idx++] = sym->symbollist[j].name; + } + } + vec[idx++] = filename; + vec[idx] = NULL; + printf("\n", filename); + exec_kernel_doc(vec); + fflush(stdout); + free(vec); +} +void intfunc(char * filename) { docfunctions(filename, NOFUNCTION); } +void extfunc(char * filename) { docfunctions(filename, FUNCTION); } + +/* + * Document spåecific function(s) in a file. + * Call kernel-doc with the following parameters: + * kernel-doc -docbook -function function1 [-function function2] + */ +void singfunc(char * filename, char * line) +{ + char *vec[200]; /* Enough for specific functions */ + int i, idx = 0; + int startofsym = 1; + vec[idx++] = KERNELDOC; + vec[idx++] = DOCBOOK; + + /* Split line up in individual parameters preceeded by FUNCTION */ + for (i=0; line[i]; i++) { + if (isspace(line[i])) { + line[i] = '\0'; + startofsym = 1; + continue; + } + if (startofsym) { + startofsym = 0; + vec[idx++] = FUNCTION; + vec[idx++] = &line[i]; + } + } + vec[idx++] = filename; + vec[idx] = NULL; + exec_kernel_doc(vec); +} + +/* + * Parse file, calling action specific functions for: + * 1) Lines containing !E + * 2) Lines containing !I + * 3) Lines containing !D + * 4) Lines containing !F + * 5) Default lines - lines not matching the above + */ +void parse_file(FILE *infile) +{ + char line[MAXLINESZ]; + char * s; + while(fgets(line, MAXLINESZ, infile)) { + if (line[0] == '!') { + s = line + 2; + switch (line[1]) { + case 'E': + while (*s && !isspace(*s)) s++; + *s = '\0'; + externalfunctions(line+2); + break; + case 'I': + while (*s && !isspace(*s)) s++; + *s = '\0'; + internalfunctions(line+2); + break; + case 'D': + while (*s && !isspace(*s)) s++; + *s = '\0'; + symbolsonly(line+2); + break; + case 'F': + /* filename */ + while (*s && !isspace(*s)) s++; + *s++ = '\0'; + /* function names */ + while (isspace(*s)) + s++; + singlefunctions(line +2, s); + break; + default: + defaultline(line); + } + } + else { + defaultline(line); + } + } + fflush(stdout); +} + + +int main(int argc, char *argv[]) +{ + FILE * infile; + if (argc != 3) { + usage(); + exit(1); + } + /* Open file, exit on error */ + infile = fopen(argv[2], "r"); + if (infile == NULL) { + fprintf(stderr, "docproc: "); + perror(argv[2]); + exit(2); + } + + if (strcmp("doc", argv[1]) == 0) + { + /* Need to do this in two passes. + * First pass is used to collect all symbols exported + * in the various files. + * Second pass generate the documentation. + * This is required because function are declared + * and exported in different files :-(( + */ + /* Collect symbols */ + defaultline = noaction; + internalfunctions = find_export_symbols; + externalfunctions = find_export_symbols; + symbolsonly = find_export_symbols; + singlefunctions = noaction2; + parse_file(infile); + + /* Rewind to start from beginning of file again */ + fseek(infile, 0, SEEK_SET); + defaultline = printline; + internalfunctions = intfunc; + externalfunctions = extfunc; + symbolsonly = printline; + singlefunctions = singfunc; + + parse_file(infile); + } + else if (strcmp("depend", argv[1]) == 0) + { + /* Create first part of dependency chain + * file.tmpl */ + printf("%s\t", argv[2]); + defaultline = noaction; + internalfunctions = adddep; + externalfunctions = adddep; + symbolsonly = adddep; + singlefunctions = adddep2; + parse_file(infile); + printf("\n"); + } + else + { + fprintf(stderr, "Unknown option: %s\n", argv[1]); + exit(1); + } + fclose(infile); + fflush(stdout); + return exitstatus; +} + diff --git a/scripts/basic/fixdep.c b/scripts/basic/fixdep.c new file mode 100644 index 000000000..df3446e35 --- /dev/null +++ b/scripts/basic/fixdep.c @@ -0,0 +1,399 @@ +/* + * "Optimize" a list of dependencies as spit out by gcc -MD + * for the kernel build + * =========================================================================== + * + * Author Kai Germaschewski + * Copyright 2002 by Kai Germaschewski + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * + * Introduction: + * + * gcc produces a very nice and correct list of dependencies which + * tells make when to remake a file. + * + * To use this list as-is however has the drawback that virtually + * every file in the kernel includes which then again + * includes + * + * If the user re-runs make *config, linux/autoconf.h will be + * regenerated. make notices that and will rebuild every file which + * includes autoconf.h, i.e. basically all files. This is extremely + * annoying if the user just changed CONFIG_HIS_DRIVER from n to m. + * + * So we play the same trick that "mkdep" played before. We replace + * the dependency on linux/autoconf.h by a dependency on every config + * option which is mentioned in any of the listed prequisites. + * + * To be exact, split-include populates a tree in include/config/, + * e.g. include/config/his/driver.h, which contains the #define/#undef + * for the CONFIG_HIS_DRIVER option. + * + * So if the user changes his CONFIG_HIS_DRIVER option, only the objects + * which depend on "include/linux/config/his/driver.h" will be rebuilt, + * so most likely only his driver ;-) + * + * The idea above dates, by the way, back to Michael E Chastain, AFAIK. + * + * So to get dependencies right, there are two issues: + * o if any of the files the compiler read changed, we need to rebuild + * o if the command line given to the compile the file changed, we + * better rebuild as well. + * + * The former is handled by using the -MD output, the later by saving + * the command line used to compile the old object and comparing it + * to the one we would now use. + * + * Again, also this idea is pretty old and has been discussed on + * kbuild-devel a long time ago. I don't have a sensibly working + * internet connection right now, so I rather don't mention names + * without double checking. + * + * This code here has been based partially based on mkdep.c, which + * says the following about its history: + * + * Copyright abandoned, Michael Chastain, . + * This is a C version of syncdep.pl by Werner Almesberger. + * + * + * It is invoked as + * + * fixdep + * + * and will read the dependency file + * + * The transformed dependency snipped is written to stdout. + * + * It first generates a line + * + * cmd_ = + * + * and then basically copies the ..d file to stdout, in the + * process filtering out the dependency on linux/autoconf.h and adding + * dependencies on include/config/my/option.h for every + * CONFIG_MY_OPTION encountered in any of the prequisites. + * + * It will also filter out all the dependencies on *.ver. We need + * to make sure that the generated version checksum are globally up + * to date before even starting the recursive build, so it's too late + * at this point anyway. + * + * The algorithm to grep for "CONFIG_..." is bit unusual, but should + * be fast ;-) We don't even try to really parse the header files, but + * merely grep, i.e. if CONFIG_FOO is mentioned in a comment, it will + * be picked up as well. It's not a problem with respect to + * correctness, since that can only give too many dependencies, thus + * we cannot miss a rebuild. Since people tend to not mention totally + * unrelated CONFIG_ options all over the place, it's not an + * efficiency problem either. + * + * (Note: it'd be easy to port over the complete mkdep state machine, + * but I don't think the added complexity is worth it) + */ +/* + * Note 2: if somebody writes HELLO_CONFIG_BOOM in a file, it will depend onto + * CONFIG_BOOM. This could seem a bug (not too hard to fix), but please do not + * fix it! Some UserModeLinux files (look at arch/um/) call CONFIG_BOOM as + * UML_CONFIG_BOOM, to avoid conflicts with /usr/include/linux/autoconf.h, + * through arch/um/include/uml-config.h; this fixdep "bug" makes sure that + * those files will have correct dependencies. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* bbox: not needed +#define INT_CONF ntohl(0x434f4e46) +#define INT_ONFI ntohl(0x4f4e4649) +#define INT_NFIG ntohl(0x4e464947) +#define INT_FIG_ ntohl(0x4649475f) +*/ + +char *target; +char *depfile; +char *cmdline; + +void usage(void) + +{ + fprintf(stderr, "Usage: fixdep \n"); + exit(1); +} + +/* + * Print out the commandline prefixed with cmd_ := + */ +void print_cmdline(void) +{ + printf("cmd_%s := %s\n\n", target, cmdline); +} + +char * str_config = NULL; +int size_config = 0; +int len_config = 0; + +/* + * Grow the configuration string to a desired length. + * Usually the first growth is plenty. + */ +void grow_config(int len) +{ + while (len_config + len > size_config) { + if (size_config == 0) + size_config = 2048; + str_config = realloc(str_config, size_config *= 2); + if (str_config == NULL) + { perror("fixdep:malloc"); exit(1); } + } +} + + + +/* + * Lookup a value in the configuration string. + */ +int is_defined_config(const char * name, int len) +{ + const char * pconfig; + const char * plast = str_config + len_config - len; + for ( pconfig = str_config + 1; pconfig < plast; pconfig++ ) { + if (pconfig[ -1] == '\n' + && pconfig[len] == '\n' + && !memcmp(pconfig, name, len)) + return 1; + } + return 0; +} + +/* + * Add a new value to the configuration string. + */ +void define_config(const char * name, int len) +{ + grow_config(len + 1); + + memcpy(str_config+len_config, name, len); + len_config += len; + str_config[len_config++] = '\n'; +} + +/* + * Clear the set of configuration strings. + */ +void clear_config(void) +{ + len_config = 0; + define_config("", 0); +} + +/* + * Record the use of a CONFIG_* word. + */ +void use_config(char *m, int slen) +{ + char s[PATH_MAX]; + char *p; + + if (is_defined_config(m, slen)) + return; + + define_config(m, slen); + + memcpy(s, m, slen); s[slen] = 0; + + for (p = s; p < s + slen; p++) { + if (*p == '_') + *p = '/'; + else + *p = tolower((int)*p); + } + printf(" $(wildcard include/config/%s.h) \\\n", s); +} + +void parse_config_file(char *map, size_t len) +{ + /* modified for bbox */ + char *end = map + len; + char *p = map; + char *q; + int off; + + for (; p < end; p++) { + if (!memcmp(p, "CONFIG_", 7)) goto conf7; + if (!memcmp(p, "ENABLE_", 7)) goto conf7; + if (!memcmp(p, "USE_", 4)) goto conf4; + if (!memcmp(p, "SKIP_", 5)) goto conf5; + continue; + conf4: off = 4; goto conf; + conf5: off = 5; goto conf; + conf7: off = 7; + conf: + if (p > map + len - off) + continue; + for (q = p + off; q < map + len; q++) { + if (!(isalnum(*q) || *q == '_')) + goto found; + } + continue; + + found: + use_config(p+off, q-p-off); + } +} + +/* test is s ends in sub */ +int strrcmp(char *s, char *sub) +{ + int slen = strlen(s); + int sublen = strlen(sub); + + if (sublen > slen) + return 1; + + return memcmp(s + slen - sublen, sub, sublen); +} + +void do_config_file(char *filename) +{ + struct stat st; + int fd; + void *map; + + fd = open(filename, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "fixdep: "); + perror(filename); + exit(2); + } + fstat(fd, &st); + if (st.st_size == 0) { + close(fd); + return; + } + map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if ((long) map == -1) { + perror("fixdep: mmap"); + close(fd); + return; + } + + parse_config_file(map, st.st_size); + + munmap(map, st.st_size); + + close(fd); +} + +void parse_dep_file(void *map, size_t len) +{ + char *m = map; + char *end = m + len; + char *p; + char s[PATH_MAX]; + + p = strchr(m, ':'); + if (!p) { + fprintf(stderr, "fixdep: parse error\n"); + exit(1); + } + memcpy(s, m, p-m); s[p-m] = 0; + printf("deps_%s := \\\n", target); + m = p+1; + + clear_config(); + + while (m < end) { + while (m < end && (*m == ' ' || *m == '\\' || *m == '\n')) + m++; + p = m; + while (p < end && *p != ' ') p++; + if (p == end) { + do p--; while (!isalnum(*p)); + p++; + } + memcpy(s, m, p-m); s[p-m] = 0; + if (strrcmp(s, "include/autoconf.h") && + strrcmp(s, "arch/um/include/uml-config.h") && + strrcmp(s, ".ver")) { + printf(" %s \\\n", s); + do_config_file(s); + } + m = p + 1; + } + printf("\n%s: $(deps_%s)\n\n", target, target); + printf("$(deps_%s):\n", target); +} + +void print_deps(void) +{ + struct stat st; + int fd; + void *map; + + fd = open(depfile, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "fixdep: "); + perror(depfile); + exit(2); + } + fstat(fd, &st); + if (st.st_size == 0) { + fprintf(stderr,"fixdep: %s is empty\n",depfile); + close(fd); + return; + } + map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if ((long) map == -1) { + perror("fixdep: mmap"); + close(fd); + return; + } + + parse_dep_file(map, st.st_size); + + munmap(map, st.st_size); + + close(fd); +} + +void traps(void) +{ +/* bbox: not needed + static char test[] __attribute__((aligned(sizeof(int)))) = "CONF"; + + if (*(int *)test != INT_CONF) { + fprintf(stderr, "fixdep: sizeof(int) != 4 or wrong endianess? %#x\n", + *(int *)test); + exit(2); + } +*/ +} + +int main(int argc, char *argv[]) +{ + traps(); + + if (argc != 4) + usage(); + + depfile = argv[1]; + target = argv[2]; + cmdline = argv[3]; + + print_cmdline(); + print_deps(); + + return 0; +} diff --git a/scripts/basic/split-include.c b/scripts/basic/split-include.c new file mode 100644 index 000000000..459c45276 --- /dev/null +++ b/scripts/basic/split-include.c @@ -0,0 +1,226 @@ +/* + * split-include.c + * + * Copyright abandoned, Michael Chastain, . + * This is a C version of syncdep.pl by Werner Almesberger. + * + * This program takes autoconf.h as input and outputs a directory full + * of one-line include files, merging onto the old values. + * + * Think of the configuration options as key-value pairs. Then there + * are five cases: + * + * key old value new value action + * + * KEY-1 VALUE-1 VALUE-1 leave file alone + * KEY-2 VALUE-2A VALUE-2B write VALUE-2B into file + * KEY-3 - VALUE-3 write VALUE-3 into file + * KEY-4 VALUE-4 - write an empty file + * KEY-5 (empty) - leave old empty file alone + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define ERROR_EXIT(strExit) \ + { \ + const int errnoSave = errno; \ + fprintf(stderr, "%s: ", str_my_name); \ + errno = errnoSave; \ + perror((strExit)); \ + exit(1); \ + } + + + +int main(int argc, const char * argv []) +{ + const char * str_my_name; + const char * str_file_autoconf; + const char * str_dir_config; + + FILE * fp_config; + FILE * fp_target; + FILE * fp_find; + + int buffer_size; + + char * line; + char * old_line; + char * list_target; + char * ptarget; + + struct stat stat_buf; + + /* Check arg count. */ + if (argc != 3) + { + fprintf(stderr, "%s: wrong number of arguments.\n", argv[0]); + exit(1); + } + + str_my_name = argv[0]; + str_file_autoconf = argv[1]; + str_dir_config = argv[2]; + + /* Find a buffer size. */ + if (stat(str_file_autoconf, &stat_buf) != 0) + ERROR_EXIT(str_file_autoconf); + buffer_size = 2 * stat_buf.st_size + 4096; + + /* Allocate buffers. */ + if ( (line = malloc(buffer_size)) == NULL + || (old_line = malloc(buffer_size)) == NULL + || (list_target = malloc(buffer_size)) == NULL ) + ERROR_EXIT(str_file_autoconf); + + /* Open autoconfig file. */ + if ((fp_config = fopen(str_file_autoconf, "r")) == NULL) + ERROR_EXIT(str_file_autoconf); + + /* Make output directory if needed. */ + if (stat(str_dir_config, &stat_buf) != 0) + { + if (mkdir(str_dir_config, 0755) != 0) + ERROR_EXIT(str_dir_config); + } + + /* Change to output directory. */ + if (chdir(str_dir_config) != 0) + ERROR_EXIT(str_dir_config); + + /* Put initial separator into target list. */ + ptarget = list_target; + *ptarget++ = '\n'; + + /* Read config lines. */ + while (fgets(line, buffer_size, fp_config)) + { + const char * str_config; + int is_same; + int itarget; + + if (line[0] != '#') + continue; + if ((str_config = strstr(line, "CONFIG_")) == NULL) + continue; + + /* Make the output file name. */ + str_config += sizeof("CONFIG_") - 1; + for (itarget = 0; !isspace(str_config[itarget]); itarget++) + { + int c = (unsigned char) str_config[itarget]; + if (isupper(c)) c = tolower(c); + if (c == '_') c = '/'; + ptarget[itarget] = c; + } + ptarget[itarget++] = '.'; + ptarget[itarget++] = 'h'; + ptarget[itarget++] = '\0'; + + /* Check for existing file. */ + is_same = 0; + if ((fp_target = fopen(ptarget, "r")) != NULL) + { + fgets(old_line, buffer_size, fp_target); + if (fclose(fp_target) != 0) + ERROR_EXIT(ptarget); + if (!strcmp(line, old_line)) + is_same = 1; + } + + if (!is_same) + { + /* Auto-create directories. */ + int islash; + for (islash = 0; islash < itarget; islash++) + { + if (ptarget[islash] == '/') + { + ptarget[islash] = '\0'; + if (stat(ptarget, &stat_buf) != 0 + && mkdir(ptarget, 0755) != 0) + ERROR_EXIT( ptarget ); + ptarget[islash] = '/'; + } + } + + /* Write the file. */ + if ((fp_target = fopen(ptarget, "w" )) == NULL) + ERROR_EXIT(ptarget); + fputs(line, fp_target); + if (ferror(fp_target) || fclose(fp_target) != 0) + ERROR_EXIT(ptarget); + } + + /* Update target list */ + ptarget += itarget; + *(ptarget-1) = '\n'; + } + + /* + * Close autoconfig file. + * Terminate the target list. + */ + if (fclose(fp_config) != 0) + ERROR_EXIT(str_file_autoconf); + *ptarget = '\0'; + + /* + * Fix up existing files which have no new value. + * This is Case 4 and Case 5. + * + * I re-read the tree and filter it against list_target. + * This is crude. But it avoids data copies. Also, list_target + * is compact and contiguous, so it easily fits into cache. + * + * Notice that list_target contains strings separated by \n, + * with a \n before the first string and after the last. + * fgets gives the incoming names a terminating \n. + * So by having an initial \n, strstr will find exact matches. + */ + + fp_find = popen("find * -type f -name \"*.h\" -print", "r"); + if (fp_find == 0) + ERROR_EXIT( "find" ); + + line[0] = '\n'; + while (fgets(line+1, buffer_size, fp_find)) + { + if (strstr(list_target, line) == NULL) + { + /* + * This is an old file with no CONFIG_* flag in autoconf.h. + */ + + /* First strip the \n. */ + line[strlen(line)-1] = '\0'; + + /* Grab size. */ + if (stat(line+1, &stat_buf) != 0) + ERROR_EXIT(line); + + /* If file is not empty, make it empty and give it a fresh date. */ + if (stat_buf.st_size != 0) + { + if ((fp_target = fopen(line+1, "w")) == NULL) + ERROR_EXIT(line); + if (fclose(fp_target) != 0) + ERROR_EXIT(line); + } + } + } + + if (pclose(fp_find) != 0) + ERROR_EXIT("find"); + + return 0; +} diff --git a/scripts/bloat-o-meter b/scripts/bloat-o-meter new file mode 100755 index 000000000..67b3d290d --- /dev/null +++ b/scripts/bloat-o-meter @@ -0,0 +1,65 @@ +#!/usr/bin/python +# +# Copyright 2004 Matt Mackall +# +# inspired by perl Bloat-O-Meter (c) 1997 by Andi Kleen +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +import sys, os, re + +if len(sys.argv) != 3: + sys.stderr.write("usage: %s file1 file2\n" % sys.argv[0]) + sys.exit(-1) + +def getsizes(file): + sym = {} + for l in os.popen("nm --size-sort " + file).readlines(): + size, type, name = l[:-1].split() + if type in "tTdDbBrR": + if "." in name: name = "static." + name.split(".")[0] + sym[name] = sym.get(name, 0) + int(size, 16) + for l in os.popen("readelf -S " + file).readlines(): + x = l.split() + if len(x)<6 or x[1] != ".rodata": continue + sym[".rodata"] = int(x[5], 16) + return sym + +old = getsizes(sys.argv[1]) +new = getsizes(sys.argv[2]) +grow, shrink, add, remove, up, down = 0, 0, 0, 0, 0, 0 +delta, common = [], {} + +for a in old: + if a in new: + common[a] = 1 + +for name in old: + if name not in common: + remove += 1 + down += old[name] + delta.append((-old[name], name)) + +for name in new: + if name not in common: + add += 1 + up += new[name] + delta.append((new[name], name)) + +for name in common: + d = new.get(name, 0) - old.get(name, 0) + if d>0: grow, up = grow+1, up+d + if d<0: shrink, down = shrink+1, down-d + delta.append((d, name)) + +delta.sort() +delta.reverse() + +print "%-48s %7s %7s %+7s" % ("function", "old", "new", "delta") +for d, n in delta: + if d: print "%-48s %7s %7s %+7d" % (n, old.get(n,"-"), new.get(n,"-"), d) +print "-"*78 +total="(add/remove: %s/%s grow/shrink: %s/%s up/down: %s/%s)%%sTotal: %s bytes"\ + % (add, remove, grow, shrink, up, -down, up-down) +print total % (" "*(80-len(total))) diff --git a/scripts/checkhelp.awk b/scripts/checkhelp.awk new file mode 100755 index 000000000..4bb4996dc --- /dev/null +++ b/scripts/checkhelp.awk @@ -0,0 +1,40 @@ +#!/usr/bin/awk -f +# AWK script to check for missing help entries for config options +# +# Copyright (C) 2006 Bernhard Fischer +# +# This file is distributed under the terms and conditions of the +# MIT/X public licenses. See http://opensource.org/licenses/mit-license.html +# and notice http://www.gnu.org/licenses/license-list.html#X11License + + +/^choice/ { is_choice = 1; } +/^endchoice/ { is_choice = 0; } +/^config/ { + pos++; + conf[pos] = $2; + file[pos] = FILENAME; + if (is_choice) { + help[pos] = 1; # do not warn about 'choice' config entries. + } else { + help[pos] = 0; + } +} +/^[ \t]*help[ \t]*$/ { + help[pos] = 1; +} +/^[ \t]*bool[ \t]*$/ { + help[pos] = 1; # ignore options which are not selectable +} +BEGIN { + pos = -1; + is_choice = 0; +} +END { + for (i = 0; i <= pos; i++) { +# printf("%s: help for #%i '%s' == %i\n", file[i], i, conf[i], help[i]); + if (help[i] == 0) { + printf("%s: No helptext for '%s'\n", file[i], conf[i]); + } + } +} diff --git a/scripts/defconfig b/scripts/defconfig new file mode 100644 index 000000000..102c21a5f --- /dev/null +++ b/scripts/defconfig @@ -0,0 +1,665 @@ +# +# +CONFIG_HAVE_DOT_CONFIG=y + +# +# Busybox Settings +# + +# +# General Configuration +# +# CONFIG_NITPICK is not set +# CONFIG_DESKTOP is not set +# CONFIG_FEATURE_BUFFERS_USE_MALLOC is not set +# CONFIG_FEATURE_BUFFERS_GO_ON_STACK is not set +# CONFIG_FEATURE_BUFFERS_GO_IN_BSS is not set +CONFIG_SHOW_USAGE=y +CONFIG_FEATURE_VERBOSE_USAGE=y +CONFIG_FEATURE_COMPRESS_USAGE=y +CONFIG_FEATURE_INSTALLER=y +CONFIG_LOCALE_SUPPORT=y +CONFIG_GETOPT_LONG=y +CONFIG_FEATURE_DEVPTS=y +# CONFIG_FEATURE_CLEAN_UP is not set +CONFIG_FEATURE_SUID=y +CONFIG_FEATURE_SYSLOG=y +CONFIG_FEATURE_SUID_CONFIG=y +CONFIG_FEATURE_SUID_CONFIG_QUIET=y +# CONFIG_SELINUX is not set +CONFIG_BUSYBOX_EXEC_PATH="/proc/self/exe" + +# +# Build Options +# +# CONFIG_STATIC is not set +# CONFIG_BUILD_LIBBUSYBOX is not set +# CONFIG_FEATURE_FULL_LIBBUSYBOX is not set +# CONFIG_FEATURE_SHARED_BUSYBOX is not set +CONFIG_LFS=y +# CONFIG_BUILD_AT_ONCE is not set + +# +# Debugging Options +# +# CONFIG_DEBUG is not set +# CONFIG_DEBUG_PESSIMIZE is not set +# CONFIG_NO_DEBUG_LIB is not set +# CONFIG_DMALLOC is not set +# CONFIG_EFENCE is not set +CONFIG_DEBUG_YANK_SUSv2=y + +# +# Installation Options +# +# CONFIG_INSTALL_NO_USR is not set +CONFIG_INSTALL_APPLET_SYMLINKS=y +# CONFIG_INSTALL_APPLET_HARDLINKS is not set +# CONFIG_INSTALL_APPLET_DONT is not set +CONFIG_PREFIX="./_install" + +# +# Busybox Library Tuning +# +CONFIG_PASSWORD_MINLEN=6 +CONFIG_MD5_SIZE_VS_SPEED=2 + +# +# Applets +# + +# +# Archival Utilities +# +CONFIG_AR=y +CONFIG_FEATURE_AR_LONG_FILENAMES=y +CONFIG_BUNZIP2=y +CONFIG_CPIO=y +# CONFIG_DPKG is not set +# CONFIG_DPKG_DEB is not set +# CONFIG_FEATURE_DPKG_DEB_EXTRACT_ONLY is not set +CONFIG_GUNZIP=y +CONFIG_FEATURE_GUNZIP_UNCOMPRESS=y +CONFIG_GZIP=y +CONFIG_RPM2CPIO=y +CONFIG_RPM=y +CONFIG_TAR=y +CONFIG_FEATURE_TAR_CREATE=y +CONFIG_FEATURE_TAR_BZIP2=y +CONFIG_FEATURE_TAR_LZMA=y +CONFIG_FEATURE_TAR_FROM=y +CONFIG_FEATURE_TAR_GZIP=y +CONFIG_FEATURE_TAR_COMPRESS=y +CONFIG_FEATURE_TAR_OLDGNU_COMPATIBILITY=y +CONFIG_FEATURE_TAR_GNU_EXTENSIONS=y +CONFIG_FEATURE_TAR_LONG_OPTIONS=y +CONFIG_UNCOMPRESS=y +CONFIG_UNLZMA=y +CONFIG_FEATURE_LZMA_FAST=y +CONFIG_UNZIP=y + +# +# Common options for cpio and tar +# +CONFIG_FEATURE_UNARCHIVE_TAPE=y +# CONFIG_FEATURE_DEB_TAR_GZ is not set +# CONFIG_FEATURE_DEB_TAR_BZ2 is not set +# CONFIG_FEATURE_DEB_TAR_LZMA is not set + +# +# Coreutils +# +CONFIG_BASENAME=y +CONFIG_CAL=y +CONFIG_CAT=y +CONFIG_CATV=y +CONFIG_CHGRP=y +CONFIG_CHMOD=y +CONFIG_CHOWN=y +CONFIG_CHROOT=y +CONFIG_CKSUM=y +CONFIG_CMP=y +CONFIG_COMM=y +CONFIG_CP=y +CONFIG_CUT=y +CONFIG_DATE=y +CONFIG_FEATURE_DATE_ISOFMT=y +CONFIG_DD=y +CONFIG_FEATURE_DD_SIGNAL_HANDLING=y +CONFIG_FEATURE_DD_IBS_OBS=y +CONFIG_DF=y +CONFIG_DIFF=y +CONFIG_FEATURE_DIFF_BINARY=y +CONFIG_FEATURE_DIFF_DIR=y +CONFIG_FEATURE_DIFF_MINIMAL=y +CONFIG_DIRNAME=y +CONFIG_DOS2UNIX=y +CONFIG_UNIX2DOS=y +CONFIG_DU=y +CONFIG_FEATURE_DU_DEFAULT_BLOCKSIZE_1K=y +CONFIG_ECHO=y +CONFIG_FEATURE_FANCY_ECHO=y +CONFIG_ENV=y +CONFIG_FEATURE_ENV_LONG_OPTIONS=y +CONFIG_EXPR=y +CONFIG_EXPR_MATH_SUPPORT_64=y +CONFIG_FALSE=y +CONFIG_FOLD=y +CONFIG_HEAD=y +CONFIG_FEATURE_FANCY_HEAD=y +CONFIG_HOSTID=y +CONFIG_ID=y +CONFIG_INSTALL=y +CONFIG_FEATURE_INSTALL_LONG_OPTIONS=y +CONFIG_LENGTH=y +CONFIG_LN=y +CONFIG_LOGNAME=y +CONFIG_LS=y +CONFIG_FEATURE_LS_FILETYPES=y +CONFIG_FEATURE_LS_FOLLOWLINKS=y +CONFIG_FEATURE_LS_RECURSIVE=y +CONFIG_FEATURE_LS_SORTFILES=y +CONFIG_FEATURE_LS_TIMESTAMPS=y +CONFIG_FEATURE_LS_USERNAME=y +CONFIG_FEATURE_LS_COLOR=y +CONFIG_FEATURE_LS_COLOR_IS_DEFAULT=y +CONFIG_MD5SUM=y +CONFIG_MKDIR=y +CONFIG_FEATURE_MKDIR_LONG_OPTIONS=y +CONFIG_MKFIFO=y +CONFIG_MKNOD=y +CONFIG_MV=y +CONFIG_FEATURE_MV_LONG_OPTIONS=y +CONFIG_NICE=y +CONFIG_NOHUP=y +CONFIG_OD=y +CONFIG_PRINTENV=y +CONFIG_PRINTF=y +CONFIG_PWD=y +CONFIG_REALPATH=y +CONFIG_RM=y +CONFIG_RMDIR=y +CONFIG_SEQ=y +CONFIG_SHA1SUM=y +CONFIG_SLEEP=y +CONFIG_FEATURE_FANCY_SLEEP=y +CONFIG_SORT=y +CONFIG_FEATURE_SORT_BIG=y +CONFIG_STAT=y +CONFIG_FEATURE_STAT_FORMAT=y +CONFIG_STTY=y +CONFIG_SUM=y +CONFIG_SYNC=y +CONFIG_TAIL=y +CONFIG_FEATURE_FANCY_TAIL=y +CONFIG_TEE=y +CONFIG_FEATURE_TEE_USE_BLOCK_IO=y +CONFIG_TEST=y +CONFIG_FEATURE_TEST_64=y +CONFIG_TOUCH=y +CONFIG_TR=y +CONFIG_FEATURE_TR_CLASSES=y +CONFIG_FEATURE_TR_EQUIV=y +CONFIG_TRUE=y +CONFIG_TTY=y +CONFIG_UNAME=y +CONFIG_UNIQ=y +CONFIG_USLEEP=y +CONFIG_UUDECODE=y +CONFIG_UUENCODE=y +CONFIG_WATCH=y +CONFIG_WC=y +CONFIG_FEATURE_WC_LARGE=y +CONFIG_WHO=y +CONFIG_WHOAMI=y +CONFIG_YES=y + +# +# Common options for cp and mv +# +CONFIG_FEATURE_PRESERVE_HARDLINKS=y + +# +# Common options for ls, more and telnet +# +CONFIG_FEATURE_AUTOWIDTH=y + +# +# Common options for df, du, ls +# +CONFIG_FEATURE_HUMAN_READABLE=y + +# +# Common options for md5sum, sha1sum +# +CONFIG_FEATURE_MD5_SHA1_SUM_CHECK=y + +# +# Console Utilities +# +CONFIG_CHVT=y +CONFIG_CLEAR=y +CONFIG_DEALLOCVT=y +CONFIG_DUMPKMAP=y +CONFIG_LOADFONT=y +CONFIG_LOADKMAP=y +CONFIG_OPENVT=y +CONFIG_RESET=y +CONFIG_RESIZE=y +CONFIG_FEATURE_RESIZE_PRINT=y +CONFIG_SETCONSOLE=y +# CONFIG_FEATURE_SETCONSOLE_LONG_OPTIONS is not set +CONFIG_SETKEYCODES=y +CONFIG_SETLOGCONS=y + +# +# Debian Utilities +# +CONFIG_MKTEMP=y +CONFIG_PIPE_PROGRESS=y +CONFIG_READLINK=y +CONFIG_FEATURE_READLINK_FOLLOW=y +CONFIG_RUN_PARTS=y +CONFIG_FEATURE_RUN_PARTS_LONG_OPTIONS=y +CONFIG_START_STOP_DAEMON=y +CONFIG_FEATURE_START_STOP_DAEMON_FANCY=y +CONFIG_FEATURE_START_STOP_DAEMON_LONG_OPTIONS=y +CONFIG_WHICH=y + +# +# Editors +# +CONFIG_AWK=y +CONFIG_FEATURE_AWK_MATH=y +CONFIG_ED=y +CONFIG_PATCH=y +CONFIG_SED=y +CONFIG_VI=y +CONFIG_FEATURE_VI_COLON=y +CONFIG_FEATURE_VI_YANKMARK=y +CONFIG_FEATURE_VI_SEARCH=y +CONFIG_FEATURE_VI_USE_SIGNALS=y +CONFIG_FEATURE_VI_DOT_CMD=y +CONFIG_FEATURE_VI_READONLY=y +CONFIG_FEATURE_VI_SETOPTS=y +CONFIG_FEATURE_VI_SET=y +CONFIG_FEATURE_VI_WIN_RESIZE=y +CONFIG_FEATURE_VI_OPTIMIZE_CURSOR=y + +# +# Finding Utilities +# +CONFIG_FIND=y +CONFIG_FEATURE_FIND_PRINT0=y +CONFIG_FEATURE_FIND_MTIME=y +CONFIG_FEATURE_FIND_MMIN=y +CONFIG_FEATURE_FIND_PERM=y +CONFIG_FEATURE_FIND_TYPE=y +CONFIG_FEATURE_FIND_XDEV=y +CONFIG_FEATURE_FIND_NEWER=y +CONFIG_FEATURE_FIND_INUM=y +CONFIG_FEATURE_FIND_EXEC=y +CONFIG_GREP=y +CONFIG_FEATURE_GREP_EGREP_ALIAS=y +CONFIG_FEATURE_GREP_FGREP_ALIAS=y +CONFIG_FEATURE_GREP_CONTEXT=y +CONFIG_XARGS=y +CONFIG_FEATURE_XARGS_SUPPORT_CONFIRMATION=y +CONFIG_FEATURE_XARGS_SUPPORT_QUOTES=y +CONFIG_FEATURE_XARGS_SUPPORT_TERMOPT=y +CONFIG_FEATURE_XARGS_SUPPORT_ZERO_TERM=y + +# +# Init Utilities +# +CONFIG_INIT=y +# CONFIG_DEBUG_INIT is not set +CONFIG_FEATURE_USE_INITTAB=y +CONFIG_FEATURE_INIT_SCTTY=y +CONFIG_FEATURE_EXTRA_QUIET=y +CONFIG_FEATURE_INIT_COREDUMPS=y +CONFIG_FEATURE_INITRD=y +CONFIG_HALT=y +CONFIG_MESG=y + +# +# Login/Password Management Utilities +# +CONFIG_FEATURE_SHADOWPASSWDS=y +CONFIG_USE_BB_SHADOW=y +CONFIG_USE_BB_PWD_GRP=y +CONFIG_ADDGROUP=y +CONFIG_DELGROUP=y +CONFIG_ADDUSER=y +CONFIG_DELUSER=y +CONFIG_GETTY=y +CONFIG_FEATURE_UTMP=y +CONFIG_FEATURE_WTMP=y +CONFIG_LOGIN=y +CONFIG_LOGIN_SCRIPTS=y +CONFIG_FEATURE_SECURETTY=y +CONFIG_PASSWD=y +CONFIG_SU=y +CONFIG_SU_SYSLOG=y +CONFIG_SULOGIN=y +CONFIG_VLOCK=y + +# +# Linux Ext2 FS Progs +# +CONFIG_CHATTR=y +# CONFIG_E2FSCK is not set +CONFIG_FSCK=y +CONFIG_LSATTR=y +# CONFIG_MKE2FS is not set +# CONFIG_TUNE2FS is not set +# CONFIG_E2LABEL is not set +# CONFIG_FINDFS is not set + +# +# Linux Module Utilities +# +CONFIG_INSMOD=y +CONFIG_FEATURE_INSMOD_VERSION_CHECKING=y +CONFIG_FEATURE_INSMOD_KSYMOOPS_SYMBOLS=y +CONFIG_FEATURE_INSMOD_LOADINKMEM=y +CONFIG_FEATURE_INSMOD_LOAD_MAP=y +CONFIG_FEATURE_INSMOD_LOAD_MAP_FULL=y +CONFIG_RMMOD=y +CONFIG_LSMOD=y +CONFIG_FEATURE_LSMOD_PRETTY_2_6_OUTPUT=y +CONFIG_MODPROBE=y +CONFIG_FEATURE_MODPROBE_MULTIPLE_OPTIONS=y +CONFIG_FEATURE_MODPROBE_FANCY_ALIAS=y + +# +# Options common to multiple modutils +# +CONFIG_FEATURE_CHECK_TAINTED_MODULE=y +CONFIG_FEATURE_2_4_MODULES=y +CONFIG_FEATURE_2_6_MODULES=y +# CONFIG_FEATURE_QUERY_MODULE_INTERFACE is not set + +# +# Linux System Utilities +# +CONFIG_DMESG=y +CONFIG_FEATURE_DMESG_PRETTY=y +CONFIG_FBSET=y +CONFIG_FEATURE_FBSET_FANCY=y +CONFIG_FEATURE_FBSET_READMODE=y +CONFIG_FDFLUSH=y +CONFIG_FDFORMAT=y +CONFIG_FDISK=y +CONFIG_FDISK_SUPPORT_LARGE_DISKS=y +CONFIG_FEATURE_FDISK_WRITABLE=y +# CONFIG_FEATURE_AIX_LABEL is not set +# CONFIG_FEATURE_SGI_LABEL is not set +# CONFIG_FEATURE_SUN_LABEL is not set +# CONFIG_FEATURE_OSF_LABEL is not set +CONFIG_FEATURE_FDISK_ADVANCED=y +CONFIG_FREERAMDISK=y +CONFIG_FSCK_MINIX=y +CONFIG_MKFS_MINIX=y + +# +# Minix filesystem support +# +CONFIG_FEATURE_MINIX2=y +CONFIG_GETOPT=y +CONFIG_HEXDUMP=y +CONFIG_HWCLOCK=y +CONFIG_FEATURE_HWCLOCK_LONG_OPTIONS=y +CONFIG_FEATURE_HWCLOCK_ADJTIME_FHS=y +CONFIG_IPCRM=y +CONFIG_IPCS=y +CONFIG_LOSETUP=y +CONFIG_MDEV=y +CONFIG_FEATURE_MDEV_CONF=y +CONFIG_FEATURE_MDEV_EXEC=y +CONFIG_MKSWAP=y +CONFIG_FEATURE_MKSWAP_V0=y +CONFIG_MORE=y +CONFIG_FEATURE_USE_TERMIOS=y +CONFIG_MOUNT=y +CONFIG_FEATURE_MOUNT_NFS=y +CONFIG_FEATURE_MOUNT_CIFS=y +CONFIG_FEATURE_MOUNT_FLAGS=y +CONFIG_FEATURE_MOUNT_FSTAB=y +CONFIG_PIVOT_ROOT=y +CONFIG_RDATE=y +CONFIG_READPROFILE=y +CONFIG_SETARCH=y +CONFIG_SWAPONOFF=y +CONFIG_SWITCH_ROOT=y +CONFIG_UMOUNT=y +CONFIG_FEATURE_UMOUNT_ALL=y + +# +# Common options for mount/umount +# +CONFIG_FEATURE_MOUNT_LOOP=y +# CONFIG_FEATURE_MTAB_SUPPORT is not set + +# +# Miscellaneous Utilities +# +CONFIG_ADJTIMEX=y +CONFIG_BBCONFIG=y +CONFIG_CROND=y +# CONFIG_DEBUG_CROND_OPTION is not set +CONFIG_FEATURE_CROND_CALL_SENDMAIL=y +CONFIG_CRONTAB=y +CONFIG_DC=y +# CONFIG_DEVFSD is not set +# CONFIG_DEVFSD_MODLOAD is not set +# CONFIG_DEVFSD_FG_NP is not set +# CONFIG_DEVFSD_VERBOSE is not set +# CONFIG_FEATURE_DEVFS is not set +CONFIG_EJECT=y +CONFIG_LAST=y +CONFIG_LESS=y +CONFIG_FEATURE_LESS_BRACKETS=y +CONFIG_FEATURE_LESS_FLAGS=y +CONFIG_FEATURE_LESS_FLAGCS=y +CONFIG_FEATURE_LESS_MARKS=y +CONFIG_FEATURE_LESS_REGEXP=y +CONFIG_HDPARM=y +CONFIG_FEATURE_HDPARM_GET_IDENTITY=y +CONFIG_FEATURE_HDPARM_HDIO_SCAN_HWIF=y +CONFIG_FEATURE_HDPARM_HDIO_UNREGISTER_HWIF=y +CONFIG_FEATURE_HDPARM_HDIO_DRIVE_RESET=y +CONFIG_FEATURE_HDPARM_HDIO_TRISTATE_HWIF=y +CONFIG_FEATURE_HDPARM_HDIO_GETSET_DMA=y +CONFIG_MAKEDEVS=y +# CONFIG_FEATURE_MAKEDEVS_LEAF is not set +CONFIG_FEATURE_MAKEDEVS_TABLE=y +CONFIG_MOUNTPOINT=y +CONFIG_MT=y +CONFIG_NMETER=y +CONFIG_RAIDAUTORUN=y +CONFIG_READAHEAD=y +CONFIG_RUNLEVEL=y +CONFIG_RX=y +CONFIG_STRINGS=y +CONFIG_SETSID=y +CONFIG_TASKSET=y +CONFIG_TIME=y +CONFIG_WATCHDOG=y + +# +# Networking Utilities +# +CONFIG_FEATURE_IPV6=y +CONFIG_ARPING=y +CONFIG_DNSD=y +CONFIG_ETHER_WAKE=y +CONFIG_FAKEIDENTD=y +CONFIG_FTPGET=y +CONFIG_FTPPUT=y +CONFIG_FEATURE_FTPGETPUT_LONG_OPTIONS=y +CONFIG_HOSTNAME=y +CONFIG_HTTPD=y +# CONFIG_FEATURE_HTTPD_RELOAD_CONFIG_SIGHUP is not set +# CONFIG_FEATURE_HTTPD_SETUID is not set +CONFIG_FEATURE_HTTPD_BASIC_AUTH=y +CONFIG_FEATURE_HTTPD_AUTH_MD5=y +CONFIG_FEATURE_HTTPD_CONFIG_WITH_MIME_TYPES=y +CONFIG_FEATURE_HTTPD_CGI=y +CONFIG_FEATURE_HTTPD_CONFIG_WITH_SCRIPT_INTERPR=y +CONFIG_FEATURE_HTTPD_SET_REMOTE_PORT_TO_ENV=y +CONFIG_FEATURE_HTTPD_ENCODE_URL_STR=y +CONFIG_IFCONFIG=y +CONFIG_FEATURE_IFCONFIG_STATUS=y +CONFIG_FEATURE_IFCONFIG_SLIP=y +CONFIG_FEATURE_IFCONFIG_MEMSTART_IOADDR_IRQ=y +CONFIG_FEATURE_IFCONFIG_HW=y +CONFIG_FEATURE_IFCONFIG_BROADCAST_PLUS=y +CONFIG_IFUPDOWN=y +CONFIG_FEATURE_IFUPDOWN_IP=y +CONFIG_FEATURE_IFUPDOWN_IP_BUILTIN=y +# CONFIG_FEATURE_IFUPDOWN_IFCONFIG_BUILTIN is not set +CONFIG_FEATURE_IFUPDOWN_IPV4=y +CONFIG_FEATURE_IFUPDOWN_IPV6=y +CONFIG_FEATURE_IFUPDOWN_IPX=y +CONFIG_FEATURE_IFUPDOWN_MAPPING=y +CONFIG_INETD=y +CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_ECHO=y +CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DISCARD=y +CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_TIME=y +CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_DAYTIME=y +CONFIG_FEATURE_INETD_SUPPORT_BUILTIN_CHARGEN=y +CONFIG_FEATURE_INETD_RPC=y +CONFIG_IP=y +CONFIG_FEATURE_IP_ADDRESS=y +CONFIG_FEATURE_IP_LINK=y +CONFIG_FEATURE_IP_ROUTE=y +CONFIG_FEATURE_IP_TUNNEL=y +CONFIG_FEATURE_IP_RULE=y +CONFIG_FEATURE_IP_SHORT_FORMS=y +CONFIG_IPADDR=y +CONFIG_IPLINK=y +CONFIG_IPROUTE=y +CONFIG_IPTUNNEL=y +CONFIG_IPRULE=y +CONFIG_IPCALC=y +CONFIG_FEATURE_IPCALC_FANCY=y +CONFIG_FEATURE_IPCALC_LONG_OPTIONS=y +CONFIG_NAMEIF=y +CONFIG_NC=y +CONFIG_NC_SERVER=y +CONFIG_NC_EXTRA=y +CONFIG_NETSTAT=y +CONFIG_NSLOOKUP=y +CONFIG_PING=y +CONFIG_FEATURE_FANCY_PING=y +CONFIG_PING6=y +CONFIG_FEATURE_FANCY_PING6=y +CONFIG_ROUTE=y +CONFIG_TELNET=y +CONFIG_FEATURE_TELNET_TTYPE=y +CONFIG_FEATURE_TELNET_AUTOLOGIN=y +CONFIG_TELNETD=y +CONFIG_TFTP=y +CONFIG_FEATURE_TFTP_GET=y +CONFIG_FEATURE_TFTP_PUT=y +CONFIG_FEATURE_TFTP_BLOCKSIZE=y +# CONFIG_DEBUG_TFTP is not set +CONFIG_TRACEROUTE=y +# CONFIG_FEATURE_TRACEROUTE_VERBOSE is not set +# CONFIG_FEATURE_TRACEROUTE_SOURCE_ROUTE is not set +# CONFIG_FEATURE_TRACEROUTE_USE_ICMP is not set +CONFIG_APP_UDHCPD=y +CONFIG_APP_DHCPRELAY=y +CONFIG_APP_DUMPLEASES=y +CONFIG_APP_UDHCPC=y +CONFIG_FEATURE_UDHCP_SYSLOG=y +# CONFIG_FEATURE_UDHCP_DEBUG is not set +CONFIG_VCONFIG=y +CONFIG_WGET=y +CONFIG_FEATURE_WGET_STATUSBAR=y +CONFIG_FEATURE_WGET_AUTHENTICATION=y +CONFIG_FEATURE_WGET_IP6_LITERAL=y +CONFIG_FEATURE_WGET_LONG_OPTIONS=y +CONFIG_ZCIP=y + +# +# Process Utilities +# +CONFIG_FREE=y +CONFIG_FUSER=y +CONFIG_KILL=y +CONFIG_KILLALL=y +CONFIG_KILLALL5=y +CONFIG_PIDOF=y +CONFIG_FEATURE_PIDOF_SINGLE=y +CONFIG_FEATURE_PIDOF_OMIT=y +CONFIG_PS=y +CONFIG_FEATURE_PS_WIDE=y +CONFIG_RENICE=y +CONFIG_BB_SYSCTL=y +CONFIG_TOP=y +CONFIG_FEATURE_TOP_CPU_USAGE_PERCENTAGE=y +CONFIG_UPTIME=y + +# +# Shells +# +# CONFIG_FEATURE_SH_IS_ASH is not set +# CONFIG_FEATURE_SH_IS_HUSH is not set +# CONFIG_FEATURE_SH_IS_LASH is not set +# CONFIG_FEATURE_SH_IS_MSH is not set +CONFIG_FEATURE_SH_IS_NONE=y +# CONFIG_ASH is not set +# CONFIG_ASH_JOB_CONTROL is not set +# CONFIG_ASH_READ_NCHARS is not set +# CONFIG_ASH_READ_TIMEOUT is not set +# CONFIG_ASH_ALIAS is not set +# CONFIG_ASH_MATH_SUPPORT is not set +# CONFIG_ASH_MATH_SUPPORT_64 is not set +# CONFIG_ASH_GETOPTS is not set +# CONFIG_ASH_BUILTIN_ECHO is not set +# CONFIG_ASH_BUILTIN_TEST is not set +# CONFIG_ASH_CMDCMD is not set +# CONFIG_ASH_MAIL is not set +# CONFIG_ASH_OPTIMIZE_FOR_SIZE is not set +# CONFIG_ASH_RANDOM_SUPPORT is not set +# CONFIG_ASH_EXPAND_PRMT is not set +# CONFIG_HUSH is not set +# CONFIG_LASH is not set +# CONFIG_MSH is not set +# CONFIG_FEATURE_SH_EXTRA_QUIET is not set +# CONFIG_FEATURE_SH_STANDALONE_SHELL is not set +# CONFIG_FEATURE_COMMAND_EDITING is not set +# CONFIG_FEATURE_COMMAND_EDITING_VI is not set +CONFIG_FEATURE_COMMAND_HISTORY= +# CONFIG_FEATURE_COMMAND_SAVEHISTORY is not set +# CONFIG_FEATURE_COMMAND_TAB_COMPLETION is not set +# CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION is not set +# CONFIG_FEATURE_SH_FANCY_PROMPT is not set + +# +# System Logging Utilities +# +CONFIG_SYSLOGD=y +CONFIG_FEATURE_ROTATE_LOGFILE=y +CONFIG_FEATURE_REMOTE_LOG=y +CONFIG_FEATURE_IPC_SYSLOG=y +CONFIG_FEATURE_IPC_SYSLOG_BUFFER_SIZE=16 +CONFIG_LOGREAD=y +CONFIG_FEATURE_LOGREAD_REDUCED_LOCKING=y +CONFIG_KLOGD=y +CONFIG_LOGGER=y + +# +# Runit Utilities +# +# CONFIG_RUNSV is not set +# CONFIG_RUNSVDIR is not set +# CONFIG_SV is not set +# CONFIG_SVLOGD is not set +CONFIG_CHPST=y +CONFIG_SETUIDGID=y +CONFIG_ENVUIDGID=y +CONFIG_ENVDIR=y +CONFIG_SOFTLIMIT=y diff --git a/scripts/individual b/scripts/individual new file mode 100755 index 000000000..a09a5dc7a --- /dev/null +++ b/scripts/individual @@ -0,0 +1,129 @@ +#!/bin/sh + +# Compile individual versions of each busybox applet. + +if [ $# -eq 0 ] +then + +# Clear out the build directory. (Make clean should do this instead of here.) + +rm -rf build +mkdir build + +# Make our prerequisites. + +make busybox.links include/bb_config.h $(pwd)/{libbb/libbb.a,archival/libunarchive/libunarchive.a,coreutils/libcoreutils/libcoreutils.a,networking/libiproute/libiproute.a} + +else +# Could very well be that we want to build an individual applet but have no +# 'build' dir yet.. + +test -d ./build || mkdir build + +fi + +# About 3/5 of the applets build from one .c file (with the same name as the +# corresponding applet), and all it needs to link against. However, to build +# them all we need more than that. + +# Figure out which applets need extra libraries added to their command line. + +function substithing() +{ + if [ "${1/ $3 //}" != "$1" ] + then + echo $2 + fi +} + +function extra_libraries() +{ + # gzip needs gunzip.c (when gunzip is enabled, anyway). + substithing " gzip " "archival/gunzip.c archival/uncompress.c" "$1" + + # init needs init_shared.c + substithing " init " "init/init_shared.c" "$1" + + # ifconfig needs interface.c + substithing " ifconfig " "networking/interface.c" "$1" + + # Applets that need libunarchive.a + substithing " ar bunzip2 unlzma cpio dpkg gunzip rpm2cpio rpm tar uncompress unzip dpkg_deb gzip " "archival/libunarchive/libunarchive.a" "$1" + + # Applets that need libcoreutils.a + substithing " cp mv " "coreutils/libcoreutils/libcoreutils.a" "$1" + + # Applets that need libiproute.a + substithing " ip " "networking/libiproute/libiproute.a" "$1" + + # What needs -libm? + substithing " awk dc " "-lm" "$1" + + # What needs -lcrypt? + substithing " httpd vlock " "-lcrypt" "$1" +} + +# Query applets.h to figure out which applets need special treatment + +strange_names=`sed -rn -e 's/\#.*//' -e 's/.*APPLET_NOUSAGE\(([^,]*),([^,]*),.*/\1 \2/p' -e 's/.*APPLET_ODDNAME\(([^,]*),([^,]*),.*, *([^)]*).*/\1 \2@\3/p' include/applets.h` + +function bonkname() +{ + while [ $# -gt 0 ] + do + if [ "$APPLET" == "$1" ] + then + APPFILT="${2/@*/}" + if [ "${APPFILT}" == "$2" ] + then + HELPNAME='"nousage\n"' # These should be _fixed_. + else + HELPNAME="${2/*@/}"_full_usage + fi + break + fi + shift 2 + done +#echo APPLET=${APPLET} APPFILT=${APPFILT} HELPNAME=${HELPNAME} 2=${2} +} + +# Iterate through every name in busybox.links + +function buildit () +{ + export APPLET="$1" + export APPFILT=${APPLET} + export HELPNAME=${APPLET}_full_usage + + bonkname $strange_names + + j=`find archival console-tools coreutils debianutils editors findutils init loginutils miscutils modutils networking procps shell sysklogd util-linux -name "${APPFILT}.c"` + if [ -z "$j" ] + then + echo no file for $APPLET + else + echo "Building $APPLET" + gcc -Os -o build/$APPLET applets/individual.c $j \ + `extra_libraries $APPFILT` libbb/libbb.a -Iinclude \ + -DBUILD_INDIVIDUAL \ + '-Drun_applet_by_name(...)' '-Dfind_applet_by_name(...)=0' \ + -DAPPLET_main=${APPFILT}_main -DAPPLET_full_usage=${HELPNAME} + if [ $? -ne 0 ]; + then + echo "Failed $APPLET" + fi + fi +} + +if [ $# -eq 0 ] +then + for APPLET in `sed 's .*/ ' busybox.links` + do + buildit "$APPLET" + done +else + buildit "$1" +fi + + +strip build/* diff --git a/scripts/kconfig/Makefile b/scripts/kconfig/Makefile new file mode 100644 index 000000000..a28414de8 --- /dev/null +++ b/scripts/kconfig/Makefile @@ -0,0 +1,254 @@ +# =========================================================================== +# Kernel configuration targets +# These targets are used from top-level makefile + +PHONY += oldconfig xconfig gconfig menuconfig config silentoldconfig update-po-config + +xconfig: $(obj)/qconf + $< Config.in + +gconfig: $(obj)/gconf + $< Config.in + +menuconfig: $(obj)/mconf + $(Q)$(MAKE) $(build)=scripts/kconfig/lxdialog + $< Config.in + +config: $(obj)/conf + $< Config.in + +oldconfig: $(obj)/conf + $< -o Config.in + +silentoldconfig: $(obj)/conf + $< -s Config.in + +update-po-config: $(obj)/kxgettext + xgettext --default-domain=linux \ + --add-comments --keyword=_ --keyword=N_ \ + --files-from=scripts/kconfig/POTFILES.in \ + --output scripts/kconfig/config.pot + $(Q)ln -fs Kconfig_i386 arch/um/Kconfig_arch + $(Q)for i in `ls arch/`; \ + do \ + scripts/kconfig/kxgettext arch/$$i/Kconfig \ + | msguniq -o scripts/kconfig/linux_$${i}.pot; \ + done + $(Q)msgcat scripts/kconfig/config.pot \ + `find scripts/kconfig/ -type f -name linux_*.pot` \ + --output scripts/kconfig/linux_raw.pot + $(Q)msguniq --sort-by-file scripts/kconfig/linux_raw.pot \ + --output scripts/kconfig/linux.pot + $(Q)rm -f arch/um/Kconfig_arch + $(Q)rm -f scripts/kconfig/linux_*.pot scripts/kconfig/config.pot + +PHONY += randconfig allyesconfig allnoconfig allmodconfig defconfig + +randconfig: $(obj)/conf + $< -r Config.in + +allyesconfig: $(obj)/conf + $< -y Config.in + +allnoconfig: $(obj)/conf + $< -n Config.in + +allmodconfig: $(obj)/conf + $< -m Config.in + +defconfig: $(obj)/conf +ifeq ($(KBUILD_DEFCONFIG),) + $< -d Config.in +else + @echo *** Default configuration is based on '$(KBUILD_DEFCONFIG)' + $(Q)$< -D $(KBUILD_DEFCONFIG) Config.in +endif + +%_defconfig: $(obj)/conf + $(Q)$< -D $@ Config.in + +# Help text used by make help +help: + @echo ' config - Update current config utilising a line-oriented program' + @echo ' menuconfig - Update current config utilising a menu based program' + @echo ' xconfig - Update current config utilising a QT based front-end' + @echo ' gconfig - Update current config utilising a GTK based front-end' + @echo ' oldconfig - Update current config utilising a provided .config as base' + @echo ' randconfig - New config with random answer to all options' + @echo ' defconfig - New config with default answer to all options' + @echo ' allmodconfig - New config selecting modules when possible' + @echo ' allyesconfig - New config where all options are accepted with yes' + @echo ' allnoconfig - New config where all options are answered with no' + +# =========================================================================== +# Shared Makefile for the various kconfig executables: +# conf: Used for defconfig, oldconfig and related targets +# mconf: Used for the mconfig target. +# Utilizes the lxdialog package +# qconf: Used for the xconfig target +# Based on QT which needs to be installed to compile it +# gconf: Used for the gconfig target +# Based on GTK which needs to be installed to compile it +# object files used by all kconfig flavours + +hostprogs-y := conf mconf qconf gconf kxgettext +conf-objs := conf.o zconf.tab.o +mconf-objs := mconf.o zconf.tab.o +kxgettext-objs := kxgettext.o zconf.tab.o + +ifeq ($(MAKECMDGOALS),xconfig) + qconf-target := 1 +endif +ifeq ($(MAKECMDGOALS),gconfig) + gconf-target := 1 +endif + + +ifeq ($(qconf-target),1) +qconf-cxxobjs := qconf.o +qconf-objs := kconfig_load.o zconf.tab.o +endif + +ifeq ($(gconf-target),1) +gconf-objs := gconf.o kconfig_load.o zconf.tab.o +endif + +clean-files := lkc_defs.h qconf.moc .tmp_qtcheck \ + .tmp_gtkcheck zconf.tab.c lex.zconf.c zconf.hash.c +subdir- += lxdialog + +# Needed for systems without gettext +KBUILD_HAVE_NLS := $(shell \ + if echo "\#include " | $(HOSTCC) $(HOSTCFLAGS) -E - > /dev/null 2>&1 ; \ + then echo yes ; \ + else echo no ; fi) +ifeq ($(KBUILD_HAVE_NLS),no) +HOSTCFLAGS += -DKBUILD_NO_NLS +endif + +# generated files seem to need this to find local include files +HOSTCFLAGS_lex.zconf.o := -I$(src) +HOSTCFLAGS_zconf.tab.o := -I$(src) + +HOSTLOADLIBES_qconf = $(KC_QT_LIBS) -ldl +HOSTCXXFLAGS_qconf.o = $(KC_QT_CFLAGS) -D LKC_DIRECT_LINK + +HOSTLOADLIBES_gconf = `pkg-config --libs gtk+-2.0 gmodule-2.0 libglade-2.0` +HOSTCFLAGS_gconf.o = `pkg-config --cflags gtk+-2.0 gmodule-2.0 libglade-2.0` \ + -D LKC_DIRECT_LINK + +$(obj)/qconf.o: $(obj)/.tmp_qtcheck + +ifeq ($(qconf-target),1) +$(obj)/.tmp_qtcheck: $(src)/Makefile +-include $(obj)/.tmp_qtcheck + +# QT needs some extra effort... +$(obj)/.tmp_qtcheck: + @set -e; echo " CHECK qt"; dir=""; pkg=""; \ + pkg-config --exists qt 2> /dev/null && pkg=qt; \ + pkg-config --exists qt-mt 2> /dev/null && pkg=qt-mt; \ + if [ -n "$$pkg" ]; then \ + cflags="\$$(shell pkg-config $$pkg --cflags)"; \ + libs="\$$(shell pkg-config $$pkg --libs)"; \ + moc="\$$(shell pkg-config $$pkg --variable=prefix)/bin/moc"; \ + dir="$$(pkg-config $$pkg --variable=prefix)"; \ + else \ + for d in $$QTDIR /usr/share/qt* /usr/lib/qt*; do \ + if [ -f $$d/include/qconfig.h ]; then dir=$$d; break; fi; \ + done; \ + if [ -z "$$dir" ]; then \ + echo "*"; \ + echo "* Unable to find the QT installation. Please make sure that"; \ + echo "* the QT development package is correctly installed and"; \ + echo "* either install pkg-config or set the QTDIR environment"; \ + echo "* variable to the correct location."; \ + echo "*"; \ + false; \ + fi; \ + libpath=$$dir/lib; lib=qt; osdir=""; \ + $(HOSTCXX) -print-multi-os-directory > /dev/null 2>&1 && \ + osdir=x$$($(HOSTCXX) -print-multi-os-directory); \ + test -d $$libpath/$$osdir && libpath=$$libpath/$$osdir; \ + test -f $$libpath/libqt-mt.so && lib=qt-mt; \ + cflags="-I$$dir/include"; \ + libs="-L$$libpath -Wl,-rpath,$$libpath -l$$lib"; \ + moc="$$dir/bin/moc"; \ + fi; \ + if [ ! -x $$dir/bin/moc -a -x /usr/bin/moc ]; then \ + echo "*"; \ + echo "* Unable to find $$dir/bin/moc, using /usr/bin/moc instead."; \ + echo "*"; \ + moc="/usr/bin/moc"; \ + fi; \ + echo "KC_QT_CFLAGS=$$cflags" > $@; \ + echo "KC_QT_LIBS=$$libs" >> $@; \ + echo "KC_QT_MOC=$$moc" >> $@ +endif + +$(obj)/gconf.o: $(obj)/.tmp_gtkcheck + +ifeq ($(gconf-target),1) +-include $(obj)/.tmp_gtkcheck + +# GTK needs some extra effort, too... +$(obj)/.tmp_gtkcheck: + @if `pkg-config --exists gtk+-2.0 gmodule-2.0 libglade-2.0`; then \ + if `pkg-config --atleast-version=2.0.0 gtk+-2.0`; then \ + touch $@; \ + else \ + echo "*"; \ + echo "* GTK+ is present but version >= 2.0.0 is required."; \ + echo "*"; \ + false; \ + fi \ + else \ + echo "*"; \ + echo "* Unable to find the GTK+ installation. Please make sure that"; \ + echo "* the GTK+ 2.0 development package is correctly installed..."; \ + echo "* You need gtk+-2.0, glib-2.0 and libglade-2.0."; \ + echo "*"; \ + false; \ + fi +endif + +$(obj)/zconf.tab.o: $(obj)/lex.zconf.c $(obj)/zconf.hash.c + +$(obj)/kconfig_load.o: $(obj)/lkc_defs.h + +$(obj)/qconf.o: $(obj)/qconf.moc $(obj)/lkc_defs.h + +$(obj)/gconf.o: $(obj)/lkc_defs.h + +$(obj)/%.moc: $(src)/%.h + $(KC_QT_MOC) -i $< -o $@ + +$(obj)/lkc_defs.h: $(src)/lkc_proto.h + sed < $< > $@ 's/P(\([^,]*\),.*/#define \1 (\*\1_p)/' + + +### +# The following requires flex/bison/gperf +# By default we use the _shipped versions, uncomment the following line if +# you are modifying the flex/bison src. +# LKC_GENPARSER := 1 + +ifdef LKC_GENPARSER + +$(obj)/zconf.tab.c: $(src)/zconf.y +$(obj)/lex.zconf.c: $(src)/zconf.l +$(obj)/zconf.hash.c: $(src)/zconf.gperf + +%.tab.c: %.y + bison -l -b $* -p $(notdir $*) $< + cp $@ $@_shipped + +lex.%.c: %.l + flex -L -P$(notdir $*) -o$@ $< + cp $@ $@_shipped + +%.hash.c: %.gperf + gperf < $< > $@ + cp $@ $@_shipped + +endif diff --git a/scripts/kconfig/POTFILES.in b/scripts/kconfig/POTFILES.in new file mode 100644 index 000000000..cc94e46a7 --- /dev/null +++ b/scripts/kconfig/POTFILES.in @@ -0,0 +1,5 @@ +scripts/kconfig/mconf.c +scripts/kconfig/conf.c +scripts/kconfig/confdata.c +scripts/kconfig/gconf.c +scripts/kconfig/qconf.cc diff --git a/scripts/kconfig/conf.c b/scripts/kconfig/conf.c new file mode 100644 index 000000000..a95ba93e2 --- /dev/null +++ b/scripts/kconfig/conf.c @@ -0,0 +1,612 @@ +/* + * Copyright (C) 2002 Roman Zippel + * Released under the terms of the GNU GPL v2.0. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define LKC_DIRECT_LINK +#include "lkc.h" + +static void conf(struct menu *menu); +static void check_conf(struct menu *menu); + +enum { + ask_all, + ask_new, + ask_silent, + set_default, + set_yes, + set_mod, + set_no, + set_random +} input_mode = ask_all; +char *defconfig_file; + +static int indent = 1; +static int valid_stdin = 1; +static int conf_cnt; +static char line[128]; +static struct menu *rootEntry; + +static char nohelp_text[] = N_("Sorry, no help available for this option yet.\n"); + +static void strip(char *str) +{ + char *p = str; + int l; + + while ((isspace(*p))) + p++; + l = strlen(p); + if (p != str) + memmove(str, p, l + 1); + if (!l) + return; + p = str + l - 1; + while ((isspace(*p))) + *p-- = 0; +} + +static void check_stdin(void) +{ + if (!valid_stdin && input_mode == ask_silent) { + printf(_("aborted!\n\n")); + printf(_("Console input/output is redirected. ")); + printf(_("Run 'make oldconfig' to update configuration.\n\n")); + exit(1); + } +} + +static void conf_askvalue(struct symbol *sym, const char *def) +{ + enum symbol_type type = sym_get_type(sym); + tristate val; + + if (!sym_has_value(sym)) + printf("(NEW) "); + + line[0] = '\n'; + line[1] = 0; + + if (!sym_is_changable(sym)) { + printf("%s\n", def); + line[0] = '\n'; + line[1] = 0; + return; + } + + switch (input_mode) { + case set_no: + case set_mod: + case set_yes: + case set_random: + if (sym_has_value(sym)) { + printf("%s\n", def); + return; + } + break; + case ask_new: + case ask_silent: + if (sym_has_value(sym)) { + printf("%s\n", def); + return; + } + check_stdin(); + case ask_all: + fflush(stdout); + fgets(line, 128, stdin); + return; + case set_default: + printf("%s\n", def); + return; + default: + break; + } + + switch (type) { + case S_INT: + case S_HEX: + case S_STRING: + printf("%s\n", def); + return; + default: + ; + } + switch (input_mode) { + case set_yes: + if (sym_tristate_within_range(sym, yes)) { + line[0] = 'y'; + line[1] = '\n'; + line[2] = 0; + break; + } + case set_mod: + if (type == S_TRISTATE) { + if (sym_tristate_within_range(sym, mod)) { + line[0] = 'm'; + line[1] = '\n'; + line[2] = 0; + break; + } + } else { + if (sym_tristate_within_range(sym, yes)) { + line[0] = 'y'; + line[1] = '\n'; + line[2] = 0; + break; + } + } + case set_no: + if (sym_tristate_within_range(sym, no)) { + line[0] = 'n'; + line[1] = '\n'; + line[2] = 0; + break; + } + case set_random: + do { + val = (tristate)(random() % 3); + } while (!sym_tristate_within_range(sym, val)); + switch (val) { + case no: line[0] = 'n'; break; + case mod: line[0] = 'm'; break; + case yes: line[0] = 'y'; break; + } + line[1] = '\n'; + line[2] = 0; + break; + default: + break; + } + printf("%s", line); +} + +int conf_string(struct menu *menu) +{ + struct symbol *sym = menu->sym; + const char *def, *help; + + while (1) { + printf("%*s%s ", indent - 1, "", menu->prompt->text); + printf("(%s) ", sym->name); + def = sym_get_string_value(sym); + if (sym_get_string_value(sym)) + printf("[%s] ", def); + conf_askvalue(sym, def); + switch (line[0]) { + case '\n': + break; + case '?': + /* print help */ + if (line[1] == '\n') { + help = nohelp_text; + if (menu->sym->help) + help = menu->sym->help; + printf("\n%s\n", menu->sym->help); + def = NULL; + break; + } + default: + line[strlen(line)-1] = 0; + def = line; + } + if (def && sym_set_string_value(sym, def)) + return 0; + } +} + +static int conf_sym(struct menu *menu) +{ + struct symbol *sym = menu->sym; + int type; + tristate oldval, newval; + const char *help; + + while (1) { + printf("%*s%s ", indent - 1, "", menu->prompt->text); + if (sym->name) + printf("(%s) ", sym->name); + type = sym_get_type(sym); + putchar('['); + oldval = sym_get_tristate_value(sym); + switch (oldval) { + case no: + putchar('N'); + break; + case mod: + putchar('M'); + break; + case yes: + putchar('Y'); + break; + } + if (oldval != no && sym_tristate_within_range(sym, no)) + printf("/n"); + if (oldval != mod && sym_tristate_within_range(sym, mod)) + printf("/m"); + if (oldval != yes && sym_tristate_within_range(sym, yes)) + printf("/y"); + if (sym->help) + printf("/?"); + printf("] "); + conf_askvalue(sym, sym_get_string_value(sym)); + strip(line); + + switch (line[0]) { + case 'n': + case 'N': + newval = no; + if (!line[1] || !strcmp(&line[1], "o")) + break; + continue; + case 'm': + case 'M': + newval = mod; + if (!line[1]) + break; + continue; + case 'y': + case 'Y': + newval = yes; + if (!line[1] || !strcmp(&line[1], "es")) + break; + continue; + case 0: + newval = oldval; + break; + case '?': + goto help; + default: + continue; + } + if (sym_set_tristate_value(sym, newval)) + return 0; +help: + help = nohelp_text; + if (sym->help) + help = sym->help; + printf("\n%s\n", help); + } +} + +static int conf_choice(struct menu *menu) +{ + struct symbol *sym, *def_sym; + struct menu *child; + int type; + bool is_new; + + sym = menu->sym; + type = sym_get_type(sym); + is_new = !sym_has_value(sym); + if (sym_is_changable(sym)) { + conf_sym(menu); + sym_calc_value(sym); + switch (sym_get_tristate_value(sym)) { + case no: + return 1; + case mod: + return 0; + case yes: + break; + } + } else { + switch (sym_get_tristate_value(sym)) { + case no: + return 1; + case mod: + printf("%*s%s\n", indent - 1, "", menu_get_prompt(menu)); + return 0; + case yes: + break; + } + } + + while (1) { + int cnt, def; + + printf("%*s%s\n", indent - 1, "", menu_get_prompt(menu)); + def_sym = sym_get_choice_value(sym); + cnt = def = 0; + line[0] = 0; + for (child = menu->list; child; child = child->next) { + if (!menu_is_visible(child)) + continue; + if (!child->sym) { + printf("%*c %s\n", indent, '*', menu_get_prompt(child)); + continue; + } + cnt++; + if (child->sym == def_sym) { + def = cnt; + printf("%*c", indent, '>'); + } else + printf("%*c", indent, ' '); + printf(" %d. %s", cnt, menu_get_prompt(child)); + if (child->sym->name) + printf(" (%s)", child->sym->name); + if (!sym_has_value(child->sym)) + printf(" (NEW)"); + printf("\n"); + } + printf("%*schoice", indent - 1, ""); + if (cnt == 1) { + printf("[1]: 1\n"); + goto conf_childs; + } + printf("[1-%d", cnt); + if (sym->help) + printf("?"); + printf("]: "); + switch (input_mode) { + case ask_new: + case ask_silent: + if (!is_new) { + cnt = def; + printf("%d\n", cnt); + break; + } + check_stdin(); + case ask_all: + fflush(stdout); + fgets(line, 128, stdin); + strip(line); + if (line[0] == '?') { + printf("\n%s\n", menu->sym->help ? + menu->sym->help : nohelp_text); + continue; + } + if (!line[0]) + cnt = def; + else if (isdigit(line[0])) + cnt = atoi(line); + else + continue; + break; + case set_random: + def = (random() % cnt) + 1; + case set_default: + case set_yes: + case set_mod: + case set_no: + cnt = def; + printf("%d\n", cnt); + break; + } + + conf_childs: + for (child = menu->list; child; child = child->next) { + if (!child->sym || !menu_is_visible(child)) + continue; + if (!--cnt) + break; + } + if (!child) + continue; + if (line[strlen(line) - 1] == '?') { + printf("\n%s\n", child->sym->help ? + child->sym->help : nohelp_text); + continue; + } + sym_set_choice_value(sym, child->sym); + if (child->list) { + indent += 2; + conf(child->list); + indent -= 2; + } + return 1; + } +} + +static void conf(struct menu *menu) +{ + struct symbol *sym; + struct property *prop; + struct menu *child; + + if (!menu_is_visible(menu)) + return; + + sym = menu->sym; + prop = menu->prompt; + if (prop) { + const char *prompt; + + switch (prop->type) { + case P_MENU: + if (input_mode == ask_silent && rootEntry != menu) { + check_conf(menu); + return; + } + case P_COMMENT: + prompt = menu_get_prompt(menu); + if (prompt) + printf("%*c\n%*c %s\n%*c\n", + indent, '*', + indent, '*', prompt, + indent, '*'); + default: + ; + } + } + + if (!sym) + goto conf_childs; + + if (sym_is_choice(sym)) { + conf_choice(menu); + if (sym->curr.tri != mod) + return; + goto conf_childs; + } + + switch (sym->type) { + case S_INT: + case S_HEX: + case S_STRING: + conf_string(menu); + break; + default: + conf_sym(menu); + break; + } + +conf_childs: + if (sym) + indent += 2; + for (child = menu->list; child; child = child->next) + conf(child); + if (sym) + indent -= 2; +} + +static void check_conf(struct menu *menu) +{ + struct symbol *sym; + struct menu *child; + + if (!menu_is_visible(menu)) + return; + + sym = menu->sym; + if (sym && !sym_has_value(sym)) { + if (sym_is_changable(sym) || + (sym_is_choice(sym) && sym_get_tristate_value(sym) == yes)) { + if (!conf_cnt++) + printf(_("*\n* Restart config...\n*\n")); + rootEntry = menu_get_parent_menu(menu); + conf(rootEntry); + } + } + + for (child = menu->list; child; child = child->next) + check_conf(child); +} + +int main(int ac, char **av) +{ + int i = 1; + const char *name; + struct stat tmpstat; + + if (ac > i && av[i][0] == '-') { + switch (av[i++][1]) { + case 'o': + input_mode = ask_new; + break; + case 's': + input_mode = ask_silent; + valid_stdin = isatty(0) && isatty(1) && isatty(2); + break; + case 'd': + input_mode = set_default; + break; + case 'D': + input_mode = set_default; + defconfig_file = av[i++]; + if (!defconfig_file) { + printf(_("%s: No default config file specified\n"), + av[0]); + exit(1); + } + break; + case 'n': + input_mode = set_no; + break; + case 'm': + input_mode = set_mod; + break; + case 'y': + input_mode = set_yes; + break; + case 'r': + input_mode = set_random; + srandom(time(NULL)); + break; + case 'h': + case '?': + fprintf(stderr, "See README for usage info\n"); + exit(0); + } + } + name = av[i]; + if (!name) { + printf(_("%s: Kconfig file missing\n"), av[0]); + } + conf_parse(name); + //zconfdump(stdout); + switch (input_mode) { + case set_default: + if (!defconfig_file) + defconfig_file = conf_get_default_confname(); + if (conf_read(defconfig_file)) { + printf("***\n" + "*** Can't find default configuration \"%s\"!\n" + "***\n", defconfig_file); + exit(1); + } + break; + case ask_silent: + if (stat(".config", &tmpstat)) { + printf(_("***\n" + "*** You have not yet configured your kernel!\n" + "***\n" + "*** Please run some configurator (e.g. \"make oldconfig\" or\n" + "*** \"make menuconfig\" or \"make xconfig\").\n" + "***\n")); + exit(1); + } + case ask_all: + case ask_new: + conf_read(NULL); + break; + case set_no: + case set_mod: + case set_yes: + case set_random: + name = getenv("KCONFIG_ALLCONFIG"); + if (name && !stat(name, &tmpstat)) { + conf_read_simple(name); + break; + } + switch (input_mode) { + case set_no: name = "allno.config"; break; + case set_mod: name = "allmod.config"; break; + case set_yes: name = "allyes.config"; break; + case set_random: name = "allrandom.config"; break; + default: break; + } + if (!stat(name, &tmpstat)) + conf_read_simple(name); + else if (!stat("all.config", &tmpstat)) + conf_read_simple("all.config"); + break; + default: + break; + } + + if (input_mode != ask_silent) { + rootEntry = &rootmenu; + conf(&rootmenu); + if (input_mode == ask_all) { + input_mode = ask_silent; + valid_stdin = 1; + } + } + do { + conf_cnt = 0; + check_conf(&rootmenu); + } while (conf_cnt); + if (conf_write(NULL)) { + fprintf(stderr, _("\n*** Error during writing of the busybox configuration.\n\n")); + return 1; + } + return 0; +} diff --git a/scripts/kconfig/confdata.c b/scripts/kconfig/confdata.c new file mode 100644 index 000000000..d38bbba93 --- /dev/null +++ b/scripts/kconfig/confdata.c @@ -0,0 +1,562 @@ +/* + * Copyright (C) 2002 Roman Zippel + * Released under the terms of the GNU GPL v2.0. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define LKC_DIRECT_LINK +#include "lkc.h" + +static void conf_warning(const char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); + +static const char *conf_filename; +static int conf_lineno, conf_warnings, conf_unsaved; + +const char conf_def_filename[] = ".config"; + +const char conf_defname[] = "scripts/defconfig"; + +const char *conf_confnames[] = { + ".config", + "/lib/modules/$UNAME_RELEASE/.config", + "/etc/kernel-config", + "/boot/config-$UNAME_RELEASE", + conf_defname, + NULL, +}; + +static void conf_warning(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "%s:%d:warning: ", conf_filename, conf_lineno); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + conf_warnings++; +} + +static char *conf_expand_value(const char *in) +{ + struct symbol *sym; + const char *src; + static char res_value[SYMBOL_MAXLENGTH]; + char *dst, name[SYMBOL_MAXLENGTH]; + + res_value[0] = 0; + dst = name; + while ((src = strchr(in, '$'))) { + strncat(res_value, in, src - in); + src++; + dst = name; + while (isalnum(*src) || *src == '_') + *dst++ = *src++; + *dst = 0; + sym = sym_lookup(name, 0); + sym_calc_value(sym); + strcat(res_value, sym_get_string_value(sym)); + in = src; + } + strcat(res_value, in); + + return res_value; +} + +char *conf_get_default_confname(void) +{ + struct stat buf; + static char fullname[PATH_MAX+1]; + char *env, *name; + + name = conf_expand_value(conf_defname); + env = getenv(SRCTREE); + if (env) { + sprintf(fullname, "%s/%s", env, name); + if (!stat(fullname, &buf)) + return fullname; + } + return name; +} + +int conf_read_simple(const char *name) +{ + FILE *in = NULL; + char line[1024]; + char *p, *p2; + struct symbol *sym; + int i; + + if (name) { + in = zconf_fopen(name); + } else { + const char **names = conf_confnames; + while ((name = *names++)) { + name = conf_expand_value(name); + in = zconf_fopen(name); + if (in) { + printf(_("#\n" + "# using defaults found in %s\n" + "#\n"), name); + break; + } + } + } + if (!in) + return 1; + + conf_filename = name; + conf_lineno = 0; + conf_warnings = 0; + conf_unsaved = 0; + + for_all_symbols(i, sym) { + sym->flags |= SYMBOL_NEW | SYMBOL_CHANGED; + if (sym_is_choice(sym)) + sym->flags &= ~SYMBOL_NEW; + sym->flags &= ~SYMBOL_VALID; + switch (sym->type) { + case S_INT: + case S_HEX: + case S_STRING: + if (sym->user.val) + free(sym->user.val); + default: + sym->user.val = NULL; + sym->user.tri = no; + } + } + + while (fgets(line, sizeof(line), in)) { + conf_lineno++; + sym = NULL; + switch (line[0]) { + case '#': + if (memcmp(line + 2, "CONFIG_", 7)) + continue; + p = strchr(line + 9, ' '); + if (!p) + continue; + *p++ = 0; + if (strncmp(p, "is not set", 10)) + continue; + sym = sym_find(line + 9); + if (!sym) { + conf_warning("trying to assign nonexistent symbol %s", line + 9); + break; + } else if (!(sym->flags & SYMBOL_NEW)) { + conf_warning("trying to reassign symbol %s", sym->name); + break; + } + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + sym->user.tri = no; + sym->flags &= ~SYMBOL_NEW; + break; + default: + ; + } + break; + case 'C': + if (memcmp(line, "CONFIG_", 7)) { + conf_warning("unexpected data"); + continue; + } + p = strchr(line + 7, '='); + if (!p) + continue; + *p++ = 0; + p2 = strchr(p, '\n'); + if (p2) + *p2 = 0; + sym = sym_find(line + 7); + if (!sym) { + conf_warning("trying to assign nonexistent symbol %s", line + 7); + break; + } else if (!(sym->flags & SYMBOL_NEW)) { + conf_warning("trying to reassign symbol %s", sym->name); + break; + } + switch (sym->type) { + case S_TRISTATE: + if (p[0] == 'm') { + sym->user.tri = mod; + sym->flags &= ~SYMBOL_NEW; + break; + } + case S_BOOLEAN: + if (p[0] == 'y') { + sym->user.tri = yes; + sym->flags &= ~SYMBOL_NEW; + break; + } + if (p[0] == 'n') { + sym->user.tri = no; + sym->flags &= ~SYMBOL_NEW; + break; + } + conf_warning("symbol value '%s' invalid for %s", p, sym->name); + break; + case S_STRING: + if (*p++ != '"') + break; + for (p2 = p; (p2 = strpbrk(p2, "\"\\")); p2++) { + if (*p2 == '"') { + *p2 = 0; + break; + } + memmove(p2, p2 + 1, strlen(p2)); + } + if (!p2) { + conf_warning("invalid string found"); + continue; + } + case S_INT: + case S_HEX: + if (sym_string_valid(sym, p)) { + sym->user.val = strdup(p); + sym->flags &= ~SYMBOL_NEW; + } else { + conf_warning("symbol value '%s' invalid for %s", p, sym->name); + continue; + } + break; + default: + ; + } + break; + case '\n': + break; + default: + conf_warning("unexpected data"); + continue; + } + if (sym && sym_is_choice_value(sym)) { + struct symbol *cs = prop_get_symbol(sym_get_choice_prop(sym)); + switch (sym->user.tri) { + case no: + break; + case mod: + if (cs->user.tri == yes) { + conf_warning("%s creates inconsistent choice state", sym->name); + cs->flags |= SYMBOL_NEW; + } + break; + case yes: + if (cs->user.tri != no) { + conf_warning("%s creates inconsistent choice state", sym->name); + cs->flags |= SYMBOL_NEW; + } else + cs->user.val = sym; + break; + } + cs->user.tri = E_OR(cs->user.tri, sym->user.tri); + } + } + fclose(in); + + if (modules_sym) + sym_calc_value(modules_sym); + return 0; +} + +int conf_read(const char *name) +{ + struct symbol *sym; + struct property *prop; + struct expr *e; + int i; + + if (conf_read_simple(name)) + return 1; + + for_all_symbols(i, sym) { + sym_calc_value(sym); + if (sym_is_choice(sym) || (sym->flags & SYMBOL_AUTO)) + goto sym_ok; + if (sym_has_value(sym) && (sym->flags & SYMBOL_WRITE)) { + /* check that calculated value agrees with saved value */ + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + if (sym->user.tri != sym_get_tristate_value(sym)) + break; + if (!sym_is_choice(sym)) + goto sym_ok; + default: + if (!strcmp(sym->curr.val, sym->user.val)) + goto sym_ok; + break; + } + } else if (!sym_has_value(sym) && !(sym->flags & SYMBOL_WRITE)) + /* no previous value and not saved */ + goto sym_ok; + conf_unsaved++; + /* maybe print value in verbose mode... */ + sym_ok: + if (sym_has_value(sym) && !sym_is_choice_value(sym)) { + if (sym->visible == no) + sym->flags |= SYMBOL_NEW; + switch (sym->type) { + case S_STRING: + case S_INT: + case S_HEX: + if (!sym_string_within_range(sym, sym->user.val)) { + sym->flags |= SYMBOL_NEW; + sym->flags &= ~SYMBOL_VALID; + } + default: + break; + } + } + if (!sym_is_choice(sym)) + continue; + prop = sym_get_choice_prop(sym); + for (e = prop->expr; e; e = e->left.expr) + if (e->right.sym->visible != no) + sym->flags |= e->right.sym->flags & SYMBOL_NEW; + } + + sym_change_count = conf_warnings || conf_unsaved; + + return 0; +} + +int conf_write(const char *name) +{ + FILE *out, *out_h; + struct symbol *sym; + struct menu *menu; + const char *basename; + char dirname[128], tmpname[128], newname[128]; + int type, l; + const char *str; + time_t now; + int use_timestamp = 1; + char *env; + + dirname[0] = 0; + if (name && name[0]) { + struct stat st; + char *slash; + + if (!stat(name, &st) && S_ISDIR(st.st_mode)) { + strcpy(dirname, name); + strcat(dirname, "/"); + basename = conf_def_filename; + } else if ((slash = strrchr(name, '/'))) { + int size = slash - name + 1; + memcpy(dirname, name, size); + dirname[size] = 0; + if (slash[1]) + basename = slash + 1; + else + basename = conf_def_filename; + } else + basename = name; + } else + basename = conf_def_filename; + + sprintf(newname, "%s.tmpconfig.%d", dirname, (int)getpid()); + out = fopen(newname, "w"); + if (!out) + return 1; + out_h = NULL; + if (!name) { + out_h = fopen(".tmpconfig.h", "w"); + if (!out_h) + return 1; + file_write_dep(NULL); + } + sym = sym_lookup("KERNELVERSION", 0); + sym_calc_value(sym); + time(&now); + env = getenv("KCONFIG_NOTIMESTAMP"); + if (env && *env) + use_timestamp = 0; + + fprintf(out, _("#\n" + "# Automatically generated make config: don't edit\n" + "# Linux kernel version: %s\n" + "%s%s" + "#\n"), + sym_get_string_value(sym), + use_timestamp ? "# " : "", + use_timestamp ? ctime(&now) : ""); + if (out_h) { + char buf[sizeof("#define AUTOCONF_TIMESTAMP " + "\"YYYY-MM-DD HH:MM:SS some_timezone\"\n")]; + buf[0] = '\0'; + if (use_timestamp) + strftime(buf, sizeof(buf), "#define AUTOCONF_TIMESTAMP " + "\"%Y-%m-%d %H:%M:%S %Z\"\n", localtime(&now)); + fprintf(out_h, "/*\n" + " * Automatically generated C config: don't edit\n" + " * Linux kernel version: %s\n" + " */\n" + "%s" + "#define AUTOCONF_INCLUDED\n", + sym_get_string_value(sym), + buf); + } + if (!sym_change_count) + sym_clear_all_valid(); + + menu = rootmenu.list; + while (menu) { + sym = menu->sym; + if (!sym) { + if (!menu_is_visible(menu)) + goto next; + str = menu_get_prompt(menu); + fprintf(out, "\n" + "#\n" + "# %s\n" + "#\n", str); + if (out_h) + fprintf(out_h, "\n" + "/*\n" + " * %s\n" + " */\n", str); + } else if (!(sym->flags & SYMBOL_CHOICE)) { + sym_calc_value(sym); +/* bbox: we want to all syms + if (!(sym->flags & SYMBOL_WRITE)) + goto next; +*/ + sym->flags &= ~SYMBOL_WRITE; + type = sym->type; + if (type == S_TRISTATE) { + sym_calc_value(modules_sym); + if (modules_sym->curr.tri == no) + type = S_BOOLEAN; + } + switch (type) { + case S_BOOLEAN: + case S_TRISTATE: + switch (sym_get_tristate_value(sym)) { + case no: + fprintf(out, "# CONFIG_%s is not set\n", sym->name); + if (out_h) { + fprintf(out_h, "#undef CONFIG_%s\n", sym->name); + /* bbox */ + fprintf(out_h, "#define ENABLE_%s 0\n", sym->name); + fprintf(out_h, "#define USE_%s(...)\n", sym->name); + fprintf(out_h, "#define SKIP_%s(...) __VA_ARGS__\n", sym->name); + } + break; + case mod: + fprintf(out, "CONFIG_%s=m\n", sym->name); + if (out_h) + fprintf(out_h, "#define CONFIG_%s_MODULE 1\n", sym->name); + break; + case yes: + fprintf(out, "CONFIG_%s=y\n", sym->name); + if (out_h) { + fprintf(out_h, "#define CONFIG_%s 1\n", sym->name); + /* bbox */ + fprintf(out_h, "#define ENABLE_%s 1\n", sym->name); + fprintf(out_h, "#define USE_%s(...) __VA_ARGS__\n", sym->name); + fprintf(out_h, "#define SKIP_%s(...)\n", sym->name); + } + break; + } + break; + case S_STRING: + // fix me + str = sym_get_string_value(sym); + fprintf(out, "CONFIG_%s=\"", sym->name); + if (out_h) + fprintf(out_h, "#define CONFIG_%s \"", sym->name); + do { + l = strcspn(str, "\"\\"); + if (l) { + fwrite(str, l, 1, out); + if (out_h) + fwrite(str, l, 1, out_h); + } + str += l; + while (*str == '\\' || *str == '"') { + fprintf(out, "\\%c", *str); + if (out_h) + fprintf(out_h, "\\%c", *str); + str++; + } + } while (*str); + fputs("\"\n", out); + if (out_h) { + fputs("\"\n", out_h); + /* bbox */ + fprintf(out_h, "#define ENABLE_%s 1\n", sym->name); + fprintf(out_h, "#define USE_%s(...) __VA_ARGS__\n", sym->name); + fprintf(out_h, "#define SKIP_%s(...)\n", sym->name); + } + break; + case S_HEX: + str = sym_get_string_value(sym); + if (str[0] != '0' || (str[1] != 'x' && str[1] != 'X')) { + fprintf(out, "CONFIG_%s=%s\n", sym->name, str); + if (out_h) { + fprintf(out_h, "#define CONFIG_%s 0x%s\n", sym->name, str); + /* bbox */ + fprintf(out_h, "#define ENABLE_%s 1\n", sym->name); + fprintf(out_h, "#define USE_%s(...) __VA_ARGS__\n", sym->name); + fprintf(out_h, "#define SKIP_%s(...)\n", sym->name); + } + break; + } + case S_INT: + str = sym_get_string_value(sym); + fprintf(out, "CONFIG_%s=%s\n", sym->name, str); + if (out_h) { + fprintf(out_h, "#define CONFIG_%s %s\n", sym->name, str); + /* bbox */ + fprintf(out_h, "#define ENABLE_%s 1\n", sym->name); + fprintf(out_h, "#define USE_%s(...) __VA_ARGS__\n", sym->name); + fprintf(out_h, "#define SKIP_%s(...)\n", sym->name); + } + break; + } + } + + next: + if (menu->list) { + menu = menu->list; + continue; + } + if (menu->next) + menu = menu->next; + else while ((menu = menu->parent)) { + if (menu->next) { + menu = menu->next; + break; + } + } + } + fclose(out); + if (out_h) { + fclose(out_h); + rename(".tmpconfig.h", "include/autoconf.h"); + } + if (!name || basename != conf_def_filename) { + if (!name) + name = conf_def_filename; + sprintf(tmpname, "%s.old", name); + rename(name, tmpname); + } + sprintf(tmpname, "%s%s", dirname, basename); + if (rename(newname, tmpname)) + return 1; + + sym_change_count = 0; + + return 0; +} diff --git a/scripts/kconfig/expr.c b/scripts/kconfig/expr.c new file mode 100644 index 000000000..30e4f9d69 --- /dev/null +++ b/scripts/kconfig/expr.c @@ -0,0 +1,1099 @@ +/* + * Copyright (C) 2002 Roman Zippel + * Released under the terms of the GNU GPL v2.0. + */ + +#include +#include +#include + +#define LKC_DIRECT_LINK +#include "lkc.h" + +#define DEBUG_EXPR 0 + +struct expr *expr_alloc_symbol(struct symbol *sym) +{ + struct expr *e = malloc(sizeof(*e)); + memset(e, 0, sizeof(*e)); + e->type = E_SYMBOL; + e->left.sym = sym; + return e; +} + +struct expr *expr_alloc_one(enum expr_type type, struct expr *ce) +{ + struct expr *e = malloc(sizeof(*e)); + memset(e, 0, sizeof(*e)); + e->type = type; + e->left.expr = ce; + return e; +} + +struct expr *expr_alloc_two(enum expr_type type, struct expr *e1, struct expr *e2) +{ + struct expr *e = malloc(sizeof(*e)); + memset(e, 0, sizeof(*e)); + e->type = type; + e->left.expr = e1; + e->right.expr = e2; + return e; +} + +struct expr *expr_alloc_comp(enum expr_type type, struct symbol *s1, struct symbol *s2) +{ + struct expr *e = malloc(sizeof(*e)); + memset(e, 0, sizeof(*e)); + e->type = type; + e->left.sym = s1; + e->right.sym = s2; + return e; +} + +struct expr *expr_alloc_and(struct expr *e1, struct expr *e2) +{ + if (!e1) + return e2; + return e2 ? expr_alloc_two(E_AND, e1, e2) : e1; +} + +struct expr *expr_alloc_or(struct expr *e1, struct expr *e2) +{ + if (!e1) + return e2; + return e2 ? expr_alloc_two(E_OR, e1, e2) : e1; +} + +struct expr *expr_copy(struct expr *org) +{ + struct expr *e; + + if (!org) + return NULL; + + e = malloc(sizeof(*org)); + memcpy(e, org, sizeof(*org)); + switch (org->type) { + case E_SYMBOL: + e->left = org->left; + break; + case E_NOT: + e->left.expr = expr_copy(org->left.expr); + break; + case E_EQUAL: + case E_UNEQUAL: + e->left.sym = org->left.sym; + e->right.sym = org->right.sym; + break; + case E_AND: + case E_OR: + case E_CHOICE: + e->left.expr = expr_copy(org->left.expr); + e->right.expr = expr_copy(org->right.expr); + break; + default: + printf("can't copy type %d\n", e->type); + free(e); + e = NULL; + break; + } + + return e; +} + +void expr_free(struct expr *e) +{ + if (!e) + return; + + switch (e->type) { + case E_SYMBOL: + break; + case E_NOT: + expr_free(e->left.expr); + return; + case E_EQUAL: + case E_UNEQUAL: + break; + case E_OR: + case E_AND: + expr_free(e->left.expr); + expr_free(e->right.expr); + break; + default: + printf("how to free type %d?\n", e->type); + break; + } + free(e); +} + +static int trans_count; + +#define e1 (*ep1) +#define e2 (*ep2) + +static void __expr_eliminate_eq(enum expr_type type, struct expr **ep1, struct expr **ep2) +{ + if (e1->type == type) { + __expr_eliminate_eq(type, &e1->left.expr, &e2); + __expr_eliminate_eq(type, &e1->right.expr, &e2); + return; + } + if (e2->type == type) { + __expr_eliminate_eq(type, &e1, &e2->left.expr); + __expr_eliminate_eq(type, &e1, &e2->right.expr); + return; + } + if (e1->type == E_SYMBOL && e2->type == E_SYMBOL && + e1->left.sym == e2->left.sym && (e1->left.sym->flags & (SYMBOL_YES|SYMBOL_NO))) + return; + if (!expr_eq(e1, e2)) + return; + trans_count++; + expr_free(e1); expr_free(e2); + switch (type) { + case E_OR: + e1 = expr_alloc_symbol(&symbol_no); + e2 = expr_alloc_symbol(&symbol_no); + break; + case E_AND: + e1 = expr_alloc_symbol(&symbol_yes); + e2 = expr_alloc_symbol(&symbol_yes); + break; + default: + ; + } +} + +void expr_eliminate_eq(struct expr **ep1, struct expr **ep2) +{ + if (!e1 || !e2) + return; + switch (e1->type) { + case E_OR: + case E_AND: + __expr_eliminate_eq(e1->type, ep1, ep2); + default: + ; + } + if (e1->type != e2->type) switch (e2->type) { + case E_OR: + case E_AND: + __expr_eliminate_eq(e2->type, ep1, ep2); + default: + ; + } + e1 = expr_eliminate_yn(e1); + e2 = expr_eliminate_yn(e2); +} + +#undef e1 +#undef e2 + +int expr_eq(struct expr *e1, struct expr *e2) +{ + int res, old_count; + + if (e1->type != e2->type) + return 0; + switch (e1->type) { + case E_EQUAL: + case E_UNEQUAL: + return e1->left.sym == e2->left.sym && e1->right.sym == e2->right.sym; + case E_SYMBOL: + return e1->left.sym == e2->left.sym; + case E_NOT: + return expr_eq(e1->left.expr, e2->left.expr); + case E_AND: + case E_OR: + e1 = expr_copy(e1); + e2 = expr_copy(e2); + old_count = trans_count; + expr_eliminate_eq(&e1, &e2); + res = (e1->type == E_SYMBOL && e2->type == E_SYMBOL && + e1->left.sym == e2->left.sym); + expr_free(e1); + expr_free(e2); + trans_count = old_count; + return res; + case E_CHOICE: + case E_RANGE: + case E_NONE: + /* panic */; + } + + if (DEBUG_EXPR) { + expr_fprint(e1, stdout); + printf(" = "); + expr_fprint(e2, stdout); + printf(" ?\n"); + } + + return 0; +} + +struct expr *expr_eliminate_yn(struct expr *e) +{ + struct expr *tmp; + + if (e) switch (e->type) { + case E_AND: + e->left.expr = expr_eliminate_yn(e->left.expr); + e->right.expr = expr_eliminate_yn(e->right.expr); + if (e->left.expr->type == E_SYMBOL) { + if (e->left.expr->left.sym == &symbol_no) { + expr_free(e->left.expr); + expr_free(e->right.expr); + e->type = E_SYMBOL; + e->left.sym = &symbol_no; + e->right.expr = NULL; + return e; + } else if (e->left.expr->left.sym == &symbol_yes) { + free(e->left.expr); + tmp = e->right.expr; + *e = *(e->right.expr); + free(tmp); + return e; + } + } + if (e->right.expr->type == E_SYMBOL) { + if (e->right.expr->left.sym == &symbol_no) { + expr_free(e->left.expr); + expr_free(e->right.expr); + e->type = E_SYMBOL; + e->left.sym = &symbol_no; + e->right.expr = NULL; + return e; + } else if (e->right.expr->left.sym == &symbol_yes) { + free(e->right.expr); + tmp = e->left.expr; + *e = *(e->left.expr); + free(tmp); + return e; + } + } + break; + case E_OR: + e->left.expr = expr_eliminate_yn(e->left.expr); + e->right.expr = expr_eliminate_yn(e->right.expr); + if (e->left.expr->type == E_SYMBOL) { + if (e->left.expr->left.sym == &symbol_no) { + free(e->left.expr); + tmp = e->right.expr; + *e = *(e->right.expr); + free(tmp); + return e; + } else if (e->left.expr->left.sym == &symbol_yes) { + expr_free(e->left.expr); + expr_free(e->right.expr); + e->type = E_SYMBOL; + e->left.sym = &symbol_yes; + e->right.expr = NULL; + return e; + } + } + if (e->right.expr->type == E_SYMBOL) { + if (e->right.expr->left.sym == &symbol_no) { + free(e->right.expr); + tmp = e->left.expr; + *e = *(e->left.expr); + free(tmp); + return e; + } else if (e->right.expr->left.sym == &symbol_yes) { + expr_free(e->left.expr); + expr_free(e->right.expr); + e->type = E_SYMBOL; + e->left.sym = &symbol_yes; + e->right.expr = NULL; + return e; + } + } + break; + default: + ; + } + return e; +} + +/* + * bool FOO!=n => FOO + */ +struct expr *expr_trans_bool(struct expr *e) +{ + if (!e) + return NULL; + switch (e->type) { + case E_AND: + case E_OR: + case E_NOT: + e->left.expr = expr_trans_bool(e->left.expr); + e->right.expr = expr_trans_bool(e->right.expr); + break; + case E_UNEQUAL: + // FOO!=n -> FOO + if (e->left.sym->type == S_TRISTATE) { + if (e->right.sym == &symbol_no) { + e->type = E_SYMBOL; + e->right.sym = NULL; + } + } + break; + default: + ; + } + return e; +} + +/* + * e1 || e2 -> ? + */ +struct expr *expr_join_or(struct expr *e1, struct expr *e2) +{ + struct expr *tmp; + struct symbol *sym1, *sym2; + + if (expr_eq(e1, e2)) + return expr_copy(e1); + if (e1->type != E_EQUAL && e1->type != E_UNEQUAL && e1->type != E_SYMBOL && e1->type != E_NOT) + return NULL; + if (e2->type != E_EQUAL && e2->type != E_UNEQUAL && e2->type != E_SYMBOL && e2->type != E_NOT) + return NULL; + if (e1->type == E_NOT) { + tmp = e1->left.expr; + if (tmp->type != E_EQUAL && tmp->type != E_UNEQUAL && tmp->type != E_SYMBOL) + return NULL; + sym1 = tmp->left.sym; + } else + sym1 = e1->left.sym; + if (e2->type == E_NOT) { + if (e2->left.expr->type != E_SYMBOL) + return NULL; + sym2 = e2->left.expr->left.sym; + } else + sym2 = e2->left.sym; + if (sym1 != sym2) + return NULL; + if (sym1->type != S_BOOLEAN && sym1->type != S_TRISTATE) + return NULL; + if (sym1->type == S_TRISTATE) { + if (e1->type == E_EQUAL && e2->type == E_EQUAL && + ((e1->right.sym == &symbol_yes && e2->right.sym == &symbol_mod) || + (e1->right.sym == &symbol_mod && e2->right.sym == &symbol_yes))) { + // (a='y') || (a='m') -> (a!='n') + return expr_alloc_comp(E_UNEQUAL, sym1, &symbol_no); + } + if (e1->type == E_EQUAL && e2->type == E_EQUAL && + ((e1->right.sym == &symbol_yes && e2->right.sym == &symbol_no) || + (e1->right.sym == &symbol_no && e2->right.sym == &symbol_yes))) { + // (a='y') || (a='n') -> (a!='m') + return expr_alloc_comp(E_UNEQUAL, sym1, &symbol_mod); + } + if (e1->type == E_EQUAL && e2->type == E_EQUAL && + ((e1->right.sym == &symbol_mod && e2->right.sym == &symbol_no) || + (e1->right.sym == &symbol_no && e2->right.sym == &symbol_mod))) { + // (a='m') || (a='n') -> (a!='y') + return expr_alloc_comp(E_UNEQUAL, sym1, &symbol_yes); + } + } + if (sym1->type == S_BOOLEAN && sym1 == sym2) { + if ((e1->type == E_NOT && e1->left.expr->type == E_SYMBOL && e2->type == E_SYMBOL) || + (e2->type == E_NOT && e2->left.expr->type == E_SYMBOL && e1->type == E_SYMBOL)) + return expr_alloc_symbol(&symbol_yes); + } + + if (DEBUG_EXPR) { + printf("optimize ("); + expr_fprint(e1, stdout); + printf(") || ("); + expr_fprint(e2, stdout); + printf(")?\n"); + } + return NULL; +} + +struct expr *expr_join_and(struct expr *e1, struct expr *e2) +{ + struct expr *tmp; + struct symbol *sym1, *sym2; + + if (expr_eq(e1, e2)) + return expr_copy(e1); + if (e1->type != E_EQUAL && e1->type != E_UNEQUAL && e1->type != E_SYMBOL && e1->type != E_NOT) + return NULL; + if (e2->type != E_EQUAL && e2->type != E_UNEQUAL && e2->type != E_SYMBOL && e2->type != E_NOT) + return NULL; + if (e1->type == E_NOT) { + tmp = e1->left.expr; + if (tmp->type != E_EQUAL && tmp->type != E_UNEQUAL && tmp->type != E_SYMBOL) + return NULL; + sym1 = tmp->left.sym; + } else + sym1 = e1->left.sym; + if (e2->type == E_NOT) { + if (e2->left.expr->type != E_SYMBOL) + return NULL; + sym2 = e2->left.expr->left.sym; + } else + sym2 = e2->left.sym; + if (sym1 != sym2) + return NULL; + if (sym1->type != S_BOOLEAN && sym1->type != S_TRISTATE) + return NULL; + + if ((e1->type == E_SYMBOL && e2->type == E_EQUAL && e2->right.sym == &symbol_yes) || + (e2->type == E_SYMBOL && e1->type == E_EQUAL && e1->right.sym == &symbol_yes)) + // (a) && (a='y') -> (a='y') + return expr_alloc_comp(E_EQUAL, sym1, &symbol_yes); + + if ((e1->type == E_SYMBOL && e2->type == E_UNEQUAL && e2->right.sym == &symbol_no) || + (e2->type == E_SYMBOL && e1->type == E_UNEQUAL && e1->right.sym == &symbol_no)) + // (a) && (a!='n') -> (a) + return expr_alloc_symbol(sym1); + + if ((e1->type == E_SYMBOL && e2->type == E_UNEQUAL && e2->right.sym == &symbol_mod) || + (e2->type == E_SYMBOL && e1->type == E_UNEQUAL && e1->right.sym == &symbol_mod)) + // (a) && (a!='m') -> (a='y') + return expr_alloc_comp(E_EQUAL, sym1, &symbol_yes); + + if (sym1->type == S_TRISTATE) { + if (e1->type == E_EQUAL && e2->type == E_UNEQUAL) { + // (a='b') && (a!='c') -> 'b'='c' ? 'n' : a='b' + sym2 = e1->right.sym; + if ((e2->right.sym->flags & SYMBOL_CONST) && (sym2->flags & SYMBOL_CONST)) + return sym2 != e2->right.sym ? expr_alloc_comp(E_EQUAL, sym1, sym2) + : expr_alloc_symbol(&symbol_no); + } + if (e1->type == E_UNEQUAL && e2->type == E_EQUAL) { + // (a='b') && (a!='c') -> 'b'='c' ? 'n' : a='b' + sym2 = e2->right.sym; + if ((e1->right.sym->flags & SYMBOL_CONST) && (sym2->flags & SYMBOL_CONST)) + return sym2 != e1->right.sym ? expr_alloc_comp(E_EQUAL, sym1, sym2) + : expr_alloc_symbol(&symbol_no); + } + if (e1->type == E_UNEQUAL && e2->type == E_UNEQUAL && + ((e1->right.sym == &symbol_yes && e2->right.sym == &symbol_no) || + (e1->right.sym == &symbol_no && e2->right.sym == &symbol_yes))) + // (a!='y') && (a!='n') -> (a='m') + return expr_alloc_comp(E_EQUAL, sym1, &symbol_mod); + + if (e1->type == E_UNEQUAL && e2->type == E_UNEQUAL && + ((e1->right.sym == &symbol_yes && e2->right.sym == &symbol_mod) || + (e1->right.sym == &symbol_mod && e2->right.sym == &symbol_yes))) + // (a!='y') && (a!='m') -> (a='n') + return expr_alloc_comp(E_EQUAL, sym1, &symbol_no); + + if (e1->type == E_UNEQUAL && e2->type == E_UNEQUAL && + ((e1->right.sym == &symbol_mod && e2->right.sym == &symbol_no) || + (e1->right.sym == &symbol_no && e2->right.sym == &symbol_mod))) + // (a!='m') && (a!='n') -> (a='m') + return expr_alloc_comp(E_EQUAL, sym1, &symbol_yes); + + if ((e1->type == E_SYMBOL && e2->type == E_EQUAL && e2->right.sym == &symbol_mod) || + (e2->type == E_SYMBOL && e1->type == E_EQUAL && e1->right.sym == &symbol_mod) || + (e1->type == E_SYMBOL && e2->type == E_UNEQUAL && e2->right.sym == &symbol_yes) || + (e2->type == E_SYMBOL && e1->type == E_UNEQUAL && e1->right.sym == &symbol_yes)) + return NULL; + } + + if (DEBUG_EXPR) { + printf("optimize ("); + expr_fprint(e1, stdout); + printf(") && ("); + expr_fprint(e2, stdout); + printf(")?\n"); + } + return NULL; +} + +static void expr_eliminate_dups1(enum expr_type type, struct expr **ep1, struct expr **ep2) +{ +#define e1 (*ep1) +#define e2 (*ep2) + struct expr *tmp; + + if (e1->type == type) { + expr_eliminate_dups1(type, &e1->left.expr, &e2); + expr_eliminate_dups1(type, &e1->right.expr, &e2); + return; + } + if (e2->type == type) { + expr_eliminate_dups1(type, &e1, &e2->left.expr); + expr_eliminate_dups1(type, &e1, &e2->right.expr); + return; + } + if (e1 == e2) + return; + + switch (e1->type) { + case E_OR: case E_AND: + expr_eliminate_dups1(e1->type, &e1, &e1); + default: + ; + } + + switch (type) { + case E_OR: + tmp = expr_join_or(e1, e2); + if (tmp) { + expr_free(e1); expr_free(e2); + e1 = expr_alloc_symbol(&symbol_no); + e2 = tmp; + trans_count++; + } + break; + case E_AND: + tmp = expr_join_and(e1, e2); + if (tmp) { + expr_free(e1); expr_free(e2); + e1 = expr_alloc_symbol(&symbol_yes); + e2 = tmp; + trans_count++; + } + break; + default: + ; + } +#undef e1 +#undef e2 +} + +static void expr_eliminate_dups2(enum expr_type type, struct expr **ep1, struct expr **ep2) +{ +#define e1 (*ep1) +#define e2 (*ep2) + struct expr *tmp, *tmp1, *tmp2; + + if (e1->type == type) { + expr_eliminate_dups2(type, &e1->left.expr, &e2); + expr_eliminate_dups2(type, &e1->right.expr, &e2); + return; + } + if (e2->type == type) { + expr_eliminate_dups2(type, &e1, &e2->left.expr); + expr_eliminate_dups2(type, &e1, &e2->right.expr); + } + if (e1 == e2) + return; + + switch (e1->type) { + case E_OR: + expr_eliminate_dups2(e1->type, &e1, &e1); + // (FOO || BAR) && (!FOO && !BAR) -> n + tmp1 = expr_transform(expr_alloc_one(E_NOT, expr_copy(e1))); + tmp2 = expr_copy(e2); + tmp = expr_extract_eq_and(&tmp1, &tmp2); + if (expr_is_yes(tmp1)) { + expr_free(e1); + e1 = expr_alloc_symbol(&symbol_no); + trans_count++; + } + expr_free(tmp2); + expr_free(tmp1); + expr_free(tmp); + break; + case E_AND: + expr_eliminate_dups2(e1->type, &e1, &e1); + // (FOO && BAR) || (!FOO || !BAR) -> y + tmp1 = expr_transform(expr_alloc_one(E_NOT, expr_copy(e1))); + tmp2 = expr_copy(e2); + tmp = expr_extract_eq_or(&tmp1, &tmp2); + if (expr_is_no(tmp1)) { + expr_free(e1); + e1 = expr_alloc_symbol(&symbol_yes); + trans_count++; + } + expr_free(tmp2); + expr_free(tmp1); + expr_free(tmp); + break; + default: + ; + } +#undef e1 +#undef e2 +} + +struct expr *expr_eliminate_dups(struct expr *e) +{ + int oldcount; + if (!e) + return e; + + oldcount = trans_count; + while (1) { + trans_count = 0; + switch (e->type) { + case E_OR: case E_AND: + expr_eliminate_dups1(e->type, &e, &e); + expr_eliminate_dups2(e->type, &e, &e); + default: + ; + } + if (!trans_count) + break; + e = expr_eliminate_yn(e); + } + trans_count = oldcount; + return e; +} + +struct expr *expr_transform(struct expr *e) +{ + struct expr *tmp; + + if (!e) + return NULL; + switch (e->type) { + case E_EQUAL: + case E_UNEQUAL: + case E_SYMBOL: + case E_CHOICE: + break; + default: + e->left.expr = expr_transform(e->left.expr); + e->right.expr = expr_transform(e->right.expr); + } + + switch (e->type) { + case E_EQUAL: + if (e->left.sym->type != S_BOOLEAN) + break; + if (e->right.sym == &symbol_no) { + e->type = E_NOT; + e->left.expr = expr_alloc_symbol(e->left.sym); + e->right.sym = NULL; + break; + } + if (e->right.sym == &symbol_mod) { + printf("boolean symbol %s tested for 'm'? test forced to 'n'\n", e->left.sym->name); + e->type = E_SYMBOL; + e->left.sym = &symbol_no; + e->right.sym = NULL; + break; + } + if (e->right.sym == &symbol_yes) { + e->type = E_SYMBOL; + e->right.sym = NULL; + break; + } + break; + case E_UNEQUAL: + if (e->left.sym->type != S_BOOLEAN) + break; + if (e->right.sym == &symbol_no) { + e->type = E_SYMBOL; + e->right.sym = NULL; + break; + } + if (e->right.sym == &symbol_mod) { + printf("boolean symbol %s tested for 'm'? test forced to 'y'\n", e->left.sym->name); + e->type = E_SYMBOL; + e->left.sym = &symbol_yes; + e->right.sym = NULL; + break; + } + if (e->right.sym == &symbol_yes) { + e->type = E_NOT; + e->left.expr = expr_alloc_symbol(e->left.sym); + e->right.sym = NULL; + break; + } + break; + case E_NOT: + switch (e->left.expr->type) { + case E_NOT: + // !!a -> a + tmp = e->left.expr->left.expr; + free(e->left.expr); + free(e); + e = tmp; + e = expr_transform(e); + break; + case E_EQUAL: + case E_UNEQUAL: + // !a='x' -> a!='x' + tmp = e->left.expr; + free(e); + e = tmp; + e->type = e->type == E_EQUAL ? E_UNEQUAL : E_EQUAL; + break; + case E_OR: + // !(a || b) -> !a && !b + tmp = e->left.expr; + e->type = E_AND; + e->right.expr = expr_alloc_one(E_NOT, tmp->right.expr); + tmp->type = E_NOT; + tmp->right.expr = NULL; + e = expr_transform(e); + break; + case E_AND: + // !(a && b) -> !a || !b + tmp = e->left.expr; + e->type = E_OR; + e->right.expr = expr_alloc_one(E_NOT, tmp->right.expr); + tmp->type = E_NOT; + tmp->right.expr = NULL; + e = expr_transform(e); + break; + case E_SYMBOL: + if (e->left.expr->left.sym == &symbol_yes) { + // !'y' -> 'n' + tmp = e->left.expr; + free(e); + e = tmp; + e->type = E_SYMBOL; + e->left.sym = &symbol_no; + break; + } + if (e->left.expr->left.sym == &symbol_mod) { + // !'m' -> 'm' + tmp = e->left.expr; + free(e); + e = tmp; + e->type = E_SYMBOL; + e->left.sym = &symbol_mod; + break; + } + if (e->left.expr->left.sym == &symbol_no) { + // !'n' -> 'y' + tmp = e->left.expr; + free(e); + e = tmp; + e->type = E_SYMBOL; + e->left.sym = &symbol_yes; + break; + } + break; + default: + ; + } + break; + default: + ; + } + return e; +} + +int expr_contains_symbol(struct expr *dep, struct symbol *sym) +{ + if (!dep) + return 0; + + switch (dep->type) { + case E_AND: + case E_OR: + return expr_contains_symbol(dep->left.expr, sym) || + expr_contains_symbol(dep->right.expr, sym); + case E_SYMBOL: + return dep->left.sym == sym; + case E_EQUAL: + case E_UNEQUAL: + return dep->left.sym == sym || + dep->right.sym == sym; + case E_NOT: + return expr_contains_symbol(dep->left.expr, sym); + default: + ; + } + return 0; +} + +bool expr_depends_symbol(struct expr *dep, struct symbol *sym) +{ + if (!dep) + return false; + + switch (dep->type) { + case E_AND: + return expr_depends_symbol(dep->left.expr, sym) || + expr_depends_symbol(dep->right.expr, sym); + case E_SYMBOL: + return dep->left.sym == sym; + case E_EQUAL: + if (dep->left.sym == sym) { + if (dep->right.sym == &symbol_yes || dep->right.sym == &symbol_mod) + return true; + } + break; + case E_UNEQUAL: + if (dep->left.sym == sym) { + if (dep->right.sym == &symbol_no) + return true; + } + break; + default: + ; + } + return false; +} + +struct expr *expr_extract_eq_and(struct expr **ep1, struct expr **ep2) +{ + struct expr *tmp = NULL; + expr_extract_eq(E_AND, &tmp, ep1, ep2); + if (tmp) { + *ep1 = expr_eliminate_yn(*ep1); + *ep2 = expr_eliminate_yn(*ep2); + } + return tmp; +} + +struct expr *expr_extract_eq_or(struct expr **ep1, struct expr **ep2) +{ + struct expr *tmp = NULL; + expr_extract_eq(E_OR, &tmp, ep1, ep2); + if (tmp) { + *ep1 = expr_eliminate_yn(*ep1); + *ep2 = expr_eliminate_yn(*ep2); + } + return tmp; +} + +void expr_extract_eq(enum expr_type type, struct expr **ep, struct expr **ep1, struct expr **ep2) +{ +#define e1 (*ep1) +#define e2 (*ep2) + if (e1->type == type) { + expr_extract_eq(type, ep, &e1->left.expr, &e2); + expr_extract_eq(type, ep, &e1->right.expr, &e2); + return; + } + if (e2->type == type) { + expr_extract_eq(type, ep, ep1, &e2->left.expr); + expr_extract_eq(type, ep, ep1, &e2->right.expr); + return; + } + if (expr_eq(e1, e2)) { + *ep = *ep ? expr_alloc_two(type, *ep, e1) : e1; + expr_free(e2); + if (type == E_AND) { + e1 = expr_alloc_symbol(&symbol_yes); + e2 = expr_alloc_symbol(&symbol_yes); + } else if (type == E_OR) { + e1 = expr_alloc_symbol(&symbol_no); + e2 = expr_alloc_symbol(&symbol_no); + } + } +#undef e1 +#undef e2 +} + +struct expr *expr_trans_compare(struct expr *e, enum expr_type type, struct symbol *sym) +{ + struct expr *e1, *e2; + + if (!e) { + e = expr_alloc_symbol(sym); + if (type == E_UNEQUAL) + e = expr_alloc_one(E_NOT, e); + return e; + } + switch (e->type) { + case E_AND: + e1 = expr_trans_compare(e->left.expr, E_EQUAL, sym); + e2 = expr_trans_compare(e->right.expr, E_EQUAL, sym); + if (sym == &symbol_yes) + e = expr_alloc_two(E_AND, e1, e2); + if (sym == &symbol_no) + e = expr_alloc_two(E_OR, e1, e2); + if (type == E_UNEQUAL) + e = expr_alloc_one(E_NOT, e); + return e; + case E_OR: + e1 = expr_trans_compare(e->left.expr, E_EQUAL, sym); + e2 = expr_trans_compare(e->right.expr, E_EQUAL, sym); + if (sym == &symbol_yes) + e = expr_alloc_two(E_OR, e1, e2); + if (sym == &symbol_no) + e = expr_alloc_two(E_AND, e1, e2); + if (type == E_UNEQUAL) + e = expr_alloc_one(E_NOT, e); + return e; + case E_NOT: + return expr_trans_compare(e->left.expr, type == E_EQUAL ? E_UNEQUAL : E_EQUAL, sym); + case E_UNEQUAL: + case E_EQUAL: + if (type == E_EQUAL) { + if (sym == &symbol_yes) + return expr_copy(e); + if (sym == &symbol_mod) + return expr_alloc_symbol(&symbol_no); + if (sym == &symbol_no) + return expr_alloc_one(E_NOT, expr_copy(e)); + } else { + if (sym == &symbol_yes) + return expr_alloc_one(E_NOT, expr_copy(e)); + if (sym == &symbol_mod) + return expr_alloc_symbol(&symbol_yes); + if (sym == &symbol_no) + return expr_copy(e); + } + break; + case E_SYMBOL: + return expr_alloc_comp(type, e->left.sym, sym); + case E_CHOICE: + case E_RANGE: + case E_NONE: + /* panic */; + } + return NULL; +} + +tristate expr_calc_value(struct expr *e) +{ + tristate val1, val2; + const char *str1, *str2; + + if (!e) + return yes; + + switch (e->type) { + case E_SYMBOL: + sym_calc_value(e->left.sym); + return e->left.sym->curr.tri; + case E_AND: + val1 = expr_calc_value(e->left.expr); + val2 = expr_calc_value(e->right.expr); + return E_AND(val1, val2); + case E_OR: + val1 = expr_calc_value(e->left.expr); + val2 = expr_calc_value(e->right.expr); + return E_OR(val1, val2); + case E_NOT: + val1 = expr_calc_value(e->left.expr); + return E_NOT(val1); + case E_EQUAL: + sym_calc_value(e->left.sym); + sym_calc_value(e->right.sym); + str1 = sym_get_string_value(e->left.sym); + str2 = sym_get_string_value(e->right.sym); + return !strcmp(str1, str2) ? yes : no; + case E_UNEQUAL: + sym_calc_value(e->left.sym); + sym_calc_value(e->right.sym); + str1 = sym_get_string_value(e->left.sym); + str2 = sym_get_string_value(e->right.sym); + return !strcmp(str1, str2) ? no : yes; + default: + printf("expr_calc_value: %d?\n", e->type); + return no; + } +} + +int expr_compare_type(enum expr_type t1, enum expr_type t2) +{ +#if 0 + return 1; +#else + if (t1 == t2) + return 0; + switch (t1) { + case E_EQUAL: + case E_UNEQUAL: + if (t2 == E_NOT) + return 1; + case E_NOT: + if (t2 == E_AND) + return 1; + case E_AND: + if (t2 == E_OR) + return 1; + case E_OR: + if (t2 == E_CHOICE) + return 1; + case E_CHOICE: + if (t2 == 0) + return 1; + default: + return -1; + } + printf("[%dgt%d?]", t1, t2); + return 0; +#endif +} + +void expr_print(struct expr *e, void (*fn)(void *, const char *), void *data, int prevtoken) +{ + if (!e) { + fn(data, "y"); + return; + } + + if (expr_compare_type(prevtoken, e->type) > 0) + fn(data, "("); + switch (e->type) { + case E_SYMBOL: + if (e->left.sym->name) + fn(data, e->left.sym->name); + else + fn(data, ""); + break; + case E_NOT: + fn(data, "!"); + expr_print(e->left.expr, fn, data, E_NOT); + break; + case E_EQUAL: + fn(data, e->left.sym->name); + fn(data, "="); + fn(data, e->right.sym->name); + break; + case E_UNEQUAL: + fn(data, e->left.sym->name); + fn(data, "!="); + fn(data, e->right.sym->name); + break; + case E_OR: + expr_print(e->left.expr, fn, data, E_OR); + fn(data, " || "); + expr_print(e->right.expr, fn, data, E_OR); + break; + case E_AND: + expr_print(e->left.expr, fn, data, E_AND); + fn(data, " && "); + expr_print(e->right.expr, fn, data, E_AND); + break; + case E_CHOICE: + fn(data, e->right.sym->name); + if (e->left.expr) { + fn(data, " ^ "); + expr_print(e->left.expr, fn, data, E_CHOICE); + } + break; + case E_RANGE: + fn(data, "["); + fn(data, e->left.sym->name); + fn(data, " "); + fn(data, e->right.sym->name); + fn(data, "]"); + break; + default: + { + char buf[32]; + sprintf(buf, "", e->type); + fn(data, buf); + break; + } + } + if (expr_compare_type(prevtoken, e->type) > 0) + fn(data, ")"); +} + +static void expr_print_file_helper(void *data, const char *str) +{ + fwrite(str, strlen(str), 1, data); +} + +void expr_fprint(struct expr *e, FILE *out) +{ + expr_print(e, expr_print_file_helper, out, E_NONE); +} + +static void expr_print_gstr_helper(void *data, const char *str) +{ + str_append((struct gstr*)data, str); +} + +void expr_gstr_print(struct expr *e, struct gstr *gs) +{ + expr_print(e, expr_print_gstr_helper, gs, E_NONE); +} diff --git a/scripts/kconfig/expr.h b/scripts/kconfig/expr.h new file mode 100644 index 000000000..1b36ef18c --- /dev/null +++ b/scripts/kconfig/expr.h @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2002 Roman Zippel + * Released under the terms of the GNU GPL v2.0. + */ + +#ifndef EXPR_H +#define EXPR_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#ifndef __cplusplus +#include +#endif + +struct file { + struct file *next; + struct file *parent; + char *name; + int lineno; + int flags; +}; + +#define FILE_BUSY 0x0001 +#define FILE_SCANNED 0x0002 +#define FILE_PRINTED 0x0004 + +typedef enum tristate { + no, mod, yes +} tristate; + +enum expr_type { + E_NONE, E_OR, E_AND, E_NOT, E_EQUAL, E_UNEQUAL, E_CHOICE, E_SYMBOL, E_RANGE +}; + +union expr_data { + struct expr *expr; + struct symbol *sym; +}; + +struct expr { + enum expr_type type; + union expr_data left, right; +}; + +#define E_OR(dep1, dep2) (((dep1)>(dep2))?(dep1):(dep2)) +#define E_AND(dep1, dep2) (((dep1)<(dep2))?(dep1):(dep2)) +#define E_NOT(dep) (2-(dep)) + +struct expr_value { + struct expr *expr; + tristate tri; +}; + +struct symbol_value { + void *val; + tristate tri; +}; + +enum symbol_type { + S_UNKNOWN, S_BOOLEAN, S_TRISTATE, S_INT, S_HEX, S_STRING, S_OTHER +}; + +struct symbol { + struct symbol *next; + char *name; + char *help; + enum symbol_type type; + struct symbol_value curr, user; + tristate visible; + int flags; + struct property *prop; + struct expr *dep, *dep2; + struct expr_value rev_dep; +}; + +#define for_all_symbols(i, sym) for (i = 0; i < 257; i++) for (sym = symbol_hash[i]; sym; sym = sym->next) if (sym->type != S_OTHER) + +#define SYMBOL_YES 0x0001 +#define SYMBOL_MOD 0x0002 +#define SYMBOL_NO 0x0004 +#define SYMBOL_CONST 0x0007 +#define SYMBOL_CHECK 0x0008 +#define SYMBOL_CHOICE 0x0010 +#define SYMBOL_CHOICEVAL 0x0020 +#define SYMBOL_PRINTED 0x0040 +#define SYMBOL_VALID 0x0080 +#define SYMBOL_OPTIONAL 0x0100 +#define SYMBOL_WRITE 0x0200 +#define SYMBOL_CHANGED 0x0400 +#define SYMBOL_NEW 0x0800 +#define SYMBOL_AUTO 0x1000 +#define SYMBOL_CHECKED 0x2000 +#define SYMBOL_WARNED 0x8000 + +#define SYMBOL_MAXLENGTH 256 +#define SYMBOL_HASHSIZE 257 +#define SYMBOL_HASHMASK 0xff + +enum prop_type { + P_UNKNOWN, P_PROMPT, P_COMMENT, P_MENU, P_DEFAULT, P_CHOICE, P_SELECT, P_RANGE +}; + +struct property { + struct property *next; + struct symbol *sym; + enum prop_type type; + const char *text; + struct expr_value visible; + struct expr *expr; + struct menu *menu; + struct file *file; + int lineno; +}; + +#define for_all_properties(sym, st, tok) \ + for (st = sym->prop; st; st = st->next) \ + if (st->type == (tok)) +#define for_all_defaults(sym, st) for_all_properties(sym, st, P_DEFAULT) +#define for_all_choices(sym, st) for_all_properties(sym, st, P_CHOICE) +#define for_all_prompts(sym, st) \ + for (st = sym->prop; st; st = st->next) \ + if (st->text) + +struct menu { + struct menu *next; + struct menu *parent; + struct menu *list; + struct symbol *sym; + struct property *prompt; + struct expr *dep; + unsigned int flags; + //char *help; + struct file *file; + int lineno; + void *data; +}; + +#define MENU_CHANGED 0x0001 +#define MENU_ROOT 0x0002 + +#ifndef SWIG + +extern struct file *file_list; +extern struct file *current_file; +struct file *lookup_file(const char *name); + +extern struct symbol symbol_yes, symbol_no, symbol_mod; +extern struct symbol *modules_sym; +extern int cdebug; +struct expr *expr_alloc_symbol(struct symbol *sym); +struct expr *expr_alloc_one(enum expr_type type, struct expr *ce); +struct expr *expr_alloc_two(enum expr_type type, struct expr *e1, struct expr *e2); +struct expr *expr_alloc_comp(enum expr_type type, struct symbol *s1, struct symbol *s2); +struct expr *expr_alloc_and(struct expr *e1, struct expr *e2); +struct expr *expr_alloc_or(struct expr *e1, struct expr *e2); +struct expr *expr_copy(struct expr *org); +void expr_free(struct expr *e); +int expr_eq(struct expr *e1, struct expr *e2); +void expr_eliminate_eq(struct expr **ep1, struct expr **ep2); +tristate expr_calc_value(struct expr *e); +struct expr *expr_eliminate_yn(struct expr *e); +struct expr *expr_trans_bool(struct expr *e); +struct expr *expr_eliminate_dups(struct expr *e); +struct expr *expr_transform(struct expr *e); +int expr_contains_symbol(struct expr *dep, struct symbol *sym); +bool expr_depends_symbol(struct expr *dep, struct symbol *sym); +struct expr *expr_extract_eq_and(struct expr **ep1, struct expr **ep2); +struct expr *expr_extract_eq_or(struct expr **ep1, struct expr **ep2); +void expr_extract_eq(enum expr_type type, struct expr **ep, struct expr **ep1, struct expr **ep2); +struct expr *expr_trans_compare(struct expr *e, enum expr_type type, struct symbol *sym); + +void expr_fprint(struct expr *e, FILE *out); +struct gstr; /* forward */ +void expr_gstr_print(struct expr *e, struct gstr *gs); + +static inline int expr_is_yes(struct expr *e) +{ + return !e || (e->type == E_SYMBOL && e->left.sym == &symbol_yes); +} + +static inline int expr_is_no(struct expr *e) +{ + return e && (e->type == E_SYMBOL && e->left.sym == &symbol_no); +} +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* EXPR_H */ diff --git a/scripts/kconfig/gconf.c b/scripts/kconfig/gconf.c new file mode 100644 index 000000000..566e35e5c --- /dev/null +++ b/scripts/kconfig/gconf.c @@ -0,0 +1,1645 @@ +/* Hey EMACS -*- linux-c -*- */ +/* + * + * Copyright (C) 2002-2003 Romain Lievin + * Released under the terms of the GNU GPL v2.0. + * + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "lkc.h" +#include "images.c" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +//#define DEBUG + +enum { + SINGLE_VIEW, SPLIT_VIEW, FULL_VIEW +}; + +static gint view_mode = FULL_VIEW; +static gboolean show_name = TRUE; +static gboolean show_range = TRUE; +static gboolean show_value = TRUE; +static gboolean show_all = FALSE; +static gboolean show_debug = FALSE; +static gboolean resizeable = FALSE; + +static gboolean config_changed = FALSE; + +static char nohelp_text[] = + N_("Sorry, no help available for this option yet.\n"); + +GtkWidget *main_wnd = NULL; +GtkWidget *tree1_w = NULL; // left frame +GtkWidget *tree2_w = NULL; // right frame +GtkWidget *text_w = NULL; +GtkWidget *hpaned = NULL; +GtkWidget *vpaned = NULL; +GtkWidget *back_btn = NULL; + +GtkTextTag *tag1, *tag2; +GdkColor color; + +GtkTreeStore *tree1, *tree2, *tree; +GtkTreeModel *model1, *model2; +static GtkTreeIter *parents[256]; +static gint indent; + +static struct menu *current; // current node for SINGLE view +static struct menu *browsed; // browsed node for SPLIT view + +enum { + COL_OPTION, COL_NAME, COL_NO, COL_MOD, COL_YES, COL_VALUE, + COL_MENU, COL_COLOR, COL_EDIT, COL_PIXBUF, + COL_PIXVIS, COL_BTNVIS, COL_BTNACT, COL_BTNINC, COL_BTNRAD, + COL_NUMBER +}; + +static void display_list(void); +static void display_tree(struct menu *menu); +static void display_tree_part(void); +static void update_tree(struct menu *src, GtkTreeIter * dst); +static void set_node(GtkTreeIter * node, struct menu *menu, gchar ** row); +static gchar **fill_row(struct menu *menu); + + +/* Helping/Debugging Functions */ + + +const char *dbg_print_stype(int val) +{ + static char buf[256]; + + memset(buf, 0, 256); + + if (val == S_UNKNOWN) + strcpy(buf, "unknown"); + if (val == S_BOOLEAN) + strcpy(buf, "boolean"); + if (val == S_TRISTATE) + strcpy(buf, "tristate"); + if (val == S_INT) + strcpy(buf, "int"); + if (val == S_HEX) + strcpy(buf, "hex"); + if (val == S_STRING) + strcpy(buf, "string"); + if (val == S_OTHER) + strcpy(buf, "other"); + +#ifdef DEBUG + printf("%s", buf); +#endif + + return buf; +} + +const char *dbg_print_flags(int val) +{ + static char buf[256]; + + memset(buf, 0, 256); + + if (val & SYMBOL_YES) + strcat(buf, "yes/"); + if (val & SYMBOL_MOD) + strcat(buf, "mod/"); + if (val & SYMBOL_NO) + strcat(buf, "no/"); + if (val & SYMBOL_CONST) + strcat(buf, "const/"); + if (val & SYMBOL_CHECK) + strcat(buf, "check/"); + if (val & SYMBOL_CHOICE) + strcat(buf, "choice/"); + if (val & SYMBOL_CHOICEVAL) + strcat(buf, "choiceval/"); + if (val & SYMBOL_PRINTED) + strcat(buf, "printed/"); + if (val & SYMBOL_VALID) + strcat(buf, "valid/"); + if (val & SYMBOL_OPTIONAL) + strcat(buf, "optional/"); + if (val & SYMBOL_WRITE) + strcat(buf, "write/"); + if (val & SYMBOL_CHANGED) + strcat(buf, "changed/"); + if (val & SYMBOL_NEW) + strcat(buf, "new/"); + if (val & SYMBOL_AUTO) + strcat(buf, "auto/"); + + buf[strlen(buf) - 1] = '\0'; +#ifdef DEBUG + printf("%s", buf); +#endif + + return buf; +} + +const char *dbg_print_ptype(int val) +{ + static char buf[256]; + + memset(buf, 0, 256); + + if (val == P_UNKNOWN) + strcpy(buf, "unknown"); + if (val == P_PROMPT) + strcpy(buf, "prompt"); + if (val == P_COMMENT) + strcpy(buf, "comment"); + if (val == P_MENU) + strcpy(buf, "menu"); + if (val == P_DEFAULT) + strcpy(buf, "default"); + if (val == P_CHOICE) + strcpy(buf, "choice"); + +#ifdef DEBUG + printf("%s", buf); +#endif + + return buf; +} + + +void replace_button_icon(GladeXML * xml, GdkDrawable * window, + GtkStyle * style, gchar * btn_name, gchar ** xpm) +{ + GdkPixmap *pixmap; + GdkBitmap *mask; + GtkToolButton *button; + GtkWidget *image; + + pixmap = gdk_pixmap_create_from_xpm_d(window, &mask, + &style->bg[GTK_STATE_NORMAL], + xpm); + + button = GTK_TOOL_BUTTON(glade_xml_get_widget(xml, btn_name)); + image = gtk_image_new_from_pixmap(pixmap, mask); + gtk_widget_show(image); + gtk_tool_button_set_icon_widget(button, image); +} + +/* Main Window Initialization */ +void init_main_window(const gchar * glade_file) +{ + GladeXML *xml; + GtkWidget *widget; + GtkTextBuffer *txtbuf; + char title[256]; + GtkStyle *style; + + xml = glade_xml_new(glade_file, "window1", NULL); + if (!xml) + g_error(_("GUI loading failed !\n")); + glade_xml_signal_autoconnect(xml); + + main_wnd = glade_xml_get_widget(xml, "window1"); + hpaned = glade_xml_get_widget(xml, "hpaned1"); + vpaned = glade_xml_get_widget(xml, "vpaned1"); + tree1_w = glade_xml_get_widget(xml, "treeview1"); + tree2_w = glade_xml_get_widget(xml, "treeview2"); + text_w = glade_xml_get_widget(xml, "textview3"); + + back_btn = glade_xml_get_widget(xml, "button1"); + gtk_widget_set_sensitive(back_btn, FALSE); + + widget = glade_xml_get_widget(xml, "show_name1"); + gtk_check_menu_item_set_active((GtkCheckMenuItem *) widget, + show_name); + + widget = glade_xml_get_widget(xml, "show_range1"); + gtk_check_menu_item_set_active((GtkCheckMenuItem *) widget, + show_range); + + widget = glade_xml_get_widget(xml, "show_data1"); + gtk_check_menu_item_set_active((GtkCheckMenuItem *) widget, + show_value); + + style = gtk_widget_get_style(main_wnd); + widget = glade_xml_get_widget(xml, "toolbar1"); + +#if 0 /* Use stock Gtk icons instead */ + replace_button_icon(xml, main_wnd->window, style, + "button1", (gchar **) xpm_back); + replace_button_icon(xml, main_wnd->window, style, + "button2", (gchar **) xpm_load); + replace_button_icon(xml, main_wnd->window, style, + "button3", (gchar **) xpm_save); +#endif + replace_button_icon(xml, main_wnd->window, style, + "button4", (gchar **) xpm_single_view); + replace_button_icon(xml, main_wnd->window, style, + "button5", (gchar **) xpm_split_view); + replace_button_icon(xml, main_wnd->window, style, + "button6", (gchar **) xpm_tree_view); + +#if 0 + switch (view_mode) { + case SINGLE_VIEW: + widget = glade_xml_get_widget(xml, "button4"); + g_signal_emit_by_name(widget, "clicked"); + break; + case SPLIT_VIEW: + widget = glade_xml_get_widget(xml, "button5"); + g_signal_emit_by_name(widget, "clicked"); + break; + case FULL_VIEW: + widget = glade_xml_get_widget(xml, "button6"); + g_signal_emit_by_name(widget, "clicked"); + break; + } +#endif + txtbuf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_w)); + tag1 = gtk_text_buffer_create_tag(txtbuf, "mytag1", + "foreground", "red", + "weight", PANGO_WEIGHT_BOLD, + NULL); + tag2 = gtk_text_buffer_create_tag(txtbuf, "mytag2", + /*"style", PANGO_STYLE_OBLIQUE, */ + NULL); + + sprintf(title, _("Linux Kernel v%s Configuration"), + getenv("KERNELVERSION")); + gtk_window_set_title(GTK_WINDOW(main_wnd), title); + + gtk_widget_show(main_wnd); +} + +void init_tree_model(void) +{ + gint i; + + tree = tree2 = gtk_tree_store_new(COL_NUMBER, + G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_POINTER, GDK_TYPE_COLOR, + G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, + G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN); + model2 = GTK_TREE_MODEL(tree2); + + for (parents[0] = NULL, i = 1; i < 256; i++) + parents[i] = (GtkTreeIter *) g_malloc(sizeof(GtkTreeIter)); + + tree1 = gtk_tree_store_new(COL_NUMBER, + G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_POINTER, GDK_TYPE_COLOR, + G_TYPE_BOOLEAN, GDK_TYPE_PIXBUF, + G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN); + model1 = GTK_TREE_MODEL(tree1); +} + +void init_left_tree(void) +{ + GtkTreeView *view = GTK_TREE_VIEW(tree1_w); + GtkCellRenderer *renderer; + GtkTreeSelection *sel; + GtkTreeViewColumn *column; + + gtk_tree_view_set_model(view, model1); + gtk_tree_view_set_headers_visible(view, TRUE); + gtk_tree_view_set_rules_hint(view, FALSE); + + column = gtk_tree_view_column_new(); + gtk_tree_view_append_column(view, column); + gtk_tree_view_column_set_title(column, _("Options")); + + renderer = gtk_cell_renderer_toggle_new(); + gtk_tree_view_column_pack_start(GTK_TREE_VIEW_COLUMN(column), + renderer, FALSE); + gtk_tree_view_column_set_attributes(GTK_TREE_VIEW_COLUMN(column), + renderer, + "active", COL_BTNACT, + "inconsistent", COL_BTNINC, + "visible", COL_BTNVIS, + "radio", COL_BTNRAD, NULL); + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_column_pack_start(GTK_TREE_VIEW_COLUMN(column), + renderer, FALSE); + gtk_tree_view_column_set_attributes(GTK_TREE_VIEW_COLUMN(column), + renderer, + "text", COL_OPTION, + "foreground-gdk", + COL_COLOR, NULL); + + sel = gtk_tree_view_get_selection(view); + gtk_tree_selection_set_mode(sel, GTK_SELECTION_SINGLE); + gtk_widget_realize(tree1_w); +} + +static void renderer_edited(GtkCellRendererText * cell, + const gchar * path_string, + const gchar * new_text, gpointer user_data); +static void renderer_toggled(GtkCellRendererToggle * cellrenderertoggle, + gchar * arg1, gpointer user_data); + +void init_right_tree(void) +{ + GtkTreeView *view = GTK_TREE_VIEW(tree2_w); + GtkCellRenderer *renderer; + GtkTreeSelection *sel; + GtkTreeViewColumn *column; + gint i; + + gtk_tree_view_set_model(view, model2); + gtk_tree_view_set_headers_visible(view, TRUE); + gtk_tree_view_set_rules_hint(view, FALSE); + + column = gtk_tree_view_column_new(); + gtk_tree_view_append_column(view, column); + gtk_tree_view_column_set_title(column, _("Options")); + + renderer = gtk_cell_renderer_pixbuf_new(); + gtk_tree_view_column_pack_start(GTK_TREE_VIEW_COLUMN(column), + renderer, FALSE); + gtk_tree_view_column_set_attributes(GTK_TREE_VIEW_COLUMN(column), + renderer, + "pixbuf", COL_PIXBUF, + "visible", COL_PIXVIS, NULL); + renderer = gtk_cell_renderer_toggle_new(); + gtk_tree_view_column_pack_start(GTK_TREE_VIEW_COLUMN(column), + renderer, FALSE); + gtk_tree_view_column_set_attributes(GTK_TREE_VIEW_COLUMN(column), + renderer, + "active", COL_BTNACT, + "inconsistent", COL_BTNINC, + "visible", COL_BTNVIS, + "radio", COL_BTNRAD, NULL); + /*g_signal_connect(G_OBJECT(renderer), "toggled", + G_CALLBACK(renderer_toggled), NULL); */ + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_column_pack_start(GTK_TREE_VIEW_COLUMN(column), + renderer, FALSE); + gtk_tree_view_column_set_attributes(GTK_TREE_VIEW_COLUMN(column), + renderer, + "text", COL_OPTION, + "foreground-gdk", + COL_COLOR, NULL); + + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes(view, -1, + _("Name"), renderer, + "text", COL_NAME, + "foreground-gdk", + COL_COLOR, NULL); + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes(view, -1, + "N", renderer, + "text", COL_NO, + "foreground-gdk", + COL_COLOR, NULL); + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes(view, -1, + "M", renderer, + "text", COL_MOD, + "foreground-gdk", + COL_COLOR, NULL); + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes(view, -1, + "Y", renderer, + "text", COL_YES, + "foreground-gdk", + COL_COLOR, NULL); + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes(view, -1, + _("Value"), renderer, + "text", COL_VALUE, + "editable", + COL_EDIT, + "foreground-gdk", + COL_COLOR, NULL); + g_signal_connect(G_OBJECT(renderer), "edited", + G_CALLBACK(renderer_edited), NULL); + + column = gtk_tree_view_get_column(view, COL_NAME); + gtk_tree_view_column_set_visible(column, show_name); + column = gtk_tree_view_get_column(view, COL_NO); + gtk_tree_view_column_set_visible(column, show_range); + column = gtk_tree_view_get_column(view, COL_MOD); + gtk_tree_view_column_set_visible(column, show_range); + column = gtk_tree_view_get_column(view, COL_YES); + gtk_tree_view_column_set_visible(column, show_range); + column = gtk_tree_view_get_column(view, COL_VALUE); + gtk_tree_view_column_set_visible(column, show_value); + + if (resizeable) { + for (i = 0; i < COL_VALUE; i++) { + column = gtk_tree_view_get_column(view, i); + gtk_tree_view_column_set_resizable(column, TRUE); + } + } + + sel = gtk_tree_view_get_selection(view); + gtk_tree_selection_set_mode(sel, GTK_SELECTION_SINGLE); +} + + +/* Utility Functions */ + + +static void text_insert_help(struct menu *menu) +{ + GtkTextBuffer *buffer; + GtkTextIter start, end; + const char *prompt = menu_get_prompt(menu); + gchar *name; + const char *help = _(nohelp_text); + + if (!menu->sym) + help = ""; + else if (menu->sym->help) + help = _(menu->sym->help); + + if (menu->sym && menu->sym->name) + name = g_strdup_printf(_(menu->sym->name)); + else + name = g_strdup(""); + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_w)); + gtk_text_buffer_get_bounds(buffer, &start, &end); + gtk_text_buffer_delete(buffer, &start, &end); + gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_w), 15); + + gtk_text_buffer_get_end_iter(buffer, &end); + gtk_text_buffer_insert_with_tags(buffer, &end, prompt, -1, tag1, + NULL); + gtk_text_buffer_insert_at_cursor(buffer, " ", 1); + gtk_text_buffer_get_end_iter(buffer, &end); + gtk_text_buffer_insert_with_tags(buffer, &end, name, -1, tag1, + NULL); + gtk_text_buffer_insert_at_cursor(buffer, "\n\n", 2); + gtk_text_buffer_get_end_iter(buffer, &end); + gtk_text_buffer_insert_with_tags(buffer, &end, help, -1, tag2, + NULL); +} + + +static void text_insert_msg(const char *title, const char *message) +{ + GtkTextBuffer *buffer; + GtkTextIter start, end; + const char *msg = message; + + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_w)); + gtk_text_buffer_get_bounds(buffer, &start, &end); + gtk_text_buffer_delete(buffer, &start, &end); + gtk_text_view_set_left_margin(GTK_TEXT_VIEW(text_w), 15); + + gtk_text_buffer_get_end_iter(buffer, &end); + gtk_text_buffer_insert_with_tags(buffer, &end, title, -1, tag1, + NULL); + gtk_text_buffer_insert_at_cursor(buffer, "\n\n", 2); + gtk_text_buffer_get_end_iter(buffer, &end); + gtk_text_buffer_insert_with_tags(buffer, &end, msg, -1, tag2, + NULL); +} + + +/* Main Windows Callbacks */ + +void on_save1_activate(GtkMenuItem * menuitem, gpointer user_data); +gboolean on_window1_delete_event(GtkWidget * widget, GdkEvent * event, + gpointer user_data) +{ + GtkWidget *dialog, *label; + gint result; + + if (config_changed == FALSE) + return FALSE; + + dialog = gtk_dialog_new_with_buttons(_("Warning !"), + GTK_WINDOW(main_wnd), + (GtkDialogFlags) + (GTK_DIALOG_MODAL | + GTK_DIALOG_DESTROY_WITH_PARENT), + GTK_STOCK_OK, + GTK_RESPONSE_YES, + GTK_STOCK_NO, + GTK_RESPONSE_NO, + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL, NULL); + gtk_dialog_set_default_response(GTK_DIALOG(dialog), + GTK_RESPONSE_CANCEL); + + label = gtk_label_new(_("\nSave configuration ?\n")); + gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), label); + gtk_widget_show(label); + + result = gtk_dialog_run(GTK_DIALOG(dialog)); + switch (result) { + case GTK_RESPONSE_YES: + on_save1_activate(NULL, NULL); + return FALSE; + case GTK_RESPONSE_NO: + return FALSE; + case GTK_RESPONSE_CANCEL: + case GTK_RESPONSE_DELETE_EVENT: + default: + gtk_widget_destroy(dialog); + return TRUE; + } + + return FALSE; +} + + +void on_window1_destroy(GtkObject * object, gpointer user_data) +{ + gtk_main_quit(); +} + + +void +on_window1_size_request(GtkWidget * widget, + GtkRequisition * requisition, gpointer user_data) +{ + static gint old_h; + gint w, h; + + if (widget->window == NULL) + gtk_window_get_default_size(GTK_WINDOW(main_wnd), &w, &h); + else + gdk_window_get_size(widget->window, &w, &h); + + if (h == old_h) + return; + old_h = h; + + gtk_paned_set_position(GTK_PANED(vpaned), 2 * h / 3); +} + + +/* Menu & Toolbar Callbacks */ + + +static void +load_filename(GtkFileSelection * file_selector, gpointer user_data) +{ + const gchar *fn; + + fn = gtk_file_selection_get_filename(GTK_FILE_SELECTION + (user_data)); + + if (conf_read(fn)) + text_insert_msg(_("Error"), _("Unable to load configuration !")); + else + display_tree(&rootmenu); +} + +void on_load1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + GtkWidget *fs; + + fs = gtk_file_selection_new(_("Load file...")); + g_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(fs)->ok_button), + "clicked", + G_CALLBACK(load_filename), (gpointer) fs); + g_signal_connect_swapped(GTK_OBJECT + (GTK_FILE_SELECTION(fs)->ok_button), + "clicked", G_CALLBACK(gtk_widget_destroy), + (gpointer) fs); + g_signal_connect_swapped(GTK_OBJECT + (GTK_FILE_SELECTION(fs)->cancel_button), + "clicked", G_CALLBACK(gtk_widget_destroy), + (gpointer) fs); + gtk_widget_show(fs); +} + + +void on_save1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + if (conf_write(NULL)) + text_insert_msg(_("Error"), _("Unable to save configuration !")); + + config_changed = FALSE; +} + + +static void +store_filename(GtkFileSelection * file_selector, gpointer user_data) +{ + const gchar *fn; + + fn = gtk_file_selection_get_filename(GTK_FILE_SELECTION + (user_data)); + + if (conf_write(fn)) + text_insert_msg(_("Error"), _("Unable to save configuration !")); + + gtk_widget_destroy(GTK_WIDGET(user_data)); +} + +void on_save_as1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + GtkWidget *fs; + + fs = gtk_file_selection_new(_("Save file as...")); + g_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(fs)->ok_button), + "clicked", + G_CALLBACK(store_filename), (gpointer) fs); + g_signal_connect_swapped(GTK_OBJECT + (GTK_FILE_SELECTION(fs)->ok_button), + "clicked", G_CALLBACK(gtk_widget_destroy), + (gpointer) fs); + g_signal_connect_swapped(GTK_OBJECT + (GTK_FILE_SELECTION(fs)->cancel_button), + "clicked", G_CALLBACK(gtk_widget_destroy), + (gpointer) fs); + gtk_widget_show(fs); +} + + +void on_quit1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + if (!on_window1_delete_event(NULL, NULL, NULL)) + gtk_widget_destroy(GTK_WIDGET(main_wnd)); +} + + +void on_show_name1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + GtkTreeViewColumn *col; + + show_name = GTK_CHECK_MENU_ITEM(menuitem)->active; + col = gtk_tree_view_get_column(GTK_TREE_VIEW(tree2_w), COL_NAME); + if (col) + gtk_tree_view_column_set_visible(col, show_name); +} + + +void on_show_range1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + GtkTreeViewColumn *col; + + show_range = GTK_CHECK_MENU_ITEM(menuitem)->active; + col = gtk_tree_view_get_column(GTK_TREE_VIEW(tree2_w), COL_NO); + if (col) + gtk_tree_view_column_set_visible(col, show_range); + col = gtk_tree_view_get_column(GTK_TREE_VIEW(tree2_w), COL_MOD); + if (col) + gtk_tree_view_column_set_visible(col, show_range); + col = gtk_tree_view_get_column(GTK_TREE_VIEW(tree2_w), COL_YES); + if (col) + gtk_tree_view_column_set_visible(col, show_range); + +} + + +void on_show_data1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + GtkTreeViewColumn *col; + + show_value = GTK_CHECK_MENU_ITEM(menuitem)->active; + col = gtk_tree_view_get_column(GTK_TREE_VIEW(tree2_w), COL_VALUE); + if (col) + gtk_tree_view_column_set_visible(col, show_value); +} + + +void +on_show_all_options1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + show_all = GTK_CHECK_MENU_ITEM(menuitem)->active; + + gtk_tree_store_clear(tree2); + display_tree(&rootmenu); // instead of update_tree to speed-up +} + + +void +on_show_debug_info1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + show_debug = GTK_CHECK_MENU_ITEM(menuitem)->active; + update_tree(&rootmenu, NULL); +} + + +void on_introduction1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + GtkWidget *dialog; + const gchar *intro_text = _( + "Welcome to gkc, the GTK+ graphical busybox configuration tool\n" + "for Linux.\n" + "For each option, a blank box indicates the feature is disabled, a\n" + "check indicates it is enabled, and a dot indicates that it is to\n" + "be compiled as a module. Clicking on the box will cycle through the three states.\n" + "\n" + "If you do not see an option (e.g., a device driver) that you\n" + "believe should be present, try turning on Show All Options\n" + "under the Options menu.\n" + "Although there is no cross reference yet to help you figure out\n" + "what other options must be enabled to support the option you\n" + "are interested in, you can still view the help of a grayed-out\n" + "option.\n" + "\n" + "Toggling Show Debug Info under the Options menu will show\n" + "the dependencies, which you can then match by examining other options."); + + dialog = gtk_message_dialog_new(GTK_WINDOW(main_wnd), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, + GTK_BUTTONS_CLOSE, intro_text); + g_signal_connect_swapped(GTK_OBJECT(dialog), "response", + G_CALLBACK(gtk_widget_destroy), + GTK_OBJECT(dialog)); + gtk_widget_show_all(dialog); +} + + +void on_about1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + GtkWidget *dialog; + const gchar *about_text = + _("gkc is copyright (c) 2002 Romain Lievin .\n" + "Based on the source code from Roman Zippel.\n"); + + dialog = gtk_message_dialog_new(GTK_WINDOW(main_wnd), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, + GTK_BUTTONS_CLOSE, about_text); + g_signal_connect_swapped(GTK_OBJECT(dialog), "response", + G_CALLBACK(gtk_widget_destroy), + GTK_OBJECT(dialog)); + gtk_widget_show_all(dialog); +} + + +void on_license1_activate(GtkMenuItem * menuitem, gpointer user_data) +{ + GtkWidget *dialog; + const gchar *license_text = + _("gkc is released under the terms of the GNU GPL v2.\n" + "For more information, please see the source code or\n" + "visit http://www.fsf.org/licenses/licenses.html\n"); + + dialog = gtk_message_dialog_new(GTK_WINDOW(main_wnd), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, + GTK_BUTTONS_CLOSE, license_text); + g_signal_connect_swapped(GTK_OBJECT(dialog), "response", + G_CALLBACK(gtk_widget_destroy), + GTK_OBJECT(dialog)); + gtk_widget_show_all(dialog); +} + + +void on_back_clicked(GtkButton * button, gpointer user_data) +{ + enum prop_type ptype; + + current = current->parent; + ptype = current->prompt ? current->prompt->type : P_UNKNOWN; + if (ptype != P_MENU) + current = current->parent; + display_tree_part(); + + if (current == &rootmenu) + gtk_widget_set_sensitive(back_btn, FALSE); +} + + +void on_load_clicked(GtkButton * button, gpointer user_data) +{ + on_load1_activate(NULL, user_data); +} + + +void on_save_clicked(GtkButton * button, gpointer user_data) +{ + on_save1_activate(NULL, user_data); +} + + +void on_single_clicked(GtkButton * button, gpointer user_data) +{ + view_mode = SINGLE_VIEW; + gtk_paned_set_position(GTK_PANED(hpaned), 0); + gtk_widget_hide(tree1_w); + current = &rootmenu; + display_tree_part(); +} + + +void on_split_clicked(GtkButton * button, gpointer user_data) +{ + gint w, h; + view_mode = SPLIT_VIEW; + gtk_widget_show(tree1_w); + gtk_window_get_default_size(GTK_WINDOW(main_wnd), &w, &h); + gtk_paned_set_position(GTK_PANED(hpaned), w / 2); + if (tree2) + gtk_tree_store_clear(tree2); + display_list(); + + /* Disable back btn, like in full mode. */ + gtk_widget_set_sensitive(back_btn, FALSE); +} + + +void on_full_clicked(GtkButton * button, gpointer user_data) +{ + view_mode = FULL_VIEW; + gtk_paned_set_position(GTK_PANED(hpaned), 0); + gtk_widget_hide(tree1_w); + if (tree2) + gtk_tree_store_clear(tree2); + display_tree(&rootmenu); + gtk_widget_set_sensitive(back_btn, FALSE); +} + + +void on_collapse_clicked(GtkButton * button, gpointer user_data) +{ + gtk_tree_view_collapse_all(GTK_TREE_VIEW(tree2_w)); +} + + +void on_expand_clicked(GtkButton * button, gpointer user_data) +{ + gtk_tree_view_expand_all(GTK_TREE_VIEW(tree2_w)); +} + + +/* CTree Callbacks */ + +/* Change hex/int/string value in the cell */ +static void renderer_edited(GtkCellRendererText * cell, + const gchar * path_string, + const gchar * new_text, gpointer user_data) +{ + GtkTreePath *path = gtk_tree_path_new_from_string(path_string); + GtkTreeIter iter; + const char *old_def, *new_def; + struct menu *menu; + struct symbol *sym; + + if (!gtk_tree_model_get_iter(model2, &iter, path)) + return; + + gtk_tree_model_get(model2, &iter, COL_MENU, &menu, -1); + sym = menu->sym; + + gtk_tree_model_get(model2, &iter, COL_VALUE, &old_def, -1); + new_def = new_text; + + sym_set_string_value(sym, new_def); + + config_changed = TRUE; + update_tree(&rootmenu, NULL); + + gtk_tree_path_free(path); +} + +/* Change the value of a symbol and update the tree */ +static void change_sym_value(struct menu *menu, gint col) +{ + struct symbol *sym = menu->sym; + tristate oldval, newval; + + if (!sym) + return; + + if (col == COL_NO) + newval = no; + else if (col == COL_MOD) + newval = mod; + else if (col == COL_YES) + newval = yes; + else + return; + + switch (sym_get_type(sym)) { + case S_BOOLEAN: + case S_TRISTATE: + oldval = sym_get_tristate_value(sym); + if (!sym_tristate_within_range(sym, newval)) + newval = yes; + sym_set_tristate_value(sym, newval); + config_changed = TRUE; + if (view_mode == FULL_VIEW) + update_tree(&rootmenu, NULL); + else if (view_mode == SPLIT_VIEW) { + update_tree(browsed, NULL); + display_list(); + } + else if (view_mode == SINGLE_VIEW) + display_tree_part(); //fixme: keep exp/coll + break; + case S_INT: + case S_HEX: + case S_STRING: + default: + break; + } +} + +static void toggle_sym_value(struct menu *menu) +{ + if (!menu->sym) + return; + + sym_toggle_tristate_value(menu->sym); + if (view_mode == FULL_VIEW) + update_tree(&rootmenu, NULL); + else if (view_mode == SPLIT_VIEW) { + update_tree(browsed, NULL); + display_list(); + } + else if (view_mode == SINGLE_VIEW) + display_tree_part(); //fixme: keep exp/coll +} + +static void renderer_toggled(GtkCellRendererToggle * cell, + gchar * path_string, gpointer user_data) +{ + GtkTreePath *path, *sel_path = NULL; + GtkTreeIter iter, sel_iter; + GtkTreeSelection *sel; + struct menu *menu; + + path = gtk_tree_path_new_from_string(path_string); + if (!gtk_tree_model_get_iter(model2, &iter, path)) + return; + + sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree2_w)); + if (gtk_tree_selection_get_selected(sel, NULL, &sel_iter)) + sel_path = gtk_tree_model_get_path(model2, &sel_iter); + if (!sel_path) + goto out1; + if (gtk_tree_path_compare(path, sel_path)) + goto out2; + + gtk_tree_model_get(model2, &iter, COL_MENU, &menu, -1); + toggle_sym_value(menu); + + out2: + gtk_tree_path_free(sel_path); + out1: + gtk_tree_path_free(path); +} + +static gint column2index(GtkTreeViewColumn * column) +{ + gint i; + + for (i = 0; i < COL_NUMBER; i++) { + GtkTreeViewColumn *col; + + col = gtk_tree_view_get_column(GTK_TREE_VIEW(tree2_w), i); + if (col == column) + return i; + } + + return -1; +} + + +/* User click: update choice (full) or goes down (single) */ +gboolean +on_treeview2_button_press_event(GtkWidget * widget, + GdkEventButton * event, gpointer user_data) +{ + GtkTreeView *view = GTK_TREE_VIEW(widget); + GtkTreePath *path; + GtkTreeViewColumn *column; + GtkTreeIter iter; + struct menu *menu; + gint col; + +#if GTK_CHECK_VERSION(2,1,4) // bug in ctree with earlier version of GTK + gint tx = (gint) event->x; + gint ty = (gint) event->y; + gint cx, cy; + + gtk_tree_view_get_path_at_pos(view, tx, ty, &path, &column, &cx, + &cy); +#else + gtk_tree_view_get_cursor(view, &path, &column); +#endif + if (path == NULL) + return FALSE; + + if (!gtk_tree_model_get_iter(model2, &iter, path)) + return FALSE; + gtk_tree_model_get(model2, &iter, COL_MENU, &menu, -1); + + col = column2index(column); + if (event->type == GDK_2BUTTON_PRESS) { + enum prop_type ptype; + ptype = menu->prompt ? menu->prompt->type : P_UNKNOWN; + + if (ptype == P_MENU && view_mode != FULL_VIEW && col == COL_OPTION) { + // goes down into menu + current = menu; + display_tree_part(); + gtk_widget_set_sensitive(back_btn, TRUE); + } else if ((col == COL_OPTION)) { + toggle_sym_value(menu); + gtk_tree_view_expand_row(view, path, TRUE); + } + } else { + if (col == COL_VALUE) { + toggle_sym_value(menu); + gtk_tree_view_expand_row(view, path, TRUE); + } else if (col == COL_NO || col == COL_MOD + || col == COL_YES) { + change_sym_value(menu, col); + gtk_tree_view_expand_row(view, path, TRUE); + } + } + + return FALSE; +} + +/* Key pressed: update choice */ +gboolean +on_treeview2_key_press_event(GtkWidget * widget, + GdkEventKey * event, gpointer user_data) +{ + GtkTreeView *view = GTK_TREE_VIEW(widget); + GtkTreePath *path; + GtkTreeViewColumn *column; + GtkTreeIter iter; + struct menu *menu; + gint col; + + gtk_tree_view_get_cursor(view, &path, &column); + if (path == NULL) + return FALSE; + + if (event->keyval == GDK_space) { + if (gtk_tree_view_row_expanded(view, path)) + gtk_tree_view_collapse_row(view, path); + else + gtk_tree_view_expand_row(view, path, FALSE); + return TRUE; + } + if (event->keyval == GDK_KP_Enter) { + } + if (widget == tree1_w) + return FALSE; + + gtk_tree_model_get_iter(model2, &iter, path); + gtk_tree_model_get(model2, &iter, COL_MENU, &menu, -1); + + if (!strcasecmp(event->string, "n")) + col = COL_NO; + else if (!strcasecmp(event->string, "m")) + col = COL_MOD; + else if (!strcasecmp(event->string, "y")) + col = COL_YES; + else + col = -1; + change_sym_value(menu, col); + + return FALSE; +} + + +/* Row selection changed: update help */ +void +on_treeview2_cursor_changed(GtkTreeView * treeview, gpointer user_data) +{ + GtkTreeSelection *selection; + GtkTreeIter iter; + struct menu *menu; + + selection = gtk_tree_view_get_selection(treeview); + if (gtk_tree_selection_get_selected(selection, &model2, &iter)) { + gtk_tree_model_get(model2, &iter, COL_MENU, &menu, -1); + text_insert_help(menu); + } +} + + +/* User click: display sub-tree in the right frame. */ +gboolean +on_treeview1_button_press_event(GtkWidget * widget, + GdkEventButton * event, gpointer user_data) +{ + GtkTreeView *view = GTK_TREE_VIEW(widget); + GtkTreePath *path; + GtkTreeViewColumn *column; + GtkTreeIter iter; + struct menu *menu; + + gint tx = (gint) event->x; + gint ty = (gint) event->y; + gint cx, cy; + + gtk_tree_view_get_path_at_pos(view, tx, ty, &path, &column, &cx, + &cy); + if (path == NULL) + return FALSE; + + gtk_tree_model_get_iter(model1, &iter, path); + gtk_tree_model_get(model1, &iter, COL_MENU, &menu, -1); + + if (event->type == GDK_2BUTTON_PRESS) { + toggle_sym_value(menu); + current = menu; + display_tree_part(); + } else { + browsed = menu; + display_tree_part(); + } + + gtk_widget_realize(tree2_w); + gtk_tree_view_set_cursor(view, path, NULL, FALSE); + gtk_widget_grab_focus(tree2_w); + + return FALSE; +} + + +/* Fill a row of strings */ +static gchar **fill_row(struct menu *menu) +{ + static gchar *row[COL_NUMBER]; + struct symbol *sym = menu->sym; + const char *def; + int stype; + tristate val; + enum prop_type ptype; + int i; + + for (i = COL_OPTION; i <= COL_COLOR; i++) + g_free(row[i]); + memset(row, 0, sizeof(row)); + + row[COL_OPTION] = + g_strdup_printf("%s %s", menu_get_prompt(menu), + sym ? (sym-> + flags & SYMBOL_NEW ? "(NEW)" : "") : + ""); + + if (show_all && !menu_is_visible(menu)) + row[COL_COLOR] = g_strdup("DarkGray"); + else + row[COL_COLOR] = g_strdup("Black"); + + ptype = menu->prompt ? menu->prompt->type : P_UNKNOWN; + switch (ptype) { + case P_MENU: + row[COL_PIXBUF] = (gchar *) xpm_menu; + if (view_mode == SINGLE_VIEW) + row[COL_PIXVIS] = GINT_TO_POINTER(TRUE); + row[COL_BTNVIS] = GINT_TO_POINTER(FALSE); + break; + case P_COMMENT: + row[COL_PIXBUF] = (gchar *) xpm_void; + row[COL_PIXVIS] = GINT_TO_POINTER(FALSE); + row[COL_BTNVIS] = GINT_TO_POINTER(FALSE); + break; + default: + row[COL_PIXBUF] = (gchar *) xpm_void; + row[COL_PIXVIS] = GINT_TO_POINTER(FALSE); + row[COL_BTNVIS] = GINT_TO_POINTER(TRUE); + break; + } + + if (!sym) + return row; + row[COL_NAME] = g_strdup(sym->name); + + sym_calc_value(sym); + sym->flags &= ~SYMBOL_CHANGED; + + if (sym_is_choice(sym)) { // parse childs for getting final value + struct menu *child; + struct symbol *def_sym = sym_get_choice_value(sym); + struct menu *def_menu = NULL; + + row[COL_BTNVIS] = GINT_TO_POINTER(FALSE); + + for (child = menu->list; child; child = child->next) { + if (menu_is_visible(child) + && child->sym == def_sym) + def_menu = child; + } + + if (def_menu) + row[COL_VALUE] = + g_strdup(menu_get_prompt(def_menu)); + } + if (sym->flags & SYMBOL_CHOICEVAL) + row[COL_BTNRAD] = GINT_TO_POINTER(TRUE); + + stype = sym_get_type(sym); + switch (stype) { + case S_BOOLEAN: + if (GPOINTER_TO_INT(row[COL_PIXVIS]) == FALSE) + row[COL_BTNVIS] = GINT_TO_POINTER(TRUE); + if (sym_is_choice(sym)) + break; + case S_TRISTATE: + val = sym_get_tristate_value(sym); + switch (val) { + case no: + row[COL_NO] = g_strdup("N"); + row[COL_VALUE] = g_strdup("N"); + row[COL_BTNACT] = GINT_TO_POINTER(FALSE); + row[COL_BTNINC] = GINT_TO_POINTER(FALSE); + break; + case mod: + row[COL_MOD] = g_strdup("M"); + row[COL_VALUE] = g_strdup("M"); + row[COL_BTNINC] = GINT_TO_POINTER(TRUE); + break; + case yes: + row[COL_YES] = g_strdup("Y"); + row[COL_VALUE] = g_strdup("Y"); + row[COL_BTNACT] = GINT_TO_POINTER(TRUE); + row[COL_BTNINC] = GINT_TO_POINTER(FALSE); + break; + } + + if (val != no && sym_tristate_within_range(sym, no)) + row[COL_NO] = g_strdup("_"); + if (val != mod && sym_tristate_within_range(sym, mod)) + row[COL_MOD] = g_strdup("_"); + if (val != yes && sym_tristate_within_range(sym, yes)) + row[COL_YES] = g_strdup("_"); + break; + case S_INT: + case S_HEX: + case S_STRING: + def = sym_get_string_value(sym); + row[COL_VALUE] = g_strdup(def); + row[COL_EDIT] = GINT_TO_POINTER(TRUE); + row[COL_BTNVIS] = GINT_TO_POINTER(FALSE); + break; + } + + return row; +} + + +/* Set the node content with a row of strings */ +static void set_node(GtkTreeIter * node, struct menu *menu, gchar ** row) +{ + GdkColor color; + gboolean success; + GdkPixbuf *pix; + + pix = gdk_pixbuf_new_from_xpm_data((const char **) + row[COL_PIXBUF]); + + gdk_color_parse(row[COL_COLOR], &color); + gdk_colormap_alloc_colors(gdk_colormap_get_system(), &color, 1, + FALSE, FALSE, &success); + + gtk_tree_store_set(tree, node, + COL_OPTION, row[COL_OPTION], + COL_NAME, row[COL_NAME], + COL_NO, row[COL_NO], + COL_MOD, row[COL_MOD], + COL_YES, row[COL_YES], + COL_VALUE, row[COL_VALUE], + COL_MENU, (gpointer) menu, + COL_COLOR, &color, + COL_EDIT, GPOINTER_TO_INT(row[COL_EDIT]), + COL_PIXBUF, pix, + COL_PIXVIS, GPOINTER_TO_INT(row[COL_PIXVIS]), + COL_BTNVIS, GPOINTER_TO_INT(row[COL_BTNVIS]), + COL_BTNACT, GPOINTER_TO_INT(row[COL_BTNACT]), + COL_BTNINC, GPOINTER_TO_INT(row[COL_BTNINC]), + COL_BTNRAD, GPOINTER_TO_INT(row[COL_BTNRAD]), + -1); + + g_object_unref(pix); +} + + +/* Add a node to the tree */ +static void place_node(struct menu *menu, char **row) +{ + GtkTreeIter *parent = parents[indent - 1]; + GtkTreeIter *node = parents[indent]; + + gtk_tree_store_append(tree, node, parent); + set_node(node, menu, row); +} + + +/* Find a node in the GTK+ tree */ +static GtkTreeIter found; + +/* + * Find a menu in the GtkTree starting at parent. + */ +GtkTreeIter *gtktree_iter_find_node(GtkTreeIter * parent, + struct menu *tofind) +{ + GtkTreeIter iter; + GtkTreeIter *child = &iter; + gboolean valid; + GtkTreeIter *ret; + + valid = gtk_tree_model_iter_children(model2, child, parent); + while (valid) { + struct menu *menu; + + gtk_tree_model_get(model2, child, 6, &menu, -1); + + if (menu == tofind) { + memcpy(&found, child, sizeof(GtkTreeIter)); + return &found; + } + + ret = gtktree_iter_find_node(child, tofind); + if (ret) + return ret; + + valid = gtk_tree_model_iter_next(model2, child); + } + + return NULL; +} + + +/* + * Update the tree by adding/removing entries + * Does not change other nodes + */ +static void update_tree(struct menu *src, GtkTreeIter * dst) +{ + struct menu *child1; + GtkTreeIter iter, tmp; + GtkTreeIter *child2 = &iter; + gboolean valid; + GtkTreeIter *sibling; + struct symbol *sym; + struct property *prop; + struct menu *menu1, *menu2; + + if (src == &rootmenu) + indent = 1; + + valid = gtk_tree_model_iter_children(model2, child2, dst); + for (child1 = src->list; child1; child1 = child1->next) { + + prop = child1->prompt; + sym = child1->sym; + + reparse: + menu1 = child1; + if (valid) + gtk_tree_model_get(model2, child2, COL_MENU, + &menu2, -1); + else + menu2 = NULL; // force adding of a first child + +#ifdef DEBUG + printf("%*c%s | %s\n", indent, ' ', + menu1 ? menu_get_prompt(menu1) : "nil", + menu2 ? menu_get_prompt(menu2) : "nil"); +#endif + + if (!menu_is_visible(child1) && !show_all) { // remove node + if (gtktree_iter_find_node(dst, menu1) != NULL) { + memcpy(&tmp, child2, sizeof(GtkTreeIter)); + valid = gtk_tree_model_iter_next(model2, + child2); + gtk_tree_store_remove(tree2, &tmp); + if (!valid) + return; // next parent + else + goto reparse; // next child + } else + continue; + } + + if (menu1 != menu2) { + if (gtktree_iter_find_node(dst, menu1) == NULL) { // add node + if (!valid && !menu2) + sibling = NULL; + else + sibling = child2; + gtk_tree_store_insert_before(tree2, + child2, + dst, sibling); + set_node(child2, menu1, fill_row(menu1)); + if (menu2 == NULL) + valid = TRUE; + } else { // remove node + memcpy(&tmp, child2, sizeof(GtkTreeIter)); + valid = gtk_tree_model_iter_next(model2, + child2); + gtk_tree_store_remove(tree2, &tmp); + if (!valid) + return; // next parent + else + goto reparse; // next child + } + } else if (sym && (sym->flags & SYMBOL_CHANGED)) { + set_node(child2, menu1, fill_row(menu1)); + } + + indent++; + update_tree(child1, child2); + indent--; + + valid = gtk_tree_model_iter_next(model2, child2); + } +} + + +/* Display the whole tree (single/split/full view) */ +static void display_tree(struct menu *menu) +{ + struct symbol *sym; + struct property *prop; + struct menu *child; + enum prop_type ptype; + + if (menu == &rootmenu) { + indent = 1; + current = &rootmenu; + } + + for (child = menu->list; child; child = child->next) { + prop = child->prompt; + sym = child->sym; + ptype = prop ? prop->type : P_UNKNOWN; + + if (sym) + sym->flags &= ~SYMBOL_CHANGED; + + if ((view_mode == SPLIT_VIEW) + && !(child->flags & MENU_ROOT) && (tree == tree1)) + continue; + + if ((view_mode == SPLIT_VIEW) && (child->flags & MENU_ROOT) + && (tree == tree2)) + continue; + + if (menu_is_visible(child) || show_all) + place_node(child, fill_row(child)); +#ifdef DEBUG + printf("%*c%s: ", indent, ' ', menu_get_prompt(child)); + printf("%s", child->flags & MENU_ROOT ? "rootmenu | " : ""); + dbg_print_ptype(ptype); + printf(" | "); + if (sym) { + dbg_print_stype(sym->type); + printf(" | "); + dbg_print_flags(sym->flags); + printf("\n"); + } else + printf("\n"); +#endif + if ((view_mode != FULL_VIEW) && (ptype == P_MENU) + && (tree == tree2)) + continue; +/* + if (((menu != &rootmenu) && !(menu->flags & MENU_ROOT)) + || (view_mode == FULL_VIEW) + || (view_mode == SPLIT_VIEW))*/ + if (((view_mode == SINGLE_VIEW) && (menu->flags & MENU_ROOT)) + || (view_mode == FULL_VIEW) + || (view_mode == SPLIT_VIEW)) { + indent++; + display_tree(child); + indent--; + } + } +} + +/* Display a part of the tree starting at current node (single/split view) */ +static void display_tree_part(void) +{ + if (tree2) + gtk_tree_store_clear(tree2); + if (view_mode == SINGLE_VIEW) + display_tree(current); + else if (view_mode == SPLIT_VIEW) + display_tree(browsed); + gtk_tree_view_expand_all(GTK_TREE_VIEW(tree2_w)); +} + +/* Display the list in the left frame (split view) */ +static void display_list(void) +{ + if (tree1) + gtk_tree_store_clear(tree1); + + tree = tree1; + display_tree(&rootmenu); + gtk_tree_view_expand_all(GTK_TREE_VIEW(tree1_w)); + tree = tree2; +} + +void fixup_rootmenu(struct menu *menu) +{ + struct menu *child; + static int menu_cnt = 0; + + menu->flags |= MENU_ROOT; + for (child = menu->list; child; child = child->next) { + if (child->prompt && child->prompt->type == P_MENU) { + menu_cnt++; + fixup_rootmenu(child); + menu_cnt--; + } else if (!menu_cnt) + fixup_rootmenu(child); + } +} + + +/* Main */ +int main(int ac, char *av[]) +{ + const char *name; + char *env; + gchar *glade_file; + +#ifndef LKC_DIRECT_LINK + kconfig_load(); +#endif + + bindtextdomain(PACKAGE, LOCALEDIR); + bind_textdomain_codeset(PACKAGE, "UTF-8"); + textdomain(PACKAGE); + + /* GTK stuffs */ + gtk_set_locale(); + gtk_init(&ac, &av); + glade_init(); + + //add_pixmap_directory (PACKAGE_DATA_DIR "/" PACKAGE "/pixmaps"); + //add_pixmap_directory (PACKAGE_SOURCE_DIR "/pixmaps"); + + /* Determine GUI path */ + env = getenv(SRCTREE); + if (env) + glade_file = g_strconcat(env, "/scripts/kconfig/gconf.glade", NULL); + else if (av[0][0] == '/') + glade_file = g_strconcat(av[0], ".glade", NULL); + else + glade_file = g_strconcat(g_get_current_dir(), "/", av[0], ".glade", NULL); + + /* Load the interface and connect signals */ + init_main_window(glade_file); + init_tree_model(); + init_left_tree(); + init_right_tree(); + + /* Conf stuffs */ + if (ac > 1 && av[1][0] == '-') { + switch (av[1][1]) { + case 'a': + //showAll = 1; + break; + case 'h': + case '?': + printf("%s \n", av[0]); + exit(0); + } + name = av[2]; + } else + name = av[1]; + + conf_parse(name); + fixup_rootmenu(&rootmenu); + conf_read(NULL); + + switch (view_mode) { + case SINGLE_VIEW: + display_tree_part(); + break; + case SPLIT_VIEW: + display_list(); + break; + case FULL_VIEW: + display_tree(&rootmenu); + break; + } + + gtk_main(); + + return 0; +} diff --git a/scripts/kconfig/gconf.glade b/scripts/kconfig/gconf.glade new file mode 100644 index 000000000..f8744ed64 --- /dev/null +++ b/scripts/kconfig/gconf.glade @@ -0,0 +1,648 @@ + + + + + + + True + Gtk Kernel Configurator + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + 640 + 480 + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + + + + + + + True + False + 0 + + + + True + + + + True + _File + True + + + + + + + True + Load a config file + _Load + True + + + + + + True + gtk-open + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + Save the config in .config + _Save + True + + + + + + True + gtk-save + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + Save the config in a file + Save _as + True + + + + + True + gtk-save-as + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + + + + + + True + _Quit + True + + + + + + True + gtk-quit + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + + + + + True + _Options + True + + + + + + + True + Show name + Show _name + True + False + + + + + + + True + Show range (Y/M/N) + Show _range + True + False + + + + + + + True + Show value of the option + Show _data + True + False + + + + + + + True + + + + + + True + Show all options + Show all _options + True + False + + + + + + + True + Show masked options + Show _debug info + True + False + + + + + + + + + + + True + _Help + True + + + + + + + True + _Introduction + True + + + + + + True + gtk-dialog-question + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + _About + True + + + + + + True + gtk-properties + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + _License + True + + + + + True + gtk-justify-fill + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + + + + 0 + False + False + + + + + + True + GTK_SHADOW_OUT + GTK_POS_LEFT + GTK_POS_TOP + + + + True + GTK_ORIENTATION_HORIZONTAL + GTK_TOOLBAR_BOTH + True + True + + + + True + Goes up of one level (single view) + Back + True + gtk-undo + True + True + False + + + + False + True + + + + + + True + True + True + False + + + + True + + + + + False + False + + + + + + True + Load a config file + Load + True + gtk-open + True + True + False + + + + False + True + + + + + + True + Save a config file + Save + True + gtk-save + True + True + False + + + + False + True + + + + + + True + True + True + False + + + + True + + + + + False + False + + + + + + True + Single view + Single + True + gtk-missing-image + True + True + False + + + + False + True + + + + + + True + Split view + Split + True + gtk-missing-image + True + True + False + + + + False + True + + + + + + True + Full view + Full + True + gtk-missing-image + True + True + False + + + + False + True + + + + + + True + True + True + False + + + + True + + + + + False + False + + + + + + True + Collapse the whole tree in the right frame + Collapse + True + gtk-remove + True + True + False + + + + False + True + + + + + + True + Expand the whole tree in the right frame + Expand + True + gtk-add + True + True + False + + + + False + True + + + + + + + 0 + False + False + + + + + + 1 + True + True + 0 + + + + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + False + False + True + + + + + + + + True + False + + + + + + True + True + 0 + + + + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + True + False + False + True + + + + + + + + True + False + + + + + + True + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + False + False + True + GTK_JUSTIFY_LEFT + GTK_WRAP_WORD + True + 0 + 0 + 0 + 0 + 0 + 0 + Sorry, no help available for this option yet. + + + + + True + True + + + + + True + True + + + + + 0 + True + True + + + + + + + diff --git a/scripts/kconfig/images.c b/scripts/kconfig/images.c new file mode 100644 index 000000000..d4f84bd4a --- /dev/null +++ b/scripts/kconfig/images.c @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2002 Roman Zippel + * Released under the terms of the GNU GPL v2.0. + */ + +static const char *xpm_load[] = { +"22 22 5 1", +". c None", +"# c #000000", +"c c #838100", +"a c #ffff00", +"b c #ffffff", +"......................", +"......................", +"......................", +"............####....#.", +"...........#....##.##.", +"..................###.", +".................####.", +".####...........#####.", +"#abab##########.......", +"#babababababab#.......", +"#ababababababa#.......", +"#babababababab#.......", +"#ababab###############", +"#babab##cccccccccccc##", +"#abab##cccccccccccc##.", +"#bab##cccccccccccc##..", +"#ab##cccccccccccc##...", +"#b##cccccccccccc##....", +"###cccccccccccc##.....", +"##cccccccccccc##......", +"###############.......", +"......................"}; + +static const char *xpm_save[] = { +"22 22 5 1", +". c None", +"# c #000000", +"a c #838100", +"b c #c5c2c5", +"c c #cdb6d5", +"......................", +".####################.", +".#aa#bbbbbbbbbbbb#bb#.", +".#aa#bbbbbbbbbbbb#bb#.", +".#aa#bbbbbbbbbcbb####.", +".#aa#bbbccbbbbbbb#aa#.", +".#aa#bbbccbbbbbbb#aa#.", +".#aa#bbbbbbbbbbbb#aa#.", +".#aa#bbbbbbbbbbbb#aa#.", +".#aa#bbbbbbbbbbbb#aa#.", +".#aa#bbbbbbbbbbbb#aa#.", +".#aaa############aaa#.", +".#aaaaaaaaaaaaaaaaaa#.", +".#aaaaaaaaaaaaaaaaaa#.", +".#aaa#############aa#.", +".#aaa#########bbb#aa#.", +".#aaa#########bbb#aa#.", +".#aaa#########bbb#aa#.", +".#aaa#########bbb#aa#.", +".#aaa#########bbb#aa#.", +"..##################..", +"......................"}; + +static const char *xpm_back[] = { +"22 22 3 1", +". c None", +"# c #000083", +"a c #838183", +"......................", +"......................", +"......................", +"......................", +"......................", +"...........######a....", +"..#......##########...", +"..##...####......##a..", +"..###.###.........##..", +"..######..........##..", +"..#####...........##..", +"..######..........##..", +"..#######.........##..", +"..########.......##a..", +"...............a###...", +"...............###....", +"......................", +"......................", +"......................", +"......................", +"......................", +"......................"}; + +static const char *xpm_tree_view[] = { +"22 22 2 1", +". c None", +"# c #000000", +"......................", +"......................", +"......#...............", +"......#...............", +"......#...............", +"......#...............", +"......#...............", +"......########........", +"......#...............", +"......#...............", +"......#...............", +"......#...............", +"......#...............", +"......########........", +"......#...............", +"......#...............", +"......#...............", +"......#...............", +"......#...............", +"......########........", +"......................", +"......................"}; + +static const char *xpm_single_view[] = { +"22 22 2 1", +". c None", +"# c #000000", +"......................", +"......................", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"..........#...........", +"......................", +"......................"}; + +static const char *xpm_split_view[] = { +"22 22 2 1", +". c None", +"# c #000000", +"......................", +"......................", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......#......#........", +"......................", +"......................"}; + +static const char *xpm_symbol_no[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .......... ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" .......... ", +" "}; + +static const char *xpm_symbol_mod[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .......... ", +" . . ", +" . . ", +" . .. . ", +" . .... . ", +" . .... . ", +" . .. . ", +" . . ", +" . . ", +" .......... ", +" "}; + +static const char *xpm_symbol_yes[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .......... ", +" . . ", +" . . ", +" . . . ", +" . .. . ", +" . . .. . ", +" . .... . ", +" . .. . ", +" . . ", +" .......... ", +" "}; + +static const char *xpm_choice_no[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .... ", +" .. .. ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" . . ", +" .. .. ", +" .... ", +" "}; + +static const char *xpm_choice_yes[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .... ", +" .. .. ", +" . . ", +" . .. . ", +" . .... . ", +" . .... . ", +" . .. . ", +" . . ", +" .. .. ", +" .... ", +" "}; + +static const char *xpm_menu[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .......... ", +" . . ", +" . .. . ", +" . .... . ", +" . ...... . ", +" . ...... . ", +" . .... . ", +" . .. . ", +" . . ", +" .......... ", +" "}; + +static const char *xpm_menu_inv[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .......... ", +" .......... ", +" .. ...... ", +" .. .... ", +" .. .. ", +" .. .. ", +" .. .... ", +" .. ...... ", +" .......... ", +" .......... ", +" "}; + +static const char *xpm_menuback[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" .......... ", +" . . ", +" . .. . ", +" . .... . ", +" . ...... . ", +" . ...... . ", +" . .... . ", +" . .. . ", +" . . ", +" .......... ", +" "}; + +static const char *xpm_void[] = { +"12 12 2 1", +" c white", +". c black", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/scripts/kconfig/kconfig_load.c b/scripts/kconfig/kconfig_load.c new file mode 100644 index 000000000..dbdcaad82 --- /dev/null +++ b/scripts/kconfig/kconfig_load.c @@ -0,0 +1,35 @@ +#include +#include +#include + +#include "lkc.h" + +#define P(name,type,arg) type (*name ## _p) arg +#include "lkc_proto.h" +#undef P + +void kconfig_load(void) +{ + void *handle; + char *error; + + handle = dlopen("./libkconfig.so", RTLD_LAZY); + if (!handle) { + handle = dlopen("./scripts/kconfig/libkconfig.so", RTLD_LAZY); + if (!handle) { + fprintf(stderr, "%s\n", dlerror()); + exit(1); + } + } + +#define P(name,type,arg) \ +{ \ + name ## _p = dlsym(handle, #name); \ + if ((error = dlerror())) { \ + fprintf(stderr, "%s\n", error); \ + exit(1); \ + } \ +} +#include "lkc_proto.h" +#undef P +} diff --git a/scripts/kconfig/kxgettext.c b/scripts/kconfig/kxgettext.c new file mode 100644 index 000000000..abee55ca6 --- /dev/null +++ b/scripts/kconfig/kxgettext.c @@ -0,0 +1,227 @@ +/* + * Arnaldo Carvalho de Melo , 2005 + * + * Released under the terms of the GNU GPL v2.0 + */ + +#include +#include + +#define LKC_DIRECT_LINK +#include "lkc.h" + +static char *escape(const char* text, char *bf, int len) +{ + char *bfp = bf; + int multiline = strchr(text, '\n') != NULL; + int eol = 0; + int textlen = strlen(text); + + if ((textlen > 0) && (text[textlen-1] == '\n')) + eol = 1; + + *bfp++ = '"'; + --len; + + if (multiline) { + *bfp++ = '"'; + *bfp++ = '\n'; + *bfp++ = '"'; + len -= 3; + } + + while (*text != '\0' && len > 1) { + if (*text == '"') + *bfp++ = '\\'; + else if (*text == '\n') { + *bfp++ = '\\'; + *bfp++ = 'n'; + *bfp++ = '"'; + *bfp++ = '\n'; + *bfp++ = '"'; + len -= 5; + ++text; + goto next; + } + *bfp++ = *text++; +next: + --len; + } + + if (multiline && eol) + bfp -= 3; + + *bfp++ = '"'; + *bfp = '\0'; + + return bf; +} + +struct file_line { + struct file_line *next; + char* file; + int lineno; +}; + +static struct file_line *file_line__new(char *file, int lineno) +{ + struct file_line *self = malloc(sizeof(*self)); + + if (self == NULL) + goto out; + + self->file = file; + self->lineno = lineno; + self->next = NULL; +out: + return self; +} + +struct message { + const char *msg; + const char *option; + struct message *next; + struct file_line *files; +}; + +static struct message *message__list; + +static struct message *message__new(const char *msg, char *option, char *file, int lineno) +{ + struct message *self = malloc(sizeof(*self)); + + if (self == NULL) + goto out; + + self->files = file_line__new(file, lineno); + if (self->files == NULL) + goto out_fail; + + self->msg = strdup(msg); + if (self->msg == NULL) + goto out_fail_msg; + + self->option = option; + self->next = NULL; +out: + return self; +out_fail_msg: + free(self->files); +out_fail: + free(self); + self = NULL; + goto out; +} + +static struct message *mesage__find(const char *msg) +{ + struct message *m = message__list; + + while (m != NULL) { + if (strcmp(m->msg, msg) == 0) + break; + m = m->next; + } + + return m; +} + +static int message__add_file_line(struct message *self, char *file, int lineno) +{ + int rc = -1; + struct file_line *fl = file_line__new(file, lineno); + + if (fl == NULL) + goto out; + + fl->next = self->files; + self->files = fl; + rc = 0; +out: + return rc; +} + +static int message__add(const char *msg, char *option, char *file, int lineno) +{ + int rc = 0; + char bf[16384]; + char *escaped = escape(msg, bf, sizeof(bf)); + struct message *m = mesage__find(escaped); + + if (m != NULL) + rc = message__add_file_line(m, file, lineno); + else { + m = message__new(escaped, option, file, lineno); + + if (m != NULL) { + m->next = message__list; + message__list = m; + } else + rc = -1; + } + return rc; +} + +void menu_build_message_list(struct menu *menu) +{ + struct menu *child; + + message__add(menu_get_prompt(menu), NULL, + menu->file == NULL ? "Root Menu" : menu->file->name, + menu->lineno); + + if (menu->sym != NULL && menu->sym->help != NULL) + message__add(menu->sym->help, menu->sym->name, + menu->file == NULL ? "Root Menu" : menu->file->name, + menu->lineno); + + for (child = menu->list; child != NULL; child = child->next) + if (child->prompt != NULL) + menu_build_message_list(child); +} + +static void message__print_file_lineno(struct message *self) +{ + struct file_line *fl = self->files; + + putchar('\n'); + if (self->option != NULL) + printf("# %s:00000\n", self->option); + + printf("#: %s:%d", fl->file, fl->lineno); + fl = fl->next; + + while (fl != NULL) { + printf(", %s:%d", fl->file, fl->lineno); + fl = fl->next; + } + + putchar('\n'); +} + +static void message__print_gettext_msgid_msgstr(struct message *self) +{ + message__print_file_lineno(self); + + printf("msgid %s\n" + "msgstr \"\"\n", self->msg); +} + +void menu__xgettext(void) +{ + struct message *m = message__list; + + while (m != NULL) { + message__print_gettext_msgid_msgstr(m); + m = m->next; + } +} + +int main(int ac, char **av) +{ + conf_parse(av[1]); + + menu_build_message_list(menu_get_root_menu(NULL)); + menu__xgettext(); + return 0; +} diff --git a/scripts/kconfig/lex.zconf.c_shipped b/scripts/kconfig/lex.zconf.c_shipped new file mode 100644 index 000000000..24e3c8cbb --- /dev/null +++ b/scripts/kconfig/lex.zconf.c_shipped @@ -0,0 +1,2317 @@ + +#line 3 "scripts/kconfig/lex.zconf.c" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 5 +#define YY_FLEX_SUBMINOR_VERSION 31 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; +#endif /* ! C99 */ + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#endif /* ! FLEXINT_H */ + +#ifdef __cplusplus + +/* The "const" storage-class-modifier is valid. */ +#define YY_USE_CONST + +#else /* ! __cplusplus */ + +#if __STDC__ + +#define YY_USE_CONST + +#endif /* __STDC__ */ +#endif /* ! __cplusplus */ + +#ifdef YY_USE_CONST +#define yyconst const +#else +#define yyconst +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an unsigned + * integer for use as an array index. If the signed char is negative, + * we want to instead treat it as an 8-bit unsigned char, hence the + * double cast. + */ +#define YY_SC_TO_UI(c) ((unsigned int) (unsigned char) c) + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN (yy_start) = 1 + 2 * + +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START (((yy_start) - 1) / 2) +#define YYSTATE YY_START + +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) + +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE zconfrestart(zconfin ) + +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#define YY_BUF_SIZE 16384 +#endif + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +extern int zconfleng; + +extern FILE *zconfin, *zconfout; + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + #define YY_LESS_LINENO(n) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up zconftext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = (yy_hold_char); \ + YY_RESTORE_YY_MORE_OFFSET \ + (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up zconftext again */ \ + } \ + while ( 0 ) + +#define unput(c) yyunput( c, (yytext_ptr) ) + +/* The following is because we cannot portably get our hands on size_t + * (without autoconf's help, which isn't available because we want + * flex-generated scanners to compile on their own). + */ + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef unsigned int yy_size_t; +#endif + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + yy_size_t yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via zconfrestart()), so that the user can continue scanning by + * just pointing zconfin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* Stack of input buffers. */ +static size_t yy_buffer_stack_top = 0; /**< index of top of stack. */ +static size_t yy_buffer_stack_max = 0; /**< capacity of stack. */ +static YY_BUFFER_STATE * yy_buffer_stack = 0; /**< Stack as an array. */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \ + ? (yy_buffer_stack)[(yy_buffer_stack_top)] \ + : NULL) + +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)] + +/* yy_hold_char holds the character lost when zconftext is formed. */ +static char yy_hold_char; +static int yy_n_chars; /* number of characters read into yy_ch_buf */ +int zconfleng; + +/* Points to current character in buffer. */ +static char *yy_c_buf_p = (char *) 0; +static int yy_init = 1; /* whether we need to initialize */ +static int yy_start = 0; /* start state number */ + +/* Flag which is used to allow zconfwrap()'s to do buffer switches + * instead of setting up a fresh zconfin. A bit of a hack ... + */ +static int yy_did_buffer_switch_on_eof; + +void zconfrestart (FILE *input_file ); +void zconf_switch_to_buffer (YY_BUFFER_STATE new_buffer ); +YY_BUFFER_STATE zconf_create_buffer (FILE *file,int size ); +void zconf_delete_buffer (YY_BUFFER_STATE b ); +void zconf_flush_buffer (YY_BUFFER_STATE b ); +void zconfpush_buffer_state (YY_BUFFER_STATE new_buffer ); +void zconfpop_buffer_state (void ); + +static void zconfensure_buffer_stack (void ); +static void zconf_load_buffer_state (void ); +static void zconf_init_buffer (YY_BUFFER_STATE b,FILE *file ); + +#define YY_FLUSH_BUFFER zconf_flush_buffer(YY_CURRENT_BUFFER ) + +YY_BUFFER_STATE zconf_scan_buffer (char *base,yy_size_t size ); +YY_BUFFER_STATE zconf_scan_string (yyconst char *yy_str ); +YY_BUFFER_STATE zconf_scan_bytes (yyconst char *bytes,int len ); + +void *zconfalloc (yy_size_t ); +void *zconfrealloc (void *,yy_size_t ); +void zconffree (void * ); + +#define yy_new_buffer zconf_create_buffer + +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + zconfensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + zconf_create_buffer(zconfin,YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } + +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + zconfensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + zconf_create_buffer(zconfin,YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } + +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* Begin user sect3 */ + +#define zconfwrap() 1 +#define YY_SKIP_YYWRAP + +typedef unsigned char YY_CHAR; + +FILE *zconfin = (FILE *) 0, *zconfout = (FILE *) 0; + +typedef int yy_state_type; + +extern int zconflineno; + +int zconflineno = 1; + +extern char *zconftext; +#define yytext_ptr zconftext +static yyconst flex_int16_t yy_nxt[][17] = + { + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0 + }, + + { + 11, 12, 13, 14, 12, 12, 15, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12 + }, + + { + 11, 12, 13, 14, 12, 12, 15, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12 + }, + + { + 11, 16, 16, 17, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 18, 16, 16, 16 + }, + + { + 11, 16, 16, 17, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 18, 16, 16, 16 + + }, + + { + 11, 19, 20, 21, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19 + }, + + { + 11, 19, 20, 21, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19 + }, + + { + 11, 22, 22, 23, 22, 24, 22, 22, 24, 22, + 22, 22, 22, 22, 22, 25, 22 + }, + + { + 11, 22, 22, 23, 22, 24, 22, 22, 24, 22, + 22, 22, 22, 22, 22, 25, 22 + }, + + { + 11, 26, 26, 27, 28, 29, 30, 31, 29, 32, + 33, 34, 35, 35, 36, 37, 38 + + }, + + { + 11, 26, 26, 27, 28, 29, 30, 31, 29, 32, + 33, 34, 35, 35, 36, 37, 38 + }, + + { + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -11, -11, -11, -11, -11, -11, -11 + }, + + { + 11, -12, -12, -12, -12, -12, -12, -12, -12, -12, + -12, -12, -12, -12, -12, -12, -12 + }, + + { + 11, -13, 39, 40, -13, -13, 41, -13, -13, -13, + -13, -13, -13, -13, -13, -13, -13 + }, + + { + 11, -14, -14, -14, -14, -14, -14, -14, -14, -14, + -14, -14, -14, -14, -14, -14, -14 + + }, + + { + 11, 42, 42, 43, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42 + }, + + { + 11, -16, -16, -16, -16, -16, -16, -16, -16, -16, + -16, -16, -16, -16, -16, -16, -16 + }, + + { + 11, -17, -17, -17, -17, -17, -17, -17, -17, -17, + -17, -17, -17, -17, -17, -17, -17 + }, + + { + 11, -18, -18, -18, -18, -18, -18, -18, -18, -18, + -18, -18, -18, 44, -18, -18, -18 + }, + + { + 11, 45, 45, -19, 45, 45, 45, 45, 45, 45, + 45, 45, 45, 45, 45, 45, 45 + + }, + + { + 11, -20, 46, 47, -20, -20, -20, -20, -20, -20, + -20, -20, -20, -20, -20, -20, -20 + }, + + { + 11, 48, -21, -21, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48 + }, + + { + 11, 49, 49, 50, 49, -22, 49, 49, -22, 49, + 49, 49, 49, 49, 49, -22, 49 + }, + + { + 11, -23, -23, -23, -23, -23, -23, -23, -23, -23, + -23, -23, -23, -23, -23, -23, -23 + }, + + { + 11, -24, -24, -24, -24, -24, -24, -24, -24, -24, + -24, -24, -24, -24, -24, -24, -24 + + }, + + { + 11, 51, 51, 52, 51, 51, 51, 51, 51, 51, + 51, 51, 51, 51, 51, 51, 51 + }, + + { + 11, -26, -26, -26, -26, -26, -26, -26, -26, -26, + -26, -26, -26, -26, -26, -26, -26 + }, + + { + 11, -27, -27, -27, -27, -27, -27, -27, -27, -27, + -27, -27, -27, -27, -27, -27, -27 + }, + + { + 11, -28, -28, -28, -28, -28, -28, -28, -28, -28, + -28, -28, -28, -28, 53, -28, -28 + }, + + { + 11, -29, -29, -29, -29, -29, -29, -29, -29, -29, + -29, -29, -29, -29, -29, -29, -29 + + }, + + { + 11, 54, 54, -30, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54 + }, + + { + 11, -31, -31, -31, -31, -31, -31, 55, -31, -31, + -31, -31, -31, -31, -31, -31, -31 + }, + + { + 11, -32, -32, -32, -32, -32, -32, -32, -32, -32, + -32, -32, -32, -32, -32, -32, -32 + }, + + { + 11, -33, -33, -33, -33, -33, -33, -33, -33, -33, + -33, -33, -33, -33, -33, -33, -33 + }, + + { + 11, -34, -34, -34, -34, -34, -34, -34, -34, -34, + -34, 56, 57, 57, -34, -34, -34 + + }, + + { + 11, -35, -35, -35, -35, -35, -35, -35, -35, -35, + -35, 57, 57, 57, -35, -35, -35 + }, + + { + 11, -36, -36, -36, -36, -36, -36, -36, -36, -36, + -36, -36, -36, -36, -36, -36, -36 + }, + + { + 11, -37, -37, 58, -37, -37, -37, -37, -37, -37, + -37, -37, -37, -37, -37, -37, -37 + }, + + { + 11, -38, -38, -38, -38, -38, -38, -38, -38, -38, + -38, -38, -38, -38, -38, -38, 59 + }, + + { + 11, -39, 39, 40, -39, -39, 41, -39, -39, -39, + -39, -39, -39, -39, -39, -39, -39 + + }, + + { + 11, -40, -40, -40, -40, -40, -40, -40, -40, -40, + -40, -40, -40, -40, -40, -40, -40 + }, + + { + 11, 42, 42, 43, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42 + }, + + { + 11, 42, 42, 43, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42 + }, + + { + 11, -43, -43, -43, -43, -43, -43, -43, -43, -43, + -43, -43, -43, -43, -43, -43, -43 + }, + + { + 11, -44, -44, -44, -44, -44, -44, -44, -44, -44, + -44, -44, -44, 44, -44, -44, -44 + + }, + + { + 11, 45, 45, -45, 45, 45, 45, 45, 45, 45, + 45, 45, 45, 45, 45, 45, 45 + }, + + { + 11, -46, 46, 47, -46, -46, -46, -46, -46, -46, + -46, -46, -46, -46, -46, -46, -46 + }, + + { + 11, 48, -47, -47, 48, 48, 48, 48, 48, 48, + 48, 48, 48, 48, 48, 48, 48 + }, + + { + 11, -48, -48, -48, -48, -48, -48, -48, -48, -48, + -48, -48, -48, -48, -48, -48, -48 + }, + + { + 11, 49, 49, 50, 49, -49, 49, 49, -49, 49, + 49, 49, 49, 49, 49, -49, 49 + + }, + + { + 11, -50, -50, -50, -50, -50, -50, -50, -50, -50, + -50, -50, -50, -50, -50, -50, -50 + }, + + { + 11, -51, -51, 52, -51, -51, -51, -51, -51, -51, + -51, -51, -51, -51, -51, -51, -51 + }, + + { + 11, -52, -52, -52, -52, -52, -52, -52, -52, -52, + -52, -52, -52, -52, -52, -52, -52 + }, + + { + 11, -53, -53, -53, -53, -53, -53, -53, -53, -53, + -53, -53, -53, -53, -53, -53, -53 + }, + + { + 11, 54, 54, -54, 54, 54, 54, 54, 54, 54, + 54, 54, 54, 54, 54, 54, 54 + + }, + + { + 11, -55, -55, -55, -55, -55, -55, -55, -55, -55, + -55, -55, -55, -55, -55, -55, -55 + }, + + { + 11, -56, -56, -56, -56, -56, -56, -56, -56, -56, + -56, 60, 57, 57, -56, -56, -56 + }, + + { + 11, -57, -57, -57, -57, -57, -57, -57, -57, -57, + -57, 57, 57, 57, -57, -57, -57 + }, + + { + 11, -58, -58, -58, -58, -58, -58, -58, -58, -58, + -58, -58, -58, -58, -58, -58, -58 + }, + + { + 11, -59, -59, -59, -59, -59, -59, -59, -59, -59, + -59, -59, -59, -59, -59, -59, -59 + + }, + + { + 11, -60, -60, -60, -60, -60, -60, -60, -60, -60, + -60, 57, 57, 57, -60, -60, -60 + }, + + } ; + +static yy_state_type yy_get_previous_state (void ); +static yy_state_type yy_try_NUL_trans (yy_state_type current_state ); +static int yy_get_next_buffer (void ); +static void yy_fatal_error (yyconst char msg[] ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up zconftext. + */ +#define YY_DO_BEFORE_ACTION \ + (yytext_ptr) = yy_bp; \ + zconfleng = (size_t) (yy_cp - yy_bp); \ + (yy_hold_char) = *yy_cp; \ + *yy_cp = '\0'; \ + (yy_c_buf_p) = yy_cp; + +#define YY_NUM_RULES 33 +#define YY_END_OF_BUFFER 34 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static yyconst flex_int16_t yy_accept[61] = + { 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 34, 5, 4, 2, 3, 7, 8, 6, 32, 29, + 31, 24, 28, 27, 26, 22, 17, 13, 16, 20, + 22, 11, 12, 19, 19, 14, 22, 22, 4, 2, + 3, 3, 1, 6, 32, 29, 31, 30, 24, 23, + 26, 25, 15, 20, 9, 19, 19, 21, 10, 18 + } ; + +static yyconst flex_int32_t yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 4, 5, 6, 1, 1, 7, 8, 9, + 10, 1, 1, 1, 11, 12, 12, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 1, 1, 1, + 14, 1, 1, 1, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 1, 15, 1, 1, 13, 1, 13, 13, 13, 13, + + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 1, 16, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + } ; + +extern int zconf_flex_debug; +int zconf_flex_debug = 0; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +char *zconftext; + +/* + * Copyright (C) 2002 Roman Zippel + * Released under the terms of the GNU GPL v2.0. + */ + +#include +#include +#include +#include +#include + +#define LKC_DIRECT_LINK +#include "lkc.h" + +#define START_STRSIZE 16 + +static struct { + struct file *file; + int lineno; +} current_pos; + +static char *text; +static int text_size, text_asize; + +struct buffer { + struct buffer *parent; + YY_BUFFER_STATE state; +}; + +struct buffer *current_buf; + +static int last_ts, first_ts; + +static void zconf_endhelp(void); +static void zconf_endfile(void); + +void new_string(void) +{ + text = malloc(START_STRSIZE); + text_asize = START_STRSIZE; + text_size = 0; + *text = 0; +} + +void append_string(const char *str, int size) +{ + int new_size = text_size + size + 1; + if (new_size > text_asize) { + new_size += START_STRSIZE - 1; + new_size &= -START_STRSIZE; + text = realloc(text, new_size); + text_asize = new_size; + } + memcpy(text + text_size, str, size); + text_size += size; + text[text_size] = 0; +} + +void alloc_string(const char *str, int size) +{ + text = malloc(size + 1); + memcpy(text, str, size); + text[size] = 0; +} + +#define INITIAL 0 +#define COMMAND 1 +#define HELP 2 +#define STRING 3 +#define PARAM 4 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int zconfwrap (void ); +#else +extern int zconfwrap (void ); +#endif +#endif + + static void yyunput (int c,char *buf_ptr ); + +#ifndef yytext_ptr +static void yy_flex_strncpy (char *,yyconst char *,int ); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * ); +#endif + +#ifndef YY_NO_INPUT + +#ifdef __cplusplus +static int yyinput (void ); +#else +static int input (void ); +#endif + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#define YY_READ_BUF_SIZE 8192 +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO (void) fwrite( zconftext, zconfleng, 1, zconfout ) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + errno=0; \ + while ( (result = read( fileno(zconfin), (char *) buf, max_size )) < 0 ) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(zconfin); \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg ) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int zconflex (void); + +#define YY_DECL int zconflex (void) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after zconftext and zconfleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + register yy_state_type yy_current_state; + register char *yy_cp, *yy_bp; + register int yy_act; + + int str = 0; + int ts, i; + + if ( (yy_init) ) + { + (yy_init) = 0; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! (yy_start) ) + (yy_start) = 1; /* first start state */ + + if ( ! zconfin ) + zconfin = stdin; + + if ( ! zconfout ) + zconfout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + zconfensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + zconf_create_buffer(zconfin,YY_BUF_SIZE ); + } + + zconf_load_buffer_state( ); + } + + while ( 1 ) /* loops until end-of-file is reached */ + { + yy_cp = (yy_c_buf_p); + + /* Support of zconftext. */ + *yy_cp = (yy_hold_char); + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = (yy_start); +yy_match: + while ( (yy_current_state = yy_nxt[yy_current_state][ yy_ec[YY_SC_TO_UI(*yy_cp)] ]) > 0 ) + ++yy_cp; + + yy_current_state = -yy_current_state; + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + + YY_DO_BEFORE_ACTION; + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ +case 1: +/* rule 1 can match eol */ +case 2: +/* rule 2 can match eol */ +YY_RULE_SETUP +{ + current_file->lineno++; + return T_EOL; +} + YY_BREAK +case 3: +YY_RULE_SETUP + + YY_BREAK +case 4: +YY_RULE_SETUP +{ + BEGIN(COMMAND); +} + YY_BREAK +case 5: +YY_RULE_SETUP +{ + unput(zconftext[0]); + BEGIN(COMMAND); +} + YY_BREAK + +case 6: +YY_RULE_SETUP +{ + struct kconf_id *id = kconf_id_lookup(zconftext, zconfleng); + BEGIN(PARAM); + current_pos.file = current_file; + current_pos.lineno = current_file->lineno; + if (id && id->flags & TF_COMMAND) { + zconflval.id = id; + return id->token; + } + alloc_string(zconftext, zconfleng); + zconflval.string = text; + return T_WORD; + } + YY_BREAK +case 7: +YY_RULE_SETUP + + YY_BREAK +case 8: +/* rule 8 can match eol */ +YY_RULE_SETUP +{ + BEGIN(INITIAL); + current_file->lineno++; + return T_EOL; + } + YY_BREAK + +case 9: +YY_RULE_SETUP +return T_AND; + YY_BREAK +case 10: +YY_RULE_SETUP +return T_OR; + YY_BREAK +case 11: +YY_RULE_SETUP +return T_OPEN_PAREN; + YY_BREAK +case 12: +YY_RULE_SETUP +return T_CLOSE_PAREN; + YY_BREAK +case 13: +YY_RULE_SETUP +return T_NOT; + YY_BREAK +case 14: +YY_RULE_SETUP +return T_EQUAL; + YY_BREAK +case 15: +YY_RULE_SETUP +return T_UNEQUAL; + YY_BREAK +case 16: +YY_RULE_SETUP +{ + str = zconftext[0]; + new_string(); + BEGIN(STRING); + } + YY_BREAK +case 17: +/* rule 17 can match eol */ +YY_RULE_SETUP +BEGIN(INITIAL); current_file->lineno++; return T_EOL; + YY_BREAK +case 18: +YY_RULE_SETUP +/* ignore */ + YY_BREAK +case 19: +YY_RULE_SETUP +{ + struct kconf_id *id = kconf_id_lookup(zconftext, zconfleng); + if (id && id->flags & TF_PARAM) { + zconflval.id = id; + return id->token; + } + alloc_string(zconftext, zconfleng); + zconflval.string = text; + return T_WORD; + } + YY_BREAK +case 20: +YY_RULE_SETUP +/* comment */ + YY_BREAK +case 21: +/* rule 21 can match eol */ +YY_RULE_SETUP +current_file->lineno++; + YY_BREAK +case 22: +YY_RULE_SETUP + + YY_BREAK +case YY_STATE_EOF(PARAM): +{ + BEGIN(INITIAL); + } + YY_BREAK + +case 23: +/* rule 23 can match eol */ +*yy_cp = (yy_hold_char); /* undo effects of setting up zconftext */ +(yy_c_buf_p) = yy_cp -= 1; +YY_DO_BEFORE_ACTION; /* set up zconftext again */ +YY_RULE_SETUP +{ + append_string(zconftext, zconfleng); + zconflval.string = text; + return T_WORD_QUOTE; + } + YY_BREAK +case 24: +YY_RULE_SETUP +{ + append_string(zconftext, zconfleng); + } + YY_BREAK +case 25: +/* rule 25 can match eol */ +*yy_cp = (yy_hold_char); /* undo effects of setting up zconftext */ +(yy_c_buf_p) = yy_cp -= 1; +YY_DO_BEFORE_ACTION; /* set up zconftext again */ +YY_RULE_SETUP +{ + append_string(zconftext + 1, zconfleng - 1); + zconflval.string = text; + return T_WORD_QUOTE; + } + YY_BREAK +case 26: +YY_RULE_SETUP +{ + append_string(zconftext + 1, zconfleng - 1); + } + YY_BREAK +case 27: +YY_RULE_SETUP +{ + if (str == zconftext[0]) { + BEGIN(PARAM); + zconflval.string = text; + return T_WORD_QUOTE; + } else + append_string(zconftext, 1); + } + YY_BREAK +case 28: +/* rule 28 can match eol */ +YY_RULE_SETUP +{ + printf("%s:%d:warning: multi-line strings not supported\n", zconf_curname(), zconf_lineno()); + current_file->lineno++; + BEGIN(INITIAL); + return T_EOL; + } + YY_BREAK +case YY_STATE_EOF(STRING): +{ + BEGIN(INITIAL); + } + YY_BREAK + +case 29: +YY_RULE_SETUP +{ + ts = 0; + for (i = 0; i < zconfleng; i++) { + if (zconftext[i] == '\t') + ts = (ts & ~7) + 8; + else + ts++; + } + last_ts = ts; + if (first_ts) { + if (ts < first_ts) { + zconf_endhelp(); + return T_HELPTEXT; + } + ts -= first_ts; + while (ts > 8) { + append_string(" ", 8); + ts -= 8; + } + append_string(" ", ts); + } + } + YY_BREAK +case 30: +/* rule 30 can match eol */ +*yy_cp = (yy_hold_char); /* undo effects of setting up zconftext */ +(yy_c_buf_p) = yy_cp -= 1; +YY_DO_BEFORE_ACTION; /* set up zconftext again */ +YY_RULE_SETUP +{ + current_file->lineno++; + zconf_endhelp(); + return T_HELPTEXT; + } + YY_BREAK +case 31: +/* rule 31 can match eol */ +YY_RULE_SETUP +{ + current_file->lineno++; + append_string("\n", 1); + } + YY_BREAK +case 32: +YY_RULE_SETUP +{ + append_string(zconftext, zconfleng); + if (!first_ts) + first_ts = last_ts; + } + YY_BREAK +case YY_STATE_EOF(HELP): +{ + zconf_endhelp(); + return T_HELPTEXT; + } + YY_BREAK + +case YY_STATE_EOF(INITIAL): +case YY_STATE_EOF(COMMAND): +{ + if (current_file) { + zconf_endfile(); + return T_EOL; + } + fclose(zconfin); + yyterminate(); +} + YY_BREAK +case 33: +YY_RULE_SETUP +YY_FATAL_ERROR( "flex scanner jammed" ); + YY_BREAK + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = (yy_hold_char); + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed zconfin at a new source and called + * zconflex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = zconfin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state ); + + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++(yy_c_buf_p); + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = (yy_c_buf_p); + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_END_OF_FILE: + { + (yy_did_buffer_switch_on_eof) = 0; + + if ( zconfwrap( ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * zconftext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = + (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + (yy_c_buf_p) = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)]; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ +} /* end of zconflex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (void) +{ + register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + register char *source = (yytext_ptr); + register int number_to_move, i; + int ret_val; + + if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr)) - 1; + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0; + + else + { + size_t num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER; + + int yy_c_buf_p_offset = + (int) ((yy_c_buf_p) - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + zconfrealloc((void *) b->yy_ch_buf,b->yy_buf_size + 2 ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = 0; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + (yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + (yy_n_chars), num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + if ( (yy_n_chars) == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + zconfrestart(zconfin ); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + (yy_n_chars) += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR; + + (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (void) +{ + register yy_state_type yy_current_state; + register char *yy_cp; + + yy_current_state = (yy_start); + + for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp ) + { + yy_current_state = yy_nxt[yy_current_state][(*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1)]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state ) +{ + register int yy_is_jam; + + yy_current_state = yy_nxt[yy_current_state][1]; + yy_is_jam = (yy_current_state <= 0); + + return yy_is_jam ? 0 : yy_current_state; +} + + static void yyunput (int c, register char * yy_bp ) +{ + register char *yy_cp; + + yy_cp = (yy_c_buf_p); + + /* undo effects of setting up zconftext */ + *yy_cp = (yy_hold_char); + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + { /* need to shift things up to make room */ + /* +2 for EOB chars. */ + register int number_to_move = (yy_n_chars) + 2; + register char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size + 2]; + register char *source = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]; + + while ( source > YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + *--dest = *--source; + + yy_cp += (int) (dest - source); + yy_bp += (int) (dest - source); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_buf_size; + + if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 ) + YY_FATAL_ERROR( "flex scanner push-back overflow" ); + } + + *--yy_cp = (char) c; + + (yytext_ptr) = yy_bp; + (yy_hold_char) = *yy_cp; + (yy_c_buf_p) = yy_cp; +} + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (void) +#else + static int input (void) +#endif + +{ + int c; + + *(yy_c_buf_p) = (yy_hold_char); + + if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + /* This was really a NUL. */ + *(yy_c_buf_p) = '\0'; + + else + { /* need more input */ + int offset = (yy_c_buf_p) - (yytext_ptr); + ++(yy_c_buf_p); + + switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + zconfrestart(zconfin ); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( zconfwrap( ) ) + return EOF; + + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(); +#else + return input(); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = (yytext_ptr) + offset; + break; + } + } + } + + c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */ + *(yy_c_buf_p) = '\0'; /* preserve zconftext */ + (yy_hold_char) = *++(yy_c_buf_p); + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * + * @note This function does not reset the start condition to @c INITIAL . + */ + void zconfrestart (FILE * input_file ) +{ + + if ( ! YY_CURRENT_BUFFER ){ + zconfensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + zconf_create_buffer(zconfin,YY_BUF_SIZE ); + } + + zconf_init_buffer(YY_CURRENT_BUFFER,input_file ); + zconf_load_buffer_state( ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * + */ + void zconf_switch_to_buffer (YY_BUFFER_STATE new_buffer ) +{ + + /* TODO. We should be able to replace this entire function body + * with + * zconfpop_buffer_state(); + * zconfpush_buffer_state(new_buffer); + */ + zconfensure_buffer_stack (); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + zconf_load_buffer_state( ); + + /* We don't actually know whether we did this switch during + * EOF (zconfwrap()) processing, but the only time this flag + * is looked at is after zconfwrap() is called, so it's safe + * to go ahead and always set it. + */ + (yy_did_buffer_switch_on_eof) = 1; +} + +static void zconf_load_buffer_state (void) +{ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + zconfin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + (yy_hold_char) = *(yy_c_buf_p); +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * + * @return the allocated buffer state. + */ + YY_BUFFER_STATE zconf_create_buffer (FILE * file, int size ) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) zconfalloc(sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in zconf_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) zconfalloc(b->yy_buf_size + 2 ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in zconf_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + zconf_init_buffer(b,file ); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with zconf_create_buffer() + * + */ + void zconf_delete_buffer (YY_BUFFER_STATE b ) +{ + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + zconffree((void *) b->yy_ch_buf ); + + zconffree((void *) b ); +} + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a zconfrestart() or at EOF. + */ + static void zconf_init_buffer (YY_BUFFER_STATE b, FILE * file ) + +{ + int oerrno = errno; + + zconf_flush_buffer(b ); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then zconf_init_buffer was _probably_ + * called from zconfrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * + */ + void zconf_flush_buffer (YY_BUFFER_STATE b ) +{ + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + zconf_load_buffer_state( ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * + */ +void zconfpush_buffer_state (YY_BUFFER_STATE new_buffer ) +{ + if (new_buffer == NULL) + return; + + zconfensure_buffer_stack(); + + /* This block is copied from zconf_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + (yy_buffer_stack_top)++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from zconf_switch_to_buffer. */ + zconf_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * + */ +void zconfpop_buffer_state (void) +{ + if (!YY_CURRENT_BUFFER) + return; + + zconf_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + if ((yy_buffer_stack_top) > 0) + --(yy_buffer_stack_top); + + if (YY_CURRENT_BUFFER) { + zconf_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void zconfensure_buffer_stack (void) +{ + int num_to_alloc; + + if (!(yy_buffer_stack)) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; + (yy_buffer_stack) = (struct yy_buffer_state**)zconfalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + ); + + memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + (yy_buffer_stack_max) = num_to_alloc; + (yy_buffer_stack_top) = 0; + return; + } + + if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + int grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = (yy_buffer_stack_max) + grow_size; + (yy_buffer_stack) = (struct yy_buffer_state**)zconfrealloc + ((yy_buffer_stack), + num_to_alloc * sizeof(struct yy_buffer_state*) + ); + + /* zero only the new slots.*/ + memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*)); + (yy_buffer_stack_max) = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE zconf_scan_buffer (char * base, yy_size_t size ) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return 0; + + b = (YY_BUFFER_STATE) zconfalloc(sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in zconf_scan_buffer()" ); + + b->yy_buf_size = size - 2; /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = 0; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + zconf_switch_to_buffer(b ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to zconflex() will + * scan from a @e copy of @a str. + * @param yy_str a NUL-terminated string to scan + * + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * zconf_scan_bytes() instead. + */ +YY_BUFFER_STATE zconf_scan_string (yyconst char * yy_str ) +{ + + return zconf_scan_bytes(yy_str,strlen(yy_str) ); +} + +/** Setup the input buffer state to scan the given bytes. The next call to zconflex() will + * scan from a @e copy of @a bytes. + * @param bytes the byte buffer to scan + * @param len the number of bytes in the buffer pointed to by @a bytes. + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE zconf_scan_bytes (yyconst char * bytes, int len ) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = len + 2; + buf = (char *) zconfalloc(n ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in zconf_scan_bytes()" ); + + for ( i = 0; i < len; ++i ) + buf[i] = bytes[i]; + + buf[len] = buf[len+1] = YY_END_OF_BUFFER_CHAR; + + b = zconf_scan_buffer(buf,n ); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in zconf_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yy_fatal_error (yyconst char* msg ) +{ + (void) fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up zconftext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + zconftext[zconfleng] = (yy_hold_char); \ + (yy_c_buf_p) = zconftext + yyless_macro_arg; \ + (yy_hold_char) = *(yy_c_buf_p); \ + *(yy_c_buf_p) = '\0'; \ + zconfleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the current line number. + * + */ +int zconfget_lineno (void) +{ + + return zconflineno; +} + +/** Get the input stream. + * + */ +FILE *zconfget_in (void) +{ + return zconfin; +} + +/** Get the output stream. + * + */ +FILE *zconfget_out (void) +{ + return zconfout; +} + +/** Get the length of the current token. + * + */ +int zconfget_leng (void) +{ + return zconfleng; +} + +/** Get the current token. + * + */ + +char *zconfget_text (void) +{ + return zconftext; +} + +/** Set the current line number. + * @param line_number + * + */ +void zconfset_lineno (int line_number ) +{ + + zconflineno = line_number; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param in_str A readable stream. + * + * @see zconf_switch_to_buffer + */ +void zconfset_in (FILE * in_str ) +{ + zconfin = in_str ; +} + +void zconfset_out (FILE * out_str ) +{ + zconfout = out_str ; +} + +int zconfget_debug (void) +{ + return zconf_flex_debug; +} + +void zconfset_debug (int bdebug ) +{ + zconf_flex_debug = bdebug ; +} + +/* zconflex_destroy is for both reentrant and non-reentrant scanners. */ +int zconflex_destroy (void) +{ + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + zconf_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + zconfpop_buffer_state(); + } + + /* Destroy the stack itself. */ + zconffree((yy_buffer_stack) ); + (yy_buffer_stack) = NULL; + + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, yyconst char * s2, int n ) +{ + register int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (yyconst char * s ) +{ + register int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +void *zconfalloc (yy_size_t size ) +{ + return (void *) malloc( size ); +} + +void *zconfrealloc (void * ptr, yy_size_t size ) +{ + /* The cast to (char *) in the following accommodates both + * implementations that use char* generic pointers, and those + * that use void* generic pointers. It works with the latter + * because both ANSI C and C++ allow castless assignment from + * any pointer type to void*, and deal with argument conversions + * as though doing an assignment. + */ + return (void *) realloc( (char *) ptr, size ); +} + +void zconffree (void * ptr ) +{ + free( (char *) ptr ); /* see zconfrealloc() for (char *) cast */ +} + +#define YYTABLES_NAME "yytables" + +#undef YY_NEW_FILE +#undef YY_FLUSH_BUFFER +#undef yy_set_bol +#undef yy_new_buffer +#undef yy_set_interactive +#undef yytext_ptr +#undef YY_DO_BEFORE_ACTION + +#ifdef YY_DECL_IS_OURS +#undef YY_DECL_IS_OURS +#undef YY_DECL +#endif + +void zconf_starthelp(void) +{ + new_string(); + last_ts = first_ts = 0; + BEGIN(HELP); +} + +static void zconf_endhelp(void) +{ + zconflval.string = text; + BEGIN(INITIAL); +} + +/* + * Try to open specified file with following names: + * ./name + * $(srctree)/name + * The latter is used when srctree is separate from objtree + * when compiling the kernel. + * Return NULL if file is not found. + */ +FILE *zconf_fopen(const char *name) +{ + char *env, fullname[PATH_MAX+1]; + FILE *f; + + f = fopen(name, "r"); + if (!f && name[0] != '/') { + env = getenv(SRCTREE); + if (env) { + sprintf(fullname, "%s/%s", env, name); + f = fopen(fullname, "r"); + } + } + return f; +} + +void zconf_initscan(const char *name) +{ + zconfin = zconf_fopen(name); + if (!zconfin) { + printf("can't find file %s\n", name); + exit(1); + } + + current_buf = malloc(sizeof(*current_buf)); + memset(current_buf, 0, sizeof(*current_buf)); + + current_file = file_lookup(name); + current_file->lineno = 1; + current_file->flags = FILE_BUSY; +} + +void zconf_nextfile(const char *name) +{ + struct file *file = file_lookup(name); + struct buffer *buf = malloc(sizeof(*buf)); + memset(buf, 0, sizeof(*buf)); + + current_buf->state = YY_CURRENT_BUFFER; + zconfin = zconf_fopen(name); + if (!zconfin) { + printf("%s:%d: can't open file \"%s\"\n", zconf_curname(), zconf_lineno(), name); + exit(1); + } + zconf_switch_to_buffer(zconf_create_buffer(zconfin,YY_BUF_SIZE)); + buf->parent = current_buf; + current_buf = buf; + + if (file->flags & FILE_BUSY) { + printf("recursive scan (%s)?\n", name); + exit(1); + } + if (file->flags & FILE_SCANNED) { + printf("file %s already scanned?\n", name); + exit(1); + } + file->flags |= FILE_BUSY; + file->lineno = 1; + file->parent = current_file; + current_file = file; +} + +static void zconf_endfile(void) +{ + struct buffer *parent; + + current_file->flags |= FILE_SCANNED; + current_file->flags &= ~FILE_BUSY; + current_file = current_file->parent; + + parent = current_buf->parent; + if (parent) { + fclose(zconfin); + zconf_delete_buffer(YY_CURRENT_BUFFER); + zconf_switch_to_buffer(parent->state); + } + free(current_buf); + current_buf = parent; +} + +int zconf_lineno(void) +{ + return current_pos.lineno; +} + +char *zconf_curname(void) +{ + return current_pos.file ? current_pos.file->name : ""; +} + diff --git a/scripts/kconfig/lkc.h b/scripts/kconfig/lkc.h new file mode 100644 index 000000000..527f60c99 --- /dev/null +++ b/scripts/kconfig/lkc.h @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2002 Roman Zippel + * Released under the terms of the GNU GPL v2.0. + */ + +#ifndef LKC_H +#define LKC_H + +#include "expr.h" + +#ifndef KBUILD_NO_NLS +# include +#else +# define gettext(Msgid) ((const char *) (Msgid)) +# define textdomain(Domainname) ((const char *) (Domainname)) +# define bindtextdomain(Domainname, Dirname) ((const char *) (Dirname)) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef LKC_DIRECT_LINK +#define P(name,type,arg) extern type name arg +#else +#include "lkc_defs.h" +#define P(name,type,arg) extern type (*name ## _p) arg +#endif +#include "lkc_proto.h" +#undef P + +#define SRCTREE "srctree" + +#define PACKAGE "linux" +#define LOCALEDIR "/usr/share/locale" + +#define _(text) gettext(text) +#define N_(text) (text) + + +#define TF_COMMAND 0x0001 +#define TF_PARAM 0x0002 + +struct kconf_id { + int name; + int token; + unsigned int flags; + enum symbol_type stype; +}; + +int zconfparse(void); +void zconfdump(FILE *out); + +extern int zconfdebug; +void zconf_starthelp(void); +FILE *zconf_fopen(const char *name); +void zconf_initscan(const char *name); +void zconf_nextfile(const char *name); +int zconf_lineno(void); +char *zconf_curname(void); + +/* confdata.c */ +extern const char conf_def_filename[]; + +char *conf_get_default_confname(void); + +/* kconfig_load.c */ +void kconfig_load(void); + +/* menu.c */ +void menu_init(void); +struct menu *menu_add_menu(void); +void menu_end_menu(void); +void menu_add_entry(struct symbol *sym); +void menu_end_entry(void); +void menu_add_dep(struct expr *dep); +struct property *menu_add_prop(enum prop_type type, char *prompt, struct expr *expr, struct expr *dep); +struct property *menu_add_prompt(enum prop_type type, char *prompt, struct expr *dep); +void menu_add_expr(enum prop_type type, struct expr *expr, struct expr *dep); +void menu_add_symbol(enum prop_type type, struct symbol *sym, struct expr *dep); +void menu_finalize(struct menu *parent); +void menu_set_type(int type); + +/* util.c */ +struct file *file_lookup(const char *name); +int file_write_dep(const char *name); + +struct gstr { + size_t len; + char *s; +}; +struct gstr str_new(void); +struct gstr str_assign(const char *s); +void str_free(struct gstr *gs); +void str_append(struct gstr *gs, const char *s); +void str_printf(struct gstr *gs, const char *fmt, ...); +const char *str_get(struct gstr *gs); + +/* symbol.c */ +void sym_init(void); +void sym_clear_all_valid(void); +void sym_set_changed(struct symbol *sym); +struct symbol *sym_check_deps(struct symbol *sym); +struct property *prop_alloc(enum prop_type type, struct symbol *sym); +struct symbol *prop_get_symbol(struct property *prop); + +static inline tristate sym_get_tristate_value(struct symbol *sym) +{ + return sym->curr.tri; +} + + +static inline struct symbol *sym_get_choice_value(struct symbol *sym) +{ + return (struct symbol *)sym->curr.val; +} + +static inline bool sym_set_choice_value(struct symbol *ch, struct symbol *chval) +{ + return sym_set_tristate_value(chval, yes); +} + +static inline bool sym_is_choice(struct symbol *sym) +{ + return sym->flags & SYMBOL_CHOICE ? true : false; +} + +static inline bool sym_is_choice_value(struct symbol *sym) +{ + return sym->flags & SYMBOL_CHOICEVAL ? true : false; +} + +static inline bool sym_is_optional(struct symbol *sym) +{ + return sym->flags & SYMBOL_OPTIONAL ? true : false; +} + +static inline bool sym_has_value(struct symbol *sym) +{ + return sym->flags & SYMBOL_NEW ? false : true; +} + +#ifdef __cplusplus +} +#endif + +#endif /* LKC_H */ diff --git a/scripts/kconfig/lkc_proto.h b/scripts/kconfig/lkc_proto.h new file mode 100644 index 000000000..b6a389c5f --- /dev/null +++ b/scripts/kconfig/lkc_proto.h @@ -0,0 +1,41 @@ + +/* confdata.c */ +P(conf_parse,void,(const char *name)); +P(conf_read,int,(const char *name)); +P(conf_read_simple,int,(const char *name)); +P(conf_write,int,(const char *name)); + +/* menu.c */ +P(rootmenu,struct menu,); + +P(menu_is_visible,bool,(struct menu *menu)); +P(menu_get_prompt,const char *,(struct menu *menu)); +P(menu_get_root_menu,struct menu *,(struct menu *menu)); +P(menu_get_parent_menu,struct menu *,(struct menu *menu)); + +/* symbol.c */ +P(symbol_hash,struct symbol *,[SYMBOL_HASHSIZE]); +P(sym_change_count,int,); + +P(sym_lookup,struct symbol *,(const char *name, int isconst)); +P(sym_find,struct symbol *,(const char *name)); +P(sym_re_search,struct symbol **,(const char *pattern)); +P(sym_type_name,const char *,(enum symbol_type type)); +P(sym_calc_value,void,(struct symbol *sym)); +P(sym_get_type,enum symbol_type,(struct symbol *sym)); +P(sym_tristate_within_range,bool,(struct symbol *sym,tristate tri)); +P(sym_set_tristate_value,bool,(struct symbol *sym,tristate tri)); +P(sym_toggle_tristate_value,tristate,(struct symbol *sym)); +P(sym_string_valid,bool,(struct symbol *sym, const char *newval)); +P(sym_string_within_range,bool,(struct symbol *sym, const char *str)); +P(sym_set_string_value,bool,(struct symbol *sym, const char *newval)); +P(sym_is_changable,bool,(struct symbol *sym)); +P(sym_get_choice_prop,struct property *,(struct symbol *sym)); +P(sym_get_default_prop,struct property *,(struct symbol *sym)); +P(sym_get_string_value,const char *,(struct symbol *sym)); + +P(prop_get_type_name,const char *,(enum prop_type type)); + +/* expr.c */ +P(expr_compare_type,int,(enum expr_type t1, enum expr_type t2)); +P(expr_print,void,(struct expr *e, void (*fn)(void *, const char *), void *data, int prevtoken)); diff --git a/scripts/kconfig/lxdialog/BIG.FAT.WARNING b/scripts/kconfig/lxdialog/BIG.FAT.WARNING new file mode 100644 index 000000000..c9bfbb902 --- /dev/null +++ b/scripts/kconfig/lxdialog/BIG.FAT.WARNING @@ -0,0 +1,4 @@ +This is NOT the official version of dialog. This version has been +significantly modified from the original. It is for use by the Linux +busybox configuration script. Please do not bother Savio Lam with +questions about this program. diff --git a/scripts/kconfig/lxdialog/Makefile b/scripts/kconfig/lxdialog/Makefile new file mode 100644 index 000000000..a8b026326 --- /dev/null +++ b/scripts/kconfig/lxdialog/Makefile @@ -0,0 +1,21 @@ +# Makefile to build lxdialog package +# + +check-lxdialog := $(srctree)/$(src)/check-lxdialog.sh + +# Use reursively expanded variables so we do not call gcc unless +# we really need to do so. (Do not call gcc as part of make mrproper) +HOST_EXTRACFLAGS = $(shell $(CONFIG_SHELL) $(check-lxdialog) -ccflags) +HOST_LOADLIBES = $(shell $(CONFIG_SHELL) $(check-lxdialog) -ldflags $(HOSTCC)) + +HOST_EXTRACFLAGS += -DLOCALE + +PHONY += dochecklxdialog +$(obj)/dochecklxdialog: + $(Q)$(CONFIG_SHELL) $(check-lxdialog) -check $(HOSTCC) $(HOST_LOADLIBES) + +hostprogs-y := lxdialog +always := $(hostprogs-y) dochecklxdialog + +lxdialog-objs := checklist.o menubox.o textbox.o yesno.o inputbox.o \ + util.o lxdialog.o msgbox.o diff --git a/scripts/kconfig/lxdialog/check-lxdialog.sh b/scripts/kconfig/lxdialog/check-lxdialog.sh new file mode 100644 index 000000000..120d624e6 --- /dev/null +++ b/scripts/kconfig/lxdialog/check-lxdialog.sh @@ -0,0 +1,84 @@ +#!/bin/sh +# Check ncurses compatibility + +# What library to link +ldflags() +{ + $cc -print-file-name=libncursesw.so | grep -q / + if [ $? -eq 0 ]; then + echo '-lncursesw' + exit + fi + $cc -print-file-name=libncurses.so | grep -q / + if [ $? -eq 0 ]; then + echo '-lncurses' + exit + fi + $cc -print-file-name=libcurses.so | grep -q / + if [ $? -eq 0 ]; then + echo '-lcurses' + exit + fi + exit 1 +} + +# Where is ncurses.h? +ccflags() +{ + if [ -f /usr/include/ncurses/ncurses.h ]; then + echo '-I/usr/include/ncurses -DCURSES_LOC=""' + elif [ -f /usr/include/ncurses/curses.h ]; then + echo '-I/usr/include/ncurses -DCURSES_LOC=""' + elif [ -f /usr/include/ncurses.h ]; then + echo '-DCURSES_LOC=""' + else + echo '-DCURSES_LOC=""' + fi +} + +# Temp file, try to clean up after us +tmp=.lxdialog.tmp +trap "rm -f $tmp" 0 1 2 3 15 + +# Check if we can link to ncurses +check() { + echo "main() {}" | $cc -xc - -o $tmp 2> /dev/null + if [ $? != 0 ]; then + echo " *** Unable to find the ncurses libraries." 1>&2 + echo " *** make menuconfig require the ncurses libraries" 1>&2 + echo " *** " 1>&2 + echo " *** Install ncurses (ncurses-devel) and try again" 1>&2 + echo " *** " 1>&2 + exit 1 + fi +} + +usage() { + printf "Usage: $0 [-check compiler options|-header|-library]\n" +} + +if [ $# == 0 ]; then + usage + exit 1 +fi + +cc="" +case "$1" in + "-check") + shift + cc="$@" + check + ;; + "-ccflags") + ccflags + ;; + "-ldflags") + shift + cc="$@" + ldflags + ;; + "*") + usage + exit 1 + ;; +esac diff --git a/scripts/kconfig/lxdialog/checklist.c b/scripts/kconfig/lxdialog/checklist.c new file mode 100644 index 000000000..be0200e9c --- /dev/null +++ b/scripts/kconfig/lxdialog/checklist.c @@ -0,0 +1,333 @@ +/* + * checklist.c -- implements the checklist box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * Stuart Herbert - S.Herbert@sheffield.ac.uk: radiolist extension + * Alessandro Rubini - rubini@ipvvis.unipv.it: merged the two + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + * + * 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. + */ + +#include "dialog.h" + +static int list_width, check_x, item_x; + +/* + * Print list item + */ +static void print_item(WINDOW * win, const char *item, int status, int choice, + int selected) +{ + int i; + + /* Clear 'residue' of last item */ + wattrset(win, menubox_attr); + wmove(win, choice, 0); + for (i = 0; i < list_width; i++) + waddch(win, ' '); + + wmove(win, choice, check_x); + wattrset(win, selected ? check_selected_attr : check_attr); + wprintw(win, "(%c)", status ? 'X' : ' '); + + wattrset(win, selected ? tag_selected_attr : tag_attr); + mvwaddch(win, choice, item_x, item[0]); + wattrset(win, selected ? item_selected_attr : item_attr); + waddstr(win, (char *)item + 1); + if (selected) { + wmove(win, choice, check_x + 1); + wrefresh(win); + } +} + +/* + * Print the scroll indicators. + */ +static void print_arrows(WINDOW * win, int choice, int item_no, int scroll, + int y, int x, int height) +{ + wmove(win, y, x); + + if (scroll > 0) { + wattrset(win, uarrow_attr); + waddch(win, ACS_UARROW); + waddstr(win, "(-)"); + } else { + wattrset(win, menubox_attr); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + } + + y = y + height + 1; + wmove(win, y, x); + + if ((height < item_no) && (scroll + choice < item_no - 1)) { + wattrset(win, darrow_attr); + waddch(win, ACS_DARROW); + waddstr(win, "(+)"); + } else { + wattrset(win, menubox_border_attr); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + } +} + +/* + * Display the termination buttons + */ +static void print_buttons(WINDOW * dialog, int height, int width, int selected) +{ + int x = width / 2 - 11; + int y = height - 2; + + print_button(dialog, "Select", y, x, selected == 0); + print_button(dialog, " Help ", y, x + 14, selected == 1); + + wmove(dialog, y, x + 1 + 14 * selected); + wrefresh(dialog); +} + +/* + * Display a dialog box with a list of options that can be turned on or off + * in the style of radiolist (only one option turned on at a time). + */ +int dialog_checklist(const char *title, const char *prompt, int height, + int width, int list_height, int item_no, + const char *const *items) +{ + int i, x, y, box_x, box_y; + int key = 0, button = 0, choice = 0, scroll = 0, max_choice, *status; + WINDOW *dialog, *list; + + /* Allocate space for storing item on/off status */ + if ((status = malloc(sizeof(int) * item_no)) == NULL) { + endwin(); + fprintf(stderr, + "\nCan't allocate memory in dialog_checklist().\n"); + exit(-1); + } + + /* Initializes status */ + for (i = 0; i < item_no; i++) { + status[i] = !strcasecmp(items[i * 3 + 2], "on"); + if ((!choice && status[i]) + || !strcasecmp(items[i * 3 + 2], "selected")) + choice = i + 1; + } + if (choice) + choice--; + + max_choice = MIN(list_height, item_no); + + /* center dialog box on screen */ + x = (COLS - width) / 2; + y = (LINES - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + draw_box(dialog, 0, 0, height, width, dialog_attr, border_attr); + wattrset(dialog, border_attr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dialog_attr); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + wattrset(dialog, dialog_attr); + print_autowrap(dialog, prompt, width - 2, 1, 3); + + list_width = width - 6; + box_y = height - list_height - 5; + box_x = (width - list_width) / 2 - 1; + + /* create new window for the list */ + list = subwin(dialog, list_height, list_width, y + box_y + 1, + x + box_x + 1); + + keypad(list, TRUE); + + /* draw a box around the list items */ + draw_box(dialog, box_y, box_x, list_height + 2, list_width + 2, + menubox_border_attr, menubox_attr); + + /* Find length of longest item in order to center checklist */ + check_x = 0; + for (i = 0; i < item_no; i++) + check_x = MAX(check_x, +strlen(items[i * 3 + 1]) + 4); + + check_x = (list_width - check_x) / 2; + item_x = check_x + 4; + + if (choice >= list_height) { + scroll = choice - list_height + 1; + choice -= scroll; + } + + /* Print the list */ + for (i = 0; i < max_choice; i++) { + print_item(list, items[(scroll + i) * 3 + 1], + status[i + scroll], i, i == choice); + } + + print_arrows(dialog, choice, item_no, scroll, + box_y, box_x + check_x + 5, list_height); + + print_buttons(dialog, height, width, 0); + + wnoutrefresh(dialog); + wnoutrefresh(list); + doupdate(); + + while (key != ESC) { + key = wgetch(dialog); + + for (i = 0; i < max_choice; i++) + if (toupper(key) == + toupper(items[(scroll + i) * 3 + 1][0])) + break; + + if (i < max_choice || key == KEY_UP || key == KEY_DOWN || + key == '+' || key == '-') { + if (key == KEY_UP || key == '-') { + if (!choice) { + if (!scroll) + continue; + /* Scroll list down */ + if (list_height > 1) { + /* De-highlight current first item */ + print_item(list, items[scroll * 3 + 1], + status[scroll], 0, FALSE); + scrollok(list, TRUE); + wscrl(list, -1); + scrollok(list, FALSE); + } + scroll--; + print_item(list, items[scroll * 3 + 1], status[scroll], 0, TRUE); + print_arrows(dialog, choice, item_no, + scroll, box_y, box_x + check_x + 5, list_height); + + wnoutrefresh(dialog); + wrefresh(list); + + continue; /* wait for another key press */ + } else + i = choice - 1; + } else if (key == KEY_DOWN || key == '+') { + if (choice == max_choice - 1) { + if (scroll + choice >= item_no - 1) + continue; + /* Scroll list up */ + if (list_height > 1) { + /* De-highlight current last item before scrolling up */ + print_item(list, items[(scroll + max_choice - 1) * 3 + 1], + status[scroll + max_choice - 1], + max_choice - 1, FALSE); + scrollok(list, TRUE); + wscrl(list, 1); + scrollok(list, FALSE); + } + scroll++; + print_item(list, items[(scroll + max_choice - 1) * 3 + 1], + status[scroll + max_choice - 1], max_choice - 1, TRUE); + + print_arrows(dialog, choice, item_no, + scroll, box_y, box_x + check_x + 5, list_height); + + wnoutrefresh(dialog); + wrefresh(list); + + continue; /* wait for another key press */ + } else + i = choice + 1; + } + if (i != choice) { + /* De-highlight current item */ + print_item(list, items[(scroll + choice) * 3 + 1], + status[scroll + choice], choice, FALSE); + /* Highlight new item */ + choice = i; + print_item(list, items[(scroll + choice) * 3 + 1], + status[scroll + choice], choice, TRUE); + wnoutrefresh(dialog); + wrefresh(list); + } + continue; /* wait for another key press */ + } + switch (key) { + case 'H': + case 'h': + case '?': + fprintf(stderr, "%s", items[(scroll + choice) * 3]); + delwin(dialog); + free(status); + return 1; + case TAB: + case KEY_LEFT: + case KEY_RIGHT: + button = ((key == KEY_LEFT ? --button : ++button) < 0) + ? 1 : (button > 1 ? 0 : button); + + print_buttons(dialog, height, width, button); + wrefresh(dialog); + break; + case 'S': + case 's': + case ' ': + case '\n': + if (!button) { + if (!status[scroll + choice]) { + for (i = 0; i < item_no; i++) + status[i] = 0; + status[scroll + choice] = 1; + for (i = 0; i < max_choice; i++) + print_item(list, items[(scroll + i) * 3 + 1], + status[scroll + i], i, i == choice); + } + wnoutrefresh(dialog); + wrefresh(list); + + for (i = 0; i < item_no; i++) + if (status[i]) + fprintf(stderr, "%s", items[i * 3]); + } else + fprintf(stderr, "%s", items[(scroll + choice) * 3]); + delwin(dialog); + free(status); + return button; + case 'X': + case 'x': + key = ESC; + case ESC: + break; + } + + /* Now, update everything... */ + doupdate(); + } + + delwin(dialog); + free(status); + return -1; /* ESC pressed */ +} diff --git a/scripts/kconfig/lxdialog/colors.h b/scripts/kconfig/lxdialog/colors.h new file mode 100644 index 000000000..db071df12 --- /dev/null +++ b/scripts/kconfig/lxdialog/colors.h @@ -0,0 +1,154 @@ +/* + * colors.h -- color attribute definitions + * + * AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * + * 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. + */ + +/* + * Default color definitions + * + * *_FG = foreground + * *_BG = background + * *_HL = highlight? + */ +#define SCREEN_FG COLOR_CYAN +#define SCREEN_BG COLOR_BLUE +#define SCREEN_HL TRUE + +#define SHADOW_FG COLOR_BLACK +#define SHADOW_BG COLOR_BLACK +#define SHADOW_HL TRUE + +#define DIALOG_FG COLOR_BLACK +#define DIALOG_BG COLOR_WHITE +#define DIALOG_HL FALSE + +#define TITLE_FG COLOR_YELLOW +#define TITLE_BG COLOR_WHITE +#define TITLE_HL TRUE + +#define BORDER_FG COLOR_WHITE +#define BORDER_BG COLOR_WHITE +#define BORDER_HL TRUE + +#define BUTTON_ACTIVE_FG COLOR_WHITE +#define BUTTON_ACTIVE_BG COLOR_BLUE +#define BUTTON_ACTIVE_HL TRUE + +#define BUTTON_INACTIVE_FG COLOR_BLACK +#define BUTTON_INACTIVE_BG COLOR_WHITE +#define BUTTON_INACTIVE_HL FALSE + +#define BUTTON_KEY_ACTIVE_FG COLOR_WHITE +#define BUTTON_KEY_ACTIVE_BG COLOR_BLUE +#define BUTTON_KEY_ACTIVE_HL TRUE + +#define BUTTON_KEY_INACTIVE_FG COLOR_RED +#define BUTTON_KEY_INACTIVE_BG COLOR_WHITE +#define BUTTON_KEY_INACTIVE_HL FALSE + +#define BUTTON_LABEL_ACTIVE_FG COLOR_YELLOW +#define BUTTON_LABEL_ACTIVE_BG COLOR_BLUE +#define BUTTON_LABEL_ACTIVE_HL TRUE + +#define BUTTON_LABEL_INACTIVE_FG COLOR_BLACK +#define BUTTON_LABEL_INACTIVE_BG COLOR_WHITE +#define BUTTON_LABEL_INACTIVE_HL TRUE + +#define INPUTBOX_FG COLOR_BLACK +#define INPUTBOX_BG COLOR_WHITE +#define INPUTBOX_HL FALSE + +#define INPUTBOX_BORDER_FG COLOR_BLACK +#define INPUTBOX_BORDER_BG COLOR_WHITE +#define INPUTBOX_BORDER_HL FALSE + +#define SEARCHBOX_FG COLOR_BLACK +#define SEARCHBOX_BG COLOR_WHITE +#define SEARCHBOX_HL FALSE + +#define SEARCHBOX_TITLE_FG COLOR_YELLOW +#define SEARCHBOX_TITLE_BG COLOR_WHITE +#define SEARCHBOX_TITLE_HL TRUE + +#define SEARCHBOX_BORDER_FG COLOR_WHITE +#define SEARCHBOX_BORDER_BG COLOR_WHITE +#define SEARCHBOX_BORDER_HL TRUE + +#define POSITION_INDICATOR_FG COLOR_YELLOW +#define POSITION_INDICATOR_BG COLOR_WHITE +#define POSITION_INDICATOR_HL TRUE + +#define MENUBOX_FG COLOR_BLACK +#define MENUBOX_BG COLOR_WHITE +#define MENUBOX_HL FALSE + +#define MENUBOX_BORDER_FG COLOR_WHITE +#define MENUBOX_BORDER_BG COLOR_WHITE +#define MENUBOX_BORDER_HL TRUE + +#define ITEM_FG COLOR_BLACK +#define ITEM_BG COLOR_WHITE +#define ITEM_HL FALSE + +#define ITEM_SELECTED_FG COLOR_WHITE +#define ITEM_SELECTED_BG COLOR_BLUE +#define ITEM_SELECTED_HL TRUE + +#define TAG_FG COLOR_YELLOW +#define TAG_BG COLOR_WHITE +#define TAG_HL TRUE + +#define TAG_SELECTED_FG COLOR_YELLOW +#define TAG_SELECTED_BG COLOR_BLUE +#define TAG_SELECTED_HL TRUE + +#define TAG_KEY_FG COLOR_YELLOW +#define TAG_KEY_BG COLOR_WHITE +#define TAG_KEY_HL TRUE + +#define TAG_KEY_SELECTED_FG COLOR_YELLOW +#define TAG_KEY_SELECTED_BG COLOR_BLUE +#define TAG_KEY_SELECTED_HL TRUE + +#define CHECK_FG COLOR_BLACK +#define CHECK_BG COLOR_WHITE +#define CHECK_HL FALSE + +#define CHECK_SELECTED_FG COLOR_WHITE +#define CHECK_SELECTED_BG COLOR_BLUE +#define CHECK_SELECTED_HL TRUE + +#define UARROW_FG COLOR_GREEN +#define UARROW_BG COLOR_WHITE +#define UARROW_HL TRUE + +#define DARROW_FG COLOR_GREEN +#define DARROW_BG COLOR_WHITE +#define DARROW_HL TRUE + +/* End of default color definitions */ + +#define C_ATTR(x,y) ((x ? A_BOLD : 0) | COLOR_PAIR((y))) +#define COLOR_NAME_LEN 10 +#define COLOR_COUNT 8 + +/* + * Global variables + */ + +extern int color_table[][3]; diff --git a/scripts/kconfig/lxdialog/dialog.h b/scripts/kconfig/lxdialog/dialog.h new file mode 100644 index 000000000..af3cf716e --- /dev/null +++ b/scripts/kconfig/lxdialog/dialog.h @@ -0,0 +1,177 @@ +/* + * dialog.h -- common declarations for all dialog modules + * + * AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * + * 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. + */ + +#include +#include +#include +#include +#include +#include + +#ifdef __sun__ +#define CURS_MACROS +#endif +#include CURSES_LOC + +/* + * Colors in ncurses 1.9.9e do not work properly since foreground and + * background colors are OR'd rather than separately masked. This version + * of dialog was hacked to work with ncurses 1.9.9e, making it incompatible + * with standard curses. The simplest fix (to make this work with standard + * curses) uses the wbkgdset() function, not used in the original hack. + * Turn it off if we're building with 1.9.9e, since it just confuses things. + */ +#if defined(NCURSES_VERSION) && defined(_NEED_WRAP) && !defined(GCC_PRINTFLIKE) +#define OLD_NCURSES 1 +#undef wbkgdset +#define wbkgdset(w,p) /*nothing */ +#else +#define OLD_NCURSES 0 +#endif + +#define TR(params) _tracef params + +#define ESC 27 +#define TAB 9 +#define MAX_LEN 2048 +#define BUF_SIZE (10*1024) +#define MIN(x,y) (x < y ? x : y) +#define MAX(x,y) (x > y ? x : y) + +#ifndef ACS_ULCORNER +#define ACS_ULCORNER '+' +#endif +#ifndef ACS_LLCORNER +#define ACS_LLCORNER '+' +#endif +#ifndef ACS_URCORNER +#define ACS_URCORNER '+' +#endif +#ifndef ACS_LRCORNER +#define ACS_LRCORNER '+' +#endif +#ifndef ACS_HLINE +#define ACS_HLINE '-' +#endif +#ifndef ACS_VLINE +#define ACS_VLINE '|' +#endif +#ifndef ACS_LTEE +#define ACS_LTEE '+' +#endif +#ifndef ACS_RTEE +#define ACS_RTEE '+' +#endif +#ifndef ACS_UARROW +#define ACS_UARROW '^' +#endif +#ifndef ACS_DARROW +#define ACS_DARROW 'v' +#endif + +/* + * Attribute names + */ +#define screen_attr attributes[0] +#define shadow_attr attributes[1] +#define dialog_attr attributes[2] +#define title_attr attributes[3] +#define border_attr attributes[4] +#define button_active_attr attributes[5] +#define button_inactive_attr attributes[6] +#define button_key_active_attr attributes[7] +#define button_key_inactive_attr attributes[8] +#define button_label_active_attr attributes[9] +#define button_label_inactive_attr attributes[10] +#define inputbox_attr attributes[11] +#define inputbox_border_attr attributes[12] +#define searchbox_attr attributes[13] +#define searchbox_title_attr attributes[14] +#define searchbox_border_attr attributes[15] +#define position_indicator_attr attributes[16] +#define menubox_attr attributes[17] +#define menubox_border_attr attributes[18] +#define item_attr attributes[19] +#define item_selected_attr attributes[20] +#define tag_attr attributes[21] +#define tag_selected_attr attributes[22] +#define tag_key_attr attributes[23] +#define tag_key_selected_attr attributes[24] +#define check_attr attributes[25] +#define check_selected_attr attributes[26] +#define uarrow_attr attributes[27] +#define darrow_attr attributes[28] + +/* number of attributes */ +#define ATTRIBUTE_COUNT 29 + +/* + * Global variables + */ +extern bool use_colors; +extern bool use_shadow; + +extern chtype attributes[]; + +extern const char *backtitle; + +/* + * Function prototypes + */ +extern void create_rc(const char *filename); +extern int parse_rc(void); + +void init_dialog(void); +void end_dialog(void); +void attr_clear(WINDOW * win, int height, int width, chtype attr); +void dialog_clear(void); +void color_setup(void); +void print_autowrap(WINDOW * win, const char *prompt, int width, int y, int x); +void print_button(WINDOW * win, const char *label, int y, int x, int selected); +void print_title(WINDOW *dialog, const char *title, int width); +void draw_box(WINDOW * win, int y, int x, int height, int width, chtype box, + chtype border); +void draw_shadow(WINDOW * win, int y, int x, int height, int width); + +int first_alpha(const char *string, const char *exempt); +int dialog_yesno(const char *title, const char *prompt, int height, int width); +int dialog_msgbox(const char *title, const char *prompt, int height, + int width, int pause); +int dialog_textbox(const char *title, const char *file, int height, int width); +int dialog_menu(const char *title, const char *prompt, int height, int width, + int menu_height, const char *choice, int item_no, + const char *const *items); +int dialog_checklist(const char *title, const char *prompt, int height, + int width, int list_height, int item_no, + const char *const *items); +extern char dialog_input_result[]; +int dialog_inputbox(const char *title, const char *prompt, int height, + int width, const char *init); + +/* + * This is the base for fictitious keys, which activate + * the buttons. + * + * Mouse-generated keys are the following: + * -- the first 32 are used as numbers, in addition to '0'-'9' + * -- the lowercase are used to signal mouse-enter events (M_EVENT + 'o') + * -- uppercase chars are used to invoke the button (M_EVENT + 'O') + */ +#define M_EVENT (KEY_MAX+1) diff --git a/scripts/kconfig/lxdialog/inputbox.c b/scripts/kconfig/lxdialog/inputbox.c new file mode 100644 index 000000000..779503726 --- /dev/null +++ b/scripts/kconfig/lxdialog/inputbox.c @@ -0,0 +1,224 @@ +/* + * inputbox.c -- implements the input box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + * + * 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. + */ + +#include "dialog.h" + +char dialog_input_result[MAX_LEN + 1]; + +/* + * Print the termination buttons + */ +static void print_buttons(WINDOW * dialog, int height, int width, int selected) +{ + int x = width / 2 - 11; + int y = height - 2; + + print_button(dialog, " Ok ", y, x, selected == 0); + print_button(dialog, " Help ", y, x + 14, selected == 1); + + wmove(dialog, y, x + 1 + 14 * selected); + wrefresh(dialog); +} + +/* + * Display a dialog box for inputing a string + */ +int dialog_inputbox(const char *title, const char *prompt, int height, int width, + const char *init) +{ + int i, x, y, box_y, box_x, box_width; + int input_x = 0, scroll = 0, key = 0, button = -1; + char *instr = dialog_input_result; + WINDOW *dialog; + + /* center dialog box on screen */ + x = (COLS - width) / 2; + y = (LINES - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + draw_box(dialog, 0, 0, height, width, dialog_attr, border_attr); + wattrset(dialog, border_attr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dialog_attr); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + wattrset(dialog, dialog_attr); + print_autowrap(dialog, prompt, width - 2, 1, 3); + + /* Draw the input field box */ + box_width = width - 6; + getyx(dialog, y, x); + box_y = y + 2; + box_x = (width - box_width) / 2; + draw_box(dialog, y + 1, box_x - 1, 3, box_width + 2, border_attr, dialog_attr); + + print_buttons(dialog, height, width, 0); + + /* Set up the initial value */ + wmove(dialog, box_y, box_x); + wattrset(dialog, inputbox_attr); + + if (!init) + instr[0] = '\0'; + else + strcpy(instr, init); + + input_x = strlen(instr); + + if (input_x >= box_width) { + scroll = input_x - box_width + 1; + input_x = box_width - 1; + for (i = 0; i < box_width - 1; i++) + waddch(dialog, instr[scroll + i]); + } else { + waddstr(dialog, instr); + } + + wmove(dialog, box_y, box_x + input_x); + + wrefresh(dialog); + + while (key != ESC) { + key = wgetch(dialog); + + if (button == -1) { /* Input box selected */ + switch (key) { + case TAB: + case KEY_UP: + case KEY_DOWN: + break; + case KEY_LEFT: + continue; + case KEY_RIGHT: + continue; + case KEY_BACKSPACE: + case 127: + if (input_x || scroll) { + wattrset(dialog, inputbox_attr); + if (!input_x) { + scroll = scroll < box_width - 1 ? 0 : scroll - (box_width - 1); + wmove(dialog, box_y, box_x); + for (i = 0; i < box_width; i++) + waddch(dialog, + instr[scroll + input_x + i] ? + instr[scroll + input_x + i] : ' '); + input_x = strlen(instr) - scroll; + } else + input_x--; + instr[scroll + input_x] = '\0'; + mvwaddch(dialog, box_y, input_x + box_x, ' '); + wmove(dialog, box_y, input_x + box_x); + wrefresh(dialog); + } + continue; + default: + if (key < 0x100 && isprint(key)) { + if (scroll + input_x < MAX_LEN) { + wattrset(dialog, inputbox_attr); + instr[scroll + input_x] = key; + instr[scroll + input_x + 1] = '\0'; + if (input_x == box_width - 1) { + scroll++; + wmove(dialog, box_y, box_x); + for (i = 0; i < box_width - 1; i++) + waddch(dialog, instr [scroll + i]); + } else { + wmove(dialog, box_y, input_x++ + box_x); + waddch(dialog, key); + } + wrefresh(dialog); + } else + flash(); /* Alarm user about overflow */ + continue; + } + } + } + switch (key) { + case 'O': + case 'o': + delwin(dialog); + return 0; + case 'H': + case 'h': + delwin(dialog); + return 1; + case KEY_UP: + case KEY_LEFT: + switch (button) { + case -1: + button = 1; /* Indicates "Cancel" button is selected */ + print_buttons(dialog, height, width, 1); + break; + case 0: + button = -1; /* Indicates input box is selected */ + print_buttons(dialog, height, width, 0); + wmove(dialog, box_y, box_x + input_x); + wrefresh(dialog); + break; + case 1: + button = 0; /* Indicates "OK" button is selected */ + print_buttons(dialog, height, width, 0); + break; + } + break; + case TAB: + case KEY_DOWN: + case KEY_RIGHT: + switch (button) { + case -1: + button = 0; /* Indicates "OK" button is selected */ + print_buttons(dialog, height, width, 0); + break; + case 0: + button = 1; /* Indicates "Cancel" button is selected */ + print_buttons(dialog, height, width, 1); + break; + case 1: + button = -1; /* Indicates input box is selected */ + print_buttons(dialog, height, width, 0); + wmove(dialog, box_y, box_x + input_x); + wrefresh(dialog); + break; + } + break; + case ' ': + case '\n': + delwin(dialog); + return (button == -1 ? 0 : button); + case 'X': + case 'x': + key = ESC; + case ESC: + break; + } + } + + delwin(dialog); + return -1; /* ESC pressed */ +} diff --git a/scripts/kconfig/lxdialog/lxdialog.c b/scripts/kconfig/lxdialog/lxdialog.c new file mode 100644 index 000000000..79f6c5fb5 --- /dev/null +++ b/scripts/kconfig/lxdialog/lxdialog.c @@ -0,0 +1,204 @@ +/* + * dialog - Display simple dialog boxes from shell scripts + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + * + * 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. + */ + +#include "dialog.h" + +static void Usage(const char *name); + +typedef int (jumperFn) (const char *title, int argc, const char *const *argv); + +struct Mode { + char *name; + int argmin, argmax, argmod; + jumperFn *jumper; +}; + +jumperFn j_menu, j_radiolist, j_yesno, j_textbox, j_inputbox; +jumperFn j_msgbox, j_infobox; + +static struct Mode modes[] = { + {"--menu", 9, 0, 3, j_menu}, + {"--radiolist", 9, 0, 3, j_radiolist}, + {"--yesno", 5, 5, 1, j_yesno}, + {"--textbox", 5, 5, 1, j_textbox}, + {"--inputbox", 5, 6, 1, j_inputbox}, + {"--msgbox", 5, 5, 1, j_msgbox}, + {"--infobox", 5, 5, 1, j_infobox}, + {NULL, 0, 0, 0, NULL} +}; + +static struct Mode *modePtr; + +#ifdef LOCALE +#include +#endif + +int main(int argc, const char *const *argv) +{ + int offset = 0, opt_clear = 0, end_common_opts = 0, retval; + const char *title = NULL; + +#ifdef LOCALE + (void)setlocale(LC_ALL, ""); +#endif + +#ifdef TRACE + trace(TRACE_CALLS | TRACE_UPDATE); +#endif + if (argc < 2) { + Usage(argv[0]); + exit(-1); + } + + while (offset < argc - 1 && !end_common_opts) { /* Common options */ + if (!strcmp(argv[offset + 1], "--title")) { + if (argc - offset < 3 || title != NULL) { + Usage(argv[0]); + exit(-1); + } else { + title = argv[offset + 2]; + offset += 2; + } + } else if (!strcmp(argv[offset + 1], "--backtitle")) { + if (backtitle != NULL) { + Usage(argv[0]); + exit(-1); + } else { + backtitle = argv[offset + 2]; + offset += 2; + } + } else if (!strcmp(argv[offset + 1], "--clear")) { + if (opt_clear) { /* Hey, "--clear" can't appear twice! */ + Usage(argv[0]); + exit(-1); + } else if (argc == 2) { /* we only want to clear the screen */ + init_dialog(); + refresh(); /* init_dialog() will clear the screen for us */ + end_dialog(); + return 0; + } else { + opt_clear = 1; + offset++; + } + } else /* no more common options */ + end_common_opts = 1; + } + + if (argc - 1 == offset) { /* no more options */ + Usage(argv[0]); + exit(-1); + } + /* use a table to look for the requested mode, to avoid code duplication */ + + for (modePtr = modes; modePtr->name; modePtr++) /* look for the mode */ + if (!strcmp(argv[offset + 1], modePtr->name)) + break; + + if (!modePtr->name) + Usage(argv[0]); + if (argc - offset < modePtr->argmin) + Usage(argv[0]); + if (modePtr->argmax && argc - offset > modePtr->argmax) + Usage(argv[0]); + + init_dialog(); + retval = (*(modePtr->jumper)) (title, argc - offset, argv + offset); + + if (opt_clear) { /* clear screen before exit */ + attr_clear(stdscr, LINES, COLS, screen_attr); + refresh(); + } + end_dialog(); + + exit(retval); +} + +/* + * Print program usage + */ +static void Usage(const char *name) +{ + fprintf(stderr, "\ +\ndialog, by Savio Lam (lam836@cs.cuhk.hk).\ +\n patched by Stuart Herbert (S.Herbert@shef.ac.uk)\ +\n modified/gutted for use as a Linux kernel config tool by \ +\n William Roadcap (roadcapw@cfw.com)\ +\n\ +\n* Display dialog boxes from shell scripts *\ +\n\ +\nUsage: %s --clear\ +\n %s [--title ] [--backtitle <backtitle>] --clear <Box options>\ +\n\ +\nBox options:\ +\n\ +\n --menu <text> <height> <width> <menu height> <tag1> <item1>...\ +\n --radiolist <text> <height> <width> <list height> <tag1> <item1> <status1>...\ +\n --textbox <file> <height> <width>\ +\n --inputbox <text> <height> <width> [<init>]\ +\n --yesno <text> <height> <width>\ +\n", name, name); + exit(-1); +} + +/* + * These are the program jumpers + */ + +int j_menu(const char *t, int ac, const char *const *av) +{ + return dialog_menu(t, av[2], atoi(av[3]), atoi(av[4]), + atoi(av[5]), av[6], (ac - 6) / 2, av + 7); +} + +int j_radiolist(const char *t, int ac, const char *const *av) +{ + return dialog_checklist(t, av[2], atoi(av[3]), atoi(av[4]), + atoi(av[5]), (ac - 6) / 3, av + 6); +} + +int j_textbox(const char *t, int ac, const char *const *av) +{ + return dialog_textbox(t, av[2], atoi(av[3]), atoi(av[4])); +} + +int j_yesno(const char *t, int ac, const char *const *av) +{ + return dialog_yesno(t, av[2], atoi(av[3]), atoi(av[4])); +} + +int j_inputbox(const char *t, int ac, const char *const *av) +{ + int ret = dialog_inputbox(t, av[2], atoi(av[3]), atoi(av[4]), + ac == 6 ? av[5] : (char *)NULL); + if (ret == 0) + fprintf(stderr, dialog_input_result); + return ret; +} + +int j_msgbox(const char *t, int ac, const char *const *av) +{ + return dialog_msgbox(t, av[2], atoi(av[3]), atoi(av[4]), 1); +} + +int j_infobox(const char *t, int ac, const char *const *av) +{ + return dialog_msgbox(t, av[2], atoi(av[3]), atoi(av[4]), 0); +} diff --git a/scripts/kconfig/lxdialog/menubox.c b/scripts/kconfig/lxdialog/menubox.c new file mode 100644 index 000000000..bf8052f4f --- /dev/null +++ b/scripts/kconfig/lxdialog/menubox.c @@ -0,0 +1,426 @@ +/* + * menubox.c -- implements the menu box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcapw@cfw.com) + * + * 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. + */ + +/* + * Changes by Clifford Wolf (god@clifford.at) + * + * [ 1998-06-13 ] + * + * *) A bugfix for the Page-Down problem + * + * *) Formerly when I used Page Down and Page Up, the cursor would be set + * to the first position in the menu box. Now lxdialog is a bit + * smarter and works more like other menu systems (just have a look at + * it). + * + * *) Formerly if I selected something my scrolling would be broken because + * lxdialog is re-invoked by the Menuconfig shell script, can't + * remember the last scrolling position, and just sets it so that the + * cursor is at the bottom of the box. Now it writes the temporary file + * lxdialog.scrltmp which contains this information. The file is + * deleted by lxdialog if the user leaves a submenu or enters a new + * one, but it would be nice if Menuconfig could make another "rm -f" + * just to be sure. Just try it out - you will recognise a difference! + * + * [ 1998-06-14 ] + * + * *) Now lxdialog is crash-safe against broken "lxdialog.scrltmp" files + * and menus change their size on the fly. + * + * *) If for some reason the last scrolling position is not saved by + * lxdialog, it sets the scrolling so that the selected item is in the + * middle of the menu box, not at the bottom. + * + * 02 January 1999, Michael Elizabeth Chastain (mec@shout.net) + * Reset 'scroll' to 0 if the value from lxdialog.scrltmp is bogus. + * This fixes a bug in Menuconfig where using ' ' to descend into menus + * would leave mis-synchronized lxdialog.scrltmp files lying around, + * fscanf would read in 'scroll', and eventually that value would get used. + */ + +#include "dialog.h" + +static int menu_width, item_x; + +/* + * Print menu item + */ +static void do_print_item(WINDOW * win, const char *item, int choice, + int selected, int hotkey) +{ + int j; + char *menu_item = malloc(menu_width + 1); + + strncpy(menu_item, item, menu_width - item_x); + menu_item[menu_width] = 0; + j = first_alpha(menu_item, "YyNnMmHh"); + + /* Clear 'residue' of last item */ + wattrset(win, menubox_attr); + wmove(win, choice, 0); +#if OLD_NCURSES + { + int i; + for (i = 0; i < menu_width; i++) + waddch(win, ' '); + } +#else + wclrtoeol(win); +#endif + wattrset(win, selected ? item_selected_attr : item_attr); + mvwaddstr(win, choice, item_x, menu_item); + if (hotkey) { + wattrset(win, selected ? tag_key_selected_attr : tag_key_attr); + mvwaddch(win, choice, item_x + j, menu_item[j]); + } + if (selected) { + wmove(win, choice, item_x + 1); + } + free(menu_item); + wrefresh(win); +} + +#define print_item(index, choice, selected) \ +do {\ + int hotkey = (items[(index) * 2][0] != ':'); \ + do_print_item(menu, items[(index) * 2 + 1], choice, selected, hotkey); \ +} while (0) + +/* + * Print the scroll indicators. + */ +static void print_arrows(WINDOW * win, int item_no, int scroll, int y, int x, + int height) +{ + int cur_y, cur_x; + + getyx(win, cur_y, cur_x); + + wmove(win, y, x); + + if (scroll > 0) { + wattrset(win, uarrow_attr); + waddch(win, ACS_UARROW); + waddstr(win, "(-)"); + } else { + wattrset(win, menubox_attr); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + } + + y = y + height + 1; + wmove(win, y, x); + wrefresh(win); + + if ((height < item_no) && (scroll + height < item_no)) { + wattrset(win, darrow_attr); + waddch(win, ACS_DARROW); + waddstr(win, "(+)"); + } else { + wattrset(win, menubox_border_attr); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + waddch(win, ACS_HLINE); + } + + wmove(win, cur_y, cur_x); + wrefresh(win); +} + +/* + * Display the termination buttons. + */ +static void print_buttons(WINDOW * win, int height, int width, int selected) +{ + int x = width / 2 - 16; + int y = height - 2; + + print_button(win, "Select", y, x, selected == 0); + print_button(win, " Exit ", y, x + 12, selected == 1); + print_button(win, " Help ", y, x + 24, selected == 2); + + wmove(win, y, x + 1 + 12 * selected); + wrefresh(win); +} + +/* scroll up n lines (n may be negative) */ +static void do_scroll(WINDOW *win, int *scroll, int n) +{ + /* Scroll menu up */ + scrollok(win, TRUE); + wscrl(win, n); + scrollok(win, FALSE); + *scroll = *scroll + n; + wrefresh(win); +} + +/* + * Display a menu for choosing among a number of options + */ +int dialog_menu(const char *title, const char *prompt, int height, int width, + int menu_height, const char *current, int item_no, + const char *const *items) +{ + int i, j, x, y, box_x, box_y; + int key = 0, button = 0, scroll = 0, choice = 0; + int first_item = 0, max_choice; + WINDOW *dialog, *menu; + FILE *f; + + max_choice = MIN(menu_height, item_no); + + /* center dialog box on screen */ + x = (COLS - width) / 2; + y = (LINES - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + draw_box(dialog, 0, 0, height, width, dialog_attr, border_attr); + wattrset(dialog, border_attr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dialog_attr); + wbkgdset(dialog, dialog_attr & A_COLOR); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + wattrset(dialog, dialog_attr); + print_autowrap(dialog, prompt, width - 2, 1, 3); + + menu_width = width - 6; + box_y = height - menu_height - 5; + box_x = (width - menu_width) / 2 - 1; + + /* create new window for the menu */ + menu = subwin(dialog, menu_height, menu_width, + y + box_y + 1, x + box_x + 1); + keypad(menu, TRUE); + + /* draw a box around the menu items */ + draw_box(dialog, box_y, box_x, menu_height + 2, menu_width + 2, + menubox_border_attr, menubox_attr); + + item_x = (menu_width - 70) / 2; + + /* Set choice to default item */ + for (i = 0; i < item_no; i++) + if (strcmp(current, items[i * 2]) == 0) + choice = i; + + /* get the scroll info from the temp file */ + if ((f = fopen("lxdialog.scrltmp", "r")) != NULL) { + if ((fscanf(f, "%d\n", &scroll) == 1) && (scroll <= choice) && + (scroll + max_choice > choice) && (scroll >= 0) && + (scroll + max_choice <= item_no)) { + first_item = scroll; + choice = choice - scroll; + fclose(f); + } else { + scroll = 0; + remove("lxdialog.scrltmp"); + fclose(f); + f = NULL; + } + } + if ((choice >= max_choice) || (f == NULL && choice >= max_choice / 2)) { + if (choice >= item_no - max_choice / 2) + scroll = first_item = item_no - max_choice; + else + scroll = first_item = choice - max_choice / 2; + choice = choice - scroll; + } + + /* Print the menu */ + for (i = 0; i < max_choice; i++) { + print_item(first_item + i, i, i == choice); + } + + wnoutrefresh(menu); + + print_arrows(dialog, item_no, scroll, + box_y, box_x + item_x + 1, menu_height); + + print_buttons(dialog, height, width, 0); + wmove(menu, choice, item_x + 1); + wrefresh(menu); + + while (key != ESC) { + key = wgetch(menu); + + if (key < 256 && isalpha(key)) + key = tolower(key); + + if (strchr("ynmh", key)) + i = max_choice; + else { + for (i = choice + 1; i < max_choice; i++) { + j = first_alpha(items[(scroll + i) * 2 + 1], "YyNnMmHh"); + if (key == tolower(items[(scroll + i) * 2 + 1][j])) + break; + } + if (i == max_choice) + for (i = 0; i < max_choice; i++) { + j = first_alpha(items [(scroll + i) * 2 + 1], "YyNnMmHh"); + if (key == tolower(items[(scroll + i) * 2 + 1][j])) + break; + } + } + + if (i < max_choice || + key == KEY_UP || key == KEY_DOWN || + key == '-' || key == '+' || + key == KEY_PPAGE || key == KEY_NPAGE) { + /* Remove highligt of current item */ + print_item(scroll + choice, choice, FALSE); + + if (key == KEY_UP || key == '-') { + if (choice < 2 && scroll) { + /* Scroll menu down */ + do_scroll(menu, &scroll, -1); + + print_item(scroll, 0, FALSE); + } else + choice = MAX(choice - 1, 0); + + } else if (key == KEY_DOWN || key == '+') { + print_item(scroll+choice, choice, FALSE); + + if ((choice > max_choice - 3) && + (scroll + max_choice < item_no)) { + /* Scroll menu up */ + do_scroll(menu, &scroll, 1); + + print_item(scroll+max_choice - 1, + max_choice - 1, FALSE); + } else + choice = MIN(choice + 1, max_choice - 1); + + } else if (key == KEY_PPAGE) { + scrollok(menu, TRUE); + for (i = 0; (i < max_choice); i++) { + if (scroll > 0) { + do_scroll(menu, &scroll, -1); + print_item(scroll, 0, FALSE); + } else { + if (choice > 0) + choice--; + } + } + + } else if (key == KEY_NPAGE) { + for (i = 0; (i < max_choice); i++) { + if (scroll + max_choice < item_no) { + do_scroll(menu, &scroll, 1); + print_item(scroll+max_choice-1, + max_choice - 1, FALSE); + } else { + if (choice + 1 < max_choice) + choice++; + } + } + } else + choice = i; + + print_item(scroll + choice, choice, TRUE); + + print_arrows(dialog, item_no, scroll, + box_y, box_x + item_x + 1, menu_height); + + wnoutrefresh(dialog); + wrefresh(menu); + + continue; /* wait for another key press */ + } + + switch (key) { + case KEY_LEFT: + case TAB: + case KEY_RIGHT: + button = ((key == KEY_LEFT ? --button : ++button) < 0) + ? 2 : (button > 2 ? 0 : button); + + print_buttons(dialog, height, width, button); + wrefresh(menu); + break; + case ' ': + case 's': + case 'y': + case 'n': + case 'm': + case '/': + /* save scroll info */ + if ((f = fopen("lxdialog.scrltmp", "w")) != NULL) { + fprintf(f, "%d\n", scroll); + fclose(f); + } + delwin(dialog); + fprintf(stderr, "%s\n", items[(scroll + choice) * 2]); + switch (key) { + case 's': + return 3; + case 'y': + return 3; + case 'n': + return 4; + case 'm': + return 5; + case ' ': + return 6; + case '/': + return 7; + } + return 0; + case 'h': + case '?': + button = 2; + case '\n': + delwin(dialog); + if (button == 2) + fprintf(stderr, "%s \"%s\"\n", + items[(scroll + choice) * 2], + items[(scroll + choice) * 2 + 1] + + first_alpha(items [(scroll + choice) * 2 + 1], "")); + else + fprintf(stderr, "%s\n", + items[(scroll + choice) * 2]); + + remove("lxdialog.scrltmp"); + return button; + case 'e': + case 'x': + key = ESC; + case ESC: + break; + } + } + + delwin(dialog); + remove("lxdialog.scrltmp"); + return -1; /* ESC pressed */ +} diff --git a/scripts/kconfig/lxdialog/msgbox.c b/scripts/kconfig/lxdialog/msgbox.c new file mode 100644 index 000000000..7323f5471 --- /dev/null +++ b/scripts/kconfig/lxdialog/msgbox.c @@ -0,0 +1,71 @@ +/* + * msgbox.c -- implements the message box and info box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcapw@cfw.com) + * + * 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. + */ + +#include "dialog.h" + +/* + * Display a message box. Program will pause and display an "OK" button + * if the parameter 'pause' is non-zero. + */ +int dialog_msgbox(const char *title, const char *prompt, int height, int width, + int pause) +{ + int i, x, y, key = 0; + WINDOW *dialog; + + /* center dialog box on screen */ + x = (COLS - width) / 2; + y = (LINES - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + draw_box(dialog, 0, 0, height, width, dialog_attr, border_attr); + + print_title(dialog, title, width); + + wattrset(dialog, dialog_attr); + print_autowrap(dialog, prompt, width - 2, 1, 2); + + if (pause) { + wattrset(dialog, border_attr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dialog_attr); + waddch(dialog, ACS_RTEE); + + print_button(dialog, " Ok ", height - 2, width / 2 - 4, TRUE); + + wrefresh(dialog); + while (key != ESC && key != '\n' && key != ' ' && + key != 'O' && key != 'o' && key != 'X' && key != 'x') + key = wgetch(dialog); + } else { + key = '\n'; + wrefresh(dialog); + } + + delwin(dialog); + return key == ESC ? -1 : 0; +} diff --git a/scripts/kconfig/lxdialog/textbox.c b/scripts/kconfig/lxdialog/textbox.c new file mode 100644 index 000000000..77848bb8e --- /dev/null +++ b/scripts/kconfig/lxdialog/textbox.c @@ -0,0 +1,533 @@ +/* + * textbox.c -- implements the text box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + * + * 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. + */ + +#include "dialog.h" + +static void back_lines(int n); +static void print_page(WINDOW * win, int height, int width); +static void print_line(WINDOW * win, int row, int width); +static char *get_line(void); +static void print_position(WINDOW * win, int height, int width); + +static int hscroll, fd, file_size, bytes_read; +static int begin_reached = 1, end_reached, page_length; +static char *buf, *page; + +/* + * Display text from a file in a dialog box. + */ +int dialog_textbox(const char *title, const char *file, int height, int width) +{ + int i, x, y, cur_x, cur_y, fpos, key = 0; + int passed_end; + char search_term[MAX_LEN + 1]; + WINDOW *dialog, *text; + + search_term[0] = '\0'; /* no search term entered yet */ + + /* Open input file for reading */ + if ((fd = open(file, O_RDONLY)) == -1) { + endwin(); + fprintf(stderr, "\nCan't open input file in dialog_textbox().\n"); + exit(-1); + } + /* Get file size. Actually, 'file_size' is the real file size - 1, + since it's only the last byte offset from the beginning */ + if ((file_size = lseek(fd, 0, SEEK_END)) == -1) { + endwin(); + fprintf(stderr, "\nError getting file size in dialog_textbox().\n"); + exit(-1); + } + /* Restore file pointer to beginning of file after getting file size */ + if (lseek(fd, 0, SEEK_SET) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer in dialog_textbox().\n"); + exit(-1); + } + /* Allocate space for read buffer */ + if ((buf = malloc(BUF_SIZE + 1)) == NULL) { + endwin(); + fprintf(stderr, "\nCan't allocate memory in dialog_textbox().\n"); + exit(-1); + } + if ((bytes_read = read(fd, buf, BUF_SIZE)) == -1) { + endwin(); + fprintf(stderr, "\nError reading file in dialog_textbox().\n"); + exit(-1); + } + buf[bytes_read] = '\0'; /* mark end of valid data */ + page = buf; /* page is pointer to start of page to be displayed */ + + /* center dialog box on screen */ + x = (COLS - width) / 2; + y = (LINES - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + /* Create window for text region, used for scrolling text */ + text = subwin(dialog, height - 4, width - 2, y + 1, x + 1); + wattrset(text, dialog_attr); + wbkgdset(text, dialog_attr & A_COLOR); + + keypad(text, TRUE); + + /* register the new window, along with its borders */ + draw_box(dialog, 0, 0, height, width, dialog_attr, border_attr); + + wattrset(dialog, border_attr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dialog_attr); + wbkgdset(dialog, dialog_attr & A_COLOR); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + print_button(dialog, " Exit ", height - 2, width / 2 - 4, TRUE); + wnoutrefresh(dialog); + getyx(dialog, cur_y, cur_x); /* Save cursor position */ + + /* Print first page of text */ + attr_clear(text, height - 4, width - 2, dialog_attr); + print_page(text, height - 4, width - 2); + print_position(dialog, height, width); + wmove(dialog, cur_y, cur_x); /* Restore cursor position */ + wrefresh(dialog); + + while ((key != ESC) && (key != '\n')) { + key = wgetch(dialog); + switch (key) { + case 'E': /* Exit */ + case 'e': + case 'X': + case 'x': + delwin(dialog); + free(buf); + close(fd); + return 0; + case 'g': /* First page */ + case KEY_HOME: + if (!begin_reached) { + begin_reached = 1; + /* First page not in buffer? */ + if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer in dialog_textbox().\n"); + exit(-1); + } + if (fpos > bytes_read) { /* Yes, we have to read it in */ + if (lseek(fd, 0, SEEK_SET) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer in " + "dialog_textbox().\n"); + exit(-1); + } + if ((bytes_read = + read(fd, buf, BUF_SIZE)) == -1) { + endwin(); + fprintf(stderr, "\nError reading file in dialog_textbox().\n"); + exit(-1); + } + buf[bytes_read] = '\0'; + } + page = buf; + print_page(text, height - 4, width - 2); + print_position(dialog, height, width); + wmove(dialog, cur_y, cur_x); /* Restore cursor position */ + wrefresh(dialog); + } + break; + case 'G': /* Last page */ + case KEY_END: + + end_reached = 1; + /* Last page not in buffer? */ + if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer in dialog_textbox().\n"); + exit(-1); + } + if (fpos < file_size) { /* Yes, we have to read it in */ + if (lseek(fd, -BUF_SIZE, SEEK_END) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer in dialog_textbox().\n"); + exit(-1); + } + if ((bytes_read = + read(fd, buf, BUF_SIZE)) == -1) { + endwin(); + fprintf(stderr, "\nError reading file in dialog_textbox().\n"); + exit(-1); + } + buf[bytes_read] = '\0'; + } + page = buf + bytes_read; + back_lines(height - 4); + print_page(text, height - 4, width - 2); + print_position(dialog, height, width); + wmove(dialog, cur_y, cur_x); /* Restore cursor position */ + wrefresh(dialog); + break; + case 'K': /* Previous line */ + case 'k': + case KEY_UP: + if (!begin_reached) { + back_lines(page_length + 1); + + /* We don't call print_page() here but use scrolling to ensure + faster screen update. However, 'end_reached' and + 'page_length' should still be updated, and 'page' should + point to start of next page. This is done by calling + get_line() in the following 'for' loop. */ + scrollok(text, TRUE); + wscrl(text, -1); /* Scroll text region down one line */ + scrollok(text, FALSE); + page_length = 0; + passed_end = 0; + for (i = 0; i < height - 4; i++) { + if (!i) { + /* print first line of page */ + print_line(text, 0, width - 2); + wnoutrefresh(text); + } else + /* Called to update 'end_reached' and 'page' */ + get_line(); + if (!passed_end) + page_length++; + if (end_reached && !passed_end) + passed_end = 1; + } + + print_position(dialog, height, width); + wmove(dialog, cur_y, cur_x); /* Restore cursor position */ + wrefresh(dialog); + } + break; + case 'B': /* Previous page */ + case 'b': + case KEY_PPAGE: + if (begin_reached) + break; + back_lines(page_length + height - 4); + print_page(text, height - 4, width - 2); + print_position(dialog, height, width); + wmove(dialog, cur_y, cur_x); + wrefresh(dialog); + break; + case 'J': /* Next line */ + case 'j': + case KEY_DOWN: + if (!end_reached) { + begin_reached = 0; + scrollok(text, TRUE); + scroll(text); /* Scroll text region up one line */ + scrollok(text, FALSE); + print_line(text, height - 5, width - 2); + wnoutrefresh(text); + print_position(dialog, height, width); + wmove(dialog, cur_y, cur_x); /* Restore cursor position */ + wrefresh(dialog); + } + break; + case KEY_NPAGE: /* Next page */ + case ' ': + if (end_reached) + break; + + begin_reached = 0; + print_page(text, height - 4, width - 2); + print_position(dialog, height, width); + wmove(dialog, cur_y, cur_x); + wrefresh(dialog); + break; + case '0': /* Beginning of line */ + case 'H': /* Scroll left */ + case 'h': + case KEY_LEFT: + if (hscroll <= 0) + break; + + if (key == '0') + hscroll = 0; + else + hscroll--; + /* Reprint current page to scroll horizontally */ + back_lines(page_length); + print_page(text, height - 4, width - 2); + wmove(dialog, cur_y, cur_x); + wrefresh(dialog); + break; + case 'L': /* Scroll right */ + case 'l': + case KEY_RIGHT: + if (hscroll >= MAX_LEN) + break; + hscroll++; + /* Reprint current page to scroll horizontally */ + back_lines(page_length); + print_page(text, height - 4, width - 2); + wmove(dialog, cur_y, cur_x); + wrefresh(dialog); + break; + case ESC: + break; + } + } + + delwin(dialog); + free(buf); + close(fd); + return -1; /* ESC pressed */ +} + +/* + * Go back 'n' lines in text file. Called by dialog_textbox(). + * 'page' will be updated to point to the desired line in 'buf'. + */ +static void back_lines(int n) +{ + int i, fpos; + + begin_reached = 0; + /* We have to distinguish between end_reached and !end_reached + since at end of file, the line is not ended by a '\n'. + The code inside 'if' basically does a '--page' to move one + character backward so as to skip '\n' of the previous line */ + if (!end_reached) { + /* Either beginning of buffer or beginning of file reached? */ + if (page == buf) { + if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer in " + "back_lines().\n"); + exit(-1); + } + if (fpos > bytes_read) { /* Not beginning of file yet */ + /* We've reached beginning of buffer, but not beginning of + file yet, so read previous part of file into buffer. + Note that we only move backward for BUF_SIZE/2 bytes, + but not BUF_SIZE bytes to avoid re-reading again in + print_page() later */ + /* Really possible to move backward BUF_SIZE/2 bytes? */ + if (fpos < BUF_SIZE / 2 + bytes_read) { + /* No, move less then */ + if (lseek(fd, 0, SEEK_SET) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer in " + "back_lines().\n"); + exit(-1); + } + page = buf + fpos - bytes_read; + } else { /* Move backward BUF_SIZE/2 bytes */ + if (lseek (fd, -(BUF_SIZE / 2 + bytes_read), SEEK_CUR) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer " + "in back_lines().\n"); + exit(-1); + } + page = buf + BUF_SIZE / 2; + } + if ((bytes_read = + read(fd, buf, BUF_SIZE)) == -1) { + endwin(); + fprintf(stderr, "\nError reading file in back_lines().\n"); + exit(-1); + } + buf[bytes_read] = '\0'; + } else { /* Beginning of file reached */ + begin_reached = 1; + return; + } + } + if (*(--page) != '\n') { /* '--page' here */ + /* Something's wrong... */ + endwin(); + fprintf(stderr, "\nInternal error in back_lines().\n"); + exit(-1); + } + } + /* Go back 'n' lines */ + for (i = 0; i < n; i++) + do { + if (page == buf) { + if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer in back_lines().\n"); + exit(-1); + } + if (fpos > bytes_read) { + /* Really possible to move backward BUF_SIZE/2 bytes? */ + if (fpos < BUF_SIZE / 2 + bytes_read) { + /* No, move less then */ + if (lseek(fd, 0, SEEK_SET) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer " + "in back_lines().\n"); + exit(-1); + } + page = buf + fpos - bytes_read; + } else { /* Move backward BUF_SIZE/2 bytes */ + if (lseek (fd, -(BUF_SIZE / 2 + bytes_read), SEEK_CUR) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer" + " in back_lines().\n"); + exit(-1); + } + page = buf + BUF_SIZE / 2; + } + if ((bytes_read = + read(fd, buf, BUF_SIZE)) == -1) { + endwin(); + fprintf(stderr, "\nError reading file in " + "back_lines().\n"); + exit(-1); + } + buf[bytes_read] = '\0'; + } else { /* Beginning of file reached */ + begin_reached = 1; + return; + } + } + } while (*(--page) != '\n'); + page++; +} + +/* + * Print a new page of text. Called by dialog_textbox(). + */ +static void print_page(WINDOW * win, int height, int width) +{ + int i, passed_end = 0; + + page_length = 0; + for (i = 0; i < height; i++) { + print_line(win, i, width); + if (!passed_end) + page_length++; + if (end_reached && !passed_end) + passed_end = 1; + } + wnoutrefresh(win); +} + +/* + * Print a new line of text. Called by dialog_textbox() and print_page(). + */ +static void print_line(WINDOW * win, int row, int width) +{ + int y, x; + char *line; + + line = get_line(); + line += MIN(strlen(line), hscroll); /* Scroll horizontally */ + wmove(win, row, 0); /* move cursor to correct line */ + waddch(win, ' '); + waddnstr(win, line, MIN(strlen(line), width - 2)); + + getyx(win, y, x); + /* Clear 'residue' of previous line */ +#if OLD_NCURSES + { + int i; + for (i = 0; i < width - x; i++) + waddch(win, ' '); + } +#else + wclrtoeol(win); +#endif +} + +/* + * Return current line of text. Called by dialog_textbox() and print_line(). + * 'page' should point to start of current line before calling, and will be + * updated to point to start of next line. + */ +static char *get_line(void) +{ + int i = 0, fpos; + static char line[MAX_LEN + 1]; + + end_reached = 0; + while (*page != '\n') { + if (*page == '\0') { + /* Either end of file or end of buffer reached */ + if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer in " + "get_line().\n"); + exit(-1); + } + if (fpos < file_size) { /* Not end of file yet */ + /* We've reached end of buffer, but not end of file yet, + so read next part of file into buffer */ + if ((bytes_read = + read(fd, buf, BUF_SIZE)) == -1) { + endwin(); + fprintf(stderr, "\nError reading file in get_line().\n"); + exit(-1); + } + buf[bytes_read] = '\0'; + page = buf; + } else { + if (!end_reached) + end_reached = 1; + break; + } + } else if (i < MAX_LEN) + line[i++] = *(page++); + else { + /* Truncate lines longer than MAX_LEN characters */ + if (i == MAX_LEN) + line[i++] = '\0'; + page++; + } + } + if (i <= MAX_LEN) + line[i] = '\0'; + if (!end_reached) + page++; /* move pass '\n' */ + + return line; +} + +/* + * Print current position + */ +static void print_position(WINDOW * win, int height, int width) +{ + int fpos, percent; + + if ((fpos = lseek(fd, 0, SEEK_CUR)) == -1) { + endwin(); + fprintf(stderr, "\nError moving file pointer in print_position().\n"); + exit(-1); + } + wattrset(win, position_indicator_attr); + wbkgdset(win, position_indicator_attr & A_COLOR); + percent = !file_size ? + 100 : ((fpos - bytes_read + page - buf) * 100) / file_size; + wmove(win, height - 3, width - 9); + wprintw(win, "(%3d%%)", percent); +} diff --git a/scripts/kconfig/lxdialog/util.c b/scripts/kconfig/lxdialog/util.c new file mode 100644 index 000000000..f82cebb9f --- /dev/null +++ b/scripts/kconfig/lxdialog/util.c @@ -0,0 +1,362 @@ +/* + * util.c + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + * + * 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. + */ + +#include "dialog.h" + +/* use colors by default? */ +bool use_colors = 1; + +const char *backtitle = NULL; + +/* + * Attribute values, default is for mono display + */ +chtype attributes[] = { + A_NORMAL, /* screen_attr */ + A_NORMAL, /* shadow_attr */ + A_NORMAL, /* dialog_attr */ + A_BOLD, /* title_attr */ + A_NORMAL, /* border_attr */ + A_REVERSE, /* button_active_attr */ + A_DIM, /* button_inactive_attr */ + A_REVERSE, /* button_key_active_attr */ + A_BOLD, /* button_key_inactive_attr */ + A_REVERSE, /* button_label_active_attr */ + A_NORMAL, /* button_label_inactive_attr */ + A_NORMAL, /* inputbox_attr */ + A_NORMAL, /* inputbox_border_attr */ + A_NORMAL, /* searchbox_attr */ + A_BOLD, /* searchbox_title_attr */ + A_NORMAL, /* searchbox_border_attr */ + A_BOLD, /* position_indicator_attr */ + A_NORMAL, /* menubox_attr */ + A_NORMAL, /* menubox_border_attr */ + A_NORMAL, /* item_attr */ + A_REVERSE, /* item_selected_attr */ + A_BOLD, /* tag_attr */ + A_REVERSE, /* tag_selected_attr */ + A_BOLD, /* tag_key_attr */ + A_REVERSE, /* tag_key_selected_attr */ + A_BOLD, /* check_attr */ + A_REVERSE, /* check_selected_attr */ + A_BOLD, /* uarrow_attr */ + A_BOLD /* darrow_attr */ +}; + +#include "colors.h" + +/* + * Table of color values + */ +int color_table[][3] = { + {SCREEN_FG, SCREEN_BG, SCREEN_HL}, + {SHADOW_FG, SHADOW_BG, SHADOW_HL}, + {DIALOG_FG, DIALOG_BG, DIALOG_HL}, + {TITLE_FG, TITLE_BG, TITLE_HL}, + {BORDER_FG, BORDER_BG, BORDER_HL}, + {BUTTON_ACTIVE_FG, BUTTON_ACTIVE_BG, BUTTON_ACTIVE_HL}, + {BUTTON_INACTIVE_FG, BUTTON_INACTIVE_BG, BUTTON_INACTIVE_HL}, + {BUTTON_KEY_ACTIVE_FG, BUTTON_KEY_ACTIVE_BG, BUTTON_KEY_ACTIVE_HL}, + {BUTTON_KEY_INACTIVE_FG, BUTTON_KEY_INACTIVE_BG, + BUTTON_KEY_INACTIVE_HL}, + {BUTTON_LABEL_ACTIVE_FG, BUTTON_LABEL_ACTIVE_BG, + BUTTON_LABEL_ACTIVE_HL}, + {BUTTON_LABEL_INACTIVE_FG, BUTTON_LABEL_INACTIVE_BG, + BUTTON_LABEL_INACTIVE_HL}, + {INPUTBOX_FG, INPUTBOX_BG, INPUTBOX_HL}, + {INPUTBOX_BORDER_FG, INPUTBOX_BORDER_BG, INPUTBOX_BORDER_HL}, + {SEARCHBOX_FG, SEARCHBOX_BG, SEARCHBOX_HL}, + {SEARCHBOX_TITLE_FG, SEARCHBOX_TITLE_BG, SEARCHBOX_TITLE_HL}, + {SEARCHBOX_BORDER_FG, SEARCHBOX_BORDER_BG, SEARCHBOX_BORDER_HL}, + {POSITION_INDICATOR_FG, POSITION_INDICATOR_BG, POSITION_INDICATOR_HL}, + {MENUBOX_FG, MENUBOX_BG, MENUBOX_HL}, + {MENUBOX_BORDER_FG, MENUBOX_BORDER_BG, MENUBOX_BORDER_HL}, + {ITEM_FG, ITEM_BG, ITEM_HL}, + {ITEM_SELECTED_FG, ITEM_SELECTED_BG, ITEM_SELECTED_HL}, + {TAG_FG, TAG_BG, TAG_HL}, + {TAG_SELECTED_FG, TAG_SELECTED_BG, TAG_SELECTED_HL}, + {TAG_KEY_FG, TAG_KEY_BG, TAG_KEY_HL}, + {TAG_KEY_SELECTED_FG, TAG_KEY_SELECTED_BG, TAG_KEY_SELECTED_HL}, + {CHECK_FG, CHECK_BG, CHECK_HL}, + {CHECK_SELECTED_FG, CHECK_SELECTED_BG, CHECK_SELECTED_HL}, + {UARROW_FG, UARROW_BG, UARROW_HL}, + {DARROW_FG, DARROW_BG, DARROW_HL}, +}; /* color_table */ + +/* + * Set window to attribute 'attr' + */ +void attr_clear(WINDOW * win, int height, int width, chtype attr) +{ + int i, j; + + wattrset(win, attr); + for (i = 0; i < height; i++) { + wmove(win, i, 0); + for (j = 0; j < width; j++) + waddch(win, ' '); + } + touchwin(win); +} + +void dialog_clear(void) +{ + attr_clear(stdscr, LINES, COLS, screen_attr); + /* Display background title if it exists ... - SLH */ + if (backtitle != NULL) { + int i; + + wattrset(stdscr, screen_attr); + mvwaddstr(stdscr, 0, 1, (char *)backtitle); + wmove(stdscr, 1, 1); + for (i = 1; i < COLS - 1; i++) + waddch(stdscr, ACS_HLINE); + } + wnoutrefresh(stdscr); +} + +/* + * Do some initialization for dialog + */ +void init_dialog(void) +{ + initscr(); /* Init curses */ + keypad(stdscr, TRUE); + cbreak(); + noecho(); + + if (use_colors) /* Set up colors */ + color_setup(); + + dialog_clear(); +} + +/* + * Setup for color display + */ +void color_setup(void) +{ + int i; + + if (has_colors()) { /* Terminal supports color? */ + start_color(); + + /* Initialize color pairs */ + for (i = 0; i < ATTRIBUTE_COUNT; i++) + init_pair(i + 1, color_table[i][0], color_table[i][1]); + + /* Setup color attributes */ + for (i = 0; i < ATTRIBUTE_COUNT; i++) + attributes[i] = C_ATTR(color_table[i][2], i + 1); + } +} + +/* + * End using dialog functions. + */ +void end_dialog(void) +{ + endwin(); +} + +/* Print the title of the dialog. Center the title and truncate + * tile if wider than dialog (- 2 chars). + **/ +void print_title(WINDOW *dialog, const char *title, int width) +{ + if (title) { + int tlen = MIN(width - 2, strlen(title)); + wattrset(dialog, title_attr); + mvwaddch(dialog, 0, (width - tlen) / 2 - 1, ' '); + mvwaddnstr(dialog, 0, (width - tlen)/2, title, tlen); + waddch(dialog, ' '); + } +} + +/* + * Print a string of text in a window, automatically wrap around to the + * next line if the string is too long to fit on one line. Newline + * characters '\n' are replaced by spaces. We start on a new line + * if there is no room for at least 4 nonblanks following a double-space. + */ +void print_autowrap(WINDOW * win, const char *prompt, int width, int y, int x) +{ + int newl, cur_x, cur_y; + int i, prompt_len, room, wlen; + char tempstr[MAX_LEN + 1], *word, *sp, *sp2; + + strcpy(tempstr, prompt); + + prompt_len = strlen(tempstr); + + /* + * Remove newlines + */ + for (i = 0; i < prompt_len; i++) { + if (tempstr[i] == '\n') + tempstr[i] = ' '; + } + + if (prompt_len <= width - x * 2) { /* If prompt is short */ + wmove(win, y, (width - prompt_len) / 2); + waddstr(win, tempstr); + } else { + cur_x = x; + cur_y = y; + newl = 1; + word = tempstr; + while (word && *word) { + sp = index(word, ' '); + if (sp) + *sp++ = 0; + + /* Wrap to next line if either the word does not fit, + or it is the first word of a new sentence, and it is + short, and the next word does not fit. */ + room = width - cur_x; + wlen = strlen(word); + if (wlen > room || + (newl && wlen < 4 && sp + && wlen + 1 + strlen(sp) > room + && (!(sp2 = index(sp, ' ')) + || wlen + 1 + (sp2 - sp) > room))) { + cur_y++; + cur_x = x; + } + wmove(win, cur_y, cur_x); + waddstr(win, word); + getyx(win, cur_y, cur_x); + cur_x++; + if (sp && *sp == ' ') { + cur_x++; /* double space */ + while (*++sp == ' ') ; + newl = 1; + } else + newl = 0; + word = sp; + } + } +} + +/* + * Print a button + */ +void print_button(WINDOW * win, const char *label, int y, int x, int selected) +{ + int i, temp; + + wmove(win, y, x); + wattrset(win, selected ? button_active_attr : button_inactive_attr); + waddstr(win, "<"); + temp = strspn(label, " "); + label += temp; + wattrset(win, selected ? button_label_active_attr + : button_label_inactive_attr); + for (i = 0; i < temp; i++) + waddch(win, ' '); + wattrset(win, selected ? button_key_active_attr + : button_key_inactive_attr); + waddch(win, label[0]); + wattrset(win, selected ? button_label_active_attr + : button_label_inactive_attr); + waddstr(win, (char *)label + 1); + wattrset(win, selected ? button_active_attr : button_inactive_attr); + waddstr(win, ">"); + wmove(win, y, x + temp + 1); +} + +/* + * Draw a rectangular box with line drawing characters + */ +void +draw_box(WINDOW * win, int y, int x, int height, int width, + chtype box, chtype border) +{ + int i, j; + + wattrset(win, 0); + for (i = 0; i < height; i++) { + wmove(win, y + i, x); + for (j = 0; j < width; j++) + if (!i && !j) + waddch(win, border | ACS_ULCORNER); + else if (i == height - 1 && !j) + waddch(win, border | ACS_LLCORNER); + else if (!i && j == width - 1) + waddch(win, box | ACS_URCORNER); + else if (i == height - 1 && j == width - 1) + waddch(win, box | ACS_LRCORNER); + else if (!i) + waddch(win, border | ACS_HLINE); + else if (i == height - 1) + waddch(win, box | ACS_HLINE); + else if (!j) + waddch(win, border | ACS_VLINE); + else if (j == width - 1) + waddch(win, box | ACS_VLINE); + else + waddch(win, box | ' '); + } +} + +/* + * Draw shadows along the right and bottom edge to give a more 3D look + * to the boxes + */ +void draw_shadow(WINDOW * win, int y, int x, int height, int width) +{ + int i; + + if (has_colors()) { /* Whether terminal supports color? */ + wattrset(win, shadow_attr); + wmove(win, y + height, x + 2); + for (i = 0; i < width; i++) + waddch(win, winch(win) & A_CHARTEXT); + for (i = y + 1; i < y + height + 1; i++) { + wmove(win, i, x + width); + waddch(win, winch(win) & A_CHARTEXT); + waddch(win, winch(win) & A_CHARTEXT); + } + wnoutrefresh(win); + } +} + +/* + * Return the position of the first alphabetic character in a string. + */ +int first_alpha(const char *string, const char *exempt) +{ + int i, in_paren = 0, c; + + for (i = 0; i < strlen(string); i++) { + c = tolower(string[i]); + + if (strchr("<[(", c)) + ++in_paren; + if (strchr(">])", c) && in_paren > 0) + --in_paren; + + if ((!in_paren) && isalpha(c) && strchr(exempt, c) == 0) + return i; + } + + return 0; +} diff --git a/scripts/kconfig/lxdialog/yesno.c b/scripts/kconfig/lxdialog/yesno.c new file mode 100644 index 000000000..cb2568aae --- /dev/null +++ b/scripts/kconfig/lxdialog/yesno.c @@ -0,0 +1,102 @@ +/* + * yesno.c -- implements the yes/no box + * + * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) + * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) + * + * 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. + */ + +#include "dialog.h" + +/* + * Display termination buttons + */ +static void print_buttons(WINDOW * dialog, int height, int width, int selected) +{ + int x = width / 2 - 10; + int y = height - 2; + + print_button(dialog, " Yes ", y, x, selected == 0); + print_button(dialog, " No ", y, x + 13, selected == 1); + + wmove(dialog, y, x + 1 + 13 * selected); + wrefresh(dialog); +} + +/* + * Display a dialog box with two buttons - Yes and No + */ +int dialog_yesno(const char *title, const char *prompt, int height, int width) +{ + int i, x, y, key = 0, button = 0; + WINDOW *dialog; + + /* center dialog box on screen */ + x = (COLS - width) / 2; + y = (LINES - height) / 2; + + draw_shadow(stdscr, y, x, height, width); + + dialog = newwin(height, width, y, x); + keypad(dialog, TRUE); + + draw_box(dialog, 0, 0, height, width, dialog_attr, border_attr); + wattrset(dialog, border_attr); + mvwaddch(dialog, height - 3, 0, ACS_LTEE); + for (i = 0; i < width - 2; i++) + waddch(dialog, ACS_HLINE); + wattrset(dialog, dialog_attr); + waddch(dialog, ACS_RTEE); + + print_title(dialog, title, width); + + wattrset(dialog, dialog_attr); + print_autowrap(dialog, prompt, width - 2, 1, 3); + + print_buttons(dialog, height, width, 0); + + while (key != ESC) { + key = wgetch(dialog); + switch (key) { + case 'Y': + case 'y': + delwin(dialog); + return 0; + case 'N': + case 'n': + delwin(dialog); + return 1; + + case TAB: + case KEY_LEFT: + case KEY_RIGHT: + button = ((key == KEY_LEFT ? --button : ++button) < 0) ? 1 : (button > 1 ? 0 : button); + + print_buttons(dialog, height, width, button); + wrefresh(dialog); + break; + case ' ': + case '\n': + delwin(dialog); + return button; + case ESC: + break; + } + } + + delwin(dialog); + return -1; /* ESC pressed */ +} diff --git a/scripts/kconfig/mconf.c b/scripts/kconfig/mconf.c new file mode 100644 index 000000000..31a1f4cd9 --- /dev/null +++ b/scripts/kconfig/mconf.c @@ -0,0 +1,1098 @@ +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + * Released under the terms of the GNU GPL v2.0. + * + * Introduced single menu mode (show all sub-menus in one large tree). + * 2002-11-06 Petr Baudis <pasky@ucw.cz> + * + * i18n, 2005, Arnaldo Carvalho de Melo <acme@conectiva.com.br> + */ + +#include <sys/ioctl.h> +#include <sys/wait.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <signal.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <unistd.h> +#include <locale.h> + +#define LKC_DIRECT_LINK +#include "lkc.h" + +static char menu_backtitle[128]; +static const char mconf_readme[] = N_( +"Overview\n" +"--------\n" +"Some kernel features may be built directly into the kernel.\n" +"Some may be made into loadable runtime modules. Some features\n" +"may be completely removed altogether. There are also certain\n" +"kernel parameters which are not really features, but must be\n" +"entered in as decimal or hexadecimal numbers or possibly text.\n" +"\n" +"Menu items beginning with [*], <M> or [ ] represent features\n" +"configured to be built in, modularized or removed respectively.\n" +"Pointed brackets <> represent module capable features.\n" +"\n" +"To change any of these features, highlight it with the cursor\n" +"keys and press <Y> to build it in, <M> to make it a module or\n" +"<N> to removed it. You may also press the <Space Bar> to cycle\n" +"through the available options (ie. Y->N->M->Y).\n" +"\n" +"Some additional keyboard hints:\n" +"\n" +"Menus\n" +"----------\n" +"o Use the Up/Down arrow keys (cursor keys) to highlight the item\n" +" you wish to change or submenu wish to select and press <Enter>.\n" +" Submenus are designated by \"--->\".\n" +"\n" +" Shortcut: Press the option's highlighted letter (hotkey).\n" +" Pressing a hotkey more than once will sequence\n" +" through all visible items which use that hotkey.\n" +"\n" +" You may also use the <PAGE UP> and <PAGE DOWN> keys to scroll\n" +" unseen options into view.\n" +"\n" +"o To exit a menu use the cursor keys to highlight the <Exit> button\n" +" and press <ENTER>.\n" +"\n" +" Shortcut: Press <ESC><ESC> or <E> or <X> if there is no hotkey\n" +" using those letters. You may press a single <ESC>, but\n" +" there is a delayed response which you may find annoying.\n" +"\n" +" Also, the <TAB> and cursor keys will cycle between <Select>,\n" +" <Exit> and <Help>\n" +"\n" +"o To get help with an item, use the cursor keys to highlight <Help>\n" +" and Press <ENTER>.\n" +"\n" +" Shortcut: Press <H> or <?>.\n" +"\n" +"\n" +"Radiolists (Choice lists)\n" +"-----------\n" +"o Use the cursor keys to select the option you wish to set and press\n" +" <S> or the <SPACE BAR>.\n" +"\n" +" Shortcut: Press the first letter of the option you wish to set then\n" +" press <S> or <SPACE BAR>.\n" +"\n" +"o To see available help for the item, use the cursor keys to highlight\n" +" <Help> and Press <ENTER>.\n" +"\n" +" Shortcut: Press <H> or <?>.\n" +"\n" +" Also, the <TAB> and cursor keys will cycle between <Select> and\n" +" <Help>\n" +"\n" +"\n" +"Data Entry\n" +"-----------\n" +"o Enter the requested information and press <ENTER>\n" +" If you are entering hexadecimal values, it is not necessary to\n" +" add the '0x' prefix to the entry.\n" +"\n" +"o For help, use the <TAB> or cursor keys to highlight the help option\n" +" and press <ENTER>. You can try <TAB><H> as well.\n" +"\n" +"\n" +"Text Box (Help Window)\n" +"--------\n" +"o Use the cursor keys to scroll up/down/left/right. The VI editor\n" +" keys h,j,k,l function here as do <SPACE BAR> and <B> for those\n" +" who are familiar with less and lynx.\n" +"\n" +"o Press <E>, <X>, <Enter> or <Esc><Esc> to exit.\n" +"\n" +"\n" +"Alternate Configuration Files\n" +"-----------------------------\n" +"Menuconfig supports the use of alternate configuration files for\n" +"those who, for various reasons, find it necessary to switch\n" +"between different busybox configurations.\n" +"\n" +"At the end of the main menu you will find two options. One is\n" +"for saving the current configuration to a file of your choosing.\n" +"The other option is for loading a previously saved alternate\n" +"configuration.\n" +"\n" +"Even if you don't use alternate configuration files, but you\n" +"find during a Menuconfig session that you have completely messed\n" +"up your settings, you may use the \"Load Alternate...\" option to\n" +"restore your previously saved settings from \".config\" without\n" +"restarting Menuconfig.\n" +"\n" +"Other information\n" +"-----------------\n" +"If you use Menuconfig in an XTERM window make sure you have your\n" +"$TERM variable set to point to a xterm definition which supports color.\n" +"Otherwise, Menuconfig will look rather bad. Menuconfig will not\n" +"display correctly in a RXVT window because rxvt displays only one\n" +"intensity of color, bright.\n" +"\n" +"Menuconfig will display larger menus on screens or xterms which are\n" +"set to display more than the standard 25 row by 80 column geometry.\n" +"In order for this to work, the \"stty size\" command must be able to\n" +"display the screen's current row and column geometry. I STRONGLY\n" +"RECOMMEND that you make sure you do NOT have the shell variables\n" +"LINES and COLUMNS exported into your environment. Some distributions\n" +"export those variables via /etc/profile. Some ncurses programs can\n" +"become confused when those variables (LINES & COLUMNS) don't reflect\n" +"the true screen size.\n" +"\n" +"Optional personality available\n" +"------------------------------\n" +"If you prefer to have all of the kernel options listed in a single\n" +"menu, rather than the default multimenu hierarchy, run the menuconfig\n" +"with MENUCONFIG_MODE environment variable set to single_menu. Example:\n" +"\n" +"make MENUCONFIG_MODE=single_menu menuconfig\n" +"\n" +"<Enter> will then unroll the appropriate category, or enfold it if it\n" +"is already unrolled.\n" +"\n" +"Note that this mode can eventually be a little more CPU expensive\n" +"(especially with a larger number of unrolled categories) than the\n" +"default mode.\n"), +menu_instructions[] = N_( + "Arrow keys navigate the menu. " + "<Enter> selects submenus --->. " + "Highlighted letters are hotkeys. " + "Pressing <Y> includes, <N> excludes, <M> modularizes features. " + "Press <Esc><Esc> to exit, <?> for Help, </> for Search. " + "Legend: [*] built-in [ ] excluded <M> module < > module capable"), +radiolist_instructions[] = N_( + "Use the arrow keys to navigate this window or " + "press the hotkey of the item you wish to select " + "followed by the <SPACE BAR>. " + "Press <?> for additional information about this option."), +inputbox_instructions_int[] = N_( + "Please enter a decimal value. " + "Fractions will not be accepted. " + "Use the <TAB> key to move from the input field to the buttons below it."), +inputbox_instructions_hex[] = N_( + "Please enter a hexadecimal value. " + "Use the <TAB> key to move from the input field to the buttons below it."), +inputbox_instructions_string[] = N_( + "Please enter a string value. " + "Use the <TAB> key to move from the input field to the buttons below it."), +setmod_text[] = N_( + "This feature depends on another which has been configured as a module.\n" + "As a result, this feature will be built as a module."), +nohelp_text[] = N_( + "There is no help available for this kernel option.\n"), +load_config_text[] = N_( + "Enter the name of the configuration file you wish to load. " + "Accept the name shown to restore the configuration you " + "last retrieved. Leave blank to abort."), +load_config_help[] = N_( + "\n" + "For various reasons, one may wish to keep several different kernel\n" + "configurations available on a single machine.\n" + "\n" + "If you have saved a previous configuration in a file other than the\n" + "kernel's default, entering the name of the file here will allow you\n" + "to modify that configuration.\n" + "\n" + "If you are uncertain, then you have probably never used alternate\n" + "configuration files. You should therefor leave this blank to abort.\n"), +save_config_text[] = N_( + "Enter a filename to which this configuration should be saved " + "as an alternate. Leave blank to abort."), +save_config_help[] = N_( + "\n" + "For various reasons, one may wish to keep different kernel\n" + "configurations available on a single machine.\n" + "\n" + "Entering a file name here will allow you to later retrieve, modify\n" + "and use the current configuration as an alternate to whatever\n" + "configuration options you have selected at that time.\n" + "\n" + "If you are uncertain what all this means then you should probably\n" + "leave this blank.\n"), +search_help[] = N_( + "\n" + "Search for CONFIG_ symbols and display their relations.\n" + "Regular expressions are allowed.\n" + "Example: search for \"^FOO\"\n" + "Result:\n" + "-----------------------------------------------------------------\n" + "Symbol: FOO [=m]\n" + "Prompt: Foo bus is used to drive the bar HW\n" + "Defined at drivers/pci/Kconfig:47\n" + "Depends on: X86_LOCAL_APIC && X86_IO_APIC || IA64\n" + "Location:\n" + " -> Bus options (PCI, PCMCIA, EISA, MCA, ISA)\n" + " -> PCI support (PCI [=y])\n" + " -> PCI access mode (<choice> [=y])\n" + "Selects: LIBCRC32\n" + "Selected by: BAR\n" + "-----------------------------------------------------------------\n" + "o The line 'Prompt:' shows the text used in the menu structure for\n" + " this CONFIG_ symbol\n" + "o The 'Defined at' line tell at what file / line number the symbol\n" + " is defined\n" + "o The 'Depends on:' line tell what symbols needs to be defined for\n" + " this symbol to be visible in the menu (selectable)\n" + "o The 'Location:' lines tell where in the menu structure this symbol\n" + " is located\n" + " A location followed by a [=y] indicate that this is a selectable\n" + " menu item - and current value is displayed inside brackets.\n" + "o The 'Selects:' line tell what symbol will be automatically\n" + " selected if this symbol is selected (y or m)\n" + "o The 'Selected by' line tell what symbol has selected this symbol\n" + "\n" + "Only relevant lines are shown.\n" + "\n\n" + "Search examples:\n" + "Examples: USB => find all CONFIG_ symbols containing USB\n" + " ^USB => find all CONFIG_ symbols starting with USB\n" + " USB$ => find all CONFIG_ symbols ending with USB\n" + "\n"); + +static char buf[4096], *bufptr = buf; +static char input_buf[4096]; +static char filename[PATH_MAX+1] = ".config"; +static char *args[1024], **argptr = args; +static int indent; +static struct termios ios_org; +static int rows = 0, cols = 0; +static struct menu *current_menu; +static int child_count; +static int do_resize; +static int single_menu_mode; + +static void conf(struct menu *menu); +static void conf_choice(struct menu *menu); +static void conf_string(struct menu *menu); +static void conf_load(void); +static void conf_save(void); +static void show_textbox(const char *title, const char *text, int r, int c); +static void show_helptext(const char *title, const char *text); +static void show_help(struct menu *menu); +static void show_file(const char *filename, const char *title, int r, int c); + +static void cprint_init(void); +static int cprint1(const char *fmt, ...); +static void cprint_done(void); +static int cprint(const char *fmt, ...); + +static void init_wsize(void) +{ + struct winsize ws; + char *env; + + if (!ioctl(STDIN_FILENO, TIOCGWINSZ, &ws)) { + rows = ws.ws_row; + cols = ws.ws_col; + } + + if (!rows) { + env = getenv("LINES"); + if (env) + rows = atoi(env); + if (!rows) + rows = 24; + } + if (!cols) { + env = getenv("COLUMNS"); + if (env) + cols = atoi(env); + if (!cols) + cols = 80; + } + + if (rows < 19 || cols < 80) { + fprintf(stderr, N_("Your display is too small to run Menuconfig!\n")); + fprintf(stderr, N_("It must be at least 19 lines by 80 columns.\n")); + exit(1); + } + + rows -= 4; + cols -= 5; +} + +static void cprint_init(void) +{ + bufptr = buf; + argptr = args; + memset(args, 0, sizeof(args)); + indent = 0; + child_count = 0; + cprint("./scripts/kconfig/lxdialog/lxdialog"); + cprint("--backtitle"); + cprint(menu_backtitle); +} + +static int cprint1(const char *fmt, ...) +{ + va_list ap; + int res; + + if (!*argptr) + *argptr = bufptr; + va_start(ap, fmt); + res = vsprintf(bufptr, fmt, ap); + va_end(ap); + bufptr += res; + + return res; +} + +static void cprint_done(void) +{ + *bufptr++ = 0; + argptr++; +} + +static int cprint(const char *fmt, ...) +{ + va_list ap; + int res; + + *argptr++ = bufptr; + va_start(ap, fmt); + res = vsprintf(bufptr, fmt, ap); + va_end(ap); + bufptr += res; + *bufptr++ = 0; + + return res; +} + +static void get_prompt_str(struct gstr *r, struct property *prop) +{ + int i, j; + struct menu *submenu[8], *menu; + + str_printf(r, "Prompt: %s\n", prop->text); + str_printf(r, " Defined at %s:%d\n", prop->menu->file->name, + prop->menu->lineno); + if (!expr_is_yes(prop->visible.expr)) { + str_append(r, " Depends on: "); + expr_gstr_print(prop->visible.expr, r); + str_append(r, "\n"); + } + menu = prop->menu->parent; + for (i = 0; menu != &rootmenu && i < 8; menu = menu->parent) + submenu[i++] = menu; + if (i > 0) { + str_printf(r, " Location:\n"); + for (j = 4; --i >= 0; j += 2) { + menu = submenu[i]; + str_printf(r, "%*c-> %s", j, ' ', menu_get_prompt(menu)); + if (menu->sym) { + str_printf(r, " (%s [=%s])", menu->sym->name ? + menu->sym->name : "<choice>", + sym_get_string_value(menu->sym)); + } + str_append(r, "\n"); + } + } +} + +static void get_symbol_str(struct gstr *r, struct symbol *sym) +{ + bool hit; + struct property *prop; + + str_printf(r, "Symbol: %s [=%s]\n", sym->name, + sym_get_string_value(sym)); + for_all_prompts(sym, prop) + get_prompt_str(r, prop); + hit = false; + for_all_properties(sym, prop, P_SELECT) { + if (!hit) { + str_append(r, " Selects: "); + hit = true; + } else + str_printf(r, " && "); + expr_gstr_print(prop->expr, r); + } + if (hit) + str_append(r, "\n"); + if (sym->rev_dep.expr) { + str_append(r, " Selected by: "); + expr_gstr_print(sym->rev_dep.expr, r); + str_append(r, "\n"); + } + str_append(r, "\n\n"); +} + +static struct gstr get_relations_str(struct symbol **sym_arr) +{ + struct symbol *sym; + struct gstr res = str_new(); + int i; + + for (i = 0; sym_arr && (sym = sym_arr[i]); i++) + get_symbol_str(&res, sym); + if (!i) + str_append(&res, "No matches found.\n"); + return res; +} + +pid_t pid; + +static void winch_handler(int sig) +{ + if (!do_resize) { + kill(pid, SIGINT); + do_resize = 1; + } +} + +static int exec_conf(void) +{ + int pipefd[2], stat, size; + struct sigaction sa; + sigset_t sset, osset; + + sigemptyset(&sset); + sigaddset(&sset, SIGINT); + sigprocmask(SIG_BLOCK, &sset, &osset); + + signal(SIGINT, SIG_DFL); + + sa.sa_handler = winch_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGWINCH, &sa, NULL); + + *argptr++ = NULL; + + pipe(pipefd); + pid = fork(); + if (pid == 0) { + sigprocmask(SIG_SETMASK, &osset, NULL); + dup2(pipefd[1], 2); + close(pipefd[0]); + close(pipefd[1]); + execv(args[0], args); + _exit(EXIT_FAILURE); + } + + close(pipefd[1]); + bufptr = input_buf; + while (1) { + size = input_buf + sizeof(input_buf) - bufptr; + size = read(pipefd[0], bufptr, size); + if (size <= 0) { + if (size < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + perror("read"); + } + break; + } + bufptr += size; + } + *bufptr++ = 0; + close(pipefd[0]); + waitpid(pid, &stat, 0); + + if (do_resize) { + init_wsize(); + do_resize = 0; + sigprocmask(SIG_SETMASK, &osset, NULL); + return -1; + } + if (WIFSIGNALED(stat)) { + printf("\finterrupted(%d)\n", WTERMSIG(stat)); + exit(1); + } +#if 0 + printf("\fexit state: %d\nexit data: '%s'\n", WEXITSTATUS(stat), input_buf); + sleep(1); +#endif + sigpending(&sset); + if (sigismember(&sset, SIGINT)) { + printf("\finterrupted\n"); + exit(1); + } + sigprocmask(SIG_SETMASK, &osset, NULL); + + return WEXITSTATUS(stat); +} + +static void search_conf(void) +{ + struct symbol **sym_arr; + int stat; + struct gstr res; + +again: + cprint_init(); + cprint("--title"); + cprint(_("Search Configuration Parameter")); + cprint("--inputbox"); + cprint(_("Enter CONFIG_ (sub)string to search for (omit CONFIG_)")); + cprint("10"); + cprint("75"); + cprint(""); + stat = exec_conf(); + if (stat < 0) + goto again; + switch (stat) { + case 0: + break; + case 1: + show_helptext(_("Search Configuration"), search_help); + goto again; + default: + return; + } + + sym_arr = sym_re_search(input_buf); + res = get_relations_str(sym_arr); + free(sym_arr); + show_textbox(_("Search Results"), str_get(&res), 0, 0); + str_free(&res); +} + +static void build_conf(struct menu *menu) +{ + struct symbol *sym; + struct property *prop; + struct menu *child; + int type, tmp, doint = 2; + tristate val; + char ch; + + if (!menu_is_visible(menu)) + return; + + sym = menu->sym; + prop = menu->prompt; + if (!sym) { + if (prop && menu != current_menu) { + const char *prompt = menu_get_prompt(menu); + switch (prop->type) { + case P_MENU: + child_count++; + cprint("m%p", menu); + + if (single_menu_mode) { + cprint1("%s%*c%s", + menu->data ? "-->" : "++>", + indent + 1, ' ', prompt); + } else + cprint1(" %*c%s --->", indent + 1, ' ', prompt); + + cprint_done(); + if (single_menu_mode && menu->data) + goto conf_childs; + return; + default: + if (prompt) { + child_count++; + cprint(":%p", menu); + cprint("---%*c%s", indent + 1, ' ', prompt); + } + } + } else + doint = 0; + goto conf_childs; + } + + type = sym_get_type(sym); + if (sym_is_choice(sym)) { + struct symbol *def_sym = sym_get_choice_value(sym); + struct menu *def_menu = NULL; + + child_count++; + for (child = menu->list; child; child = child->next) { + if (menu_is_visible(child) && child->sym == def_sym) + def_menu = child; + } + + val = sym_get_tristate_value(sym); + if (sym_is_changable(sym)) { + cprint("t%p", menu); + switch (type) { + case S_BOOLEAN: + cprint1("[%c]", val == no ? ' ' : '*'); + break; + case S_TRISTATE: + switch (val) { + case yes: ch = '*'; break; + case mod: ch = 'M'; break; + default: ch = ' '; break; + } + cprint1("<%c>", ch); + break; + } + } else { + cprint("%c%p", def_menu ? 't' : ':', menu); + cprint1(" "); + } + + cprint1("%*c%s", indent + 1, ' ', menu_get_prompt(menu)); + if (val == yes) { + if (def_menu) { + cprint1(" (%s)", menu_get_prompt(def_menu)); + cprint1(" --->"); + cprint_done(); + if (def_menu->list) { + indent += 2; + build_conf(def_menu); + indent -= 2; + } + } else + cprint_done(); + return; + } + cprint_done(); + } else { + if (menu == current_menu) { + cprint(":%p", menu); + cprint("---%*c%s", indent + 1, ' ', menu_get_prompt(menu)); + goto conf_childs; + } + child_count++; + val = sym_get_tristate_value(sym); + if (sym_is_choice_value(sym) && val == yes) { + cprint(":%p", menu); + cprint1(" "); + } else { + switch (type) { + case S_BOOLEAN: + cprint("t%p", menu); + if (sym_is_changable(sym)) + cprint1("[%c]", val == no ? ' ' : '*'); + else + cprint1("---"); + break; + case S_TRISTATE: + cprint("t%p", menu); + switch (val) { + case yes: ch = '*'; break; + case mod: ch = 'M'; break; + default: ch = ' '; break; + } + if (sym_is_changable(sym)) + cprint1("<%c>", ch); + else + cprint1("---"); + break; + default: + cprint("s%p", menu); + tmp = cprint1("(%s)", sym_get_string_value(sym)); + tmp = indent - tmp + 4; + if (tmp < 0) + tmp = 0; + cprint1("%*c%s%s", tmp, ' ', menu_get_prompt(menu), + (sym_has_value(sym) || !sym_is_changable(sym)) ? + "" : " (NEW)"); + cprint_done(); + goto conf_childs; + } + } + cprint1("%*c%s%s", indent + 1, ' ', menu_get_prompt(menu), + (sym_has_value(sym) || !sym_is_changable(sym)) ? + "" : " (NEW)"); + if (menu->prompt->type == P_MENU) { + cprint1(" --->"); + cprint_done(); + return; + } + cprint_done(); + } + +conf_childs: + indent += doint; + for (child = menu->list; child; child = child->next) + build_conf(child); + indent -= doint; +} + +static void conf(struct menu *menu) +{ + struct menu *submenu; + const char *prompt = menu_get_prompt(menu); + struct symbol *sym; + char active_entry[40]; + int stat, type, i; + + unlink("lxdialog.scrltmp"); + active_entry[0] = 0; + while (1) { + cprint_init(); + cprint("--title"); + cprint("%s", prompt ? prompt : _("Main Menu")); + cprint("--menu"); + cprint(_(menu_instructions)); + cprint("%d", rows); + cprint("%d", cols); + cprint("%d", rows - 10); + cprint("%s", active_entry); + current_menu = menu; + build_conf(menu); + if (!child_count) + break; + if (menu == &rootmenu) { + cprint(":"); + cprint("--- "); + cprint("L"); + cprint(_(" Load an Alternate Configuration File")); + cprint("S"); + cprint(_(" Save Configuration to an Alternate File")); + } + stat = exec_conf(); + if (stat < 0) + continue; + + if (stat == 1 || stat == 255) + break; + + type = input_buf[0]; + if (!type) + continue; + + for (i = 0; input_buf[i] && !isspace(input_buf[i]); i++) + ; + if (i >= sizeof(active_entry)) + i = sizeof(active_entry) - 1; + input_buf[i] = 0; + strcpy(active_entry, input_buf); + + sym = NULL; + submenu = NULL; + if (sscanf(input_buf + 1, "%p", &submenu) == 1) + sym = submenu->sym; + + switch (stat) { + case 0: + switch (type) { + case 'm': + if (single_menu_mode) + submenu->data = (void *) (long) !submenu->data; + else + conf(submenu); + break; + case 't': + if (sym_is_choice(sym) && sym_get_tristate_value(sym) == yes) + conf_choice(submenu); + else if (submenu->prompt->type == P_MENU) + conf(submenu); + break; + case 's': + conf_string(submenu); + break; + case 'L': + conf_load(); + break; + case 'S': + conf_save(); + break; + } + break; + case 2: + if (sym) + show_help(submenu); + else + show_helptext("README", _(mconf_readme)); + break; + case 3: + if (type == 't') { + if (sym_set_tristate_value(sym, yes)) + break; + if (sym_set_tristate_value(sym, mod)) + show_textbox(NULL, setmod_text, 6, 74); + } + break; + case 4: + if (type == 't') + sym_set_tristate_value(sym, no); + break; + case 5: + if (type == 't') + sym_set_tristate_value(sym, mod); + break; + case 6: + if (type == 't') + sym_toggle_tristate_value(sym); + else if (type == 'm') + conf(submenu); + break; + case 7: + search_conf(); + break; + } + } +} + +static void show_textbox(const char *title, const char *text, int r, int c) +{ + int fd; + + fd = creat(".help.tmp", 0777); + write(fd, text, strlen(text)); + close(fd); + show_file(".help.tmp", title, r, c); + unlink(".help.tmp"); +} + +static void show_helptext(const char *title, const char *text) +{ + show_textbox(title, text, 0, 0); +} + +static void show_help(struct menu *menu) +{ + struct gstr help = str_new(); + struct symbol *sym = menu->sym; + + if (sym->help) + { + if (sym->name) { + str_printf(&help, "CONFIG_%s:\n\n", sym->name); + str_append(&help, _(sym->help)); + str_append(&help, "\n"); + } + } else { + str_append(&help, nohelp_text); + } + get_symbol_str(&help, sym); + show_helptext(menu_get_prompt(menu), str_get(&help)); + str_free(&help); +} + +static void show_file(const char *filename, const char *title, int r, int c) +{ + do { + cprint_init(); + if (title) { + cprint("--title"); + cprint("%s", title); + } + cprint("--textbox"); + cprint("%s", filename); + cprint("%d", r ? r : rows); + cprint("%d", c ? c : cols); + } while (exec_conf() < 0); +} + +static void conf_choice(struct menu *menu) +{ + const char *prompt = menu_get_prompt(menu); + struct menu *child; + struct symbol *active; + int stat; + + active = sym_get_choice_value(menu->sym); + while (1) { + cprint_init(); + cprint("--title"); + cprint("%s", prompt ? prompt : _("Main Menu")); + cprint("--radiolist"); + cprint(_(radiolist_instructions)); + cprint("15"); + cprint("70"); + cprint("6"); + + current_menu = menu; + for (child = menu->list; child; child = child->next) { + if (!menu_is_visible(child)) + continue; + cprint("%p", child); + cprint("%s", menu_get_prompt(child)); + if (child->sym == sym_get_choice_value(menu->sym)) + cprint("ON"); + else if (child->sym == active) + cprint("SELECTED"); + else + cprint("OFF"); + } + + stat = exec_conf(); + switch (stat) { + case 0: + if (sscanf(input_buf, "%p", &child) != 1) + break; + sym_set_tristate_value(child->sym, yes); + return; + case 1: + if (sscanf(input_buf, "%p", &child) == 1) { + show_help(child); + active = child->sym; + } else + show_help(menu); + break; + case 255: + return; + } + } +} + +static void conf_string(struct menu *menu) +{ + const char *prompt = menu_get_prompt(menu); + int stat; + + while (1) { + cprint_init(); + cprint("--title"); + cprint("%s", prompt ? prompt : _("Main Menu")); + cprint("--inputbox"); + switch (sym_get_type(menu->sym)) { + case S_INT: + cprint(_(inputbox_instructions_int)); + break; + case S_HEX: + cprint(_(inputbox_instructions_hex)); + break; + case S_STRING: + cprint(_(inputbox_instructions_string)); + break; + default: + /* panic? */; + } + cprint("10"); + cprint("75"); + cprint("%s", sym_get_string_value(menu->sym)); + stat = exec_conf(); + switch (stat) { + case 0: + if (sym_set_string_value(menu->sym, input_buf)) + return; + show_textbox(NULL, _("You have made an invalid entry."), 5, 43); + break; + case 1: + show_help(menu); + break; + case 255: + return; + } + } +} + +static void conf_load(void) +{ + int stat; + + while (1) { + cprint_init(); + cprint("--inputbox"); + cprint(load_config_text); + cprint("11"); + cprint("55"); + cprint("%s", filename); + stat = exec_conf(); + switch(stat) { + case 0: + if (!input_buf[0]) + return; + if (!conf_read(input_buf)) + return; + show_textbox(NULL, _("File does not exist!"), 5, 38); + break; + case 1: + show_helptext(_("Load Alternate Configuration"), load_config_help); + break; + case 255: + return; + } + } +} + +static void conf_save(void) +{ + int stat; + + while (1) { + cprint_init(); + cprint("--inputbox"); + cprint(save_config_text); + cprint("11"); + cprint("55"); + cprint("%s", filename); + stat = exec_conf(); + switch(stat) { + case 0: + if (!input_buf[0]) + return; + if (!conf_write(input_buf)) + return; + show_textbox(NULL, _("Can't create file! Probably a nonexistent directory."), 5, 60); + break; + case 1: + show_helptext(_("Save Alternate Configuration"), save_config_help); + break; + case 255: + return; + } + } +} + +static void conf_cleanup(void) +{ + tcsetattr(1, TCSAFLUSH, &ios_org); + unlink(".help.tmp"); + unlink("lxdialog.scrltmp"); +} + +int main(int ac, char **av) +{ + struct symbol *sym; + char *mode; + int stat; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + conf_parse(av[1]); + conf_read(NULL); + + sym = sym_lookup("KERNELVERSION", 0); + sym_calc_value(sym); + sprintf(menu_backtitle, _("Linux Kernel v%s Configuration"), + sym_get_string_value(sym)); + + mode = getenv("MENUCONFIG_MODE"); + if (mode) { + if (!strcasecmp(mode, "single_menu")) + single_menu_mode = 1; + } + + tcgetattr(1, &ios_org); + atexit(conf_cleanup); + init_wsize(); + conf(&rootmenu); + + do { + cprint_init(); + cprint("--yesno"); + cprint(_("Do you wish to save your new busybox configuration?")); + cprint("5"); + cprint("60"); + stat = exec_conf(); + } while (stat < 0); + + if (stat == 0) { + if (conf_write(NULL)) { + fprintf(stderr, _("\n\n" + "Error during writing of the busybox configuration.\n" + "Your busybox configuration changes were NOT saved." + "\n\n")); + return 1; + } + printf(_("\n\n" + "*** End of busybox configuration.\n" + "*** Execute 'make' to build the kernel or try 'make help'." + "\n\n")); + } else { + fprintf(stderr, _("\n\n" + "Your busybox configuration changes were NOT saved." + "\n\n")); + } + + return 0; +} diff --git a/scripts/kconfig/menu.c b/scripts/kconfig/menu.c new file mode 100644 index 000000000..0fce20cb7 --- /dev/null +++ b/scripts/kconfig/menu.c @@ -0,0 +1,397 @@ +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + * Released under the terms of the GNU GPL v2.0. + */ + +#include <stdlib.h> +#include <string.h> + +#define LKC_DIRECT_LINK +#include "lkc.h" + +struct menu rootmenu; +static struct menu **last_entry_ptr; + +struct file *file_list; +struct file *current_file; + +static void menu_warn(struct menu *menu, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "%s:%d:warning: ", menu->file->name, menu->lineno); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); +} + +static void prop_warn(struct property *prop, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "%s:%d:warning: ", prop->file->name, prop->lineno); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); +} + +void menu_init(void) +{ + current_entry = current_menu = &rootmenu; + last_entry_ptr = &rootmenu.list; +} + +void menu_add_entry(struct symbol *sym) +{ + struct menu *menu; + + menu = malloc(sizeof(*menu)); + memset(menu, 0, sizeof(*menu)); + menu->sym = sym; + menu->parent = current_menu; + menu->file = current_file; + menu->lineno = zconf_lineno(); + + *last_entry_ptr = menu; + last_entry_ptr = &menu->next; + current_entry = menu; +} + +void menu_end_entry(void) +{ +} + +struct menu *menu_add_menu(void) +{ + menu_end_entry(); + last_entry_ptr = ¤t_entry->list; + return current_menu = current_entry; +} + +void menu_end_menu(void) +{ + last_entry_ptr = ¤t_menu->next; + current_menu = current_menu->parent; +} + +struct expr *menu_check_dep(struct expr *e) +{ + if (!e) + return e; + + switch (e->type) { + case E_NOT: + e->left.expr = menu_check_dep(e->left.expr); + break; + case E_OR: + case E_AND: + e->left.expr = menu_check_dep(e->left.expr); + e->right.expr = menu_check_dep(e->right.expr); + break; + case E_SYMBOL: + /* change 'm' into 'm' && MODULES */ + if (e->left.sym == &symbol_mod) + return expr_alloc_and(e, expr_alloc_symbol(modules_sym)); + break; + default: + break; + } + return e; +} + +void menu_add_dep(struct expr *dep) +{ + current_entry->dep = expr_alloc_and(current_entry->dep, menu_check_dep(dep)); +} + +void menu_set_type(int type) +{ + struct symbol *sym = current_entry->sym; + + if (sym->type == type) + return; + if (sym->type == S_UNKNOWN) { + sym->type = type; + return; + } + menu_warn(current_entry, "type of '%s' redefined from '%s' to '%s'\n", + sym->name ? sym->name : "<choice>", + sym_type_name(sym->type), sym_type_name(type)); +} + +struct property *menu_add_prop(enum prop_type type, char *prompt, struct expr *expr, struct expr *dep) +{ + struct property *prop = prop_alloc(type, current_entry->sym); + + prop->menu = current_entry; + prop->text = prompt; + prop->expr = expr; + prop->visible.expr = menu_check_dep(dep); + + if (prompt) { + if (current_entry->prompt) + menu_warn(current_entry, "prompt redefined\n"); + current_entry->prompt = prop; + } + + return prop; +} + +struct property *menu_add_prompt(enum prop_type type, char *prompt, struct expr *dep) +{ + return menu_add_prop(type, prompt, NULL, dep); +} + +void menu_add_expr(enum prop_type type, struct expr *expr, struct expr *dep) +{ + menu_add_prop(type, NULL, expr, dep); +} + +void menu_add_symbol(enum prop_type type, struct symbol *sym, struct expr *dep) +{ + menu_add_prop(type, NULL, expr_alloc_symbol(sym), dep); +} + +static int menu_range_valid_sym(struct symbol *sym, struct symbol *sym2) +{ + return sym2->type == S_INT || sym2->type == S_HEX || + (sym2->type == S_UNKNOWN && sym_string_valid(sym, sym2->name)); +} + +void sym_check_prop(struct symbol *sym) +{ + struct property *prop; + struct symbol *sym2; + for (prop = sym->prop; prop; prop = prop->next) { + switch (prop->type) { + case P_DEFAULT: + if ((sym->type == S_STRING || sym->type == S_INT || sym->type == S_HEX) && + prop->expr->type != E_SYMBOL) + prop_warn(prop, + "default for config symbol '%'" + " must be a single symbol", sym->name); + break; + case P_SELECT: + sym2 = prop_get_symbol(prop); + if (sym->type != S_BOOLEAN && sym->type != S_TRISTATE) + prop_warn(prop, + "config symbol '%s' uses select, but is " + "not boolean or tristate", sym->name); + else if (sym2->type == S_UNKNOWN) + prop_warn(prop, + "'select' used by config symbol '%s' " + "refer to undefined symbol '%s'", + sym->name, sym2->name); + else if (sym2->type != S_BOOLEAN && sym2->type != S_TRISTATE) + prop_warn(prop, + "'%s' has wrong type. 'select' only " + "accept arguments of boolean and " + "tristate type", sym2->name); + break; + case P_RANGE: + if (sym->type != S_INT && sym->type != S_HEX) + prop_warn(prop, "range is only allowed " + "for int or hex symbols"); + if (!menu_range_valid_sym(sym, prop->expr->left.sym) || + !menu_range_valid_sym(sym, prop->expr->right.sym)) + prop_warn(prop, "range is invalid"); + break; + default: + ; + } + } +} + +void menu_finalize(struct menu *parent) +{ + struct menu *menu, *last_menu; + struct symbol *sym; + struct property *prop; + struct expr *parentdep, *basedep, *dep, *dep2, **ep; + + sym = parent->sym; + if (parent->list) { + if (sym && sym_is_choice(sym)) { + /* find the first choice value and find out choice type */ + for (menu = parent->list; menu; menu = menu->next) { + if (menu->sym) { + current_entry = parent; + menu_set_type(menu->sym->type); + current_entry = menu; + menu_set_type(sym->type); + break; + } + } + parentdep = expr_alloc_symbol(sym); + } else if (parent->prompt) + parentdep = parent->prompt->visible.expr; + else + parentdep = parent->dep; + + for (menu = parent->list; menu; menu = menu->next) { + basedep = expr_transform(menu->dep); + basedep = expr_alloc_and(expr_copy(parentdep), basedep); + basedep = expr_eliminate_dups(basedep); + menu->dep = basedep; + if (menu->sym) + prop = menu->sym->prop; + else + prop = menu->prompt; + for (; prop; prop = prop->next) { + if (prop->menu != menu) + continue; + dep = expr_transform(prop->visible.expr); + dep = expr_alloc_and(expr_copy(basedep), dep); + dep = expr_eliminate_dups(dep); + if (menu->sym && menu->sym->type != S_TRISTATE) + dep = expr_trans_bool(dep); + prop->visible.expr = dep; + if (prop->type == P_SELECT) { + struct symbol *es = prop_get_symbol(prop); + es->rev_dep.expr = expr_alloc_or(es->rev_dep.expr, + expr_alloc_and(expr_alloc_symbol(menu->sym), expr_copy(dep))); + } + } + } + for (menu = parent->list; menu; menu = menu->next) + menu_finalize(menu); + } else if (sym) { + basedep = parent->prompt ? parent->prompt->visible.expr : NULL; + basedep = expr_trans_compare(basedep, E_UNEQUAL, &symbol_no); + basedep = expr_eliminate_dups(expr_transform(basedep)); + last_menu = NULL; + for (menu = parent->next; menu; menu = menu->next) { + dep = menu->prompt ? menu->prompt->visible.expr : menu->dep; + if (!expr_contains_symbol(dep, sym)) + break; + if (expr_depends_symbol(dep, sym)) + goto next; + dep = expr_trans_compare(dep, E_UNEQUAL, &symbol_no); + dep = expr_eliminate_dups(expr_transform(dep)); + dep2 = expr_copy(basedep); + expr_eliminate_eq(&dep, &dep2); + expr_free(dep); + if (!expr_is_yes(dep2)) { + expr_free(dep2); + break; + } + expr_free(dep2); + next: + menu_finalize(menu); + menu->parent = parent; + last_menu = menu; + } + if (last_menu) { + parent->list = parent->next; + parent->next = last_menu->next; + last_menu->next = NULL; + } + } + for (menu = parent->list; menu; menu = menu->next) { + if (sym && sym_is_choice(sym) && menu->sym) { + menu->sym->flags |= SYMBOL_CHOICEVAL; + if (!menu->prompt) + menu_warn(menu, "choice value must have a prompt"); + for (prop = menu->sym->prop; prop; prop = prop->next) { + if (prop->type == P_PROMPT && prop->menu != menu) { + prop_warn(prop, "choice values " + "currently only support a " + "single prompt"); + } + if (prop->type == P_DEFAULT) + prop_warn(prop, "defaults for choice " + "values not supported"); + } + current_entry = menu; + menu_set_type(sym->type); + menu_add_symbol(P_CHOICE, sym, NULL); + prop = sym_get_choice_prop(sym); + for (ep = &prop->expr; *ep; ep = &(*ep)->left.expr) + ; + *ep = expr_alloc_one(E_CHOICE, NULL); + (*ep)->right.sym = menu->sym; + } + if (menu->list && (!menu->prompt || !menu->prompt->text)) { + for (last_menu = menu->list; ; last_menu = last_menu->next) { + last_menu->parent = parent; + if (!last_menu->next) + break; + } + last_menu->next = menu->next; + menu->next = menu->list; + menu->list = NULL; + } + } + + if (sym && !(sym->flags & SYMBOL_WARNED)) { + if (sym->type == S_UNKNOWN) + menu_warn(parent, "config symbol defined " + "without type\n"); + + if (sym_is_choice(sym) && !parent->prompt) + menu_warn(parent, "choice must have a prompt\n"); + + /* Check properties connected to this symbol */ + sym_check_prop(sym); + sym->flags |= SYMBOL_WARNED; + } + + if (sym && !sym_is_optional(sym) && parent->prompt) { + sym->rev_dep.expr = expr_alloc_or(sym->rev_dep.expr, + expr_alloc_and(parent->prompt->visible.expr, + expr_alloc_symbol(&symbol_mod))); + } +} + +bool menu_is_visible(struct menu *menu) +{ + struct menu *child; + struct symbol *sym; + tristate visible; + + if (!menu->prompt) + return false; + sym = menu->sym; + if (sym) { + sym_calc_value(sym); + visible = menu->prompt->visible.tri; + } else + visible = menu->prompt->visible.tri = expr_calc_value(menu->prompt->visible.expr); + + if (visible != no) + return true; + if (!sym || sym_get_tristate_value(menu->sym) == no) + return false; + + for (child = menu->list; child; child = child->next) + if (menu_is_visible(child)) + return true; + return false; +} + +const char *menu_get_prompt(struct menu *menu) +{ + if (menu->prompt) + return _(menu->prompt->text); + else if (menu->sym) + return _(menu->sym->name); + return NULL; +} + +struct menu *menu_get_root_menu(struct menu *menu) +{ + return &rootmenu; +} + +struct menu *menu_get_parent_menu(struct menu *menu) +{ + enum prop_type type; + + for (; menu != &rootmenu; menu = menu->parent) { + type = menu->prompt ? menu->prompt->type : 0; + if (type == P_MENU) + break; + } + return menu; +} + diff --git a/scripts/kconfig/qconf.cc b/scripts/kconfig/qconf.cc new file mode 100644 index 000000000..74b8470e8 --- /dev/null +++ b/scripts/kconfig/qconf.cc @@ -0,0 +1,1426 @@ +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + * Released under the terms of the GNU GPL v2.0. + */ + +#include <qapplication.h> +#include <qmainwindow.h> +#include <qtoolbar.h> +#include <qvbox.h> +#include <qsplitter.h> +#include <qlistview.h> +#include <qtextview.h> +#include <qlineedit.h> +#include <qmenubar.h> +#include <qmessagebox.h> +#include <qaction.h> +#include <qheader.h> +#include <qfiledialog.h> +#include <qregexp.h> + +#include <stdlib.h> + +#include "lkc.h" +#include "qconf.h" + +#include "qconf.moc" +#include "images.c" + +#ifdef _ +# undef _ +# define _ qgettext +#endif + +static QApplication *configApp; + +static inline QString qgettext(const char* str) +{ + return QString::fromLocal8Bit(gettext(str)); +} + +static inline QString qgettext(const QString& str) +{ + return QString::fromLocal8Bit(gettext(str.latin1())); +} + +ConfigSettings::ConfigSettings() + : showAll(false), showName(false), showRange(false), showData(false) +{ +} + +#if QT_VERSION >= 300 +/** + * Reads the list column settings from the application settings. + */ +void ConfigSettings::readListSettings() +{ + showAll = readBoolEntry("/kconfig/qconf/showAll", false); + showName = readBoolEntry("/kconfig/qconf/showName", false); + showRange = readBoolEntry("/kconfig/qconf/showRange", false); + showData = readBoolEntry("/kconfig/qconf/showData", false); +} + +/** + * Reads a list of integer values from the application settings. + */ +QValueList<int> ConfigSettings::readSizes(const QString& key, bool *ok) +{ + QValueList<int> result; + QStringList entryList = readListEntry(key, ok); + if (ok) { + QStringList::Iterator it; + for (it = entryList.begin(); it != entryList.end(); ++it) + result.push_back((*it).toInt()); + } + + return result; +} + +/** + * Writes a list of integer values to the application settings. + */ +bool ConfigSettings::writeSizes(const QString& key, const QValueList<int>& value) +{ + QStringList stringList; + QValueList<int>::ConstIterator it; + + for (it = value.begin(); it != value.end(); ++it) + stringList.push_back(QString::number(*it)); + return writeEntry(key, stringList); +} +#endif + + +/* + * update all the children of a menu entry + * removes/adds the entries from the parent widget as necessary + * + * parent: either the menu list widget or a menu entry widget + * menu: entry to be updated + */ +template <class P> +void ConfigList::updateMenuList(P* parent, struct menu* menu) +{ + struct menu* child; + ConfigItem* item; + ConfigItem* last; + bool visible; + enum prop_type type; + + if (!menu) { + while ((item = parent->firstChild())) + delete item; + return; + } + + last = parent->firstChild(); + if (last && !last->goParent) + last = 0; + for (child = menu->list; child; child = child->next) { + item = last ? last->nextSibling() : parent->firstChild(); + type = child->prompt ? child->prompt->type : P_UNKNOWN; + + switch (mode) { + case menuMode: + if (!(child->flags & MENU_ROOT)) + goto hide; + break; + case symbolMode: + if (child->flags & MENU_ROOT) + goto hide; + break; + default: + break; + } + + visible = menu_is_visible(child); + if (showAll || visible) { + if (!item || item->menu != child) + item = new ConfigItem(parent, last, child, visible); + else + item->testUpdateMenu(visible); + + if (mode == fullMode || mode == menuMode || type != P_MENU) + updateMenuList(item, child); + else + updateMenuList(item, 0); + last = item; + continue; + } + hide: + if (item && item->menu == child) { + last = parent->firstChild(); + if (last == item) + last = 0; + else while (last->nextSibling() != item) + last = last->nextSibling(); + delete item; + } + } +} + +#if QT_VERSION >= 300 +/* + * set the new data + * TODO check the value + */ +void ConfigItem::okRename(int col) +{ + Parent::okRename(col); + sym_set_string_value(menu->sym, text(dataColIdx).latin1()); +} +#endif + +/* + * update the displayed of a menu entry + */ +void ConfigItem::updateMenu(void) +{ + ConfigList* list; + struct symbol* sym; + struct property *prop; + QString prompt; + int type; + tristate expr; + + list = listView(); + if (goParent) { + setPixmap(promptColIdx, list->menuBackPix); + prompt = ".."; + goto set_prompt; + } + + sym = menu->sym; + prop = menu->prompt; + prompt = QString::fromLocal8Bit(menu_get_prompt(menu)); + + if (prop) switch (prop->type) { + case P_MENU: + if (list->mode == singleMode || list->mode == symbolMode) { + /* a menuconfig entry is displayed differently + * depending whether it's at the view root or a child. + */ + if (sym && list->rootEntry == menu) + break; + setPixmap(promptColIdx, list->menuPix); + } else { + if (sym) + break; + setPixmap(promptColIdx, 0); + } + goto set_prompt; + case P_COMMENT: + setPixmap(promptColIdx, 0); + goto set_prompt; + default: + ; + } + if (!sym) + goto set_prompt; + + setText(nameColIdx, QString::fromLocal8Bit(sym->name)); + + type = sym_get_type(sym); + switch (type) { + case S_BOOLEAN: + case S_TRISTATE: + char ch; + + if (!sym_is_changable(sym) && !list->showAll) { + setPixmap(promptColIdx, 0); + setText(noColIdx, QString::null); + setText(modColIdx, QString::null); + setText(yesColIdx, QString::null); + break; + } + expr = sym_get_tristate_value(sym); + switch (expr) { + case yes: + if (sym_is_choice_value(sym) && type == S_BOOLEAN) + setPixmap(promptColIdx, list->choiceYesPix); + else + setPixmap(promptColIdx, list->symbolYesPix); + setText(yesColIdx, "Y"); + ch = 'Y'; + break; + case mod: + setPixmap(promptColIdx, list->symbolModPix); + setText(modColIdx, "M"); + ch = 'M'; + break; + default: + if (sym_is_choice_value(sym) && type == S_BOOLEAN) + setPixmap(promptColIdx, list->choiceNoPix); + else + setPixmap(promptColIdx, list->symbolNoPix); + setText(noColIdx, "N"); + ch = 'N'; + break; + } + if (expr != no) + setText(noColIdx, sym_tristate_within_range(sym, no) ? "_" : 0); + if (expr != mod) + setText(modColIdx, sym_tristate_within_range(sym, mod) ? "_" : 0); + if (expr != yes) + setText(yesColIdx, sym_tristate_within_range(sym, yes) ? "_" : 0); + + setText(dataColIdx, QChar(ch)); + break; + case S_INT: + case S_HEX: + case S_STRING: + const char* data; + + data = sym_get_string_value(sym); + +#if QT_VERSION >= 300 + int i = list->mapIdx(dataColIdx); + if (i >= 0) + setRenameEnabled(i, TRUE); +#endif + setText(dataColIdx, data); + if (type == S_STRING) + prompt = QString("%1: %2").arg(prompt).arg(data); + else + prompt = QString("(%2) %1").arg(prompt).arg(data); + break; + } + if (!sym_has_value(sym) && visible) + prompt += " (NEW)"; +set_prompt: + setText(promptColIdx, prompt); +} + +void ConfigItem::testUpdateMenu(bool v) +{ + ConfigItem* i; + + visible = v; + if (!menu) + return; + + sym_calc_value(menu->sym); + if (menu->flags & MENU_CHANGED) { + /* the menu entry changed, so update all list items */ + menu->flags &= ~MENU_CHANGED; + for (i = (ConfigItem*)menu->data; i; i = i->nextItem) + i->updateMenu(); + } else if (listView()->updateAll) + updateMenu(); +} + +void ConfigItem::paintCell(QPainter* p, const QColorGroup& cg, int column, int width, int align) +{ + ConfigList* list = listView(); + + if (visible) { + if (isSelected() && !list->hasFocus() && list->mode == menuMode) + Parent::paintCell(p, list->inactivedColorGroup, column, width, align); + else + Parent::paintCell(p, cg, column, width, align); + } else + Parent::paintCell(p, list->disabledColorGroup, column, width, align); +} + +/* + * construct a menu entry + */ +void ConfigItem::init(void) +{ + if (menu) { + ConfigList* list = listView(); + nextItem = (ConfigItem*)menu->data; + menu->data = this; + + if (list->mode != fullMode) + setOpen(TRUE); + sym_calc_value(menu->sym); + } + updateMenu(); +} + +/* + * destruct a menu entry + */ +ConfigItem::~ConfigItem(void) +{ + if (menu) { + ConfigItem** ip = (ConfigItem**)&menu->data; + for (; *ip; ip = &(*ip)->nextItem) { + if (*ip == this) { + *ip = nextItem; + break; + } + } + } +} + +void ConfigLineEdit::show(ConfigItem* i) +{ + item = i; + if (sym_get_string_value(item->menu->sym)) + setText(QString::fromLocal8Bit(sym_get_string_value(item->menu->sym))); + else + setText(QString::null); + Parent::show(); + setFocus(); +} + +void ConfigLineEdit::keyPressEvent(QKeyEvent* e) +{ + switch (e->key()) { + case Key_Escape: + break; + case Key_Return: + case Key_Enter: + sym_set_string_value(item->menu->sym, text().latin1()); + parent()->updateList(item); + break; + default: + Parent::keyPressEvent(e); + return; + } + e->accept(); + parent()->list->setFocus(); + hide(); +} + +ConfigList::ConfigList(ConfigView* p, ConfigMainWindow* cv, ConfigSettings* configSettings) + : Parent(p), cview(cv), + updateAll(false), + symbolYesPix(xpm_symbol_yes), symbolModPix(xpm_symbol_mod), symbolNoPix(xpm_symbol_no), + choiceYesPix(xpm_choice_yes), choiceNoPix(xpm_choice_no), + menuPix(xpm_menu), menuInvPix(xpm_menu_inv), menuBackPix(xpm_menuback), voidPix(xpm_void), + showAll(false), showName(false), showRange(false), showData(false), + rootEntry(0) +{ + int i; + + setSorting(-1); + setRootIsDecorated(TRUE); + disabledColorGroup = palette().active(); + disabledColorGroup.setColor(QColorGroup::Text, palette().disabled().text()); + inactivedColorGroup = palette().active(); + inactivedColorGroup.setColor(QColorGroup::Highlight, palette().disabled().highlight()); + + connect(this, SIGNAL(selectionChanged(void)), + SLOT(updateSelection(void))); + + if (configSettings) { + showAll = configSettings->showAll; + showName = configSettings->showName; + showRange = configSettings->showRange; + showData = configSettings->showData; + } + + for (i = 0; i < colNr; i++) + colMap[i] = colRevMap[i] = -1; + addColumn(promptColIdx, "Option"); + + reinit(); +} + +void ConfigList::reinit(void) +{ + removeColumn(dataColIdx); + removeColumn(yesColIdx); + removeColumn(modColIdx); + removeColumn(noColIdx); + removeColumn(nameColIdx); + + if (showName) + addColumn(nameColIdx, "Name"); + if (showRange) { + addColumn(noColIdx, "N"); + addColumn(modColIdx, "M"); + addColumn(yesColIdx, "Y"); + } + if (showData) + addColumn(dataColIdx, "Value"); + + updateListAll(); +} + +void ConfigList::updateSelection(void) +{ + struct menu *menu; + enum prop_type type; + + ConfigItem* item = (ConfigItem*)selectedItem(); + if (!item) + return; + + cview->setHelp(item); + + menu = item->menu; + if (!menu) + return; + type = menu->prompt ? menu->prompt->type : P_UNKNOWN; + if (mode == menuMode && type == P_MENU) + emit menuSelected(menu); +} + +void ConfigList::updateList(ConfigItem* item) +{ + ConfigItem* last = 0; + + if (!rootEntry) + goto update; + + if (rootEntry != &rootmenu && (mode == singleMode || + (mode == symbolMode && rootEntry->parent != &rootmenu))) { + item = firstChild(); + if (!item) + item = new ConfigItem(this, 0, true); + last = item; + } + if ((mode == singleMode || (mode == symbolMode && !(rootEntry->flags & MENU_ROOT))) && + rootEntry->sym && rootEntry->prompt) { + item = last ? last->nextSibling() : firstChild(); + if (!item) + item = new ConfigItem(this, last, rootEntry, true); + else + item->testUpdateMenu(true); + + updateMenuList(item, rootEntry); + triggerUpdate(); + return; + } +update: + updateMenuList(this, rootEntry); + triggerUpdate(); +} + +void ConfigList::setAllOpen(bool open) +{ + QListViewItemIterator it(this); + + for (; it.current(); it++) + it.current()->setOpen(open); +} + +void ConfigList::setValue(ConfigItem* item, tristate val) +{ + struct symbol* sym; + int type; + tristate oldval; + + sym = item->menu ? item->menu->sym : 0; + if (!sym) + return; + + type = sym_get_type(sym); + switch (type) { + case S_BOOLEAN: + case S_TRISTATE: + oldval = sym_get_tristate_value(sym); + + if (!sym_set_tristate_value(sym, val)) + return; + if (oldval == no && item->menu->list) + item->setOpen(TRUE); + parent()->updateList(item); + break; + } +} + +void ConfigList::changeValue(ConfigItem* item) +{ + struct symbol* sym; + struct menu* menu; + int type, oldexpr, newexpr; + + menu = item->menu; + if (!menu) + return; + sym = menu->sym; + if (!sym) { + if (item->menu->list) + item->setOpen(!item->isOpen()); + return; + } + + type = sym_get_type(sym); + switch (type) { + case S_BOOLEAN: + case S_TRISTATE: + oldexpr = sym_get_tristate_value(sym); + newexpr = sym_toggle_tristate_value(sym); + if (item->menu->list) { + if (oldexpr == newexpr) + item->setOpen(!item->isOpen()); + else if (oldexpr == no) + item->setOpen(TRUE); + } + if (oldexpr != newexpr) + parent()->updateList(item); + break; + case S_INT: + case S_HEX: + case S_STRING: +#if QT_VERSION >= 300 + if (colMap[dataColIdx] >= 0) + item->startRename(colMap[dataColIdx]); + else +#endif + parent()->lineEdit->show(item); + break; + } +} + +void ConfigList::setRootMenu(struct menu *menu) +{ + enum prop_type type; + + if (rootEntry == menu) + return; + type = menu && menu->prompt ? menu->prompt->type : P_UNKNOWN; + if (type != P_MENU) + return; + updateMenuList(this, 0); + rootEntry = menu; + updateListAll(); + setSelected(currentItem(), hasFocus()); +} + +void ConfigList::setParentMenu(void) +{ + ConfigItem* item; + struct menu *oldroot; + + oldroot = rootEntry; + if (rootEntry == &rootmenu) + return; + setRootMenu(menu_get_parent_menu(rootEntry->parent)); + + QListViewItemIterator it(this); + for (; (item = (ConfigItem*)it.current()); it++) { + if (item->menu == oldroot) { + setCurrentItem(item); + ensureItemVisible(item); + break; + } + } +} + +void ConfigList::keyPressEvent(QKeyEvent* ev) +{ + QListViewItem* i = currentItem(); + ConfigItem* item; + struct menu *menu; + enum prop_type type; + + if (ev->key() == Key_Escape && mode != fullMode) { + emit parentSelected(); + ev->accept(); + return; + } + + if (!i) { + Parent::keyPressEvent(ev); + return; + } + item = (ConfigItem*)i; + + switch (ev->key()) { + case Key_Return: + case Key_Enter: + if (item->goParent) { + emit parentSelected(); + break; + } + menu = item->menu; + if (!menu) + break; + type = menu->prompt ? menu->prompt->type : P_UNKNOWN; + if (type == P_MENU && rootEntry != menu && + mode != fullMode && mode != menuMode) { + emit menuSelected(menu); + break; + } + case Key_Space: + changeValue(item); + break; + case Key_N: + setValue(item, no); + break; + case Key_M: + setValue(item, mod); + break; + case Key_Y: + setValue(item, yes); + break; + default: + Parent::keyPressEvent(ev); + return; + } + ev->accept(); +} + +void ConfigList::contentsMousePressEvent(QMouseEvent* e) +{ + //QPoint p(contentsToViewport(e->pos())); + //printf("contentsMousePressEvent: %d,%d\n", p.x(), p.y()); + Parent::contentsMousePressEvent(e); +} + +void ConfigList::contentsMouseReleaseEvent(QMouseEvent* e) +{ + QPoint p(contentsToViewport(e->pos())); + ConfigItem* item = (ConfigItem*)itemAt(p); + struct menu *menu; + enum prop_type ptype; + const QPixmap* pm; + int idx, x; + + if (!item) + goto skip; + + menu = item->menu; + x = header()->offset() + p.x(); + idx = colRevMap[header()->sectionAt(x)]; + switch (idx) { + case promptColIdx: + pm = item->pixmap(promptColIdx); + if (pm) { + int off = header()->sectionPos(0) + itemMargin() + + treeStepSize() * (item->depth() + (rootIsDecorated() ? 1 : 0)); + if (x >= off && x < off + pm->width()) { + if (item->goParent) { + emit parentSelected(); + break; + } else if (!menu) + break; + ptype = menu->prompt ? menu->prompt->type : P_UNKNOWN; + if (ptype == P_MENU && rootEntry != menu && + mode != fullMode && mode != menuMode) + emit menuSelected(menu); + else + changeValue(item); + } + } + break; + case noColIdx: + setValue(item, no); + break; + case modColIdx: + setValue(item, mod); + break; + case yesColIdx: + setValue(item, yes); + break; + case dataColIdx: + changeValue(item); + break; + } + +skip: + //printf("contentsMouseReleaseEvent: %d,%d\n", p.x(), p.y()); + Parent::contentsMouseReleaseEvent(e); +} + +void ConfigList::contentsMouseMoveEvent(QMouseEvent* e) +{ + //QPoint p(contentsToViewport(e->pos())); + //printf("contentsMouseMoveEvent: %d,%d\n", p.x(), p.y()); + Parent::contentsMouseMoveEvent(e); +} + +void ConfigList::contentsMouseDoubleClickEvent(QMouseEvent* e) +{ + QPoint p(contentsToViewport(e->pos())); + ConfigItem* item = (ConfigItem*)itemAt(p); + struct menu *menu; + enum prop_type ptype; + + if (!item) + goto skip; + if (item->goParent) { + emit parentSelected(); + goto skip; + } + menu = item->menu; + if (!menu) + goto skip; + ptype = menu->prompt ? menu->prompt->type : P_UNKNOWN; + if (ptype == P_MENU && (mode == singleMode || mode == symbolMode)) + emit menuSelected(menu); + else if (menu->sym) + changeValue(item); + +skip: + //printf("contentsMouseDoubleClickEvent: %d,%d\n", p.x(), p.y()); + Parent::contentsMouseDoubleClickEvent(e); +} + +void ConfigList::focusInEvent(QFocusEvent *e) +{ + Parent::focusInEvent(e); + + QListViewItem* item = currentItem(); + if (!item) + return; + + setSelected(item, TRUE); + emit gotFocus(); +} + +ConfigView* ConfigView::viewList; + +ConfigView::ConfigView(QWidget* parent, ConfigMainWindow* cview, + ConfigSettings *configSettings) + : Parent(parent) +{ + list = new ConfigList(this, cview, configSettings); + lineEdit = new ConfigLineEdit(this); + lineEdit->hide(); + + this->nextView = viewList; + viewList = this; +} + +ConfigView::~ConfigView(void) +{ + ConfigView** vp; + + for (vp = &viewList; *vp; vp = &(*vp)->nextView) { + if (*vp == this) { + *vp = nextView; + break; + } + } +} + +void ConfigView::updateList(ConfigItem* item) +{ + ConfigView* v; + + for (v = viewList; v; v = v->nextView) + v->list->updateList(item); +} + +void ConfigView::updateListAll(void) +{ + ConfigView* v; + + for (v = viewList; v; v = v->nextView) + v->list->updateListAll(); +} + +/* + * Construct the complete config widget + */ +ConfigMainWindow::ConfigMainWindow(void) +{ + QMenuBar* menu; + bool ok; + int x, y, width, height; + + QWidget *d = configApp->desktop(); + + ConfigSettings* configSettings = new ConfigSettings(); +#if QT_VERSION >= 300 + width = configSettings->readNumEntry("/kconfig/qconf/window width", d->width() - 64); + height = configSettings->readNumEntry("/kconfig/qconf/window height", d->height() - 64); + resize(width, height); + x = configSettings->readNumEntry("/kconfig/qconf/window x", 0, &ok); + if (ok) + y = configSettings->readNumEntry("/kconfig/qconf/window y", 0, &ok); + if (ok) + move(x, y); + showDebug = configSettings->readBoolEntry("/kconfig/qconf/showDebug", false); + + // read list settings into configSettings, will be used later for ConfigList setup + configSettings->readListSettings(); +#else + width = d->width() - 64; + height = d->height() - 64; + resize(width, height); + showDebug = false; +#endif + + split1 = new QSplitter(this); + split1->setOrientation(QSplitter::Horizontal); + setCentralWidget(split1); + + menuView = new ConfigView(split1, this, configSettings); + menuList = menuView->list; + + split2 = new QSplitter(split1); + split2->setOrientation(QSplitter::Vertical); + + // create config tree + configView = new ConfigView(split2, this, configSettings); + configList = configView->list; + + helpText = new QTextView(split2); + helpText->setTextFormat(Qt::RichText); + + setTabOrder(configList, helpText); + configList->setFocus(); + + menu = menuBar(); + toolBar = new QToolBar("Tools", this); + + backAction = new QAction("Back", QPixmap(xpm_back), "Back", 0, this); + connect(backAction, SIGNAL(activated()), SLOT(goBack())); + backAction->setEnabled(FALSE); + QAction *quitAction = new QAction("Quit", "&Quit", CTRL+Key_Q, this); + connect(quitAction, SIGNAL(activated()), SLOT(close())); + QAction *loadAction = new QAction("Load", QPixmap(xpm_load), "&Load", CTRL+Key_L, this); + connect(loadAction, SIGNAL(activated()), SLOT(loadConfig())); + QAction *saveAction = new QAction("Save", QPixmap(xpm_save), "&Save", CTRL+Key_S, this); + connect(saveAction, SIGNAL(activated()), SLOT(saveConfig())); + QAction *saveAsAction = new QAction("Save As...", "Save &As...", 0, this); + connect(saveAsAction, SIGNAL(activated()), SLOT(saveConfigAs())); + QAction *singleViewAction = new QAction("Single View", QPixmap(xpm_single_view), "Split View", 0, this); + connect(singleViewAction, SIGNAL(activated()), SLOT(showSingleView())); + QAction *splitViewAction = new QAction("Split View", QPixmap(xpm_split_view), "Split View", 0, this); + connect(splitViewAction, SIGNAL(activated()), SLOT(showSplitView())); + QAction *fullViewAction = new QAction("Full View", QPixmap(xpm_tree_view), "Full View", 0, this); + connect(fullViewAction, SIGNAL(activated()), SLOT(showFullView())); + + QAction *showNameAction = new QAction(NULL, "Show Name", 0, this); + showNameAction->setToggleAction(TRUE); + showNameAction->setOn(configList->showName); + connect(showNameAction, SIGNAL(toggled(bool)), SLOT(setShowName(bool))); + QAction *showRangeAction = new QAction(NULL, "Show Range", 0, this); + showRangeAction->setToggleAction(TRUE); + showRangeAction->setOn(configList->showRange); + connect(showRangeAction, SIGNAL(toggled(bool)), SLOT(setShowRange(bool))); + QAction *showDataAction = new QAction(NULL, "Show Data", 0, this); + showDataAction->setToggleAction(TRUE); + showDataAction->setOn(configList->showData); + connect(showDataAction, SIGNAL(toggled(bool)), SLOT(setShowData(bool))); + QAction *showAllAction = new QAction(NULL, "Show All Options", 0, this); + showAllAction->setToggleAction(TRUE); + showAllAction->setOn(configList->showAll); + connect(showAllAction, SIGNAL(toggled(bool)), SLOT(setShowAll(bool))); + QAction *showDebugAction = new QAction(NULL, "Show Debug Info", 0, this); + showDebugAction->setToggleAction(TRUE); + showDebugAction->setOn(showDebug); + connect(showDebugAction, SIGNAL(toggled(bool)), SLOT(setShowDebug(bool))); + + QAction *showIntroAction = new QAction(NULL, "Introduction", 0, this); + connect(showIntroAction, SIGNAL(activated()), SLOT(showIntro())); + QAction *showAboutAction = new QAction(NULL, "About", 0, this); + connect(showAboutAction, SIGNAL(activated()), SLOT(showAbout())); + + // init tool bar + backAction->addTo(toolBar); + toolBar->addSeparator(); + loadAction->addTo(toolBar); + saveAction->addTo(toolBar); + toolBar->addSeparator(); + singleViewAction->addTo(toolBar); + splitViewAction->addTo(toolBar); + fullViewAction->addTo(toolBar); + + // create config menu + QPopupMenu* config = new QPopupMenu(this); + menu->insertItem("&File", config); + loadAction->addTo(config); + saveAction->addTo(config); + saveAsAction->addTo(config); + config->insertSeparator(); + quitAction->addTo(config); + + // create options menu + QPopupMenu* optionMenu = new QPopupMenu(this); + menu->insertItem("&Option", optionMenu); + showNameAction->addTo(optionMenu); + showRangeAction->addTo(optionMenu); + showDataAction->addTo(optionMenu); + optionMenu->insertSeparator(); + showAllAction->addTo(optionMenu); + showDebugAction->addTo(optionMenu); + + // create help menu + QPopupMenu* helpMenu = new QPopupMenu(this); + menu->insertSeparator(); + menu->insertItem("&Help", helpMenu); + showIntroAction->addTo(helpMenu); + showAboutAction->addTo(helpMenu); + + connect(configList, SIGNAL(menuSelected(struct menu *)), + SLOT(changeMenu(struct menu *))); + connect(configList, SIGNAL(parentSelected()), + SLOT(goBack())); + connect(menuList, SIGNAL(menuSelected(struct menu *)), + SLOT(changeMenu(struct menu *))); + + connect(configList, SIGNAL(gotFocus(void)), + SLOT(listFocusChanged(void))); + connect(menuList, SIGNAL(gotFocus(void)), + SLOT(listFocusChanged(void))); + +#if QT_VERSION >= 300 + QString listMode = configSettings->readEntry("/kconfig/qconf/listMode", "symbol"); + if (listMode == "single") + showSingleView(); + else if (listMode == "full") + showFullView(); + else /*if (listMode == "split")*/ + showSplitView(); + + // UI setup done, restore splitter positions + QValueList<int> sizes = configSettings->readSizes("/kconfig/qconf/split1", &ok); + if (ok) + split1->setSizes(sizes); + + sizes = configSettings->readSizes("/kconfig/qconf/split2", &ok); + if (ok) + split2->setSizes(sizes); +#else + showSplitView(); +#endif + delete configSettings; +} + +static QString print_filter(const QString &str) +{ + QRegExp re("[<>&\"\\n]"); + QString res = str; + for (int i = 0; (i = res.find(re, i)) >= 0;) { + switch (res[i].latin1()) { + case '<': + res.replace(i, 1, "<"); + i += 4; + break; + case '>': + res.replace(i, 1, ">"); + i += 4; + break; + case '&': + res.replace(i, 1, "&"); + i += 5; + break; + case '"': + res.replace(i, 1, """); + i += 6; + break; + case '\n': + res.replace(i, 1, "<br>"); + i += 4; + break; + } + } + return res; +} + +static void expr_print_help(void *data, const char *str) +{ + reinterpret_cast<QString*>(data)->append(print_filter(str)); +} + +/* + * display a new help entry as soon as a new menu entry is selected + */ +void ConfigMainWindow::setHelp(QListViewItem* item) +{ + struct symbol* sym; + struct menu* menu = 0; + + configList->parent()->lineEdit->hide(); + if (item) + menu = ((ConfigItem*)item)->menu; + if (!menu) { + helpText->setText(QString::null); + return; + } + + QString head, debug, help; + menu = ((ConfigItem*)item)->menu; + sym = menu->sym; + if (sym) { + if (menu->prompt) { + head += "<big><b>"; + head += print_filter(_(menu->prompt->text)); + head += "</b></big>"; + if (sym->name) { + head += " ("; + head += print_filter(_(sym->name)); + head += ")"; + } + } else if (sym->name) { + head += "<big><b>"; + head += print_filter(_(sym->name)); + head += "</b></big>"; + } + head += "<br><br>"; + + if (showDebug) { + debug += "type: "; + debug += print_filter(sym_type_name(sym->type)); + if (sym_is_choice(sym)) + debug += " (choice)"; + debug += "<br>"; + if (sym->rev_dep.expr) { + debug += "reverse dep: "; + expr_print(sym->rev_dep.expr, expr_print_help, &debug, E_NONE); + debug += "<br>"; + } + for (struct property *prop = sym->prop; prop; prop = prop->next) { + switch (prop->type) { + case P_PROMPT: + case P_MENU: + debug += "prompt: "; + debug += print_filter(_(prop->text)); + debug += "<br>"; + break; + case P_DEFAULT: + debug += "default: "; + expr_print(prop->expr, expr_print_help, &debug, E_NONE); + debug += "<br>"; + break; + case P_CHOICE: + if (sym_is_choice(sym)) { + debug += "choice: "; + expr_print(prop->expr, expr_print_help, &debug, E_NONE); + debug += "<br>"; + } + break; + case P_SELECT: + debug += "select: "; + expr_print(prop->expr, expr_print_help, &debug, E_NONE); + debug += "<br>"; + break; + case P_RANGE: + debug += "range: "; + expr_print(prop->expr, expr_print_help, &debug, E_NONE); + debug += "<br>"; + break; + default: + debug += "unknown property: "; + debug += prop_get_type_name(prop->type); + debug += "<br>"; + } + if (prop->visible.expr) { + debug += "    dep: "; + expr_print(prop->visible.expr, expr_print_help, &debug, E_NONE); + debug += "<br>"; + } + } + debug += "<br>"; + } + + help = print_filter(_(sym->help)); + } else if (menu->prompt) { + head += "<big><b>"; + head += print_filter(_(menu->prompt->text)); + head += "</b></big><br><br>"; + if (showDebug) { + if (menu->prompt->visible.expr) { + debug += "  dep: "; + expr_print(menu->prompt->visible.expr, expr_print_help, &debug, E_NONE); + debug += "<br><br>"; + } + } + } + if (showDebug) + debug += QString().sprintf("defined at %s:%d<br><br>", menu->file->name, menu->lineno); + helpText->setText(head + debug + help); +} + +void ConfigMainWindow::loadConfig(void) +{ + QString s = QFileDialog::getOpenFileName(".config", NULL, this); + if (s.isNull()) + return; + if (conf_read(QFile::encodeName(s))) + QMessageBox::information(this, "qconf", "Unable to load configuration!"); + ConfigView::updateListAll(); +} + +void ConfigMainWindow::saveConfig(void) +{ + if (conf_write(NULL)) + QMessageBox::information(this, "qconf", "Unable to save configuration!"); +} + +void ConfigMainWindow::saveConfigAs(void) +{ + QString s = QFileDialog::getSaveFileName(".config", NULL, this); + if (s.isNull()) + return; + if (conf_write(QFile::encodeName(s))) + QMessageBox::information(this, "qconf", "Unable to save configuration!"); +} + +void ConfigMainWindow::changeMenu(struct menu *menu) +{ + configList->setRootMenu(menu); + backAction->setEnabled(TRUE); +} + +void ConfigMainWindow::listFocusChanged(void) +{ + if (menuList->hasFocus()) { + if (menuList->mode == menuMode) + configList->clearSelection(); + setHelp(menuList->selectedItem()); + } else if (configList->hasFocus()) { + setHelp(configList->selectedItem()); + } +} + +void ConfigMainWindow::goBack(void) +{ + ConfigItem* item; + + configList->setParentMenu(); + if (configList->rootEntry == &rootmenu) + backAction->setEnabled(FALSE); + item = (ConfigItem*)menuList->selectedItem(); + while (item) { + if (item->menu == configList->rootEntry) { + menuList->setSelected(item, TRUE); + break; + } + item = (ConfigItem*)item->parent(); + } +} + +void ConfigMainWindow::showSingleView(void) +{ + menuView->hide(); + menuList->setRootMenu(0); + configList->mode = singleMode; + if (configList->rootEntry == &rootmenu) + configList->updateListAll(); + else + configList->setRootMenu(&rootmenu); + configList->setAllOpen(TRUE); + configList->setFocus(); +} + +void ConfigMainWindow::showSplitView(void) +{ + configList->mode = symbolMode; + if (configList->rootEntry == &rootmenu) + configList->updateListAll(); + else + configList->setRootMenu(&rootmenu); + configList->setAllOpen(TRUE); + configApp->processEvents(); + menuList->mode = menuMode; + menuList->setRootMenu(&rootmenu); + menuList->setAllOpen(TRUE); + menuView->show(); + menuList->setFocus(); +} + +void ConfigMainWindow::showFullView(void) +{ + menuView->hide(); + menuList->setRootMenu(0); + configList->mode = fullMode; + if (configList->rootEntry == &rootmenu) + configList->updateListAll(); + else + configList->setRootMenu(&rootmenu); + configList->setAllOpen(FALSE); + configList->setFocus(); +} + +void ConfigMainWindow::setShowAll(bool b) +{ + if (configList->showAll == b) + return; + configList->showAll = b; + configList->updateListAll(); + menuList->showAll = b; + menuList->updateListAll(); +} + +void ConfigMainWindow::setShowDebug(bool b) +{ + if (showDebug == b) + return; + showDebug = b; +} + +void ConfigMainWindow::setShowName(bool b) +{ + if (configList->showName == b) + return; + configList->showName = b; + configList->reinit(); + menuList->showName = b; + menuList->reinit(); +} + +void ConfigMainWindow::setShowRange(bool b) +{ + if (configList->showRange == b) + return; + configList->showRange = b; + configList->reinit(); + menuList->showRange = b; + menuList->reinit(); +} + +void ConfigMainWindow::setShowData(bool b) +{ + if (configList->showData == b) + return; + configList->showData = b; + configList->reinit(); + menuList->showData = b; + menuList->reinit(); +} + +/* + * ask for saving configuration before quitting + * TODO ask only when something changed + */ +void ConfigMainWindow::closeEvent(QCloseEvent* e) +{ + if (!sym_change_count) { + e->accept(); + return; + } + QMessageBox mb("qconf", "Save configuration?", QMessageBox::Warning, + QMessageBox::Yes | QMessageBox::Default, QMessageBox::No, QMessageBox::Cancel | QMessageBox::Escape); + mb.setButtonText(QMessageBox::Yes, "&Save Changes"); + mb.setButtonText(QMessageBox::No, "&Discard Changes"); + mb.setButtonText(QMessageBox::Cancel, "Cancel Exit"); + switch (mb.exec()) { + case QMessageBox::Yes: + conf_write(NULL); + case QMessageBox::No: + e->accept(); + break; + case QMessageBox::Cancel: + e->ignore(); + break; + } +} + +void ConfigMainWindow::showIntro(void) +{ + static char str[] = "Welcome to the qconf graphical busybox configuration tool for Linux.\n\n" + "For each option, a blank box indicates the feature is disabled, a check\n" + "indicates it is enabled, and a dot indicates that it is to be compiled\n" + "as a module. Clicking on the box will cycle through the three states.\n\n" + "If you do not see an option (e.g., a device driver) that you believe\n" + "should be present, try turning on Show All Options under the Options menu.\n" + "Although there is no cross reference yet to help you figure out what other\n" + "options must be enabled to support the option you are interested in, you can\n" + "still view the help of a grayed-out option.\n\n" + "Toggling Show Debug Info under the Options menu will show the dependencies,\n" + "which you can then match by examining other options.\n\n"; + + QMessageBox::information(this, "qconf", str); +} + +void ConfigMainWindow::showAbout(void) +{ + static char str[] = "qconf is Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org>.\n\n" + "Bug reports and feature request can also be entered at http://bugzilla.kernel.org/\n"; + + QMessageBox::information(this, "qconf", str); +} + +void ConfigMainWindow::saveSettings(void) +{ +#if QT_VERSION >= 300 + ConfigSettings *configSettings = new ConfigSettings; + configSettings->writeEntry("/kconfig/qconf/window x", pos().x()); + configSettings->writeEntry("/kconfig/qconf/window y", pos().y()); + configSettings->writeEntry("/kconfig/qconf/window width", size().width()); + configSettings->writeEntry("/kconfig/qconf/window height", size().height()); + configSettings->writeEntry("/kconfig/qconf/showName", configList->showName); + configSettings->writeEntry("/kconfig/qconf/showRange", configList->showRange); + configSettings->writeEntry("/kconfig/qconf/showData", configList->showData); + configSettings->writeEntry("/kconfig/qconf/showAll", configList->showAll); + configSettings->writeEntry("/kconfig/qconf/showDebug", showDebug); + + QString entry; + switch(configList->mode) { + case singleMode : + entry = "single"; + break; + + case symbolMode : + entry = "split"; + break; + + case fullMode : + entry = "full"; + break; + } + configSettings->writeEntry("/kconfig/qconf/listMode", entry); + + configSettings->writeSizes("/kconfig/qconf/split1", split1->sizes()); + configSettings->writeSizes("/kconfig/qconf/split2", split2->sizes()); + + delete configSettings; +#endif +} + +void fixup_rootmenu(struct menu *menu) +{ + struct menu *child; + static int menu_cnt = 0; + + menu->flags |= MENU_ROOT; + for (child = menu->list; child; child = child->next) { + if (child->prompt && child->prompt->type == P_MENU) { + menu_cnt++; + fixup_rootmenu(child); + menu_cnt--; + } else if (!menu_cnt) + fixup_rootmenu(child); + } +} + +static const char *progname; + +static void usage(void) +{ + printf("%s <config>\n", progname); + exit(0); +} + +int main(int ac, char** av) +{ + ConfigMainWindow* v; + const char *name; + + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + +#ifndef LKC_DIRECT_LINK + kconfig_load(); +#endif + + progname = av[0]; + configApp = new QApplication(ac, av); + if (ac > 1 && av[1][0] == '-') { + switch (av[1][1]) { + case 'h': + case '?': + usage(); + } + name = av[2]; + } else + name = av[1]; + if (!name) + usage(); + + conf_parse(name); + fixup_rootmenu(&rootmenu); + conf_read(NULL); + //zconfdump(stdout); + + v = new ConfigMainWindow(); + + //zconfdump(stdout); + v->show(); + configApp->connect(configApp, SIGNAL(lastWindowClosed()), SLOT(quit())); + configApp->connect(configApp, SIGNAL(aboutToQuit()), v, SLOT(saveSettings())); + configApp->exec(); + + return 0; +} diff --git a/scripts/kconfig/qconf.h b/scripts/kconfig/qconf.h new file mode 100644 index 000000000..e52f3e90b --- /dev/null +++ b/scripts/kconfig/qconf.h @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + * Released under the terms of the GNU GPL v2.0. + */ + +#include <qlistview.h> +#if QT_VERSION >= 300 +#include <qsettings.h> +#else +class QSettings { }; +#endif + +class ConfigList; +class ConfigItem; +class ConfigLineEdit; +class ConfigMainWindow; + + +class ConfigSettings : public QSettings { +public: + ConfigSettings(); + +#if QT_VERSION >= 300 + void readListSettings(); + QValueList<int> readSizes(const QString& key, bool *ok); + bool writeSizes(const QString& key, const QValueList<int>& value); +#endif + + bool showAll; + bool showName; + bool showRange; + bool showData; +}; + +class ConfigView : public QVBox { + Q_OBJECT + typedef class QVBox Parent; +public: + ConfigView(QWidget* parent, ConfigMainWindow* cview, ConfigSettings* configSettings); + ~ConfigView(void); + static void updateList(ConfigItem* item); + static void updateListAll(void); + +public: + ConfigList* list; + ConfigLineEdit* lineEdit; + + static ConfigView* viewList; + ConfigView* nextView; +}; + +enum colIdx { + promptColIdx, nameColIdx, noColIdx, modColIdx, yesColIdx, dataColIdx, colNr +}; +enum listMode { + singleMode, menuMode, symbolMode, fullMode +}; + +class ConfigList : public QListView { + Q_OBJECT + typedef class QListView Parent; +public: + ConfigList(ConfigView* p, ConfigMainWindow* cview, ConfigSettings *configSettings); + void reinit(void); + ConfigView* parent(void) const + { + return (ConfigView*)Parent::parent(); + } + +protected: + ConfigMainWindow* cview; + + void keyPressEvent(QKeyEvent *e); + void contentsMousePressEvent(QMouseEvent *e); + void contentsMouseReleaseEvent(QMouseEvent *e); + void contentsMouseMoveEvent(QMouseEvent *e); + void contentsMouseDoubleClickEvent(QMouseEvent *e); + void focusInEvent(QFocusEvent *e); +public slots: + void setRootMenu(struct menu *menu); + + void updateList(ConfigItem *item); + void setValue(ConfigItem* item, tristate val); + void changeValue(ConfigItem* item); + void updateSelection(void); +signals: + void menuSelected(struct menu *menu); + void parentSelected(void); + void gotFocus(void); + +public: + void updateListAll(void) + { + updateAll = true; + updateList(NULL); + updateAll = false; + } + ConfigList* listView() + { + return this; + } + ConfigItem* firstChild() const + { + return (ConfigItem *)Parent::firstChild(); + } + int mapIdx(colIdx idx) + { + return colMap[idx]; + } + void addColumn(colIdx idx, const QString& label) + { + colMap[idx] = Parent::addColumn(label); + colRevMap[colMap[idx]] = idx; + } + void removeColumn(colIdx idx) + { + int col = colMap[idx]; + if (col >= 0) { + Parent::removeColumn(col); + colRevMap[col] = colMap[idx] = -1; + } + } + void setAllOpen(bool open); + void setParentMenu(void); + + template <class P> + void updateMenuList(P*, struct menu*); + + bool updateAll; + + QPixmap symbolYesPix, symbolModPix, symbolNoPix; + QPixmap choiceYesPix, choiceNoPix; + QPixmap menuPix, menuInvPix, menuBackPix, voidPix; + + bool showAll, showName, showRange, showData; + enum listMode mode; + struct menu *rootEntry; + QColorGroup disabledColorGroup; + QColorGroup inactivedColorGroup; + +private: + int colMap[colNr]; + int colRevMap[colNr]; +}; + +class ConfigItem : public QListViewItem { + typedef class QListViewItem Parent; +public: + ConfigItem(QListView *parent, ConfigItem *after, struct menu *m, bool v) + : Parent(parent, after), menu(m), visible(v), goParent(false) + { + init(); + } + ConfigItem(ConfigItem *parent, ConfigItem *after, struct menu *m, bool v) + : Parent(parent, after), menu(m), visible(v), goParent(false) + { + init(); + } + ConfigItem(QListView *parent, ConfigItem *after, bool v) + : Parent(parent, after), menu(0), visible(v), goParent(true) + { + init(); + } + ~ConfigItem(void); + void init(void); +#if QT_VERSION >= 300 + void okRename(int col); +#endif + void updateMenu(void); + void testUpdateMenu(bool v); + ConfigList* listView() const + { + return (ConfigList*)Parent::listView(); + } + ConfigItem* firstChild() const + { + return (ConfigItem *)Parent::firstChild(); + } + ConfigItem* nextSibling() const + { + return (ConfigItem *)Parent::nextSibling(); + } + void setText(colIdx idx, const QString& text) + { + Parent::setText(listView()->mapIdx(idx), text); + } + QString text(colIdx idx) const + { + return Parent::text(listView()->mapIdx(idx)); + } + void setPixmap(colIdx idx, const QPixmap& pm) + { + Parent::setPixmap(listView()->mapIdx(idx), pm); + } + const QPixmap* pixmap(colIdx idx) const + { + return Parent::pixmap(listView()->mapIdx(idx)); + } + void paintCell(QPainter* p, const QColorGroup& cg, int column, int width, int align); + + ConfigItem* nextItem; + struct menu *menu; + bool visible; + bool goParent; +}; + +class ConfigLineEdit : public QLineEdit { + Q_OBJECT + typedef class QLineEdit Parent; +public: + ConfigLineEdit(ConfigView* parent) + : Parent(parent) + { } + ConfigView* parent(void) const + { + return (ConfigView*)Parent::parent(); + } + void show(ConfigItem *i); + void keyPressEvent(QKeyEvent *e); + +public: + ConfigItem *item; +}; + +class ConfigMainWindow : public QMainWindow { + Q_OBJECT +public: + ConfigMainWindow(void); +public slots: + void setHelp(QListViewItem* item); + void changeMenu(struct menu *); + void listFocusChanged(void); + void goBack(void); + void loadConfig(void); + void saveConfig(void); + void saveConfigAs(void); + void showSingleView(void); + void showSplitView(void); + void showFullView(void); + void setShowAll(bool); + void setShowDebug(bool); + void setShowRange(bool); + void setShowName(bool); + void setShowData(bool); + void showIntro(void); + void showAbout(void); + void saveSettings(void); + +protected: + void closeEvent(QCloseEvent *e); + + ConfigView *menuView; + ConfigList *menuList; + ConfigView *configView; + ConfigList *configList; + QTextView *helpText; + QToolBar *toolBar; + QAction *backAction; + QSplitter* split1; + QSplitter* split2; + + bool showDebug; +}; diff --git a/scripts/kconfig/symbol.c b/scripts/kconfig/symbol.c new file mode 100644 index 000000000..3d7877afc --- /dev/null +++ b/scripts/kconfig/symbol.c @@ -0,0 +1,882 @@ +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + * Released under the terms of the GNU GPL v2.0. + */ + +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <regex.h> +#include <sys/utsname.h> + +#define LKC_DIRECT_LINK +#include "lkc.h" + +struct symbol symbol_yes = { + .name = "y", + .curr = { "y", yes }, + .flags = SYMBOL_YES|SYMBOL_VALID, +}, symbol_mod = { + .name = "m", + .curr = { "m", mod }, + .flags = SYMBOL_MOD|SYMBOL_VALID, +}, symbol_no = { + .name = "n", + .curr = { "n", no }, + .flags = SYMBOL_NO|SYMBOL_VALID, +}, symbol_empty = { + .name = "", + .curr = { "", no }, + .flags = SYMBOL_VALID, +}; + +int sym_change_count; +struct symbol *modules_sym; +tristate modules_val; + +void sym_add_default(struct symbol *sym, const char *def) +{ + struct property *prop = prop_alloc(P_DEFAULT, sym); + + prop->expr = expr_alloc_symbol(sym_lookup(def, 1)); +} + +void sym_init(void) +{ + struct symbol *sym; + struct utsname uts; + char *p; + static bool inited = false; + + if (inited) + return; + inited = true; + + uname(&uts); + + sym = sym_lookup("ARCH", 0); + sym->type = S_STRING; + sym->flags |= SYMBOL_AUTO; + p = getenv("ARCH"); + if (p) + sym_add_default(sym, p); + + sym = sym_lookup("KERNELVERSION", 0); + sym->type = S_STRING; + sym->flags |= SYMBOL_AUTO; + p = getenv("KERNELVERSION"); + if (p) + sym_add_default(sym, p); + + sym = sym_lookup("UNAME_RELEASE", 0); + sym->type = S_STRING; + sym->flags |= SYMBOL_AUTO; + sym_add_default(sym, uts.release); +} + +enum symbol_type sym_get_type(struct symbol *sym) +{ + enum symbol_type type = sym->type; + + if (type == S_TRISTATE) { + if (sym_is_choice_value(sym) && sym->visible == yes) + type = S_BOOLEAN; + else if (modules_val == no) + type = S_BOOLEAN; + } + return type; +} + +const char *sym_type_name(enum symbol_type type) +{ + switch (type) { + case S_BOOLEAN: + return "boolean"; + case S_TRISTATE: + return "tristate"; + case S_INT: + return "integer"; + case S_HEX: + return "hex"; + case S_STRING: + return "string"; + case S_UNKNOWN: + return "unknown"; + case S_OTHER: + break; + } + return "???"; +} + +struct property *sym_get_choice_prop(struct symbol *sym) +{ + struct property *prop; + + for_all_choices(sym, prop) + return prop; + return NULL; +} + +struct property *sym_get_default_prop(struct symbol *sym) +{ + struct property *prop; + + for_all_defaults(sym, prop) { + prop->visible.tri = expr_calc_value(prop->visible.expr); + if (prop->visible.tri != no) + return prop; + } + return NULL; +} + +struct property *sym_get_range_prop(struct symbol *sym) +{ + struct property *prop; + + for_all_properties(sym, prop, P_RANGE) { + prop->visible.tri = expr_calc_value(prop->visible.expr); + if (prop->visible.tri != no) + return prop; + } + return NULL; +} + +static int sym_get_range_val(struct symbol *sym, int base) +{ + sym_calc_value(sym); + switch (sym->type) { + case S_INT: + base = 10; + break; + case S_HEX: + base = 16; + break; + default: + break; + } + return strtol(sym->curr.val, NULL, base); +} + +static void sym_validate_range(struct symbol *sym) +{ + struct property *prop; + int base, val, val2; + char str[64]; + + switch (sym->type) { + case S_INT: + base = 10; + break; + case S_HEX: + base = 16; + break; + default: + return; + } + prop = sym_get_range_prop(sym); + if (!prop) + return; + val = strtol(sym->curr.val, NULL, base); + val2 = sym_get_range_val(prop->expr->left.sym, base); + if (val >= val2) { + val2 = sym_get_range_val(prop->expr->right.sym, base); + if (val <= val2) + return; + } + if (sym->type == S_INT) + sprintf(str, "%d", val2); + else + sprintf(str, "0x%x", val2); + sym->curr.val = strdup(str); +} + +static void sym_calc_visibility(struct symbol *sym) +{ + struct property *prop; + tristate tri; + + /* any prompt visible? */ + tri = no; + for_all_prompts(sym, prop) { + prop->visible.tri = expr_calc_value(prop->visible.expr); + tri = E_OR(tri, prop->visible.tri); + } + if (tri == mod && (sym->type != S_TRISTATE || modules_val == no)) + tri = yes; + if (sym->visible != tri) { + sym->visible = tri; + sym_set_changed(sym); + } + if (sym_is_choice_value(sym)) + return; + tri = no; + if (sym->rev_dep.expr) + tri = expr_calc_value(sym->rev_dep.expr); + if (tri == mod && sym_get_type(sym) == S_BOOLEAN) + tri = yes; + if (sym->rev_dep.tri != tri) { + sym->rev_dep.tri = tri; + sym_set_changed(sym); + } +} + +static struct symbol *sym_calc_choice(struct symbol *sym) +{ + struct symbol *def_sym; + struct property *prop; + struct expr *e; + + /* is the user choice visible? */ + def_sym = sym->user.val; + if (def_sym) { + sym_calc_visibility(def_sym); + if (def_sym->visible != no) + return def_sym; + } + + /* any of the defaults visible? */ + for_all_defaults(sym, prop) { + prop->visible.tri = expr_calc_value(prop->visible.expr); + if (prop->visible.tri == no) + continue; + def_sym = prop_get_symbol(prop); + sym_calc_visibility(def_sym); + if (def_sym->visible != no) + return def_sym; + } + + /* just get the first visible value */ + prop = sym_get_choice_prop(sym); + for (e = prop->expr; e; e = e->left.expr) { + def_sym = e->right.sym; + sym_calc_visibility(def_sym); + if (def_sym->visible != no) + return def_sym; + } + + /* no choice? reset tristate value */ + sym->curr.tri = no; + return NULL; +} + +void sym_calc_value(struct symbol *sym) +{ + struct symbol_value newval, oldval; + struct property *prop; + struct expr *e; + + if (!sym) + return; + + if (sym->flags & SYMBOL_VALID) + return; + sym->flags |= SYMBOL_VALID; + + oldval = sym->curr; + + switch (sym->type) { + case S_INT: + case S_HEX: + case S_STRING: + newval = symbol_empty.curr; + break; + case S_BOOLEAN: + case S_TRISTATE: + newval = symbol_no.curr; + break; + default: + sym->curr.val = sym->name; + sym->curr.tri = no; + return; + } + if (!sym_is_choice_value(sym)) + sym->flags &= ~SYMBOL_WRITE; + + sym_calc_visibility(sym); + + /* set default if recursively called */ + sym->curr = newval; + + switch (sym_get_type(sym)) { + case S_BOOLEAN: + case S_TRISTATE: + if (sym_is_choice_value(sym) && sym->visible == yes) { + prop = sym_get_choice_prop(sym); + newval.tri = (prop_get_symbol(prop)->curr.val == sym) ? yes : no; + } else if (E_OR(sym->visible, sym->rev_dep.tri) != no) { + sym->flags |= SYMBOL_WRITE; + if (sym_has_value(sym)) + newval.tri = sym->user.tri; + else if (!sym_is_choice(sym)) { + prop = sym_get_default_prop(sym); + if (prop) + newval.tri = expr_calc_value(prop->expr); + } + newval.tri = E_OR(E_AND(newval.tri, sym->visible), sym->rev_dep.tri); + } else if (!sym_is_choice(sym)) { + prop = sym_get_default_prop(sym); + if (prop) { + sym->flags |= SYMBOL_WRITE; + newval.tri = expr_calc_value(prop->expr); + } + } + if (newval.tri == mod && sym_get_type(sym) == S_BOOLEAN) + newval.tri = yes; + break; + case S_STRING: + case S_HEX: + case S_INT: + if (sym->visible != no) { + sym->flags |= SYMBOL_WRITE; + if (sym_has_value(sym)) { + newval.val = sym->user.val; + break; + } + } + prop = sym_get_default_prop(sym); + if (prop) { + struct symbol *ds = prop_get_symbol(prop); + if (ds) { + sym->flags |= SYMBOL_WRITE; + sym_calc_value(ds); + newval.val = ds->curr.val; + } + } + break; + default: + ; + } + + sym->curr = newval; + if (sym_is_choice(sym) && newval.tri == yes) + sym->curr.val = sym_calc_choice(sym); + sym_validate_range(sym); + + if (memcmp(&oldval, &sym->curr, sizeof(oldval))) + sym_set_changed(sym); + if (modules_sym == sym) + modules_val = modules_sym->curr.tri; + + if (sym_is_choice(sym)) { + int flags = sym->flags & (SYMBOL_CHANGED | SYMBOL_WRITE); + prop = sym_get_choice_prop(sym); + for (e = prop->expr; e; e = e->left.expr) { + e->right.sym->flags |= flags; + if (flags & SYMBOL_CHANGED) + sym_set_changed(e->right.sym); + } + } +} + +void sym_clear_all_valid(void) +{ + struct symbol *sym; + int i; + + for_all_symbols(i, sym) + sym->flags &= ~SYMBOL_VALID; + sym_change_count++; + if (modules_sym) + sym_calc_value(modules_sym); +} + +void sym_set_changed(struct symbol *sym) +{ + struct property *prop; + + sym->flags |= SYMBOL_CHANGED; + for (prop = sym->prop; prop; prop = prop->next) { + if (prop->menu) + prop->menu->flags |= MENU_CHANGED; + } +} + +void sym_set_all_changed(void) +{ + struct symbol *sym; + int i; + + for_all_symbols(i, sym) + sym_set_changed(sym); +} + +bool sym_tristate_within_range(struct symbol *sym, tristate val) +{ + int type = sym_get_type(sym); + + if (sym->visible == no) + return false; + + if (type != S_BOOLEAN && type != S_TRISTATE) + return false; + + if (type == S_BOOLEAN && val == mod) + return false; + if (sym->visible <= sym->rev_dep.tri) + return false; + if (sym_is_choice_value(sym) && sym->visible == yes) + return val == yes; + return val >= sym->rev_dep.tri && val <= sym->visible; +} + +bool sym_set_tristate_value(struct symbol *sym, tristate val) +{ + tristate oldval = sym_get_tristate_value(sym); + + if (oldval != val && !sym_tristate_within_range(sym, val)) + return false; + + if (sym->flags & SYMBOL_NEW) { + sym->flags &= ~SYMBOL_NEW; + sym_set_changed(sym); + } + /* + * setting a choice value also resets the new flag of the choice + * symbol and all other choice values. + */ + if (sym_is_choice_value(sym) && val == yes) { + struct symbol *cs = prop_get_symbol(sym_get_choice_prop(sym)); + struct property *prop; + struct expr *e; + + cs->user.val = sym; + cs->flags &= ~SYMBOL_NEW; + prop = sym_get_choice_prop(cs); + for (e = prop->expr; e; e = e->left.expr) { + if (e->right.sym->visible != no) + e->right.sym->flags &= ~SYMBOL_NEW; + } + } + + sym->user.tri = val; + if (oldval != val) { + sym_clear_all_valid(); + if (sym == modules_sym) + sym_set_all_changed(); + } + + return true; +} + +tristate sym_toggle_tristate_value(struct symbol *sym) +{ + tristate oldval, newval; + + oldval = newval = sym_get_tristate_value(sym); + do { + switch (newval) { + case no: + newval = mod; + break; + case mod: + newval = yes; + break; + case yes: + newval = no; + break; + } + if (sym_set_tristate_value(sym, newval)) + break; + } while (oldval != newval); + return newval; +} + +bool sym_string_valid(struct symbol *sym, const char *str) +{ + signed char ch; + + switch (sym->type) { + case S_STRING: + return true; + case S_INT: + ch = *str++; + if (ch == '-') + ch = *str++; + if (!isdigit(ch)) + return false; + if (ch == '0' && *str != 0) + return false; + while ((ch = *str++)) { + if (!isdigit(ch)) + return false; + } + return true; + case S_HEX: + if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) + str += 2; + ch = *str++; + do { + if (!isxdigit(ch)) + return false; + } while ((ch = *str++)); + return true; + case S_BOOLEAN: + case S_TRISTATE: + switch (str[0]) { + case 'y': case 'Y': + case 'm': case 'M': + case 'n': case 'N': + return true; + } + return false; + default: + return false; + } +} + +bool sym_string_within_range(struct symbol *sym, const char *str) +{ + struct property *prop; + int val; + + switch (sym->type) { + case S_STRING: + return sym_string_valid(sym, str); + case S_INT: + if (!sym_string_valid(sym, str)) + return false; + prop = sym_get_range_prop(sym); + if (!prop) + return true; + val = strtol(str, NULL, 10); + return val >= sym_get_range_val(prop->expr->left.sym, 10) && + val <= sym_get_range_val(prop->expr->right.sym, 10); + case S_HEX: + if (!sym_string_valid(sym, str)) + return false; + prop = sym_get_range_prop(sym); + if (!prop) + return true; + val = strtol(str, NULL, 16); + return val >= sym_get_range_val(prop->expr->left.sym, 16) && + val <= sym_get_range_val(prop->expr->right.sym, 16); + case S_BOOLEAN: + case S_TRISTATE: + switch (str[0]) { + case 'y': case 'Y': + return sym_tristate_within_range(sym, yes); + case 'm': case 'M': + return sym_tristate_within_range(sym, mod); + case 'n': case 'N': + return sym_tristate_within_range(sym, no); + } + return false; + default: + return false; + } +} + +bool sym_set_string_value(struct symbol *sym, const char *newval) +{ + const char *oldval; + char *val; + int size; + + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + switch (newval[0]) { + case 'y': case 'Y': + return sym_set_tristate_value(sym, yes); + case 'm': case 'M': + return sym_set_tristate_value(sym, mod); + case 'n': case 'N': + return sym_set_tristate_value(sym, no); + } + return false; + default: + ; + } + + if (!sym_string_within_range(sym, newval)) + return false; + + if (sym->flags & SYMBOL_NEW) { + sym->flags &= ~SYMBOL_NEW; + sym_set_changed(sym); + } + + oldval = sym->user.val; + size = strlen(newval) + 1; + if (sym->type == S_HEX && (newval[0] != '0' || (newval[1] != 'x' && newval[1] != 'X'))) { + size += 2; + sym->user.val = val = malloc(size); + *val++ = '0'; + *val++ = 'x'; + } else if (!oldval || strcmp(oldval, newval)) + sym->user.val = val = malloc(size); + else + return true; + + strcpy(val, newval); + free((void *)oldval); + sym_clear_all_valid(); + + return true; +} + +const char *sym_get_string_value(struct symbol *sym) +{ + tristate val; + + switch (sym->type) { + case S_BOOLEAN: + case S_TRISTATE: + val = sym_get_tristate_value(sym); + switch (val) { + case no: + return "n"; + case mod: + return "m"; + case yes: + return "y"; + } + break; + default: + ; + } + return (const char *)sym->curr.val; +} + +bool sym_is_changable(struct symbol *sym) +{ + return sym->visible > sym->rev_dep.tri; +} + +struct symbol *sym_lookup(const char *name, int isconst) +{ + struct symbol *symbol; + const char *ptr; + char *new_name; + int hash = 0; + + if (name) { + if (name[0] && !name[1]) { + switch (name[0]) { + case 'y': return &symbol_yes; + case 'm': return &symbol_mod; + case 'n': return &symbol_no; + } + } + for (ptr = name; *ptr; ptr++) + hash += *ptr; + hash &= 0xff; + + for (symbol = symbol_hash[hash]; symbol; symbol = symbol->next) { + if (!strcmp(symbol->name, name)) { + if ((isconst && symbol->flags & SYMBOL_CONST) || + (!isconst && !(symbol->flags & SYMBOL_CONST))) + return symbol; + } + } + new_name = strdup(name); + } else { + new_name = NULL; + hash = 256; + } + + symbol = malloc(sizeof(*symbol)); + memset(symbol, 0, sizeof(*symbol)); + symbol->name = new_name; + symbol->type = S_UNKNOWN; + symbol->flags = SYMBOL_NEW; + if (isconst) + symbol->flags |= SYMBOL_CONST; + + symbol->next = symbol_hash[hash]; + symbol_hash[hash] = symbol; + + return symbol; +} + +struct symbol *sym_find(const char *name) +{ + struct symbol *symbol = NULL; + const char *ptr; + int hash = 0; + + if (!name) + return NULL; + + if (name[0] && !name[1]) { + switch (name[0]) { + case 'y': return &symbol_yes; + case 'm': return &symbol_mod; + case 'n': return &symbol_no; + } + } + for (ptr = name; *ptr; ptr++) + hash += *ptr; + hash &= 0xff; + + for (symbol = symbol_hash[hash]; symbol; symbol = symbol->next) { + if (!strcmp(symbol->name, name) && + !(symbol->flags & SYMBOL_CONST)) + break; + } + + return symbol; +} + +struct symbol **sym_re_search(const char *pattern) +{ + struct symbol *sym, **sym_arr = NULL; + int i, cnt, size; + regex_t re; + + cnt = size = 0; + /* Skip if empty */ + if (strlen(pattern) == 0) + return NULL; + if (regcomp(&re, pattern, REG_EXTENDED|REG_NOSUB|REG_ICASE)) + return NULL; + + for_all_symbols(i, sym) { + if (sym->flags & SYMBOL_CONST || !sym->name) + continue; + if (regexec(&re, sym->name, 0, NULL, 0)) + continue; + if (cnt + 1 >= size) { + void *tmp = sym_arr; + size += 16; + sym_arr = realloc(sym_arr, size * sizeof(struct symbol *)); + if (!sym_arr) { + free(tmp); + return NULL; + } + } + sym_arr[cnt++] = sym; + } + if (sym_arr) + sym_arr[cnt] = NULL; + regfree(&re); + + return sym_arr; +} + + +struct symbol *sym_check_deps(struct symbol *sym); + +static struct symbol *sym_check_expr_deps(struct expr *e) +{ + struct symbol *sym; + + if (!e) + return NULL; + switch (e->type) { + case E_OR: + case E_AND: + sym = sym_check_expr_deps(e->left.expr); + if (sym) + return sym; + return sym_check_expr_deps(e->right.expr); + case E_NOT: + return sym_check_expr_deps(e->left.expr); + case E_EQUAL: + case E_UNEQUAL: + sym = sym_check_deps(e->left.sym); + if (sym) + return sym; + return sym_check_deps(e->right.sym); + case E_SYMBOL: + return sym_check_deps(e->left.sym); + default: + break; + } + printf("Oops! How to check %d?\n", e->type); + return NULL; +} + +struct symbol *sym_check_deps(struct symbol *sym) +{ + struct symbol *sym2; + struct property *prop; + + if (sym->flags & SYMBOL_CHECK) { + printf("Warning! Found recursive dependency: %s", sym->name); + return sym; + } + if (sym->flags & SYMBOL_CHECKED) + return NULL; + + sym->flags |= (SYMBOL_CHECK | SYMBOL_CHECKED); + sym2 = sym_check_expr_deps(sym->rev_dep.expr); + if (sym2) + goto out; + + for (prop = sym->prop; prop; prop = prop->next) { + if (prop->type == P_CHOICE || prop->type == P_SELECT) + continue; + sym2 = sym_check_expr_deps(prop->visible.expr); + if (sym2) + goto out; + if (prop->type != P_DEFAULT || sym_is_choice(sym)) + continue; + sym2 = sym_check_expr_deps(prop->expr); + if (sym2) + goto out; + } +out: + if (sym2) { + printf(" %s", sym->name); + if (sym2 == sym) { + printf("\n"); + sym2 = NULL; + } + } + sym->flags &= ~SYMBOL_CHECK; + return sym2; +} + +struct property *prop_alloc(enum prop_type type, struct symbol *sym) +{ + struct property *prop; + struct property **propp; + + prop = malloc(sizeof(*prop)); + memset(prop, 0, sizeof(*prop)); + prop->type = type; + prop->sym = sym; + prop->file = current_file; + prop->lineno = zconf_lineno(); + + /* append property to the prop list of symbol */ + if (sym) { + for (propp = &sym->prop; *propp; propp = &(*propp)->next) + ; + *propp = prop; + } + + return prop; +} + +struct symbol *prop_get_symbol(struct property *prop) +{ + if (prop->expr && (prop->expr->type == E_SYMBOL || + prop->expr->type == E_CHOICE)) + return prop->expr->left.sym; + return NULL; +} + +const char *prop_get_type_name(enum prop_type type) +{ + switch (type) { + case P_PROMPT: + return "prompt"; + case P_COMMENT: + return "comment"; + case P_MENU: + return "menu"; + case P_DEFAULT: + return "default"; + case P_CHOICE: + return "choice"; + case P_SELECT: + return "select"; + case P_RANGE: + return "range"; + case P_UNKNOWN: + break; + } + return "unknown"; +} diff --git a/scripts/kconfig/util.c b/scripts/kconfig/util.c new file mode 100644 index 000000000..aea8d56ce --- /dev/null +++ b/scripts/kconfig/util.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2002-2005 Roman Zippel <zippel@linux-m68k.org> + * Copyright (C) 2002-2005 Sam Ravnborg <sam@ravnborg.org> + * + * Released under the terms of the GNU GPL v2.0. + */ + +#include <string.h> +#include "lkc.h" + +/* file already present in list? If not add it */ +struct file *file_lookup(const char *name) +{ + struct file *file; + + for (file = file_list; file; file = file->next) { + if (!strcmp(name, file->name)) + return file; + } + + file = malloc(sizeof(*file)); + memset(file, 0, sizeof(*file)); + file->name = strdup(name); + file->next = file_list; + file_list = file; + return file; +} + +/* write a dependency file as used by kbuild to track dependencies */ +int file_write_dep(const char *name) +{ + struct file *file; + FILE *out; + + if (!name) + name = ".kconfig.d"; + out = fopen("..config.tmp", "w"); + if (!out) + return 1; + fprintf(out, "deps_config := \\\n"); + for (file = file_list; file; file = file->next) { + if (file->next) + fprintf(out, "\t%s \\\n", file->name); + else + fprintf(out, "\t%s\n", file->name); + } + fprintf(out, "\n.config include/autoconf.h: $(deps_config)\n\n$(deps_config):\n"); + fclose(out); + rename("..config.tmp", name); + return 0; +} + + +/* Allocate initial growable sting */ +struct gstr str_new(void) +{ + struct gstr gs; + gs.s = malloc(sizeof(char) * 64); + gs.len = 16; + strcpy(gs.s, "\0"); + return gs; +} + +/* Allocate and assign growable string */ +struct gstr str_assign(const char *s) +{ + struct gstr gs; + gs.s = strdup(s); + gs.len = strlen(s) + 1; + return gs; +} + +/* Free storage for growable string */ +void str_free(struct gstr *gs) +{ + if (gs->s) + free(gs->s); + gs->s = NULL; + gs->len = 0; +} + +/* Append to growable string */ +void str_append(struct gstr *gs, const char *s) +{ + size_t l = strlen(gs->s) + strlen(s) + 1; + if (l > gs->len) { + gs->s = realloc(gs->s, l); + gs->len = l; + } + strcat(gs->s, s); +} + +/* Append printf formatted string to growable string */ +void str_printf(struct gstr *gs, const char *fmt, ...) +{ + va_list ap; + char s[10000]; /* big enough... */ + va_start(ap, fmt); + vsnprintf(s, sizeof(s), fmt, ap); + str_append(gs, s); + va_end(ap); +} + +/* Retrieve value of growable string */ +const char *str_get(struct gstr *gs) +{ + return gs->s; +} + diff --git a/scripts/kconfig/zconf.gperf b/scripts/kconfig/zconf.gperf new file mode 100644 index 000000000..b03220600 --- /dev/null +++ b/scripts/kconfig/zconf.gperf @@ -0,0 +1,43 @@ +%language=ANSI-C +%define hash-function-name kconf_id_hash +%define lookup-function-name kconf_id_lookup +%define string-pool-name kconf_id_strings +%compare-strncmp +%enum +%pic +%struct-type + +struct kconf_id; + +%% +mainmenu, T_MAINMENU, TF_COMMAND +menu, T_MENU, TF_COMMAND +endmenu, T_ENDMENU, TF_COMMAND +source, T_SOURCE, TF_COMMAND +choice, T_CHOICE, TF_COMMAND +endchoice, T_ENDCHOICE, TF_COMMAND +comment, T_COMMENT, TF_COMMAND +config, T_CONFIG, TF_COMMAND +menuconfig, T_MENUCONFIG, TF_COMMAND +help, T_HELP, TF_COMMAND +if, T_IF, TF_COMMAND|TF_PARAM +endif, T_ENDIF, TF_COMMAND +depends, T_DEPENDS, TF_COMMAND +requires, T_REQUIRES, TF_COMMAND +optional, T_OPTIONAL, TF_COMMAND +default, T_DEFAULT, TF_COMMAND, S_UNKNOWN +prompt, T_PROMPT, TF_COMMAND +tristate, T_TYPE, TF_COMMAND, S_TRISTATE +def_tristate, T_DEFAULT, TF_COMMAND, S_TRISTATE +bool, T_TYPE, TF_COMMAND, S_BOOLEAN +boolean, T_TYPE, TF_COMMAND, S_BOOLEAN +def_bool, T_DEFAULT, TF_COMMAND, S_BOOLEAN +def_boolean, T_DEFAULT, TF_COMMAND, S_BOOLEAN +int, T_TYPE, TF_COMMAND, S_INT +hex, T_TYPE, TF_COMMAND, S_HEX +string, T_TYPE, TF_COMMAND, S_STRING +select, T_SELECT, TF_COMMAND +enable, T_SELECT, TF_COMMAND +range, T_RANGE, TF_COMMAND +on, T_ON, TF_PARAM +%% diff --git a/scripts/kconfig/zconf.hash.c_shipped b/scripts/kconfig/zconf.hash.c_shipped new file mode 100644 index 000000000..345f0fc07 --- /dev/null +++ b/scripts/kconfig/zconf.hash.c_shipped @@ -0,0 +1,231 @@ +/* ANSI-C code produced by gperf version 3.0.1 */ +/* Command-line: gperf */ +/* Computed positions: -k'1,3' */ + +#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ + && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ + && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \ + && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \ + && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \ + && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \ + && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \ + && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \ + && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \ + && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \ + && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \ + && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \ + && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \ + && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \ + && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \ + && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \ + && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \ + && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \ + && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \ + && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \ + && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \ + && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \ + && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126)) +/* The character set is not based on ISO-646. */ +#error "gperf generated tables don't work with this execution character set. Please report a bug to <bug-gnu-gperf@gnu.org>." +#endif + +struct kconf_id; +/* maximum key range = 45, duplicates = 0 */ + +#ifdef __GNUC__ +__inline +#else +#ifdef __cplusplus +inline +#endif +#endif +static unsigned int +kconf_id_hash (register const char *str, register unsigned int len) +{ + static unsigned char asso_values[] = + { + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 25, 10, 15, + 0, 0, 5, 47, 0, 0, 47, 47, 0, 10, + 0, 20, 20, 20, 5, 0, 0, 20, 47, 47, + 20, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47, 47, 47, 47, 47, + 47, 47, 47, 47, 47, 47 + }; + register int hval = len; + + switch (hval) + { + default: + hval += asso_values[(unsigned char)str[2]]; + /*FALLTHROUGH*/ + case 2: + case 1: + hval += asso_values[(unsigned char)str[0]]; + break; + } + return hval; +} + +struct kconf_id_strings_t + { + char kconf_id_strings_str2[sizeof("if")]; + char kconf_id_strings_str3[sizeof("int")]; + char kconf_id_strings_str4[sizeof("help")]; + char kconf_id_strings_str5[sizeof("endif")]; + char kconf_id_strings_str6[sizeof("select")]; + char kconf_id_strings_str7[sizeof("endmenu")]; + char kconf_id_strings_str8[sizeof("tristate")]; + char kconf_id_strings_str9[sizeof("endchoice")]; + char kconf_id_strings_str10[sizeof("range")]; + char kconf_id_strings_str11[sizeof("string")]; + char kconf_id_strings_str12[sizeof("default")]; + char kconf_id_strings_str13[sizeof("def_bool")]; + char kconf_id_strings_str14[sizeof("menu")]; + char kconf_id_strings_str16[sizeof("def_boolean")]; + char kconf_id_strings_str17[sizeof("def_tristate")]; + char kconf_id_strings_str18[sizeof("mainmenu")]; + char kconf_id_strings_str20[sizeof("menuconfig")]; + char kconf_id_strings_str21[sizeof("config")]; + char kconf_id_strings_str22[sizeof("on")]; + char kconf_id_strings_str23[sizeof("hex")]; + char kconf_id_strings_str26[sizeof("source")]; + char kconf_id_strings_str27[sizeof("depends")]; + char kconf_id_strings_str28[sizeof("optional")]; + char kconf_id_strings_str31[sizeof("enable")]; + char kconf_id_strings_str32[sizeof("comment")]; + char kconf_id_strings_str33[sizeof("requires")]; + char kconf_id_strings_str34[sizeof("bool")]; + char kconf_id_strings_str37[sizeof("boolean")]; + char kconf_id_strings_str41[sizeof("choice")]; + char kconf_id_strings_str46[sizeof("prompt")]; + }; +static struct kconf_id_strings_t kconf_id_strings_contents = + { + "if", + "int", + "help", + "endif", + "select", + "endmenu", + "tristate", + "endchoice", + "range", + "string", + "default", + "def_bool", + "menu", + "def_boolean", + "def_tristate", + "mainmenu", + "menuconfig", + "config", + "on", + "hex", + "source", + "depends", + "optional", + "enable", + "comment", + "requires", + "bool", + "boolean", + "choice", + "prompt" + }; +#define kconf_id_strings ((const char *) &kconf_id_strings_contents) +#ifdef __GNUC__ +__inline +#endif +struct kconf_id * +kconf_id_lookup (register const char *str, register unsigned int len) +{ + enum + { + TOTAL_KEYWORDS = 30, + MIN_WORD_LENGTH = 2, + MAX_WORD_LENGTH = 12, + MIN_HASH_VALUE = 2, + MAX_HASH_VALUE = 46 + }; + + static struct kconf_id wordlist[] = + { + {-1}, {-1}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str2, T_IF, TF_COMMAND|TF_PARAM}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str3, T_TYPE, TF_COMMAND, S_INT}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str4, T_HELP, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str5, T_ENDIF, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str6, T_SELECT, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str7, T_ENDMENU, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str8, T_TYPE, TF_COMMAND, S_TRISTATE}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str9, T_ENDCHOICE, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str10, T_RANGE, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str11, T_TYPE, TF_COMMAND, S_STRING}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str12, T_DEFAULT, TF_COMMAND, S_UNKNOWN}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str13, T_DEFAULT, TF_COMMAND, S_BOOLEAN}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str14, T_MENU, TF_COMMAND}, + {-1}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str16, T_DEFAULT, TF_COMMAND, S_BOOLEAN}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str17, T_DEFAULT, TF_COMMAND, S_TRISTATE}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str18, T_MAINMENU, TF_COMMAND}, + {-1}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str20, T_MENUCONFIG, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str21, T_CONFIG, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str22, T_ON, TF_PARAM}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str23, T_TYPE, TF_COMMAND, S_HEX}, + {-1}, {-1}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str26, T_SOURCE, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str27, T_DEPENDS, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str28, T_OPTIONAL, TF_COMMAND}, + {-1}, {-1}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str31, T_SELECT, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str32, T_COMMENT, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str33, T_REQUIRES, TF_COMMAND}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str34, T_TYPE, TF_COMMAND, S_BOOLEAN}, + {-1}, {-1}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str37, T_TYPE, TF_COMMAND, S_BOOLEAN}, + {-1}, {-1}, {-1}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str41, T_CHOICE, TF_COMMAND}, + {-1}, {-1}, {-1}, {-1}, + {(int)(long)&((struct kconf_id_strings_t *)0)->kconf_id_strings_str46, T_PROMPT, TF_COMMAND} + }; + + if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) + { + register int key = kconf_id_hash (str, len); + + if (key <= MAX_HASH_VALUE && key >= 0) + { + register int o = wordlist[key].name; + if (o >= 0) + { + register const char *s = o + kconf_id_strings; + + if (*str == *s && !strncmp (str + 1, s + 1, len - 1) && s[len] == '\0') + return &wordlist[key]; + } + } + } + return 0; +} + diff --git a/scripts/kconfig/zconf.l b/scripts/kconfig/zconf.l new file mode 100644 index 000000000..cfa46077c --- /dev/null +++ b/scripts/kconfig/zconf.l @@ -0,0 +1,350 @@ +%option backup nostdinit noyywrap never-interactive full ecs +%option 8bit backup nodefault perf-report perf-report +%x COMMAND HELP STRING PARAM +%{ +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + * Released under the terms of the GNU GPL v2.0. + */ + +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define LKC_DIRECT_LINK +#include "lkc.h" + +#define START_STRSIZE 16 + +static struct { + struct file *file; + int lineno; +} current_pos; + +static char *text; +static int text_size, text_asize; + +struct buffer { + struct buffer *parent; + YY_BUFFER_STATE state; +}; + +struct buffer *current_buf; + +static int last_ts, first_ts; + +static void zconf_endhelp(void); +static void zconf_endfile(void); + +void new_string(void) +{ + text = malloc(START_STRSIZE); + text_asize = START_STRSIZE; + text_size = 0; + *text = 0; +} + +void append_string(const char *str, int size) +{ + int new_size = text_size + size + 1; + if (new_size > text_asize) { + new_size += START_STRSIZE - 1; + new_size &= -START_STRSIZE; + text = realloc(text, new_size); + text_asize = new_size; + } + memcpy(text + text_size, str, size); + text_size += size; + text[text_size] = 0; +} + +void alloc_string(const char *str, int size) +{ + text = malloc(size + 1); + memcpy(text, str, size); + text[size] = 0; +} +%} + +ws [ \n\t] +n [A-Za-z0-9_] + +%% + int str = 0; + int ts, i; + +[ \t]*#.*\n | +[ \t]*\n { + current_file->lineno++; + return T_EOL; +} +[ \t]*#.* + + +[ \t]+ { + BEGIN(COMMAND); +} + +. { + unput(yytext[0]); + BEGIN(COMMAND); +} + + +<COMMAND>{ + {n}+ { + struct kconf_id *id = kconf_id_lookup(yytext, yyleng); + BEGIN(PARAM); + current_pos.file = current_file; + current_pos.lineno = current_file->lineno; + if (id && id->flags & TF_COMMAND) { + zconflval.id = id; + return id->token; + } + alloc_string(yytext, yyleng); + zconflval.string = text; + return T_WORD; + } + . + \n { + BEGIN(INITIAL); + current_file->lineno++; + return T_EOL; + } +} + +<PARAM>{ + "&&" return T_AND; + "||" return T_OR; + "(" return T_OPEN_PAREN; + ")" return T_CLOSE_PAREN; + "!" return T_NOT; + "=" return T_EQUAL; + "!=" return T_UNEQUAL; + \"|\' { + str = yytext[0]; + new_string(); + BEGIN(STRING); + } + \n BEGIN(INITIAL); current_file->lineno++; return T_EOL; + --- /* ignore */ + ({n}|[-/.])+ { + struct kconf_id *id = kconf_id_lookup(yytext, yyleng); + if (id && id->flags & TF_PARAM) { + zconflval.id = id; + return id->token; + } + alloc_string(yytext, yyleng); + zconflval.string = text; + return T_WORD; + } + #.* /* comment */ + \\\n current_file->lineno++; + . + <<EOF>> { + BEGIN(INITIAL); + } +} + +<STRING>{ + [^'"\\\n]+/\n { + append_string(yytext, yyleng); + zconflval.string = text; + return T_WORD_QUOTE; + } + [^'"\\\n]+ { + append_string(yytext, yyleng); + } + \\.?/\n { + append_string(yytext + 1, yyleng - 1); + zconflval.string = text; + return T_WORD_QUOTE; + } + \\.? { + append_string(yytext + 1, yyleng - 1); + } + \'|\" { + if (str == yytext[0]) { + BEGIN(PARAM); + zconflval.string = text; + return T_WORD_QUOTE; + } else + append_string(yytext, 1); + } + \n { + printf("%s:%d:warning: multi-line strings not supported\n", zconf_curname(), zconf_lineno()); + current_file->lineno++; + BEGIN(INITIAL); + return T_EOL; + } + <<EOF>> { + BEGIN(INITIAL); + } +} + +<HELP>{ + [ \t]+ { + ts = 0; + for (i = 0; i < yyleng; i++) { + if (yytext[i] == '\t') + ts = (ts & ~7) + 8; + else + ts++; + } + last_ts = ts; + if (first_ts) { + if (ts < first_ts) { + zconf_endhelp(); + return T_HELPTEXT; + } + ts -= first_ts; + while (ts > 8) { + append_string(" ", 8); + ts -= 8; + } + append_string(" ", ts); + } + } + [ \t]*\n/[^ \t\n] { + current_file->lineno++; + zconf_endhelp(); + return T_HELPTEXT; + } + [ \t]*\n { + current_file->lineno++; + append_string("\n", 1); + } + [^ \t\n].* { + append_string(yytext, yyleng); + if (!first_ts) + first_ts = last_ts; + } + <<EOF>> { + zconf_endhelp(); + return T_HELPTEXT; + } +} + +<<EOF>> { + if (current_file) { + zconf_endfile(); + return T_EOL; + } + fclose(yyin); + yyterminate(); +} + +%% +void zconf_starthelp(void) +{ + new_string(); + last_ts = first_ts = 0; + BEGIN(HELP); +} + +static void zconf_endhelp(void) +{ + zconflval.string = text; + BEGIN(INITIAL); +} + + +/* + * Try to open specified file with following names: + * ./name + * $(srctree)/name + * The latter is used when srctree is separate from objtree + * when compiling the kernel. + * Return NULL if file is not found. + */ +FILE *zconf_fopen(const char *name) +{ + char *env, fullname[PATH_MAX+1]; + FILE *f; + + f = fopen(name, "r"); + if (!f && name[0] != '/') { + env = getenv(SRCTREE); + if (env) { + sprintf(fullname, "%s/%s", env, name); + f = fopen(fullname, "r"); + } + } + return f; +} + +void zconf_initscan(const char *name) +{ + yyin = zconf_fopen(name); + if (!yyin) { + printf("can't find file %s\n", name); + exit(1); + } + + current_buf = malloc(sizeof(*current_buf)); + memset(current_buf, 0, sizeof(*current_buf)); + + current_file = file_lookup(name); + current_file->lineno = 1; + current_file->flags = FILE_BUSY; +} + +void zconf_nextfile(const char *name) +{ + struct file *file = file_lookup(name); + struct buffer *buf = malloc(sizeof(*buf)); + memset(buf, 0, sizeof(*buf)); + + current_buf->state = YY_CURRENT_BUFFER; + yyin = zconf_fopen(name); + if (!yyin) { + printf("%s:%d: can't open file \"%s\"\n", zconf_curname(), zconf_lineno(), name); + exit(1); + } + yy_switch_to_buffer(yy_create_buffer(yyin, YY_BUF_SIZE)); + buf->parent = current_buf; + current_buf = buf; + + if (file->flags & FILE_BUSY) { + printf("recursive scan (%s)?\n", name); + exit(1); + } + if (file->flags & FILE_SCANNED) { + printf("file %s already scanned?\n", name); + exit(1); + } + file->flags |= FILE_BUSY; + file->lineno = 1; + file->parent = current_file; + current_file = file; +} + +static void zconf_endfile(void) +{ + struct buffer *parent; + + current_file->flags |= FILE_SCANNED; + current_file->flags &= ~FILE_BUSY; + current_file = current_file->parent; + + parent = current_buf->parent; + if (parent) { + fclose(yyin); + yy_delete_buffer(YY_CURRENT_BUFFER); + yy_switch_to_buffer(parent->state); + } + free(current_buf); + current_buf = parent; +} + +int zconf_lineno(void) +{ + return current_pos.lineno; +} + +char *zconf_curname(void) +{ + return current_pos.file ? current_pos.file->name : "<none>"; +} diff --git a/scripts/kconfig/zconf.tab.c_shipped b/scripts/kconfig/zconf.tab.c_shipped new file mode 100644 index 000000000..e14eafa6c --- /dev/null +++ b/scripts/kconfig/zconf.tab.c_shipped @@ -0,0 +1,2173 @@ +/* A Bison parser, made by GNU Bison 2.0. */ + +/* Skeleton parser for Yacc-like parsing with Bison, + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc. + + 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, 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., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +/* As a special exception, when this file is copied by Bison into a + Bison output file, you may use that output file without restriction. + This special exception was added by the Free Software Foundation + in version 1.24 of Bison. */ + +/* Written by Richard Stallman by simplifying the original so called + ``semantic'' parser. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 0 + +/* Using locations. */ +#define YYLSP_NEEDED 0 + +/* Substitute the variable and function names. */ +#define yyparse zconfparse +#define yylex zconflex +#define yyerror zconferror +#define yylval zconflval +#define yychar zconfchar +#define yydebug zconfdebug +#define yynerrs zconfnerrs + + +/* Tokens. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + /* Put the tokens into the symbol table, so that GDB and other debuggers + know about them. */ + enum yytokentype { + T_MAINMENU = 258, + T_MENU = 259, + T_ENDMENU = 260, + T_SOURCE = 261, + T_CHOICE = 262, + T_ENDCHOICE = 263, + T_COMMENT = 264, + T_CONFIG = 265, + T_MENUCONFIG = 266, + T_HELP = 267, + T_HELPTEXT = 268, + T_IF = 269, + T_ENDIF = 270, + T_DEPENDS = 271, + T_REQUIRES = 272, + T_OPTIONAL = 273, + T_PROMPT = 274, + T_TYPE = 275, + T_DEFAULT = 276, + T_SELECT = 277, + T_RANGE = 278, + T_ON = 279, + T_WORD = 280, + T_WORD_QUOTE = 281, + T_UNEQUAL = 282, + T_CLOSE_PAREN = 283, + T_OPEN_PAREN = 284, + T_EOL = 285, + T_OR = 286, + T_AND = 287, + T_EQUAL = 288, + T_NOT = 289 + }; +#endif +#define T_MAINMENU 258 +#define T_MENU 259 +#define T_ENDMENU 260 +#define T_SOURCE 261 +#define T_CHOICE 262 +#define T_ENDCHOICE 263 +#define T_COMMENT 264 +#define T_CONFIG 265 +#define T_MENUCONFIG 266 +#define T_HELP 267 +#define T_HELPTEXT 268 +#define T_IF 269 +#define T_ENDIF 270 +#define T_DEPENDS 271 +#define T_REQUIRES 272 +#define T_OPTIONAL 273 +#define T_PROMPT 274 +#define T_TYPE 275 +#define T_DEFAULT 276 +#define T_SELECT 277 +#define T_RANGE 278 +#define T_ON 279 +#define T_WORD 280 +#define T_WORD_QUOTE 281 +#define T_UNEQUAL 282 +#define T_CLOSE_PAREN 283 +#define T_OPEN_PAREN 284 +#define T_EOL 285 +#define T_OR 286 +#define T_AND 287 +#define T_EQUAL 288 +#define T_NOT 289 + + + + +/* Copy the first part of user declarations. */ + + +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + * Released under the terms of the GNU GPL v2.0. + */ + +#include <ctype.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> + +#define LKC_DIRECT_LINK +#include "lkc.h" + +#include "zconf.hash.c" + +#define printd(mask, fmt...) if (cdebug & (mask)) printf(fmt) + +#define PRINTD 0x0001 +#define DEBUG_PARSE 0x0002 + +int cdebug = PRINTD; + +extern int zconflex(void); +static void zconfprint(const char *err, ...); +static void zconf_error(const char *err, ...); +static void zconferror(const char *err); +static bool zconf_endtoken(struct kconf_id *id, int starttoken, int endtoken); + +struct symbol *symbol_hash[257]; + +static struct menu *current_menu, *current_entry; + +#define YYDEBUG 0 +#if YYDEBUG +#define YYERROR_VERBOSE +#endif + + +/* Enabling traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif + +/* Enabling verbose error messages. */ +#ifdef YYERROR_VERBOSE +# undef YYERROR_VERBOSE +# define YYERROR_VERBOSE 1 +#else +# define YYERROR_VERBOSE 0 +#endif + +#if ! defined (YYSTYPE) && ! defined (YYSTYPE_IS_DECLARED) + +typedef union YYSTYPE { + char *string; + struct file *file; + struct symbol *symbol; + struct expr *expr; + struct menu *menu; + struct kconf_id *id; +} YYSTYPE; +/* Line 190 of yacc.c. */ + +# define yystype YYSTYPE /* obsolescent; will be withdrawn */ +# define YYSTYPE_IS_DECLARED 1 +# define YYSTYPE_IS_TRIVIAL 1 +#endif + + + +/* Copy the second part of user declarations. */ + + +/* Line 213 of yacc.c. */ + + +#if ! defined (yyoverflow) || YYERROR_VERBOSE + +# ifndef YYFREE +# define YYFREE free +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# endif + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# else +# define YYSTACK_ALLOC alloca +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's `empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0) +# else +# if defined (__STDC__) || defined (__cplusplus) +# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# endif +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# endif +#endif /* ! defined (yyoverflow) || YYERROR_VERBOSE */ + + +#if (! defined (yyoverflow) \ + && (! defined (__cplusplus) \ + || (defined (YYSTYPE_IS_TRIVIAL) && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + short int yyss; + YYSTYPE yyvs; + }; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (short int) + sizeof (YYSTYPE)) \ + + YYSTACK_GAP_MAXIMUM) + +/* Copy COUNT objects from FROM to TO. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined (__GNUC__) && 1 < __GNUC__ +# define YYCOPY(To, From, Count) \ + __builtin_memcpy (To, From, (Count) * sizeof (*(From))) +# else +# define YYCOPY(To, From, Count) \ + do \ + { \ + register YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (To)[yyi] = (From)[yyi]; \ + } \ + while (0) +# endif +# endif + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack, Stack, yysize); \ + Stack = &yyptr->Stack; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (0) + +#endif + +#if defined (__STDC__) || defined (__cplusplus) + typedef signed char yysigned_char; +#else + typedef short int yysigned_char; +#endif + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 3 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 264 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 35 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 42 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 104 +/* YYNRULES -- Number of states. */ +#define YYNSTATES 175 + +/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ +#define YYUNDEFTOK 2 +#define YYMAXUTOK 289 + +#define YYTRANSLATE(YYX) \ + ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + +/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */ +static const unsigned char yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34 +}; + +#if YYDEBUG +/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in + YYRHS. */ +static const unsigned short int yyprhs[] = +{ + 0, 0, 3, 5, 6, 9, 12, 15, 20, 23, + 28, 33, 37, 39, 41, 43, 45, 47, 49, 51, + 53, 55, 57, 59, 61, 63, 67, 70, 74, 77, + 81, 84, 85, 88, 91, 94, 97, 100, 104, 109, + 114, 119, 125, 128, 131, 133, 137, 138, 141, 144, + 147, 150, 153, 158, 162, 165, 170, 171, 174, 178, + 180, 184, 185, 188, 191, 194, 198, 201, 203, 207, + 208, 211, 214, 217, 221, 225, 228, 231, 234, 235, + 238, 241, 244, 249, 253, 257, 258, 261, 263, 265, + 268, 271, 274, 276, 279, 280, 283, 285, 289, 293, + 297, 300, 304, 308, 310 +}; + +/* YYRHS -- A `-1'-separated list of the rules' RHS. */ +static const yysigned_char yyrhs[] = +{ + 36, 0, -1, 37, -1, -1, 37, 39, -1, 37, + 50, -1, 37, 61, -1, 37, 3, 71, 73, -1, + 37, 72, -1, 37, 25, 1, 30, -1, 37, 38, + 1, 30, -1, 37, 1, 30, -1, 16, -1, 19, + -1, 20, -1, 22, -1, 18, -1, 23, -1, 21, + -1, 30, -1, 56, -1, 65, -1, 42, -1, 44, + -1, 63, -1, 25, 1, 30, -1, 1, 30, -1, + 10, 25, 30, -1, 41, 45, -1, 11, 25, 30, + -1, 43, 45, -1, -1, 45, 46, -1, 45, 69, + -1, 45, 67, -1, 45, 40, -1, 45, 30, -1, + 20, 70, 30, -1, 19, 71, 74, 30, -1, 21, + 75, 74, 30, -1, 22, 25, 74, 30, -1, 23, + 76, 76, 74, 30, -1, 7, 30, -1, 47, 51, + -1, 72, -1, 48, 53, 49, -1, -1, 51, 52, + -1, 51, 69, -1, 51, 67, -1, 51, 30, -1, + 51, 40, -1, 19, 71, 74, 30, -1, 20, 70, + 30, -1, 18, 30, -1, 21, 25, 74, 30, -1, + -1, 53, 39, -1, 14, 75, 73, -1, 72, -1, + 54, 57, 55, -1, -1, 57, 39, -1, 57, 61, + -1, 57, 50, -1, 4, 71, 30, -1, 58, 68, + -1, 72, -1, 59, 62, 60, -1, -1, 62, 39, + -1, 62, 61, -1, 62, 50, -1, 6, 71, 30, + -1, 9, 71, 30, -1, 64, 68, -1, 12, 30, + -1, 66, 13, -1, -1, 68, 69, -1, 68, 30, + -1, 68, 40, -1, 16, 24, 75, 30, -1, 16, + 75, 30, -1, 17, 75, 30, -1, -1, 71, 74, + -1, 25, -1, 26, -1, 5, 30, -1, 8, 30, + -1, 15, 30, -1, 30, -1, 73, 30, -1, -1, + 14, 75, -1, 76, -1, 76, 33, 76, -1, 76, + 27, 76, -1, 29, 75, 28, -1, 34, 75, -1, + 75, 31, 75, -1, 75, 32, 75, -1, 25, -1, + 26, -1 +}; + +/* YYRLINE[YYN] -- source line where rule number YYN was defined. */ +static const unsigned short int yyrline[] = +{ + 0, 103, 103, 105, 107, 108, 109, 110, 111, 112, + 113, 117, 121, 121, 121, 121, 121, 121, 121, 125, + 126, 127, 128, 129, 130, 134, 135, 141, 149, 155, + 163, 173, 175, 176, 177, 178, 179, 182, 190, 196, + 206, 212, 220, 229, 234, 242, 245, 247, 248, 249, + 250, 251, 254, 260, 271, 277, 287, 289, 294, 302, + 310, 313, 315, 316, 317, 322, 329, 334, 342, 345, + 347, 348, 349, 352, 360, 367, 374, 380, 387, 389, + 390, 391, 394, 399, 404, 412, 414, 419, 420, 423, + 424, 425, 429, 430, 433, 434, 437, 438, 439, 440, + 441, 442, 443, 446, 447 +}; +#endif + +#if YYDEBUG || YYERROR_VERBOSE +/* YYTNME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "$end", "error", "$undefined", "T_MAINMENU", "T_MENU", "T_ENDMENU", + "T_SOURCE", "T_CHOICE", "T_ENDCHOICE", "T_COMMENT", "T_CONFIG", + "T_MENUCONFIG", "T_HELP", "T_HELPTEXT", "T_IF", "T_ENDIF", "T_DEPENDS", + "T_REQUIRES", "T_OPTIONAL", "T_PROMPT", "T_TYPE", "T_DEFAULT", + "T_SELECT", "T_RANGE", "T_ON", "T_WORD", "T_WORD_QUOTE", "T_UNEQUAL", + "T_CLOSE_PAREN", "T_OPEN_PAREN", "T_EOL", "T_OR", "T_AND", "T_EQUAL", + "T_NOT", "$accept", "input", "stmt_list", "option_name", "common_stmt", + "option_error", "config_entry_start", "config_stmt", + "menuconfig_entry_start", "menuconfig_stmt", "config_option_list", + "config_option", "choice", "choice_entry", "choice_end", "choice_stmt", + "choice_option_list", "choice_option", "choice_block", "if_entry", + "if_end", "if_stmt", "if_block", "menu", "menu_entry", "menu_end", + "menu_stmt", "menu_block", "source_stmt", "comment", "comment_stmt", + "help_start", "help", "depends_list", "depends", "prompt_stmt_opt", + "prompt", "end", "nl", "if_expr", "expr", "symbol", 0 +}; +#endif + +# ifdef YYPRINT +/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to + token YYLEX-NUM. */ +static const unsigned short int yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, + 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, + 285, 286, 287, 288, 289 +}; +# endif + +/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const unsigned char yyr1[] = +{ + 0, 35, 36, 37, 37, 37, 37, 37, 37, 37, + 37, 37, 38, 38, 38, 38, 38, 38, 38, 39, + 39, 39, 39, 39, 39, 40, 40, 41, 42, 43, + 44, 45, 45, 45, 45, 45, 45, 46, 46, 46, + 46, 46, 47, 48, 49, 50, 51, 51, 51, 51, + 51, 51, 52, 52, 52, 52, 53, 53, 54, 55, + 56, 57, 57, 57, 57, 58, 59, 60, 61, 62, + 62, 62, 62, 63, 64, 65, 66, 67, 68, 68, + 68, 68, 69, 69, 69, 70, 70, 71, 71, 72, + 72, 72, 73, 73, 74, 74, 75, 75, 75, 75, + 75, 75, 75, 76, 76 +}; + +/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ +static const unsigned char yyr2[] = +{ + 0, 2, 1, 0, 2, 2, 2, 4, 2, 4, + 4, 3, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 3, 2, 3, 2, 3, + 2, 0, 2, 2, 2, 2, 2, 3, 4, 4, + 4, 5, 2, 2, 1, 3, 0, 2, 2, 2, + 2, 2, 4, 3, 2, 4, 0, 2, 3, 1, + 3, 0, 2, 2, 2, 3, 2, 1, 3, 0, + 2, 2, 2, 3, 3, 2, 2, 2, 0, 2, + 2, 2, 4, 3, 3, 0, 2, 1, 1, 2, + 2, 2, 1, 2, 0, 2, 1, 3, 3, 3, + 2, 3, 3, 1, 1 +}; + +/* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state + STATE-NUM when YYTABLE doesn't specify something else to do. Zero + means the default is an error. */ +static const unsigned char yydefact[] = +{ + 3, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 12, 16, 13, 14, + 18, 15, 17, 0, 19, 0, 4, 31, 22, 31, + 23, 46, 56, 5, 61, 20, 78, 69, 6, 24, + 78, 21, 8, 11, 87, 88, 0, 0, 89, 0, + 42, 90, 0, 0, 0, 103, 104, 0, 0, 0, + 96, 91, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 92, 7, 65, 73, 74, 27, 29, 0, + 100, 0, 0, 58, 0, 0, 9, 10, 0, 0, + 0, 0, 0, 85, 0, 0, 0, 0, 36, 35, + 32, 0, 34, 33, 0, 0, 85, 0, 50, 51, + 47, 49, 48, 57, 45, 44, 62, 64, 60, 63, + 59, 80, 81, 79, 70, 72, 68, 71, 67, 93, + 99, 101, 102, 98, 97, 26, 76, 0, 0, 0, + 94, 0, 94, 94, 94, 0, 0, 77, 54, 94, + 0, 94, 0, 83, 84, 0, 0, 37, 86, 0, + 0, 94, 25, 0, 53, 0, 82, 95, 38, 39, + 40, 0, 52, 55, 41 +}; + +/* YYDEFGOTO[NTERM-NUM]. */ +static const short int yydefgoto[] = +{ + -1, 1, 2, 25, 26, 99, 27, 28, 29, 30, + 64, 100, 31, 32, 114, 33, 66, 110, 67, 34, + 118, 35, 68, 36, 37, 126, 38, 70, 39, 40, + 41, 101, 102, 69, 103, 141, 142, 42, 73, 156, + 59, 60 +}; + +/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +#define YYPACT_NINF -78 +static const short int yypact[] = +{ + -78, 2, 159, -78, -21, 0, 0, -12, 0, 1, + 4, 0, 27, 38, 60, 58, -78, -78, -78, -78, + -78, -78, -78, 100, -78, 104, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, 86, 113, -78, 114, + -78, -78, 125, 127, 128, -78, -78, 60, 60, 210, + 65, -78, 141, 142, 39, 103, 182, 200, 6, 66, + 6, 131, -78, 146, -78, -78, -78, -78, -78, 196, + -78, 60, 60, 146, 40, 40, -78, -78, 155, 156, + -2, 60, 0, 0, 60, 105, 40, 194, -78, -78, + -78, 206, -78, -78, 183, 0, 0, 195, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, -78, -78, -78, -78, + -78, 197, -78, -78, -78, -78, -78, 60, 213, 216, + 212, 203, 212, 190, 212, 40, 208, -78, -78, 212, + 222, 212, 219, -78, -78, 60, 223, -78, -78, 224, + 225, 212, -78, 226, -78, 227, -78, 47, -78, -78, + -78, 228, -78, -78, -78 +}; + +/* YYPGOTO[NTERM-NUM]. */ +static const short int yypgoto[] = +{ + -78, -78, -78, -78, 164, -36, -78, -78, -78, -78, + 230, -78, -78, -78, -78, 29, -78, -78, -78, -78, + -78, -78, -78, -78, -78, -78, 59, -78, -78, -78, + -78, -78, 198, 220, 24, 157, -5, 169, 202, 74, + -53, -77 +}; + +/* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule which + number is the opposite. If zero, do what YYDEFACT says. + If YYTABLE_NINF, syntax error. */ +#define YYTABLE_NINF -76 +static const short int yytable[] = +{ + 46, 47, 3, 49, 79, 80, 52, 133, 134, 43, + 6, 7, 8, 9, 10, 11, 12, 13, 48, 145, + 14, 15, 137, 55, 56, 44, 45, 57, 131, 132, + 109, 50, 58, 122, 51, 122, 24, 138, 139, -28, + 88, 143, -28, -28, -28, -28, -28, -28, -28, -28, + -28, 89, 53, -28, -28, 90, 91, -28, 92, 93, + 94, 95, 96, 54, 97, 55, 56, 88, 161, 98, + -66, -66, -66, -66, -66, -66, -66, -66, 81, 82, + -66, -66, 90, 91, 152, 55, 56, 140, 61, 57, + 112, 97, 84, 123, 58, 123, 121, 117, 85, 125, + 149, 62, 167, -30, 88, 63, -30, -30, -30, -30, + -30, -30, -30, -30, -30, 89, 72, -30, -30, 90, + 91, -30, 92, 93, 94, 95, 96, 119, 97, 127, + 144, -75, 88, 98, -75, -75, -75, -75, -75, -75, + -75, -75, -75, 74, 75, -75, -75, 90, 91, -75, + -75, -75, -75, -75, -75, 76, 97, 77, 78, -2, + 4, 121, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 86, 87, 14, 15, 16, 129, 17, 18, 19, + 20, 21, 22, 88, 23, 135, 136, -43, -43, 24, + -43, -43, -43, -43, 89, 146, -43, -43, 90, 91, + 104, 105, 106, 107, 155, 7, 8, 97, 10, 11, + 12, 13, 108, 148, 14, 15, 158, 159, 160, 147, + 151, 81, 82, 163, 130, 165, 155, 81, 82, 82, + 24, 113, 116, 157, 124, 171, 115, 120, 162, 128, + 72, 81, 82, 153, 81, 82, 154, 81, 82, 166, + 81, 82, 164, 168, 169, 170, 172, 173, 174, 65, + 71, 83, 0, 150, 111 +}; + +static const short int yycheck[] = +{ + 5, 6, 0, 8, 57, 58, 11, 84, 85, 30, + 4, 5, 6, 7, 8, 9, 10, 11, 30, 96, + 14, 15, 24, 25, 26, 25, 26, 29, 81, 82, + 66, 30, 34, 69, 30, 71, 30, 90, 91, 0, + 1, 94, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 25, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 25, 25, 25, 26, 1, 145, 30, + 4, 5, 6, 7, 8, 9, 10, 11, 31, 32, + 14, 15, 16, 17, 137, 25, 26, 92, 30, 29, + 66, 25, 27, 69, 34, 71, 30, 68, 33, 70, + 105, 1, 155, 0, 1, 1, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 30, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 68, 25, 70, + 25, 0, 1, 30, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 30, 30, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 30, 25, 30, 30, 0, + 1, 30, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 30, 30, 14, 15, 16, 30, 18, 19, 20, + 21, 22, 23, 1, 25, 30, 30, 5, 6, 30, + 8, 9, 10, 11, 12, 1, 14, 15, 16, 17, + 18, 19, 20, 21, 14, 5, 6, 25, 8, 9, + 10, 11, 30, 30, 14, 15, 142, 143, 144, 13, + 25, 31, 32, 149, 28, 151, 14, 31, 32, 32, + 30, 67, 68, 30, 70, 161, 67, 68, 30, 70, + 30, 31, 32, 30, 31, 32, 30, 31, 32, 30, + 31, 32, 30, 30, 30, 30, 30, 30, 30, 29, + 40, 59, -1, 106, 66 +}; + +/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const unsigned char yystos[] = +{ + 0, 36, 37, 0, 1, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 14, 15, 16, 18, 19, 20, + 21, 22, 23, 25, 30, 38, 39, 41, 42, 43, + 44, 47, 48, 50, 54, 56, 58, 59, 61, 63, + 64, 65, 72, 30, 25, 26, 71, 71, 30, 71, + 30, 30, 71, 25, 25, 25, 26, 29, 34, 75, + 76, 30, 1, 1, 45, 45, 51, 53, 57, 68, + 62, 68, 30, 73, 30, 30, 30, 30, 30, 75, + 75, 31, 32, 73, 27, 33, 30, 30, 1, 12, + 16, 17, 19, 20, 21, 22, 23, 25, 30, 40, + 46, 66, 67, 69, 18, 19, 20, 21, 30, 40, + 52, 67, 69, 39, 49, 72, 39, 50, 55, 61, + 72, 30, 40, 69, 39, 50, 60, 61, 72, 30, + 28, 75, 75, 76, 76, 30, 30, 24, 75, 75, + 71, 70, 71, 75, 25, 76, 1, 13, 30, 71, + 70, 25, 75, 30, 30, 14, 74, 30, 74, 74, + 74, 76, 30, 74, 30, 74, 30, 75, 30, 30, + 30, 74, 30, 30, 30 +}; + +#if ! defined (YYSIZE_T) && defined (__SIZE_TYPE__) +# define YYSIZE_T __SIZE_TYPE__ +#endif +#if ! defined (YYSIZE_T) && defined (size_t) +# define YYSIZE_T size_t +#endif +#if ! defined (YYSIZE_T) +# if defined (__STDC__) || defined (__cplusplus) +# include <stddef.h> /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# endif +#endif +#if ! defined (YYSIZE_T) +# define YYSIZE_T unsigned int +#endif + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +/* Like YYERROR except do call yyerror. This remains here temporarily + to ease the transition to the new meaning of YYERROR, for GCC. + Once GCC version 2 has supplanted version 1, this can go. */ + +#define YYFAIL goto yyerrlab + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY && yylen == 1) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + yytoken = YYTRANSLATE (yychar); \ + YYPOPSTACK; \ + goto yybackup; \ + } \ + else \ + { \ + yyerror ("syntax error: cannot back up");\ + YYERROR; \ + } \ +while (0) + + +#define YYTERROR 1 +#define YYERRCODE 256 + + +/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. + If N is 0, then set CURRENT to the empty location which ends + the previous symbol: RHS[0] (always defined). */ + +#define YYRHSLOC(Rhs, K) ((Rhs)[K]) +#ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (N) \ + { \ + (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ + (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ + (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ + (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ + } \ + else \ + { \ + (Current).first_line = (Current).last_line = \ + YYRHSLOC (Rhs, 0).last_line; \ + (Current).first_column = (Current).last_column = \ + YYRHSLOC (Rhs, 0).last_column; \ + } \ + while (0) +#endif + + +/* YY_LOCATION_PRINT -- Print the location on the stream. + This macro was not mandated originally: define only if we know + we won't break user code: when these are the locations we know. */ + +#ifndef YY_LOCATION_PRINT +# if YYLTYPE_IS_TRIVIAL +# define YY_LOCATION_PRINT(File, Loc) \ + fprintf (File, "%d.%d-%d.%d", \ + (Loc).first_line, (Loc).first_column, \ + (Loc).last_line, (Loc).last_column) +# else +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +# endif +#endif + + +/* YYLEX -- calling `yylex' with the right arguments. */ + +#ifdef YYLEX_PARAM +# define YYLEX yylex (YYLEX_PARAM) +#else +# define YYLEX yylex () +#endif + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include <stdio.h> /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (0) + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yysymprint (stderr, \ + Type, Value); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (0) + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +#if defined (__STDC__) || defined (__cplusplus) +static void +yy_stack_print (short int *bottom, short int *top) +#else +static void +yy_stack_print (bottom, top) + short int *bottom; + short int *top; +#endif +{ + YYFPRINTF (stderr, "Stack now"); + for (/* Nothing. */; bottom <= top; ++bottom) + YYFPRINTF (stderr, " %d", *bottom); + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (0) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +#if defined (__STDC__) || defined (__cplusplus) +static void +yy_reduce_print (int yyrule) +#else +static void +yy_reduce_print (yyrule) + int yyrule; +#endif +{ + int yyi; + unsigned int yylno = yyrline[yyrule]; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %u), ", + yyrule - 1, yylno); + /* Print the symbols being reduced, and their result. */ + for (yyi = yyprhs[yyrule]; 0 <= yyrhs[yyi]; yyi++) + YYFPRINTF (stderr, "%s ", yytname [yyrhs[yyi]]); + YYFPRINTF (stderr, "-> %s\n", yytname [yyr1[yyrule]]); +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (Rule); \ +} while (0) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + SIZE_MAX < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + + +#if YYERROR_VERBOSE + +# ifndef yystrlen +# if defined (__GLIBC__) && defined (_STRING_H) +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +static YYSIZE_T +# if defined (__STDC__) || defined (__cplusplus) +yystrlen (const char *yystr) +# else +yystrlen (yystr) + const char *yystr; +# endif +{ + register const char *yys = yystr; + + while (*yys++ != '\0') + continue; + + return yys - yystr - 1; +} +# endif +# endif + +# ifndef yystpcpy +# if defined (__GLIBC__) && defined (_STRING_H) && defined (_GNU_SOURCE) +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +static char * +# if defined (__STDC__) || defined (__cplusplus) +yystpcpy (char *yydest, const char *yysrc) +# else +yystpcpy (yydest, yysrc) + char *yydest; + const char *yysrc; +# endif +{ + register char *yyd = yydest; + register const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif + +#endif /* !YYERROR_VERBOSE */ + + + +#if YYDEBUG +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +#if defined (__STDC__) || defined (__cplusplus) +static void +yysymprint (FILE *yyoutput, int yytype, YYSTYPE *yyvaluep) +#else +static void +yysymprint (yyoutput, yytype, yyvaluep) + FILE *yyoutput; + int yytype; + YYSTYPE *yyvaluep; +#endif +{ + /* Pacify ``unused variable'' warnings. */ + (void) yyvaluep; + + if (yytype < YYNTOKENS) + YYFPRINTF (yyoutput, "token %s (", yytname[yytype]); + else + YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]); + + +# ifdef YYPRINT + if (yytype < YYNTOKENS) + YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); +# endif + switch (yytype) + { + default: + break; + } + YYFPRINTF (yyoutput, ")"); +} + +#endif /* ! YYDEBUG */ +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +#if defined (__STDC__) || defined (__cplusplus) +static void +yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep) +#else +static void +yydestruct (yymsg, yytype, yyvaluep) + const char *yymsg; + int yytype; + YYSTYPE *yyvaluep; +#endif +{ + /* Pacify ``unused variable'' warnings. */ + (void) yyvaluep; + + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + switch (yytype) + { + case 48: /* choice_entry */ + + { + fprintf(stderr, "%s:%d: missing end statement for this entry\n", + (yyvaluep->menu)->file->name, (yyvaluep->menu)->lineno); + if (current_menu == (yyvaluep->menu)) + menu_end_menu(); +}; + + break; + case 54: /* if_entry */ + + { + fprintf(stderr, "%s:%d: missing end statement for this entry\n", + (yyvaluep->menu)->file->name, (yyvaluep->menu)->lineno); + if (current_menu == (yyvaluep->menu)) + menu_end_menu(); +}; + + break; + case 59: /* menu_entry */ + + { + fprintf(stderr, "%s:%d: missing end statement for this entry\n", + (yyvaluep->menu)->file->name, (yyvaluep->menu)->lineno); + if (current_menu == (yyvaluep->menu)) + menu_end_menu(); +}; + + break; + + default: + break; + } +} + + +/* Prevent warnings from -Wmissing-prototypes. */ + +#ifdef YYPARSE_PARAM +# if defined (__STDC__) || defined (__cplusplus) +int yyparse (void *YYPARSE_PARAM); +# else +int yyparse (); +# endif +#else /* ! YYPARSE_PARAM */ +#if defined (__STDC__) || defined (__cplusplus) +int yyparse (void); +#else +int yyparse (); +#endif +#endif /* ! YYPARSE_PARAM */ + + + +/* The look-ahead symbol. */ +int yychar; + +/* The semantic value of the look-ahead symbol. */ +YYSTYPE yylval; + +/* Number of syntax errors so far. */ +int yynerrs; + + + +/*----------. +| yyparse. | +`----------*/ + +#ifdef YYPARSE_PARAM +# if defined (__STDC__) || defined (__cplusplus) +int yyparse (void *YYPARSE_PARAM) +# else +int yyparse (YYPARSE_PARAM) + void *YYPARSE_PARAM; +# endif +#else /* ! YYPARSE_PARAM */ +#if defined (__STDC__) || defined (__cplusplus) +int +yyparse (void) +#else +int +yyparse () + +#endif +#endif +{ + + register int yystate; + register int yyn; + int yyresult; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + /* Look-ahead token as an internal (translated) token number. */ + int yytoken = 0; + + /* Three stacks and their tools: + `yyss': related to states, + `yyvs': related to semantic values, + `yyls': related to locations. + + Refer to the stacks thru separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + short int yyssa[YYINITDEPTH]; + short int *yyss = yyssa; + register short int *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs = yyvsa; + register YYSTYPE *yyvsp; + + + +#define YYPOPSTACK (yyvsp--, yyssp--) + + YYSIZE_T yystacksize = YYINITDEPTH; + + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + + + /* When reducing, the number of symbols on the RHS of the reduced + rule. */ + int yylen; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + + /* Initialize stack pointers. + Waste one element of value and location stack + so that they stay on the same level as the state stack. + The wasted elements are never initialized. */ + + yyssp = yyss; + yyvsp = yyvs; + + + yyvsp[0] = yylval; + + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. so pushing a state here evens the stacks. + */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyss + yystacksize - 1 <= yyssp) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + short int *yyss1 = yyss; + + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow ("parser stack overflow", + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + + &yystacksize); + + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyoverflowlab; +# else + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyoverflowlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + short int *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyoverflowlab; + YYSTACK_RELOCATE (yyss); + YYSTACK_RELOCATE (yyvs); + +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + goto yybackup; + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + +/* Do appropriate processing given the current state. */ +/* Read a look-ahead token if we need one and don't already have one. */ +/* yyresume: */ + + /* First try to decide what to do without reference to look-ahead token. */ + + yyn = yypact[yystate]; + if (yyn == YYPACT_NINF) + goto yydefault; + + /* Not known => get a look-ahead token if don't already have one. */ + + /* YYCHAR is either YYEMPTY or YYEOF or a valid look-ahead symbol. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = YYLEX; + } + + if (yychar <= YYEOF) + { + yychar = yytoken = YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yyn == 0 || yyn == YYTABLE_NINF) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + if (yyn == YYFINAL) + YYACCEPT; + + /* Shift the look-ahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the token being shifted unless it is eof. */ + if (yychar != YYEOF) + yychar = YYEMPTY; + + *++yyvsp = yylval; + + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + yystate = yyn; + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + `$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 8: + + { zconf_error("unexpected end statement"); ;} + break; + + case 9: + + { zconf_error("unknown statement \"%s\"", (yyvsp[-2].string)); ;} + break; + + case 10: + + { + zconf_error("unexpected option \"%s\"", kconf_id_strings + (yyvsp[-2].id)->name); +;} + break; + + case 11: + + { zconf_error("invalid statement"); ;} + break; + + case 25: + + { zconf_error("unknown option \"%s\"", (yyvsp[-2].string)); ;} + break; + + case 26: + + { zconf_error("invalid option"); ;} + break; + + case 27: + + { + struct symbol *sym = sym_lookup((yyvsp[-1].string), 0); + sym->flags |= SYMBOL_OPTIONAL; + menu_add_entry(sym); + printd(DEBUG_PARSE, "%s:%d:config %s\n", zconf_curname(), zconf_lineno(), (yyvsp[-1].string)); +;} + break; + + case 28: + + { + menu_end_entry(); + printd(DEBUG_PARSE, "%s:%d:endconfig\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 29: + + { + struct symbol *sym = sym_lookup((yyvsp[-1].string), 0); + sym->flags |= SYMBOL_OPTIONAL; + menu_add_entry(sym); + printd(DEBUG_PARSE, "%s:%d:menuconfig %s\n", zconf_curname(), zconf_lineno(), (yyvsp[-1].string)); +;} + break; + + case 30: + + { + if (current_entry->prompt) + current_entry->prompt->type = P_MENU; + else + zconfprint("warning: menuconfig statement without prompt"); + menu_end_entry(); + printd(DEBUG_PARSE, "%s:%d:endconfig\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 37: + + { + menu_set_type((yyvsp[-2].id)->stype); + printd(DEBUG_PARSE, "%s:%d:type(%u)\n", + zconf_curname(), zconf_lineno(), + (yyvsp[-2].id)->stype); +;} + break; + + case 38: + + { + menu_add_prompt(P_PROMPT, (yyvsp[-2].string), (yyvsp[-1].expr)); + printd(DEBUG_PARSE, "%s:%d:prompt\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 39: + + { + menu_add_expr(P_DEFAULT, (yyvsp[-2].expr), (yyvsp[-1].expr)); + if ((yyvsp[-3].id)->stype != S_UNKNOWN) + menu_set_type((yyvsp[-3].id)->stype); + printd(DEBUG_PARSE, "%s:%d:default(%u)\n", + zconf_curname(), zconf_lineno(), + (yyvsp[-3].id)->stype); +;} + break; + + case 40: + + { + menu_add_symbol(P_SELECT, sym_lookup((yyvsp[-2].string), 0), (yyvsp[-1].expr)); + printd(DEBUG_PARSE, "%s:%d:select\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 41: + + { + menu_add_expr(P_RANGE, expr_alloc_comp(E_RANGE,(yyvsp[-3].symbol), (yyvsp[-2].symbol)), (yyvsp[-1].expr)); + printd(DEBUG_PARSE, "%s:%d:range\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 42: + + { + struct symbol *sym = sym_lookup(NULL, 0); + sym->flags |= SYMBOL_CHOICE; + menu_add_entry(sym); + menu_add_expr(P_CHOICE, NULL, NULL); + printd(DEBUG_PARSE, "%s:%d:choice\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 43: + + { + (yyval.menu) = menu_add_menu(); +;} + break; + + case 44: + + { + if (zconf_endtoken((yyvsp[0].id), T_CHOICE, T_ENDCHOICE)) { + menu_end_menu(); + printd(DEBUG_PARSE, "%s:%d:endchoice\n", zconf_curname(), zconf_lineno()); + } +;} + break; + + case 52: + + { + menu_add_prompt(P_PROMPT, (yyvsp[-2].string), (yyvsp[-1].expr)); + printd(DEBUG_PARSE, "%s:%d:prompt\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 53: + + { + if ((yyvsp[-2].id)->stype == S_BOOLEAN || (yyvsp[-2].id)->stype == S_TRISTATE) { + menu_set_type((yyvsp[-2].id)->stype); + printd(DEBUG_PARSE, "%s:%d:type(%u)\n", + zconf_curname(), zconf_lineno(), + (yyvsp[-2].id)->stype); + } else + YYERROR; +;} + break; + + case 54: + + { + current_entry->sym->flags |= SYMBOL_OPTIONAL; + printd(DEBUG_PARSE, "%s:%d:optional\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 55: + + { + if ((yyvsp[-3].id)->stype == S_UNKNOWN) { + menu_add_symbol(P_DEFAULT, sym_lookup((yyvsp[-2].string), 0), (yyvsp[-1].expr)); + printd(DEBUG_PARSE, "%s:%d:default\n", + zconf_curname(), zconf_lineno()); + } else + YYERROR; +;} + break; + + case 58: + + { + printd(DEBUG_PARSE, "%s:%d:if\n", zconf_curname(), zconf_lineno()); + menu_add_entry(NULL); + menu_add_dep((yyvsp[-1].expr)); + (yyval.menu) = menu_add_menu(); +;} + break; + + case 59: + + { + if (zconf_endtoken((yyvsp[0].id), T_IF, T_ENDIF)) { + menu_end_menu(); + printd(DEBUG_PARSE, "%s:%d:endif\n", zconf_curname(), zconf_lineno()); + } +;} + break; + + case 65: + + { + menu_add_entry(NULL); + menu_add_prompt(P_MENU, (yyvsp[-1].string), NULL); + printd(DEBUG_PARSE, "%s:%d:menu\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 66: + + { + (yyval.menu) = menu_add_menu(); +;} + break; + + case 67: + + { + if (zconf_endtoken((yyvsp[0].id), T_MENU, T_ENDMENU)) { + menu_end_menu(); + printd(DEBUG_PARSE, "%s:%d:endmenu\n", zconf_curname(), zconf_lineno()); + } +;} + break; + + case 73: + + { + printd(DEBUG_PARSE, "%s:%d:source %s\n", zconf_curname(), zconf_lineno(), (yyvsp[-1].string)); + zconf_nextfile((yyvsp[-1].string)); +;} + break; + + case 74: + + { + menu_add_entry(NULL); + menu_add_prompt(P_COMMENT, (yyvsp[-1].string), NULL); + printd(DEBUG_PARSE, "%s:%d:comment\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 75: + + { + menu_end_entry(); +;} + break; + + case 76: + + { + printd(DEBUG_PARSE, "%s:%d:help\n", zconf_curname(), zconf_lineno()); + zconf_starthelp(); +;} + break; + + case 77: + + { + current_entry->sym->help = (yyvsp[0].string); +;} + break; + + case 82: + + { + menu_add_dep((yyvsp[-1].expr)); + printd(DEBUG_PARSE, "%s:%d:depends on\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 83: + + { + menu_add_dep((yyvsp[-1].expr)); + printd(DEBUG_PARSE, "%s:%d:depends\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 84: + + { + menu_add_dep((yyvsp[-1].expr)); + printd(DEBUG_PARSE, "%s:%d:requires\n", zconf_curname(), zconf_lineno()); +;} + break; + + case 86: + + { + menu_add_prompt(P_PROMPT, (yyvsp[-1].string), (yyvsp[0].expr)); +;} + break; + + case 89: + + { (yyval.id) = (yyvsp[-1].id); ;} + break; + + case 90: + + { (yyval.id) = (yyvsp[-1].id); ;} + break; + + case 91: + + { (yyval.id) = (yyvsp[-1].id); ;} + break; + + case 94: + + { (yyval.expr) = NULL; ;} + break; + + case 95: + + { (yyval.expr) = (yyvsp[0].expr); ;} + break; + + case 96: + + { (yyval.expr) = expr_alloc_symbol((yyvsp[0].symbol)); ;} + break; + + case 97: + + { (yyval.expr) = expr_alloc_comp(E_EQUAL, (yyvsp[-2].symbol), (yyvsp[0].symbol)); ;} + break; + + case 98: + + { (yyval.expr) = expr_alloc_comp(E_UNEQUAL, (yyvsp[-2].symbol), (yyvsp[0].symbol)); ;} + break; + + case 99: + + { (yyval.expr) = (yyvsp[-1].expr); ;} + break; + + case 100: + + { (yyval.expr) = expr_alloc_one(E_NOT, (yyvsp[0].expr)); ;} + break; + + case 101: + + { (yyval.expr) = expr_alloc_two(E_OR, (yyvsp[-2].expr), (yyvsp[0].expr)); ;} + break; + + case 102: + + { (yyval.expr) = expr_alloc_two(E_AND, (yyvsp[-2].expr), (yyvsp[0].expr)); ;} + break; + + case 103: + + { (yyval.symbol) = sym_lookup((yyvsp[0].string), 0); free((yyvsp[0].string)); ;} + break; + + case 104: + + { (yyval.symbol) = sym_lookup((yyvsp[0].string), 1); free((yyvsp[0].string)); ;} + break; + + + } + +/* Line 1037 of yacc.c. */ + + + yyvsp -= yylen; + yyssp -= yylen; + + + YY_STACK_PRINT (yyss, yyssp); + + *++yyvsp = yyval; + + + /* Now `shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; + if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTOKENS]; + + goto yynewstate; + + +/*------------------------------------. +| yyerrlab -- here on detecting error | +`------------------------------------*/ +yyerrlab: + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; +#if YYERROR_VERBOSE + yyn = yypact[yystate]; + + if (YYPACT_NINF < yyn && yyn < YYLAST) + { + YYSIZE_T yysize = 0; + int yytype = YYTRANSLATE (yychar); + const char* yyprefix; + char *yymsg; + int yyx; + + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yycount = 0; + + yyprefix = ", expecting "; + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR) + { + yysize += yystrlen (yyprefix) + yystrlen (yytname [yyx]); + yycount += 1; + if (yycount == 5) + { + yysize = 0; + break; + } + } + yysize += (sizeof ("syntax error, unexpected ") + + yystrlen (yytname[yytype])); + yymsg = (char *) YYSTACK_ALLOC (yysize); + if (yymsg != 0) + { + char *yyp = yystpcpy (yymsg, "syntax error, unexpected "); + yyp = yystpcpy (yyp, yytname[yytype]); + + if (yycount < 5) + { + yyprefix = ", expecting "; + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR) + { + yyp = yystpcpy (yyp, yyprefix); + yyp = yystpcpy (yyp, yytname[yyx]); + yyprefix = " or "; + } + } + yyerror (yymsg); + YYSTACK_FREE (yymsg); + } + else + yyerror ("syntax error; also virtual memory exhausted"); + } + else +#endif /* YYERROR_VERBOSE */ + yyerror ("syntax error"); + } + + + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse look-ahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* If at end of input, pop the error token, + then the rest of the stack, then return failure. */ + if (yychar == YYEOF) + for (;;) + { + + YYPOPSTACK; + if (yyssp == yyss) + YYABORT; + yydestruct ("Error: popping", + yystos[*yyssp], yyvsp); + } + } + else + { + yydestruct ("Error: discarding", yytoken, &yylval); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse look-ahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + +#ifdef __GNUC__ + /* Pacify GCC when the user code never invokes YYERROR and the label + yyerrorlab therefore never appears in user code. */ + if (0) + goto yyerrorlab; +#endif + +yyvsp -= yylen; + yyssp -= yylen; + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + for (;;) + { + yyn = yypact[yystate]; + if (yyn != YYPACT_NINF) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + + yydestruct ("Error: popping", yystos[yystate], yyvsp); + YYPOPSTACK; + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + if (yyn == YYFINAL) + YYACCEPT; + + *++yyvsp = yylval; + + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yydestruct ("Error: discarding lookahead", + yytoken, &yylval); + yychar = YYEMPTY; + yyresult = 1; + goto yyreturn; + +#ifndef yyoverflow +/*----------------------------------------------. +| yyoverflowlab -- parser overflow comes here. | +`----------------------------------------------*/ +yyoverflowlab: + yyerror ("parser stack overflow"); + yyresult = 2; + /* Fall through. */ +#endif + +yyreturn: +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif + return yyresult; +} + + + + + +void conf_parse(const char *name) +{ + struct symbol *sym; + int i; + + zconf_initscan(name); + + sym_init(); + menu_init(); + modules_sym = sym_lookup("MODULES", 0); + rootmenu.prompt = menu_add_prompt(P_MENU, "Busybox Configuration", NULL); + +#if YYDEBUG + if (getenv("ZCONF_DEBUG")) + zconfdebug = 1; +#endif + zconfparse(); + if (zconfnerrs) + exit(1); + menu_finalize(&rootmenu); + for_all_symbols(i, sym) { + sym_check_deps(sym); + } + + sym_change_count = 1; +} + +const char *zconf_tokenname(int token) +{ + switch (token) { + case T_MENU: return "menu"; + case T_ENDMENU: return "endmenu"; + case T_CHOICE: return "choice"; + case T_ENDCHOICE: return "endchoice"; + case T_IF: return "if"; + case T_ENDIF: return "endif"; + case T_DEPENDS: return "depends"; + } + return "<token>"; +} + +static bool zconf_endtoken(struct kconf_id *id, int starttoken, int endtoken) +{ + if (id->token != endtoken) { + zconf_error("unexpected '%s' within %s block", + kconf_id_strings + id->name, zconf_tokenname(starttoken)); + zconfnerrs++; + return false; + } + if (current_menu->file != current_file) { + zconf_error("'%s' in different file than '%s'", + kconf_id_strings + id->name, zconf_tokenname(starttoken)); + fprintf(stderr, "%s:%d: location of the '%s'\n", + current_menu->file->name, current_menu->lineno, + zconf_tokenname(starttoken)); + zconfnerrs++; + return false; + } + return true; +} + +static void zconfprint(const char *err, ...) +{ + va_list ap; + + fprintf(stderr, "%s:%d: ", zconf_curname(), zconf_lineno()); + va_start(ap, err); + vfprintf(stderr, err, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +static void zconf_error(const char *err, ...) +{ + va_list ap; + + zconfnerrs++; + fprintf(stderr, "%s:%d: ", zconf_curname(), zconf_lineno()); + va_start(ap, err); + vfprintf(stderr, err, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +static void zconferror(const char *err) +{ +#if YYDEBUG + fprintf(stderr, "%s:%d: %s\n", zconf_curname(), zconf_lineno() + 1, err); +#endif +} + +void print_quoted_string(FILE *out, const char *str) +{ + const char *p; + int len; + + putc('"', out); + while ((p = strchr(str, '"'))) { + len = p - str; + if (len) + fprintf(out, "%.*s", len, str); + fputs("\\\"", out); + str = p + 1; + } + fputs(str, out); + putc('"', out); +} + +void print_symbol(FILE *out, struct menu *menu) +{ + struct symbol *sym = menu->sym; + struct property *prop; + + if (sym_is_choice(sym)) + fprintf(out, "choice\n"); + else + fprintf(out, "config %s\n", sym->name); + switch (sym->type) { + case S_BOOLEAN: + fputs(" boolean\n", out); + break; + case S_TRISTATE: + fputs(" tristate\n", out); + break; + case S_STRING: + fputs(" string\n", out); + break; + case S_INT: + fputs(" integer\n", out); + break; + case S_HEX: + fputs(" hex\n", out); + break; + default: + fputs(" ???\n", out); + break; + } + for (prop = sym->prop; prop; prop = prop->next) { + if (prop->menu != menu) + continue; + switch (prop->type) { + case P_PROMPT: + fputs(" prompt ", out); + print_quoted_string(out, prop->text); + if (!expr_is_yes(prop->visible.expr)) { + fputs(" if ", out); + expr_fprint(prop->visible.expr, out); + } + fputc('\n', out); + break; + case P_DEFAULT: + fputs( " default ", out); + expr_fprint(prop->expr, out); + if (!expr_is_yes(prop->visible.expr)) { + fputs(" if ", out); + expr_fprint(prop->visible.expr, out); + } + fputc('\n', out); + break; + case P_CHOICE: + fputs(" #choice value\n", out); + break; + default: + fprintf(out, " unknown prop %d!\n", prop->type); + break; + } + } + if (sym->help) { + int len = strlen(sym->help); + while (sym->help[--len] == '\n') + sym->help[len] = 0; + fprintf(out, " help\n%s\n", sym->help); + } + fputc('\n', out); +} + +void zconfdump(FILE *out) +{ + struct property *prop; + struct symbol *sym; + struct menu *menu; + + menu = rootmenu.list; + while (menu) { + if ((sym = menu->sym)) + print_symbol(out, menu); + else if ((prop = menu->prompt)) { + switch (prop->type) { + case P_COMMENT: + fputs("\ncomment ", out); + print_quoted_string(out, prop->text); + fputs("\n", out); + break; + case P_MENU: + fputs("\nmenu ", out); + print_quoted_string(out, prop->text); + fputs("\n", out); + break; + default: + ; + } + if (!expr_is_yes(prop->visible.expr)) { + fputs(" depends ", out); + expr_fprint(prop->visible.expr, out); + fputc('\n', out); + } + fputs("\n", out); + } + + if (menu->list) + menu = menu->list; + else if (menu->next) + menu = menu->next; + else while ((menu = menu->parent)) { + if (menu->prompt && menu->prompt->type == P_MENU) + fputs("\nendmenu\n", out); + if (menu->next) { + menu = menu->next; + break; + } + } + } +} + +#include "lex.zconf.c" +#include "util.c" +#include "confdata.c" +#include "expr.c" +#include "symbol.c" +#include "menu.c" + + diff --git a/scripts/kconfig/zconf.y b/scripts/kconfig/zconf.y new file mode 100644 index 000000000..0a7a79664 --- /dev/null +++ b/scripts/kconfig/zconf.y @@ -0,0 +1,681 @@ +%{ +/* + * Copyright (C) 2002 Roman Zippel <zippel@linux-m68k.org> + * Released under the terms of the GNU GPL v2.0. + */ + +#include <ctype.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> + +#define LKC_DIRECT_LINK +#include "lkc.h" + +#include "zconf.hash.c" + +#define printd(mask, fmt...) if (cdebug & (mask)) printf(fmt) + +#define PRINTD 0x0001 +#define DEBUG_PARSE 0x0002 + +int cdebug = PRINTD; + +extern int zconflex(void); +static void zconfprint(const char *err, ...); +static void zconf_error(const char *err, ...); +static void zconferror(const char *err); +static bool zconf_endtoken(struct kconf_id *id, int starttoken, int endtoken); + +struct symbol *symbol_hash[257]; + +static struct menu *current_menu, *current_entry; + +#define YYDEBUG 0 +#if YYDEBUG +#define YYERROR_VERBOSE +#endif +%} +%expect 26 + +%union +{ + char *string; + struct file *file; + struct symbol *symbol; + struct expr *expr; + struct menu *menu; + struct kconf_id *id; +} + +%token <id>T_MAINMENU +%token <id>T_MENU +%token <id>T_ENDMENU +%token <id>T_SOURCE +%token <id>T_CHOICE +%token <id>T_ENDCHOICE +%token <id>T_COMMENT +%token <id>T_CONFIG +%token <id>T_MENUCONFIG +%token <id>T_HELP +%token <string> T_HELPTEXT +%token <id>T_IF +%token <id>T_ENDIF +%token <id>T_DEPENDS +%token <id>T_REQUIRES +%token <id>T_OPTIONAL +%token <id>T_PROMPT +%token <id>T_TYPE +%token <id>T_DEFAULT +%token <id>T_SELECT +%token <id>T_RANGE +%token <id>T_ON +%token <string> T_WORD +%token <string> T_WORD_QUOTE +%token T_UNEQUAL +%token T_CLOSE_PAREN +%token T_OPEN_PAREN +%token T_EOL + +%left T_OR +%left T_AND +%left T_EQUAL T_UNEQUAL +%nonassoc T_NOT + +%type <string> prompt +%type <symbol> symbol +%type <expr> expr +%type <expr> if_expr +%type <id> end +%type <id> option_name +%type <menu> if_entry menu_entry choice_entry + +%destructor { + fprintf(stderr, "%s:%d: missing end statement for this entry\n", + $$->file->name, $$->lineno); + if (current_menu == $$) + menu_end_menu(); +} if_entry menu_entry choice_entry + +%% +input: stmt_list; + +stmt_list: + /* empty */ + | stmt_list common_stmt + | stmt_list choice_stmt + | stmt_list menu_stmt + | stmt_list T_MAINMENU prompt nl + | stmt_list end { zconf_error("unexpected end statement"); } + | stmt_list T_WORD error T_EOL { zconf_error("unknown statement \"%s\"", $2); } + | stmt_list option_name error T_EOL +{ + zconf_error("unexpected option \"%s\"", kconf_id_strings + $2->name); +} + | stmt_list error T_EOL { zconf_error("invalid statement"); } +; + +option_name: + T_DEPENDS | T_PROMPT | T_TYPE | T_SELECT | T_OPTIONAL | T_RANGE | T_DEFAULT +; + +common_stmt: + T_EOL + | if_stmt + | comment_stmt + | config_stmt + | menuconfig_stmt + | source_stmt +; + +option_error: + T_WORD error T_EOL { zconf_error("unknown option \"%s\"", $1); } + | error T_EOL { zconf_error("invalid option"); } +; + + +/* config/menuconfig entry */ + +config_entry_start: T_CONFIG T_WORD T_EOL +{ + struct symbol *sym = sym_lookup($2, 0); + sym->flags |= SYMBOL_OPTIONAL; + menu_add_entry(sym); + printd(DEBUG_PARSE, "%s:%d:config %s\n", zconf_curname(), zconf_lineno(), $2); +}; + +config_stmt: config_entry_start config_option_list +{ + menu_end_entry(); + printd(DEBUG_PARSE, "%s:%d:endconfig\n", zconf_curname(), zconf_lineno()); +}; + +menuconfig_entry_start: T_MENUCONFIG T_WORD T_EOL +{ + struct symbol *sym = sym_lookup($2, 0); + sym->flags |= SYMBOL_OPTIONAL; + menu_add_entry(sym); + printd(DEBUG_PARSE, "%s:%d:menuconfig %s\n", zconf_curname(), zconf_lineno(), $2); +}; + +menuconfig_stmt: menuconfig_entry_start config_option_list +{ + if (current_entry->prompt) + current_entry->prompt->type = P_MENU; + else + zconfprint("warning: menuconfig statement without prompt"); + menu_end_entry(); + printd(DEBUG_PARSE, "%s:%d:endconfig\n", zconf_curname(), zconf_lineno()); +}; + +config_option_list: + /* empty */ + | config_option_list config_option + | config_option_list depends + | config_option_list help + | config_option_list option_error + | config_option_list T_EOL +; + +config_option: T_TYPE prompt_stmt_opt T_EOL +{ + menu_set_type($1->stype); + printd(DEBUG_PARSE, "%s:%d:type(%u)\n", + zconf_curname(), zconf_lineno(), + $1->stype); +}; + +config_option: T_PROMPT prompt if_expr T_EOL +{ + menu_add_prompt(P_PROMPT, $2, $3); + printd(DEBUG_PARSE, "%s:%d:prompt\n", zconf_curname(), zconf_lineno()); +}; + +config_option: T_DEFAULT expr if_expr T_EOL +{ + menu_add_expr(P_DEFAULT, $2, $3); + if ($1->stype != S_UNKNOWN) + menu_set_type($1->stype); + printd(DEBUG_PARSE, "%s:%d:default(%u)\n", + zconf_curname(), zconf_lineno(), + $1->stype); +}; + +config_option: T_SELECT T_WORD if_expr T_EOL +{ + menu_add_symbol(P_SELECT, sym_lookup($2, 0), $3); + printd(DEBUG_PARSE, "%s:%d:select\n", zconf_curname(), zconf_lineno()); +}; + +config_option: T_RANGE symbol symbol if_expr T_EOL +{ + menu_add_expr(P_RANGE, expr_alloc_comp(E_RANGE,$2, $3), $4); + printd(DEBUG_PARSE, "%s:%d:range\n", zconf_curname(), zconf_lineno()); +}; + +/* choice entry */ + +choice: T_CHOICE T_EOL +{ + struct symbol *sym = sym_lookup(NULL, 0); + sym->flags |= SYMBOL_CHOICE; + menu_add_entry(sym); + menu_add_expr(P_CHOICE, NULL, NULL); + printd(DEBUG_PARSE, "%s:%d:choice\n", zconf_curname(), zconf_lineno()); +}; + +choice_entry: choice choice_option_list +{ + $$ = menu_add_menu(); +}; + +choice_end: end +{ + if (zconf_endtoken($1, T_CHOICE, T_ENDCHOICE)) { + menu_end_menu(); + printd(DEBUG_PARSE, "%s:%d:endchoice\n", zconf_curname(), zconf_lineno()); + } +}; + +choice_stmt: choice_entry choice_block choice_end +; + +choice_option_list: + /* empty */ + | choice_option_list choice_option + | choice_option_list depends + | choice_option_list help + | choice_option_list T_EOL + | choice_option_list option_error +; + +choice_option: T_PROMPT prompt if_expr T_EOL +{ + menu_add_prompt(P_PROMPT, $2, $3); + printd(DEBUG_PARSE, "%s:%d:prompt\n", zconf_curname(), zconf_lineno()); +}; + +choice_option: T_TYPE prompt_stmt_opt T_EOL +{ + if ($1->stype == S_BOOLEAN || $1->stype == S_TRISTATE) { + menu_set_type($1->stype); + printd(DEBUG_PARSE, "%s:%d:type(%u)\n", + zconf_curname(), zconf_lineno(), + $1->stype); + } else + YYERROR; +}; + +choice_option: T_OPTIONAL T_EOL +{ + current_entry->sym->flags |= SYMBOL_OPTIONAL; + printd(DEBUG_PARSE, "%s:%d:optional\n", zconf_curname(), zconf_lineno()); +}; + +choice_option: T_DEFAULT T_WORD if_expr T_EOL +{ + if ($1->stype == S_UNKNOWN) { + menu_add_symbol(P_DEFAULT, sym_lookup($2, 0), $3); + printd(DEBUG_PARSE, "%s:%d:default\n", + zconf_curname(), zconf_lineno()); + } else + YYERROR; +}; + +choice_block: + /* empty */ + | choice_block common_stmt +; + +/* if entry */ + +if_entry: T_IF expr nl +{ + printd(DEBUG_PARSE, "%s:%d:if\n", zconf_curname(), zconf_lineno()); + menu_add_entry(NULL); + menu_add_dep($2); + $$ = menu_add_menu(); +}; + +if_end: end +{ + if (zconf_endtoken($1, T_IF, T_ENDIF)) { + menu_end_menu(); + printd(DEBUG_PARSE, "%s:%d:endif\n", zconf_curname(), zconf_lineno()); + } +}; + +if_stmt: if_entry if_block if_end +; + +if_block: + /* empty */ + | if_block common_stmt + | if_block menu_stmt + | if_block choice_stmt +; + +/* menu entry */ + +menu: T_MENU prompt T_EOL +{ + menu_add_entry(NULL); + menu_add_prompt(P_MENU, $2, NULL); + printd(DEBUG_PARSE, "%s:%d:menu\n", zconf_curname(), zconf_lineno()); +}; + +menu_entry: menu depends_list +{ + $$ = menu_add_menu(); +}; + +menu_end: end +{ + if (zconf_endtoken($1, T_MENU, T_ENDMENU)) { + menu_end_menu(); + printd(DEBUG_PARSE, "%s:%d:endmenu\n", zconf_curname(), zconf_lineno()); + } +}; + +menu_stmt: menu_entry menu_block menu_end +; + +menu_block: + /* empty */ + | menu_block common_stmt + | menu_block menu_stmt + | menu_block choice_stmt +; + +source_stmt: T_SOURCE prompt T_EOL +{ + printd(DEBUG_PARSE, "%s:%d:source %s\n", zconf_curname(), zconf_lineno(), $2); + zconf_nextfile($2); +}; + +/* comment entry */ + +comment: T_COMMENT prompt T_EOL +{ + menu_add_entry(NULL); + menu_add_prompt(P_COMMENT, $2, NULL); + printd(DEBUG_PARSE, "%s:%d:comment\n", zconf_curname(), zconf_lineno()); +}; + +comment_stmt: comment depends_list +{ + menu_end_entry(); +}; + +/* help option */ + +help_start: T_HELP T_EOL +{ + printd(DEBUG_PARSE, "%s:%d:help\n", zconf_curname(), zconf_lineno()); + zconf_starthelp(); +}; + +help: help_start T_HELPTEXT +{ + current_entry->sym->help = $2; +}; + +/* depends option */ + +depends_list: + /* empty */ + | depends_list depends + | depends_list T_EOL + | depends_list option_error +; + +depends: T_DEPENDS T_ON expr T_EOL +{ + menu_add_dep($3); + printd(DEBUG_PARSE, "%s:%d:depends on\n", zconf_curname(), zconf_lineno()); +} + | T_DEPENDS expr T_EOL +{ + menu_add_dep($2); + printd(DEBUG_PARSE, "%s:%d:depends\n", zconf_curname(), zconf_lineno()); +} + | T_REQUIRES expr T_EOL +{ + menu_add_dep($2); + printd(DEBUG_PARSE, "%s:%d:requires\n", zconf_curname(), zconf_lineno()); +}; + +/* prompt statement */ + +prompt_stmt_opt: + /* empty */ + | prompt if_expr +{ + menu_add_prompt(P_PROMPT, $1, $2); +}; + +prompt: T_WORD + | T_WORD_QUOTE +; + +end: T_ENDMENU T_EOL { $$ = $1; } + | T_ENDCHOICE T_EOL { $$ = $1; } + | T_ENDIF T_EOL { $$ = $1; } +; + +nl: + T_EOL + | nl T_EOL +; + +if_expr: /* empty */ { $$ = NULL; } + | T_IF expr { $$ = $2; } +; + +expr: symbol { $$ = expr_alloc_symbol($1); } + | symbol T_EQUAL symbol { $$ = expr_alloc_comp(E_EQUAL, $1, $3); } + | symbol T_UNEQUAL symbol { $$ = expr_alloc_comp(E_UNEQUAL, $1, $3); } + | T_OPEN_PAREN expr T_CLOSE_PAREN { $$ = $2; } + | T_NOT expr { $$ = expr_alloc_one(E_NOT, $2); } + | expr T_OR expr { $$ = expr_alloc_two(E_OR, $1, $3); } + | expr T_AND expr { $$ = expr_alloc_two(E_AND, $1, $3); } +; + +symbol: T_WORD { $$ = sym_lookup($1, 0); free($1); } + | T_WORD_QUOTE { $$ = sym_lookup($1, 1); free($1); } +; + +%% + +void conf_parse(const char *name) +{ + struct symbol *sym; + int i; + + zconf_initscan(name); + + sym_init(); + menu_init(); + modules_sym = sym_lookup("MODULES", 0); + rootmenu.prompt = menu_add_prompt(P_MENU, "Busybox Configuration", NULL); + +#if YYDEBUG + if (getenv("ZCONF_DEBUG")) + zconfdebug = 1; +#endif + zconfparse(); + if (zconfnerrs) + exit(1); + menu_finalize(&rootmenu); + for_all_symbols(i, sym) { + sym_check_deps(sym); + } + + sym_change_count = 1; +} + +const char *zconf_tokenname(int token) +{ + switch (token) { + case T_MENU: return "menu"; + case T_ENDMENU: return "endmenu"; + case T_CHOICE: return "choice"; + case T_ENDCHOICE: return "endchoice"; + case T_IF: return "if"; + case T_ENDIF: return "endif"; + case T_DEPENDS: return "depends"; + } + return "<token>"; +} + +static bool zconf_endtoken(struct kconf_id *id, int starttoken, int endtoken) +{ + if (id->token != endtoken) { + zconf_error("unexpected '%s' within %s block", + kconf_id_strings + id->name, zconf_tokenname(starttoken)); + zconfnerrs++; + return false; + } + if (current_menu->file != current_file) { + zconf_error("'%s' in different file than '%s'", + kconf_id_strings + id->name, zconf_tokenname(starttoken)); + fprintf(stderr, "%s:%d: location of the '%s'\n", + current_menu->file->name, current_menu->lineno, + zconf_tokenname(starttoken)); + zconfnerrs++; + return false; + } + return true; +} + +static void zconfprint(const char *err, ...) +{ + va_list ap; + + fprintf(stderr, "%s:%d: ", zconf_curname(), zconf_lineno()); + va_start(ap, err); + vfprintf(stderr, err, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +static void zconf_error(const char *err, ...) +{ + va_list ap; + + zconfnerrs++; + fprintf(stderr, "%s:%d: ", zconf_curname(), zconf_lineno()); + va_start(ap, err); + vfprintf(stderr, err, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +static void zconferror(const char *err) +{ +#if YYDEBUG + fprintf(stderr, "%s:%d: %s\n", zconf_curname(), zconf_lineno() + 1, err); +#endif +} + +void print_quoted_string(FILE *out, const char *str) +{ + const char *p; + int len; + + putc('"', out); + while ((p = strchr(str, '"'))) { + len = p - str; + if (len) + fprintf(out, "%.*s", len, str); + fputs("\\\"", out); + str = p + 1; + } + fputs(str, out); + putc('"', out); +} + +void print_symbol(FILE *out, struct menu *menu) +{ + struct symbol *sym = menu->sym; + struct property *prop; + + if (sym_is_choice(sym)) + fprintf(out, "choice\n"); + else + fprintf(out, "config %s\n", sym->name); + switch (sym->type) { + case S_BOOLEAN: + fputs(" boolean\n", out); + break; + case S_TRISTATE: + fputs(" tristate\n", out); + break; + case S_STRING: + fputs(" string\n", out); + break; + case S_INT: + fputs(" integer\n", out); + break; + case S_HEX: + fputs(" hex\n", out); + break; + default: + fputs(" ???\n", out); + break; + } + for (prop = sym->prop; prop; prop = prop->next) { + if (prop->menu != menu) + continue; + switch (prop->type) { + case P_PROMPT: + fputs(" prompt ", out); + print_quoted_string(out, prop->text); + if (!expr_is_yes(prop->visible.expr)) { + fputs(" if ", out); + expr_fprint(prop->visible.expr, out); + } + fputc('\n', out); + break; + case P_DEFAULT: + fputs( " default ", out); + expr_fprint(prop->expr, out); + if (!expr_is_yes(prop->visible.expr)) { + fputs(" if ", out); + expr_fprint(prop->visible.expr, out); + } + fputc('\n', out); + break; + case P_CHOICE: + fputs(" #choice value\n", out); + break; + default: + fprintf(out, " unknown prop %d!\n", prop->type); + break; + } + } + if (sym->help) { + int len = strlen(sym->help); + while (sym->help[--len] == '\n') + sym->help[len] = 0; + fprintf(out, " help\n%s\n", sym->help); + } + fputc('\n', out); +} + +void zconfdump(FILE *out) +{ + struct property *prop; + struct symbol *sym; + struct menu *menu; + + menu = rootmenu.list; + while (menu) { + if ((sym = menu->sym)) + print_symbol(out, menu); + else if ((prop = menu->prompt)) { + switch (prop->type) { + case P_COMMENT: + fputs("\ncomment ", out); + print_quoted_string(out, prop->text); + fputs("\n", out); + break; + case P_MENU: + fputs("\nmenu ", out); + print_quoted_string(out, prop->text); + fputs("\n", out); + break; + default: + ; + } + if (!expr_is_yes(prop->visible.expr)) { + fputs(" depends ", out); + expr_fprint(prop->visible.expr, out); + fputc('\n', out); + } + fputs("\n", out); + } + + if (menu->list) + menu = menu->list; + else if (menu->next) + menu = menu->next; + else while ((menu = menu->parent)) { + if (menu->prompt && menu->prompt->type == P_MENU) + fputs("\nendmenu\n", out); + if (menu->next) { + menu = menu->next; + break; + } + } + } +} + +#include "lex.zconf.c" +#include "util.c" +#include "confdata.c" +#include "expr.c" +#include "symbol.c" +#include "menu.c" diff --git a/scripts/mkconfigs b/scripts/mkconfigs new file mode 100755 index 000000000..fda9de72f --- /dev/null +++ b/scripts/mkconfigs @@ -0,0 +1,51 @@ +#!/bin/sh +# +# Copyright (C) 2002 Khalid Aziz <khalid_aziz at hp.com> +# Copyright (C) 2002 Randy Dunlap <rddunlap at osdl.org> +# Copyright (C) 2002 Al Stone <ahs3 at fc.hp.com> +# Copyright (C) 2002 Hewlett-Packard Company +# +# 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. +# +# Busybox version by Matteo Croce <3297627799 at wind.it> +# +# Rules to generate bbconfig.h from .config: +# - Retain lines that begin with "CONFIG_" +# - Retain lines that begin with "# CONFIG_" +# - lines that use double-quotes must \\-escape-quote them + +if [ $# -lt 1 ] +then + config=.config +else config=$1 +fi + +echo "#ifndef _BBCONFIGOPTS_H" +echo "#define _BBCONFIGOPTS_H" +echo \ +"/* + * busybox configuration settings. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * This file is generated automatically by scripts/config/mkconfigs. + * Do not edit. + * + */" + +echo "static const char * const bbconfig_config =" +echo "`sed 's/\"/\\\\\"/g' $config | grep "^#\? \?CONFIG_" | awk '{print "\\"" $0 "\\\\n\\"";}'`" +echo ";" +echo "#endif /* _BBCONFIGOPTS_H */" diff --git a/scripts/mkmakefile b/scripts/mkmakefile new file mode 100755 index 000000000..7f9d544f9 --- /dev/null +++ b/scripts/mkmakefile @@ -0,0 +1,36 @@ +#!/bin/sh +# Generates a small Makefile used in the root of the output +# directory, to allow make to be started from there. +# The Makefile also allow for more convinient build of external modules + +# Usage +# $1 - Kernel src directory +# $2 - Output directory +# $3 - version +# $4 - patchlevel + + +test ! -r $2/Makefile -o -O $2/Makefile || exit 0 +echo " GEN $2/Makefile" + +cat << EOF > $2/Makefile +# Automatically generated by $0: don't edit + +VERSION = $3 +PATCHLEVEL = $4 + +KERNELSRC := $1 +KERNELOUTPUT := $2 + +MAKEFLAGS += --no-print-directory + +.PHONY: all \$(MAKECMDGOALS) + +all: + \$(MAKE) -C \$(KERNELSRC) O=\$(KERNELOUTPUT) + +Makefile:; + +\$(filter-out all Makefile,\$(MAKECMDGOALS)) %/: + \$(MAKE) -C \$(KERNELSRC) O=\$(KERNELOUTPUT) \$@ +EOF diff --git a/scripts/objsizes b/scripts/objsizes new file mode 100755 index 000000000..4f6576d9a --- /dev/null +++ b/scripts/objsizes @@ -0,0 +1,7 @@ +#!/bin/sh + +printf "%9s %11s %9s %9s %s\n" "text+data" text+rodata rwdata bss filename +find -name '*.o' | sed 's:^\./::' | xargs size | grep '^ *[0-9]' \ +| while read text data bss dec hex filename; do + printf "%9d %11d %9d %9d %s\n" $((text+data)) $text $data $bss "$filename" +done | sort -r diff --git a/scripts/showasm b/scripts/showasm new file mode 100755 index 000000000..046442653 --- /dev/null +++ b/scripts/showasm @@ -0,0 +1,21 @@ +#!/bin/sh + +# Copyright 2006 Rob Landley <rob@landley.net> +# Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + +# Dumb little utility function to print out the assembly dump of a single +# function, or list the functions so dumpable in an executable. You'd think +# there would be a way to get objdump to do this, but I can't find it. + +[ $# -lt 1 ] || [ $# -gt 2 ] && { echo "usage: showasm file function"; exit 1; } + +[ ! -f $1 ] && { echo "File $1 not found"; exit 1; } + +if [ $# -eq 1 ] +then + objdump -d $1 | sed -n -e 's/^[0-9a-fA-F]* <\(.*\)>:$/\1/p' + exit 0 +fi + +objdump -d $1 | sed -n -e '/./{H;$!d}' -e "x;/^.[0-9a-fA-F]* <$2>:/p" + diff --git a/scripts/trylink b/scripts/trylink new file mode 100755 index 000000000..dfe282db5 --- /dev/null +++ b/scripts/trylink @@ -0,0 +1,18 @@ +#!/bin/sh + +debug=false + +function try { + added="$1" + shift + $debug && echo "Trying: $* $added" + "$@" $added >/dev/null 2>&1 \ + && exit 0 +} + +try "" "$@" +try "-lm" "$@" +try "-lcrypt" "$@" +try "-Wl,--start-group -lcrypt -lm -Wl,--end-group" "$@" +# It failed. Rerun & let people see the error messages +"$@" $added diff --git a/shell/Config.in b/shell/Config.in new file mode 100644 index 000000000..9ac233155 --- /dev/null +++ b/shell/Config.in @@ -0,0 +1,295 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Shells" + +choice + prompt "Choose your default shell" + default FEATURE_SH_IS_NONE + help + Choose a shell. The ash shell is the most bash compatible + and full featured one. + +config FEATURE_SH_IS_ASH + select ASH + bool "ash" + +config FEATURE_SH_IS_HUSH + select HUSH + bool "hush" + +config FEATURE_SH_IS_LASH + select LASH + bool "lash" + +config FEATURE_SH_IS_MSH + select MSH + bool "msh" + +config FEATURE_SH_IS_NONE + bool "none" + +endchoice + +config ASH + bool "ash" + default n + select TEST + help + Tha 'ash' shell adds about 60k in the default configuration and is + the most complete and most pedantically correct shell included with + busybox. This shell is actually a derivative of the Debian 'dash' + shell (by Herbert Xu), which was created by porting the 'ash' shell + (written by Kenneth Almquist) from NetBSD. + +comment "Ash Shell Options" + depends on ASH + +config ASH_JOB_CONTROL + bool "Job control" + default y + depends on ASH + help + Enable job control in the ash shell. + +config ASH_READ_NCHARS + bool "'read -n N' and 'read -s' support" + default n + depends on ASH + help + 'read -n N' will return a value after N characters have been read. + 'read -s' will read without echoing the user's input. + +config ASH_READ_TIMEOUT + bool "'read -t S' support." + default n + depends on ASH + help + 'read -t S' will return a value after S seconds have passed. + This implementation will allow fractional seconds, expressed + as a decimal fraction, e.g. 'read -t 2.5 foo'. + +config ASH_ALIAS + bool "alias support" + default y + depends on ASH + help + Enable alias support in the ash shell. + +config ASH_MATH_SUPPORT + bool "Posix math support" + default y + depends on ASH + help + Enable math support in the ash shell. + +config ASH_MATH_SUPPORT_64 + bool "Extend Posix math support to 64 bit" + default n + depends on ASH_MATH_SUPPORT + help + Enable 64-bit math support in the ash shell. This will make + the shell slightly larger, but will allow computation with very + large numbers. + +config ASH_GETOPTS + bool "Builtin getopt to parse positional parameters" + default n + depends on ASH + help + Enable getopts builtin in the ash shell. + +config ASH_BUILTIN_ECHO + bool "Builtin version of 'echo'" + default y + select ECHO + depends on ASH + help + Enable support for echo, built in to ash. + +config ASH_BUILTIN_TEST + bool "Builtin version of 'test'" + default y + select TEST + depends on ASH + help + Enable support for test, built in to ash. + +config ASH_CMDCMD + bool "'command' command to override shell builtins" + default n + depends on ASH + help + Enable support for the ash 'command' builtin, which allows + you to run the specified command with the specified arguments, + even when there is an ash builtin command with the same name. + +config ASH_MAIL + bool "Check for new mail on interactive shells" + default y + depends on ASH + help + Enable "check for new mail" in the ash shell. + +config ASH_OPTIMIZE_FOR_SIZE + bool "Optimize for size instead of speed" + default y + depends on ASH + help + Compile ash for reduced size at the price of speed. + +config ASH_RANDOM_SUPPORT + bool "Pseudorandom generator and variable $RANDOM" + default n + depends on ASH + help + Enable pseudorandom generator and dynamic variable "$RANDOM". + Each read of "$RANDOM" will generate a new pseudorandom value. + You can reset the generator by using a specified start value. + After "unset RANDOM" then generator will switch off and this + variable will no longer have special treatment. + +config ASH_EXPAND_PRMT + bool "Expand prompt string" + default n + depends on ASH + help + "PS#" may be contain volatile content, such as backquote commands. + This option recreates the prompt string from the environment + variable each time it is displayed. + +config HUSH + bool "hush" + default n + select TRUE + select FALSE + select TEST + help + hush is a very small shell (just 18k) and it has fairly complete + Bourne shell grammar. It even handles all the normal flow control + options such as if/then/elif/else/fi, for/in/do/done, while loops, + etc. + + It does not handle case/esac, select, function, here documents ( << + word ), arithmetic expansion, aliases, brace expansion, tilde + expansion, &> and >& redirection of stdout+stderr, etc. + + +config LASH + bool "lash" + default n + select TRUE + select FALSE + select TEST + help + lash is the very smallest shell (adds just 10k) and it is quite + usable as a command prompt, but it is not suitable for any but the + most trivial scripting (such as an initrd that calls insmod a few + times) since it does not understand any Bourne shell grammar. It + does handle pipes, redirects, and job control though. Adding in + command editing makes it a very nice lightweight command prompt. + + +config MSH + bool "msh" + default n + select TRUE + select FALSE + select TEST + help + The minix shell (adds just 30k) is quite complete and handles things + like for/do/done, case/esac and all the things you expect a Bourne + shell to do. It is not always pedantically correct about Bourne + shell grammar (try running the shell testscript "tests/sh.testcases" + on it and compare vs bash) but for most things it works quite well. + It also uses only vfork, so it can be used on uClinux systems. + +comment "Bourne Shell Options" + depends on MSH || LASH || HUSH || ASH + +config FEATURE_SH_EXTRA_QUIET + bool "Hide message on interactive shell startup" + default n + depends on MSH || LASH || HUSH || ASH + help + Remove the busybox introduction when starting a shell. + +config FEATURE_SH_STANDALONE_SHELL + bool "Standalone shell" + default n + depends on MSH || LASH || HUSH || ASH + help + This option causes the selected busybox shell to use busybox applets + in preference to executables in the PATH whenever possible. For + example, entering the command 'ifconfig' into the shell would cause + busybox to use the ifconfig busybox applet. Specifying the fully + qualified executable name, such as '/sbin/ifconfig' will still + execute the /sbin/ifconfig executable on the filesystem. This option + is generally used when creating a statically linked version of busybox + for use as a rescue shell, in the event that you screw up your system. + + Note that this will *also* cause applets to take precedence + over shell builtins of the same name. So turning this on will + eliminate any performance gained by turning on the builtin "echo" + and "test" commands in ash. + + Note that when using this option, the shell will attempt to directly + run '/bin/busybox'. If you do not have the busybox binary sitting in + that exact location with that exact name, this option will not work at + all. + +config FEATURE_COMMAND_EDITING + bool "Command line editing" + default n + depends on MSH || LASH || HUSH || ASH + help + Enable command editing in shell. + +config FEATURE_COMMAND_EDITING_VI + bool "vi-style line editing commands" + default n + depends on FEATURE_COMMAND_EDITING + help + Enable vi-style line editing in the shell. This mode can be + turned on and off with "set -o vi" and "set +o vi". + +config FEATURE_COMMAND_HISTORY + int "History size" + range 0 99999 + default 15 + depends on FEATURE_COMMAND_EDITING + help + Specify command history size in shell. + +config FEATURE_COMMAND_SAVEHISTORY + bool "History saving" + default n + depends on ASH && FEATURE_COMMAND_EDITING + help + Enable history saving in ash shell. + +config FEATURE_COMMAND_TAB_COMPLETION + bool "Tab completion" + default n + depends on FEATURE_COMMAND_EDITING + help + Enable tab completion in shell. + +config FEATURE_COMMAND_USERNAME_COMPLETION + bool "Username completion" + default n + depends on FEATURE_COMMAND_TAB_COMPLETION + help + Enable username completion in shell. + +config FEATURE_SH_FANCY_PROMPT + bool "Fancy shell prompts" + default n + depends on FEATURE_COMMAND_EDITING + help + Setting this option allows for prompts to use things like \w and + \$ and also using escape codes. + +endmenu diff --git a/shell/Kbuild b/shell/Kbuild new file mode 100644 index 000000000..eb0199ee2 --- /dev/null +++ b/shell/Kbuild @@ -0,0 +1,12 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen <andersen@codepoet.org> +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_ASH) += ash.o +lib-$(CONFIG_HUSH) += hush.o +lib-$(CONFIG_LASH) += lash.o +lib-$(CONFIG_MSH) += msh.o +lib-$(CONFIG_FEATURE_COMMAND_EDITING) += cmdedit.o diff --git a/shell/ash.c b/shell/ash.c new file mode 100644 index 000000000..3a9998fc0 --- /dev/null +++ b/shell/ash.c @@ -0,0 +1,13701 @@ +/* vi: set sw=4 ts=4: */ +/* + * ash shell port for busybox + * + * Copyright (c) 1989, 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Copyright (c) 1997-2005 Herbert Xu <herbert@gondor.apana.org.au> + * was re-ported from NetBSD and debianized. + * + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + * + * Original BSD copyright notice is retained at the end of this file. + */ + +/* + * rewrite arith.y to micro stack based cryptic algorithm by + * Copyright (c) 2001 Aaron Lehmann <aaronl@vitelus.com> + * + * Modified by Paul Mundt <lethal@linux-sh.org> (c) 2004 to support + * dynamic variables. + * + * Modified by Vladimir Oleynik <dzo@simtreas.ru> (c) 2001-2005 to be + * used in busybox and size optimizations, + * rewrote arith (see notes to this), added locale support, + * rewrote dynamic variables. + * + */ + + +/* + * The follow should be set to reflect the type of system you have: + * JOBS -> 1 if you have Berkeley job control, 0 otherwise. + * define SYSV if you are running under System V. + * define DEBUG=1 to compile in debugging ('set -o debug' to turn on) + * define DEBUG=2 to compile in and turn on debugging. + * + * When debugging is on, debugging info will be written to ./trace and + * a quit signal will generate a core dump. + */ +#define DEBUG 0 + + +#define IFS_BROKEN + +#define PROFILE 0 + +#include "busybox.h" + +#if DEBUG +#define _GNU_SOURCE +#endif + +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/wait.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <stdarg.h> +#include <stddef.h> +#include <assert.h> +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <paths.h> +#include <setjmp.h> +#include <signal.h> +/*#include <stdint.h>*/ +#include <time.h> +#include <fnmatch.h> + +#ifdef CONFIG_ASH_JOB_CONTROL +#define JOBS 1 +#else +#undef JOBS +#endif + +#if JOBS || defined(CONFIG_ASH_READ_NCHARS) +#include <termios.h> +#endif + +#include "cmdedit.h" + +#ifdef __GLIBC__ +/* glibc sucks */ +static int *dash_errno; +#undef errno +#define errno (*dash_errno) +#endif + +#if defined(__uClinux__) +#error "Do not even bother, ash will not run on uClinux" +#endif + +#if DEBUG +#define _DIAGASSERT(assert_expr) assert(assert_expr) +#else +#define _DIAGASSERT(assert_expr) +#endif + + +#ifdef CONFIG_ASH_ALIAS +/* alias.h */ + +#define ALIASINUSE 1 +#define ALIASDEAD 2 + +struct alias { + struct alias *next; + char *name; + char *val; + int flag; +}; + +static struct alias *lookupalias(const char *, int); +static int aliascmd(int, char **); +static int unaliascmd(int, char **); +static void rmaliases(void); +static int unalias(const char *); +static void printalias(const struct alias *); +#endif + +/* cd.h */ + + +static void setpwd(const char *, int); + +/* error.h */ + + +/* + * Types of operations (passed to the errmsg routine). + */ + + +static const char not_found_msg[] = "%s: not found"; + + +#define E_OPEN "No such file" /* opening a file */ +#define E_CREAT "Directory nonexistent" /* creating a file */ +#define E_EXEC not_found_msg+4 /* executing a program */ + +/* + * We enclose jmp_buf in a structure so that we can declare pointers to + * jump locations. The global variable handler contains the location to + * jump to when an exception occurs, and the global variable exception + * contains a code identifying the exception. To implement nested + * exception handlers, the user should save the value of handler on entry + * to an inner scope, set handler to point to a jmploc structure for the + * inner scope, and restore handler on exit from the scope. + */ + +struct jmploc { + jmp_buf loc; +}; + +static struct jmploc *handler; +static int exception; +static volatile int suppressint; +static volatile sig_atomic_t intpending; + +/* exceptions */ +#define EXINT 0 /* SIGINT received */ +#define EXERROR 1 /* a generic error */ +#define EXSHELLPROC 2 /* execute a shell procedure */ +#define EXEXEC 3 /* command execution failed */ +#define EXEXIT 4 /* exit the shell */ +#define EXSIG 5 /* trapped signal in wait(1) */ + + +/* do we generate EXSIG events */ +static int exsig; +/* last pending signal */ +static volatile sig_atomic_t pendingsigs; + +/* + * These macros allow the user to suspend the handling of interrupt signals + * over a period of time. This is similar to SIGHOLD to or sigblock, but + * much more efficient and portable. (But hacking the kernel is so much + * more fun than worrying about efficiency and portability. :-)) + */ + +#define xbarrier() ({ __asm__ __volatile__ ("": : :"memory"); }) +#define INTOFF \ + ({ \ + suppressint++; \ + xbarrier(); \ + 0; \ + }) +#define SAVEINT(v) ((v) = suppressint) +#define RESTOREINT(v) \ + ({ \ + xbarrier(); \ + if ((suppressint = (v)) == 0 && intpending) onint(); \ + 0; \ + }) +#define EXSIGON() \ + ({ \ + exsig++; \ + xbarrier(); \ + if (pendingsigs) \ + exraise(EXSIG); \ + 0; \ + }) +/* EXSIG is turned off by evalbltin(). */ + + +static void exraise(int) ATTRIBUTE_NORETURN; +static void onint(void) ATTRIBUTE_NORETURN; + +static void sh_error(const char *, ...) ATTRIBUTE_NORETURN; +static void exerror(int, const char *, ...) ATTRIBUTE_NORETURN; + +static void sh_warnx(const char *, ...); + +#ifdef CONFIG_ASH_OPTIMIZE_FOR_SIZE +static void +inton(void) { + if (--suppressint == 0 && intpending) { + onint(); + } +} +#define INTON inton() +static void forceinton(void) +{ + suppressint = 0; + if (intpending) + onint(); +} +#define FORCEINTON forceinton() +#else +#define INTON \ + ({ \ + xbarrier(); \ + if (--suppressint == 0 && intpending) onint(); \ + 0; \ + }) +#define FORCEINTON \ + ({ \ + xbarrier(); \ + suppressint = 0; \ + if (intpending) onint(); \ + 0; \ + }) +#endif /* CONFIG_ASH_OPTIMIZE_FOR_SIZE */ + +/* expand.h */ + +struct strlist { + struct strlist *next; + char *text; +}; + + +struct arglist { + struct strlist *list; + struct strlist **lastp; +}; + +/* + * expandarg() flags + */ +#define EXP_FULL 0x1 /* perform word splitting & file globbing */ +#define EXP_TILDE 0x2 /* do normal tilde expansion */ +#define EXP_VARTILDE 0x4 /* expand tildes in an assignment */ +#define EXP_REDIR 0x8 /* file glob for a redirection (1 match only) */ +#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ +#define EXP_RECORD 0x20 /* need to record arguments for ifs breakup */ +#define EXP_VARTILDE2 0x40 /* expand tildes after colons only */ +#define EXP_WORD 0x80 /* expand word in parameter expansion */ +#define EXP_QWORD 0x100 /* expand word in quoted parameter expansion */ + + +union node; +static void expandarg(union node *, struct arglist *, int); +#define rmescapes(p) _rmescapes((p), 0) +static char *_rmescapes(char *, int); +static int casematch(union node *, char *); + +#ifdef CONFIG_ASH_MATH_SUPPORT +static void expari(int); +#endif + +/* eval.h */ + +static char *commandname; /* currently executing command */ +static struct strlist *cmdenviron; /* environment for builtin command */ +static int exitstatus; /* exit status of last command */ +static int back_exitstatus; /* exit status of backquoted command */ + + +struct backcmd { /* result of evalbackcmd */ + int fd; /* file descriptor to read from */ + char *buf; /* buffer */ + int nleft; /* number of chars in buffer */ + struct job *jp; /* job structure for command */ +}; + +/* + * This file was generated by the mknodes program. + */ + +#define NCMD 0 +#define NPIPE 1 +#define NREDIR 2 +#define NBACKGND 3 +#define NSUBSHELL 4 +#define NAND 5 +#define NOR 6 +#define NSEMI 7 +#define NIF 8 +#define NWHILE 9 +#define NUNTIL 10 +#define NFOR 11 +#define NCASE 12 +#define NCLIST 13 +#define NDEFUN 14 +#define NARG 15 +#define NTO 16 +#define NCLOBBER 17 +#define NFROM 18 +#define NFROMTO 19 +#define NAPPEND 20 +#define NTOFD 21 +#define NFROMFD 22 +#define NHERE 23 +#define NXHERE 24 +#define NNOT 25 + + + +struct ncmd { + int type; + union node *assign; + union node *args; + union node *redirect; +}; + + +struct npipe { + int type; + int backgnd; + struct nodelist *cmdlist; +}; + + +struct nredir { + int type; + union node *n; + union node *redirect; +}; + + +struct nbinary { + int type; + union node *ch1; + union node *ch2; +}; + + +struct nif { + int type; + union node *test; + union node *ifpart; + union node *elsepart; +}; + + +struct nfor { + int type; + union node *args; + union node *body; + char *var; +}; + + +struct ncase { + int type; + union node *expr; + union node *cases; +}; + + +struct nclist { + int type; + union node *next; + union node *pattern; + union node *body; +}; + + +struct narg { + int type; + union node *next; + char *text; + struct nodelist *backquote; +}; + + +struct nfile { + int type; + union node *next; + int fd; + union node *fname; + char *expfname; +}; + + +struct ndup { + int type; + union node *next; + int fd; + int dupfd; + union node *vname; +}; + + +struct nhere { + int type; + union node *next; + int fd; + union node *doc; +}; + + +struct nnot { + int type; + union node *com; +}; + + +union node { + int type; + struct ncmd ncmd; + struct npipe npipe; + struct nredir nredir; + struct nbinary nbinary; + struct nif nif; + struct nfor nfor; + struct ncase ncase; + struct nclist nclist; + struct narg narg; + struct nfile nfile; + struct ndup ndup; + struct nhere nhere; + struct nnot nnot; +}; + + +struct nodelist { + struct nodelist *next; + union node *n; +}; + + +struct funcnode { + int count; + union node n; +}; + + +static void freefunc(struct funcnode *); +/* parser.h */ + +/* control characters in argument strings */ +#define CTL_FIRST '\201' /* first 'special' character */ +#define CTLESC '\201' /* escape next character */ +#define CTLVAR '\202' /* variable defn */ +#define CTLENDVAR '\203' +#define CTLBACKQ '\204' +#define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */ +/* CTLBACKQ | CTLQUOTE == '\205' */ +#define CTLARI '\206' /* arithmetic expression */ +#define CTLENDARI '\207' +#define CTLQUOTEMARK '\210' +#define CTL_LAST '\210' /* last 'special' character */ + +/* variable substitution byte (follows CTLVAR) */ +#define VSTYPE 0x0f /* type of variable substitution */ +#define VSNUL 0x10 /* colon--treat the empty string as unset */ +#define VSQUOTE 0x80 /* inside double quotes--suppress splitting */ + +/* values of VSTYPE field */ +#define VSNORMAL 0x1 /* normal variable: $var or ${var} */ +#define VSMINUS 0x2 /* ${var-text} */ +#define VSPLUS 0x3 /* ${var+text} */ +#define VSQUESTION 0x4 /* ${var?message} */ +#define VSASSIGN 0x5 /* ${var=text} */ +#define VSTRIMRIGHT 0x6 /* ${var%pattern} */ +#define VSTRIMRIGHTMAX 0x7 /* ${var%%pattern} */ +#define VSTRIMLEFT 0x8 /* ${var#pattern} */ +#define VSTRIMLEFTMAX 0x9 /* ${var##pattern} */ +#define VSLENGTH 0xa /* ${#var} */ + +/* values of checkkwd variable */ +#define CHKALIAS 0x1 +#define CHKKWD 0x2 +#define CHKNL 0x4 + +#define IBUFSIZ (BUFSIZ + 1) + +/* + * NEOF is returned by parsecmd when it encounters an end of file. It + * must be distinct from NULL, so we use the address of a variable that + * happens to be handy. + */ +static int plinno = 1; /* input line number */ + +/* number of characters left in input buffer */ +static int parsenleft; /* copy of parsefile->nleft */ +static int parselleft; /* copy of parsefile->lleft */ + +/* next character in input buffer */ +static char *parsenextc; /* copy of parsefile->nextc */ + +struct strpush { + struct strpush *prev; /* preceding string on stack */ + char *prevstring; + int prevnleft; +#ifdef CONFIG_ASH_ALIAS + struct alias *ap; /* if push was associated with an alias */ +#endif + char *string; /* remember the string since it may change */ +}; + +struct parsefile { + struct parsefile *prev; /* preceding file on stack */ + int linno; /* current line */ + int fd; /* file descriptor (or -1 if string) */ + int nleft; /* number of chars left in this line */ + int lleft; /* number of chars left in this buffer */ + char *nextc; /* next char in buffer */ + char *buf; /* input buffer */ + struct strpush *strpush; /* for pushing strings at this level */ + struct strpush basestrpush; /* so pushing one is fast */ +}; + +static struct parsefile basepf; /* top level input file */ +#define basebuf bb_common_bufsiz1 /* buffer for top level input file */ +static struct parsefile *parsefile = &basepf; /* current input file */ + + +static int tokpushback; /* last token pushed back */ +#define NEOF ((union node *)&tokpushback) +static int parsebackquote; /* nonzero if we are inside backquotes */ +static int doprompt; /* if set, prompt the user */ +static int needprompt; /* true if interactive and at start of line */ +static int lasttoken; /* last token read */ +static char *wordtext; /* text of last word returned by readtoken */ +static int checkkwd; +static struct nodelist *backquotelist; +static union node *redirnode; +static struct heredoc *heredoc; +static int quoteflag; /* set if (part of) last token was quoted */ +static int startlinno; /* line # where last token started */ + +static union node *parsecmd(int); +static void fixredir(union node *, const char *, int); +static const char *const *findkwd(const char *); +static char *endofname(const char *); + +/* shell.h */ + +typedef void *pointer; + +static char nullstr[1]; /* zero length string */ +static const char spcstr[] = " "; +static const char snlfmt[] = "%s\n"; +static const char dolatstr[] = { CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' }; +static const char illnum[] = "Illegal number: %s"; +static const char homestr[] = "HOME"; + +#if DEBUG +#define TRACE(param) trace param +#define TRACEV(param) tracev param +#else +#define TRACE(param) +#define TRACEV(param) +#endif + +#if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96) +#define __builtin_expect(x, expected_value) (x) +#endif + +#define xlikely(x) __builtin_expect((x),1) + + +#define TEOF 0 +#define TNL 1 +#define TREDIR 2 +#define TWORD 3 +#define TSEMI 4 +#define TBACKGND 5 +#define TAND 6 +#define TOR 7 +#define TPIPE 8 +#define TLP 9 +#define TRP 10 +#define TENDCASE 11 +#define TENDBQUOTE 12 +#define TNOT 13 +#define TCASE 14 +#define TDO 15 +#define TDONE 16 +#define TELIF 17 +#define TELSE 18 +#define TESAC 19 +#define TFI 20 +#define TFOR 21 +#define TIF 22 +#define TIN 23 +#define TTHEN 24 +#define TUNTIL 25 +#define TWHILE 26 +#define TBEGIN 27 +#define TEND 28 + +/* first char is indicating which tokens mark the end of a list */ +static const char *const tokname_array[] = { + "\1end of file", + "\0newline", + "\0redirection", + "\0word", + "\0;", + "\0&", + "\0&&", + "\0||", + "\0|", + "\0(", + "\1)", + "\1;;", + "\1`", +#define KWDOFFSET 13 + /* the following are keywords */ + "\0!", + "\0case", + "\1do", + "\1done", + "\1elif", + "\1else", + "\1esac", + "\1fi", + "\0for", + "\0if", + "\0in", + "\1then", + "\0until", + "\0while", + "\0{", + "\1}", +}; + +static const char *tokname(int tok) +{ + static char buf[16]; + + if (tok >= TSEMI) + buf[0] = '"'; + sprintf(buf + (tok >= TSEMI), "%s%c", + tokname_array[tok] + 1, (tok >= TSEMI ? '"' : 0)); + return buf; +} + +/* machdep.h */ + +/* + * Most machines require the value returned from malloc to be aligned + * in some way. The following macro will get this right on many machines. + */ + +#define SHELL_SIZE (sizeof(union {int i; char *cp; double d; }) - 1) +/* + * It appears that grabstackstr() will barf with such alignments + * because stalloc() will return a string allocated in a new stackblock. + */ +#define SHELL_ALIGN(nbytes) (((nbytes) + SHELL_SIZE) & ~SHELL_SIZE) + +/* + * This file was generated by the mksyntax program. + */ + + +/* Syntax classes */ +#define CWORD 0 /* character is nothing special */ +#define CNL 1 /* newline character */ +#define CBACK 2 /* a backslash character */ +#define CSQUOTE 3 /* single quote */ +#define CDQUOTE 4 /* double quote */ +#define CENDQUOTE 5 /* a terminating quote */ +#define CBQUOTE 6 /* backwards single quote */ +#define CVAR 7 /* a dollar sign */ +#define CENDVAR 8 /* a '}' character */ +#define CLP 9 /* a left paren in arithmetic */ +#define CRP 10 /* a right paren in arithmetic */ +#define CENDFILE 11 /* end of file */ +#define CCTL 12 /* like CWORD, except it must be escaped */ +#define CSPCL 13 /* these terminate a word */ +#define CIGN 14 /* character should be ignored */ + +#ifdef CONFIG_ASH_ALIAS +#define SYNBASE 130 +#define PEOF -130 +#define PEOA -129 +#define PEOA_OR_PEOF PEOA +#else +#define SYNBASE 129 +#define PEOF -129 +#define PEOA_OR_PEOF PEOF +#endif + +#define is_digit(c) ((unsigned)((c) - '0') <= 9) +#define is_name(c) ((c) == '_' || isalpha((unsigned char)(c))) +#define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c))) + +/* C99 say: "char" declaration may be signed or unsigned default */ +#define SC2INT(chr2may_be_negative_int) (int)((signed char)chr2may_be_negative_int) + +/* + * is_special(c) evaluates to 1 for c in "!#$*-0123456789?@"; 0 otherwise + * (assuming ascii char codes, as the original implementation did) + */ +#define is_special(c) \ + ( (((unsigned int)c) - 33 < 32) \ + && ((0xc1ff920dUL >> (((unsigned int)c) - 33)) & 1)) + +#define digit_val(c) ((c) - '0') + +/* + * This file was generated by the mksyntax program. + */ + +#ifdef CONFIG_ASH_OPTIMIZE_FOR_SIZE +#define USE_SIT_FUNCTION +#endif + +/* number syntax index */ +#define BASESYNTAX 0 /* not in quotes */ +#define DQSYNTAX 1 /* in double quotes */ +#define SQSYNTAX 2 /* in single quotes */ +#define ARISYNTAX 3 /* in arithmetic */ + +#ifdef CONFIG_ASH_MATH_SUPPORT +static const char S_I_T[][4] = { +#ifdef CONFIG_ASH_ALIAS + {CSPCL, CIGN, CIGN, CIGN}, /* 0, PEOA */ +#endif + {CSPCL, CWORD, CWORD, CWORD}, /* 1, ' ' */ + {CNL, CNL, CNL, CNL}, /* 2, \n */ + {CWORD, CCTL, CCTL, CWORD}, /* 3, !*-/:=?[]~ */ + {CDQUOTE, CENDQUOTE, CWORD, CWORD}, /* 4, '"' */ + {CVAR, CVAR, CWORD, CVAR}, /* 5, $ */ + {CSQUOTE, CWORD, CENDQUOTE, CWORD}, /* 6, "'" */ + {CSPCL, CWORD, CWORD, CLP}, /* 7, ( */ + {CSPCL, CWORD, CWORD, CRP}, /* 8, ) */ + {CBACK, CBACK, CCTL, CBACK}, /* 9, \ */ + {CBQUOTE, CBQUOTE, CWORD, CBQUOTE}, /* 10, ` */ + {CENDVAR, CENDVAR, CWORD, CENDVAR}, /* 11, } */ +#ifndef USE_SIT_FUNCTION + {CENDFILE, CENDFILE, CENDFILE, CENDFILE}, /* 12, PEOF */ + {CWORD, CWORD, CWORD, CWORD}, /* 13, 0-9A-Za-z */ + {CCTL, CCTL, CCTL, CCTL} /* 14, CTLESC ... */ +#endif +}; +#else +static const char S_I_T[][3] = { +#ifdef CONFIG_ASH_ALIAS + {CSPCL, CIGN, CIGN}, /* 0, PEOA */ +#endif + {CSPCL, CWORD, CWORD}, /* 1, ' ' */ + {CNL, CNL, CNL}, /* 2, \n */ + {CWORD, CCTL, CCTL}, /* 3, !*-/:=?[]~ */ + {CDQUOTE, CENDQUOTE, CWORD}, /* 4, '"' */ + {CVAR, CVAR, CWORD}, /* 5, $ */ + {CSQUOTE, CWORD, CENDQUOTE}, /* 6, "'" */ + {CSPCL, CWORD, CWORD}, /* 7, ( */ + {CSPCL, CWORD, CWORD}, /* 8, ) */ + {CBACK, CBACK, CCTL}, /* 9, \ */ + {CBQUOTE, CBQUOTE, CWORD}, /* 10, ` */ + {CENDVAR, CENDVAR, CWORD}, /* 11, } */ +#ifndef USE_SIT_FUNCTION + {CENDFILE, CENDFILE, CENDFILE}, /* 12, PEOF */ + {CWORD, CWORD, CWORD}, /* 13, 0-9A-Za-z */ + {CCTL, CCTL, CCTL} /* 14, CTLESC ... */ +#endif +}; +#endif /* CONFIG_ASH_MATH_SUPPORT */ + +#ifdef USE_SIT_FUNCTION + +#define U_C(c) ((unsigned char)(c)) + +static int SIT(int c, int syntax) +{ + static const char spec_symbls[] = "\t\n !\"$&'()*-/:;<=>?[\\]`|}~"; +#ifdef CONFIG_ASH_ALIAS + static const char syntax_index_table[] = { + 1, 2, 1, 3, 4, 5, 1, 6, /* "\t\n !\"$&'" */ + 7, 8, 3, 3, 3, 3, 1, 1, /* "()*-/:;<" */ + 3, 1, 3, 3, 9, 3, 10, 1, /* "=>?[\\]`|" */ + 11, 3 /* "}~" */ + }; +#else + static const char syntax_index_table[] = { + 0, 1, 0, 2, 3, 4, 0, 5, /* "\t\n !\"$&'" */ + 6, 7, 2, 2, 2, 2, 0, 0, /* "()*-/:;<" */ + 2, 0, 2, 2, 8, 2, 9, 0, /* "=>?[\\]`|" */ + 10, 2 /* "}~" */ + }; +#endif + const char *s; + int indx; + + if (c == PEOF) /* 2^8+2 */ + return CENDFILE; +#ifdef CONFIG_ASH_ALIAS + if (c == PEOA) /* 2^8+1 */ + indx = 0; + else +#endif + if (U_C(c) >= U_C(CTLESC) && U_C(c) <= U_C(CTLQUOTEMARK)) + return CCTL; + else { + s = strchr(spec_symbls, c); + if (s == 0 || *s == 0) + return CWORD; + indx = syntax_index_table[(s - spec_symbls)]; + } + return S_I_T[indx][syntax]; +} + +#else /* USE_SIT_FUNCTION */ + +#define SIT(c, syntax) S_I_T[(int)syntax_index_table[((int)c)+SYNBASE]][syntax] + +#ifdef CONFIG_ASH_ALIAS +#define CSPCL_CIGN_CIGN_CIGN 0 +#define CSPCL_CWORD_CWORD_CWORD 1 +#define CNL_CNL_CNL_CNL 2 +#define CWORD_CCTL_CCTL_CWORD 3 +#define CDQUOTE_CENDQUOTE_CWORD_CWORD 4 +#define CVAR_CVAR_CWORD_CVAR 5 +#define CSQUOTE_CWORD_CENDQUOTE_CWORD 6 +#define CSPCL_CWORD_CWORD_CLP 7 +#define CSPCL_CWORD_CWORD_CRP 8 +#define CBACK_CBACK_CCTL_CBACK 9 +#define CBQUOTE_CBQUOTE_CWORD_CBQUOTE 10 +#define CENDVAR_CENDVAR_CWORD_CENDVAR 11 +#define CENDFILE_CENDFILE_CENDFILE_CENDFILE 12 +#define CWORD_CWORD_CWORD_CWORD 13 +#define CCTL_CCTL_CCTL_CCTL 14 +#else +#define CSPCL_CWORD_CWORD_CWORD 0 +#define CNL_CNL_CNL_CNL 1 +#define CWORD_CCTL_CCTL_CWORD 2 +#define CDQUOTE_CENDQUOTE_CWORD_CWORD 3 +#define CVAR_CVAR_CWORD_CVAR 4 +#define CSQUOTE_CWORD_CENDQUOTE_CWORD 5 +#define CSPCL_CWORD_CWORD_CLP 6 +#define CSPCL_CWORD_CWORD_CRP 7 +#define CBACK_CBACK_CCTL_CBACK 8 +#define CBQUOTE_CBQUOTE_CWORD_CBQUOTE 9 +#define CENDVAR_CENDVAR_CWORD_CENDVAR 10 +#define CENDFILE_CENDFILE_CENDFILE_CENDFILE 11 +#define CWORD_CWORD_CWORD_CWORD 12 +#define CCTL_CCTL_CCTL_CCTL 13 +#endif + +static const char syntax_index_table[258] = { + /* BASESYNTAX_DQSYNTAX_SQSYNTAX_ARISYNTAX */ + /* 0 PEOF */ CENDFILE_CENDFILE_CENDFILE_CENDFILE, +#ifdef CONFIG_ASH_ALIAS + /* 1 PEOA */ CSPCL_CIGN_CIGN_CIGN, +#endif + /* 2 -128 0x80 */ CWORD_CWORD_CWORD_CWORD, + /* 3 -127 CTLESC */ CCTL_CCTL_CCTL_CCTL, + /* 4 -126 CTLVAR */ CCTL_CCTL_CCTL_CCTL, + /* 5 -125 CTLENDVAR */ CCTL_CCTL_CCTL_CCTL, + /* 6 -124 CTLBACKQ */ CCTL_CCTL_CCTL_CCTL, + /* 7 -123 CTLQUOTE */ CCTL_CCTL_CCTL_CCTL, + /* 8 -122 CTLARI */ CCTL_CCTL_CCTL_CCTL, + /* 9 -121 CTLENDARI */ CCTL_CCTL_CCTL_CCTL, + /* 10 -120 CTLQUOTEMARK */ CCTL_CCTL_CCTL_CCTL, + /* 11 -119 */ CWORD_CWORD_CWORD_CWORD, + /* 12 -118 */ CWORD_CWORD_CWORD_CWORD, + /* 13 -117 */ CWORD_CWORD_CWORD_CWORD, + /* 14 -116 */ CWORD_CWORD_CWORD_CWORD, + /* 15 -115 */ CWORD_CWORD_CWORD_CWORD, + /* 16 -114 */ CWORD_CWORD_CWORD_CWORD, + /* 17 -113 */ CWORD_CWORD_CWORD_CWORD, + /* 18 -112 */ CWORD_CWORD_CWORD_CWORD, + /* 19 -111 */ CWORD_CWORD_CWORD_CWORD, + /* 20 -110 */ CWORD_CWORD_CWORD_CWORD, + /* 21 -109 */ CWORD_CWORD_CWORD_CWORD, + /* 22 -108 */ CWORD_CWORD_CWORD_CWORD, + /* 23 -107 */ CWORD_CWORD_CWORD_CWORD, + /* 24 -106 */ CWORD_CWORD_CWORD_CWORD, + /* 25 -105 */ CWORD_CWORD_CWORD_CWORD, + /* 26 -104 */ CWORD_CWORD_CWORD_CWORD, + /* 27 -103 */ CWORD_CWORD_CWORD_CWORD, + /* 28 -102 */ CWORD_CWORD_CWORD_CWORD, + /* 29 -101 */ CWORD_CWORD_CWORD_CWORD, + /* 30 -100 */ CWORD_CWORD_CWORD_CWORD, + /* 31 -99 */ CWORD_CWORD_CWORD_CWORD, + /* 32 -98 */ CWORD_CWORD_CWORD_CWORD, + /* 33 -97 */ CWORD_CWORD_CWORD_CWORD, + /* 34 -96 */ CWORD_CWORD_CWORD_CWORD, + /* 35 -95 */ CWORD_CWORD_CWORD_CWORD, + /* 36 -94 */ CWORD_CWORD_CWORD_CWORD, + /* 37 -93 */ CWORD_CWORD_CWORD_CWORD, + /* 38 -92 */ CWORD_CWORD_CWORD_CWORD, + /* 39 -91 */ CWORD_CWORD_CWORD_CWORD, + /* 40 -90 */ CWORD_CWORD_CWORD_CWORD, + /* 41 -89 */ CWORD_CWORD_CWORD_CWORD, + /* 42 -88 */ CWORD_CWORD_CWORD_CWORD, + /* 43 -87 */ CWORD_CWORD_CWORD_CWORD, + /* 44 -86 */ CWORD_CWORD_CWORD_CWORD, + /* 45 -85 */ CWORD_CWORD_CWORD_CWORD, + /* 46 -84 */ CWORD_CWORD_CWORD_CWORD, + /* 47 -83 */ CWORD_CWORD_CWORD_CWORD, + /* 48 -82 */ CWORD_CWORD_CWORD_CWORD, + /* 49 -81 */ CWORD_CWORD_CWORD_CWORD, + /* 50 -80 */ CWORD_CWORD_CWORD_CWORD, + /* 51 -79 */ CWORD_CWORD_CWORD_CWORD, + /* 52 -78 */ CWORD_CWORD_CWORD_CWORD, + /* 53 -77 */ CWORD_CWORD_CWORD_CWORD, + /* 54 -76 */ CWORD_CWORD_CWORD_CWORD, + /* 55 -75 */ CWORD_CWORD_CWORD_CWORD, + /* 56 -74 */ CWORD_CWORD_CWORD_CWORD, + /* 57 -73 */ CWORD_CWORD_CWORD_CWORD, + /* 58 -72 */ CWORD_CWORD_CWORD_CWORD, + /* 59 -71 */ CWORD_CWORD_CWORD_CWORD, + /* 60 -70 */ CWORD_CWORD_CWORD_CWORD, + /* 61 -69 */ CWORD_CWORD_CWORD_CWORD, + /* 62 -68 */ CWORD_CWORD_CWORD_CWORD, + /* 63 -67 */ CWORD_CWORD_CWORD_CWORD, + /* 64 -66 */ CWORD_CWORD_CWORD_CWORD, + /* 65 -65 */ CWORD_CWORD_CWORD_CWORD, + /* 66 -64 */ CWORD_CWORD_CWORD_CWORD, + /* 67 -63 */ CWORD_CWORD_CWORD_CWORD, + /* 68 -62 */ CWORD_CWORD_CWORD_CWORD, + /* 69 -61 */ CWORD_CWORD_CWORD_CWORD, + /* 70 -60 */ CWORD_CWORD_CWORD_CWORD, + /* 71 -59 */ CWORD_CWORD_CWORD_CWORD, + /* 72 -58 */ CWORD_CWORD_CWORD_CWORD, + /* 73 -57 */ CWORD_CWORD_CWORD_CWORD, + /* 74 -56 */ CWORD_CWORD_CWORD_CWORD, + /* 75 -55 */ CWORD_CWORD_CWORD_CWORD, + /* 76 -54 */ CWORD_CWORD_CWORD_CWORD, + /* 77 -53 */ CWORD_CWORD_CWORD_CWORD, + /* 78 -52 */ CWORD_CWORD_CWORD_CWORD, + /* 79 -51 */ CWORD_CWORD_CWORD_CWORD, + /* 80 -50 */ CWORD_CWORD_CWORD_CWORD, + /* 81 -49 */ CWORD_CWORD_CWORD_CWORD, + /* 82 -48 */ CWORD_CWORD_CWORD_CWORD, + /* 83 -47 */ CWORD_CWORD_CWORD_CWORD, + /* 84 -46 */ CWORD_CWORD_CWORD_CWORD, + /* 85 -45 */ CWORD_CWORD_CWORD_CWORD, + /* 86 -44 */ CWORD_CWORD_CWORD_CWORD, + /* 87 -43 */ CWORD_CWORD_CWORD_CWORD, + /* 88 -42 */ CWORD_CWORD_CWORD_CWORD, + /* 89 -41 */ CWORD_CWORD_CWORD_CWORD, + /* 90 -40 */ CWORD_CWORD_CWORD_CWORD, + /* 91 -39 */ CWORD_CWORD_CWORD_CWORD, + /* 92 -38 */ CWORD_CWORD_CWORD_CWORD, + /* 93 -37 */ CWORD_CWORD_CWORD_CWORD, + /* 94 -36 */ CWORD_CWORD_CWORD_CWORD, + /* 95 -35 */ CWORD_CWORD_CWORD_CWORD, + /* 96 -34 */ CWORD_CWORD_CWORD_CWORD, + /* 97 -33 */ CWORD_CWORD_CWORD_CWORD, + /* 98 -32 */ CWORD_CWORD_CWORD_CWORD, + /* 99 -31 */ CWORD_CWORD_CWORD_CWORD, + /* 100 -30 */ CWORD_CWORD_CWORD_CWORD, + /* 101 -29 */ CWORD_CWORD_CWORD_CWORD, + /* 102 -28 */ CWORD_CWORD_CWORD_CWORD, + /* 103 -27 */ CWORD_CWORD_CWORD_CWORD, + /* 104 -26 */ CWORD_CWORD_CWORD_CWORD, + /* 105 -25 */ CWORD_CWORD_CWORD_CWORD, + /* 106 -24 */ CWORD_CWORD_CWORD_CWORD, + /* 107 -23 */ CWORD_CWORD_CWORD_CWORD, + /* 108 -22 */ CWORD_CWORD_CWORD_CWORD, + /* 109 -21 */ CWORD_CWORD_CWORD_CWORD, + /* 110 -20 */ CWORD_CWORD_CWORD_CWORD, + /* 111 -19 */ CWORD_CWORD_CWORD_CWORD, + /* 112 -18 */ CWORD_CWORD_CWORD_CWORD, + /* 113 -17 */ CWORD_CWORD_CWORD_CWORD, + /* 114 -16 */ CWORD_CWORD_CWORD_CWORD, + /* 115 -15 */ CWORD_CWORD_CWORD_CWORD, + /* 116 -14 */ CWORD_CWORD_CWORD_CWORD, + /* 117 -13 */ CWORD_CWORD_CWORD_CWORD, + /* 118 -12 */ CWORD_CWORD_CWORD_CWORD, + /* 119 -11 */ CWORD_CWORD_CWORD_CWORD, + /* 120 -10 */ CWORD_CWORD_CWORD_CWORD, + /* 121 -9 */ CWORD_CWORD_CWORD_CWORD, + /* 122 -8 */ CWORD_CWORD_CWORD_CWORD, + /* 123 -7 */ CWORD_CWORD_CWORD_CWORD, + /* 124 -6 */ CWORD_CWORD_CWORD_CWORD, + /* 125 -5 */ CWORD_CWORD_CWORD_CWORD, + /* 126 -4 */ CWORD_CWORD_CWORD_CWORD, + /* 127 -3 */ CWORD_CWORD_CWORD_CWORD, + /* 128 -2 */ CWORD_CWORD_CWORD_CWORD, + /* 129 -1 */ CWORD_CWORD_CWORD_CWORD, + /* 130 0 */ CWORD_CWORD_CWORD_CWORD, + /* 131 1 */ CWORD_CWORD_CWORD_CWORD, + /* 132 2 */ CWORD_CWORD_CWORD_CWORD, + /* 133 3 */ CWORD_CWORD_CWORD_CWORD, + /* 134 4 */ CWORD_CWORD_CWORD_CWORD, + /* 135 5 */ CWORD_CWORD_CWORD_CWORD, + /* 136 6 */ CWORD_CWORD_CWORD_CWORD, + /* 137 7 */ CWORD_CWORD_CWORD_CWORD, + /* 138 8 */ CWORD_CWORD_CWORD_CWORD, + /* 139 9 "\t" */ CSPCL_CWORD_CWORD_CWORD, + /* 140 10 "\n" */ CNL_CNL_CNL_CNL, + /* 141 11 */ CWORD_CWORD_CWORD_CWORD, + /* 142 12 */ CWORD_CWORD_CWORD_CWORD, + /* 143 13 */ CWORD_CWORD_CWORD_CWORD, + /* 144 14 */ CWORD_CWORD_CWORD_CWORD, + /* 145 15 */ CWORD_CWORD_CWORD_CWORD, + /* 146 16 */ CWORD_CWORD_CWORD_CWORD, + /* 147 17 */ CWORD_CWORD_CWORD_CWORD, + /* 148 18 */ CWORD_CWORD_CWORD_CWORD, + /* 149 19 */ CWORD_CWORD_CWORD_CWORD, + /* 150 20 */ CWORD_CWORD_CWORD_CWORD, + /* 151 21 */ CWORD_CWORD_CWORD_CWORD, + /* 152 22 */ CWORD_CWORD_CWORD_CWORD, + /* 153 23 */ CWORD_CWORD_CWORD_CWORD, + /* 154 24 */ CWORD_CWORD_CWORD_CWORD, + /* 155 25 */ CWORD_CWORD_CWORD_CWORD, + /* 156 26 */ CWORD_CWORD_CWORD_CWORD, + /* 157 27 */ CWORD_CWORD_CWORD_CWORD, + /* 158 28 */ CWORD_CWORD_CWORD_CWORD, + /* 159 29 */ CWORD_CWORD_CWORD_CWORD, + /* 160 30 */ CWORD_CWORD_CWORD_CWORD, + /* 161 31 */ CWORD_CWORD_CWORD_CWORD, + /* 162 32 " " */ CSPCL_CWORD_CWORD_CWORD, + /* 163 33 "!" */ CWORD_CCTL_CCTL_CWORD, + /* 164 34 """ */ CDQUOTE_CENDQUOTE_CWORD_CWORD, + /* 165 35 "#" */ CWORD_CWORD_CWORD_CWORD, + /* 166 36 "$" */ CVAR_CVAR_CWORD_CVAR, + /* 167 37 "%" */ CWORD_CWORD_CWORD_CWORD, + /* 168 38 "&" */ CSPCL_CWORD_CWORD_CWORD, + /* 169 39 "'" */ CSQUOTE_CWORD_CENDQUOTE_CWORD, + /* 170 40 "(" */ CSPCL_CWORD_CWORD_CLP, + /* 171 41 ")" */ CSPCL_CWORD_CWORD_CRP, + /* 172 42 "*" */ CWORD_CCTL_CCTL_CWORD, + /* 173 43 "+" */ CWORD_CWORD_CWORD_CWORD, + /* 174 44 "," */ CWORD_CWORD_CWORD_CWORD, + /* 175 45 "-" */ CWORD_CCTL_CCTL_CWORD, + /* 176 46 "." */ CWORD_CWORD_CWORD_CWORD, + /* 177 47 "/" */ CWORD_CCTL_CCTL_CWORD, + /* 178 48 "0" */ CWORD_CWORD_CWORD_CWORD, + /* 179 49 "1" */ CWORD_CWORD_CWORD_CWORD, + /* 180 50 "2" */ CWORD_CWORD_CWORD_CWORD, + /* 181 51 "3" */ CWORD_CWORD_CWORD_CWORD, + /* 182 52 "4" */ CWORD_CWORD_CWORD_CWORD, + /* 183 53 "5" */ CWORD_CWORD_CWORD_CWORD, + /* 184 54 "6" */ CWORD_CWORD_CWORD_CWORD, + /* 185 55 "7" */ CWORD_CWORD_CWORD_CWORD, + /* 186 56 "8" */ CWORD_CWORD_CWORD_CWORD, + /* 187 57 "9" */ CWORD_CWORD_CWORD_CWORD, + /* 188 58 ":" */ CWORD_CCTL_CCTL_CWORD, + /* 189 59 ";" */ CSPCL_CWORD_CWORD_CWORD, + /* 190 60 "<" */ CSPCL_CWORD_CWORD_CWORD, + /* 191 61 "=" */ CWORD_CCTL_CCTL_CWORD, + /* 192 62 ">" */ CSPCL_CWORD_CWORD_CWORD, + /* 193 63 "?" */ CWORD_CCTL_CCTL_CWORD, + /* 194 64 "@" */ CWORD_CWORD_CWORD_CWORD, + /* 195 65 "A" */ CWORD_CWORD_CWORD_CWORD, + /* 196 66 "B" */ CWORD_CWORD_CWORD_CWORD, + /* 197 67 "C" */ CWORD_CWORD_CWORD_CWORD, + /* 198 68 "D" */ CWORD_CWORD_CWORD_CWORD, + /* 199 69 "E" */ CWORD_CWORD_CWORD_CWORD, + /* 200 70 "F" */ CWORD_CWORD_CWORD_CWORD, + /* 201 71 "G" */ CWORD_CWORD_CWORD_CWORD, + /* 202 72 "H" */ CWORD_CWORD_CWORD_CWORD, + /* 203 73 "I" */ CWORD_CWORD_CWORD_CWORD, + /* 204 74 "J" */ CWORD_CWORD_CWORD_CWORD, + /* 205 75 "K" */ CWORD_CWORD_CWORD_CWORD, + /* 206 76 "L" */ CWORD_CWORD_CWORD_CWORD, + /* 207 77 "M" */ CWORD_CWORD_CWORD_CWORD, + /* 208 78 "N" */ CWORD_CWORD_CWORD_CWORD, + /* 209 79 "O" */ CWORD_CWORD_CWORD_CWORD, + /* 210 80 "P" */ CWORD_CWORD_CWORD_CWORD, + /* 211 81 "Q" */ CWORD_CWORD_CWORD_CWORD, + /* 212 82 "R" */ CWORD_CWORD_CWORD_CWORD, + /* 213 83 "S" */ CWORD_CWORD_CWORD_CWORD, + /* 214 84 "T" */ CWORD_CWORD_CWORD_CWORD, + /* 215 85 "U" */ CWORD_CWORD_CWORD_CWORD, + /* 216 86 "V" */ CWORD_CWORD_CWORD_CWORD, + /* 217 87 "W" */ CWORD_CWORD_CWORD_CWORD, + /* 218 88 "X" */ CWORD_CWORD_CWORD_CWORD, + /* 219 89 "Y" */ CWORD_CWORD_CWORD_CWORD, + /* 220 90 "Z" */ CWORD_CWORD_CWORD_CWORD, + /* 221 91 "[" */ CWORD_CCTL_CCTL_CWORD, + /* 222 92 "\" */ CBACK_CBACK_CCTL_CBACK, + /* 223 93 "]" */ CWORD_CCTL_CCTL_CWORD, + /* 224 94 "^" */ CWORD_CWORD_CWORD_CWORD, + /* 225 95 "_" */ CWORD_CWORD_CWORD_CWORD, + /* 226 96 "`" */ CBQUOTE_CBQUOTE_CWORD_CBQUOTE, + /* 227 97 "a" */ CWORD_CWORD_CWORD_CWORD, + /* 228 98 "b" */ CWORD_CWORD_CWORD_CWORD, + /* 229 99 "c" */ CWORD_CWORD_CWORD_CWORD, + /* 230 100 "d" */ CWORD_CWORD_CWORD_CWORD, + /* 231 101 "e" */ CWORD_CWORD_CWORD_CWORD, + /* 232 102 "f" */ CWORD_CWORD_CWORD_CWORD, + /* 233 103 "g" */ CWORD_CWORD_CWORD_CWORD, + /* 234 104 "h" */ CWORD_CWORD_CWORD_CWORD, + /* 235 105 "i" */ CWORD_CWORD_CWORD_CWORD, + /* 236 106 "j" */ CWORD_CWORD_CWORD_CWORD, + /* 237 107 "k" */ CWORD_CWORD_CWORD_CWORD, + /* 238 108 "l" */ CWORD_CWORD_CWORD_CWORD, + /* 239 109 "m" */ CWORD_CWORD_CWORD_CWORD, + /* 240 110 "n" */ CWORD_CWORD_CWORD_CWORD, + /* 241 111 "o" */ CWORD_CWORD_CWORD_CWORD, + /* 242 112 "p" */ CWORD_CWORD_CWORD_CWORD, + /* 243 113 "q" */ CWORD_CWORD_CWORD_CWORD, + /* 244 114 "r" */ CWORD_CWORD_CWORD_CWORD, + /* 245 115 "s" */ CWORD_CWORD_CWORD_CWORD, + /* 246 116 "t" */ CWORD_CWORD_CWORD_CWORD, + /* 247 117 "u" */ CWORD_CWORD_CWORD_CWORD, + /* 248 118 "v" */ CWORD_CWORD_CWORD_CWORD, + /* 249 119 "w" */ CWORD_CWORD_CWORD_CWORD, + /* 250 120 "x" */ CWORD_CWORD_CWORD_CWORD, + /* 251 121 "y" */ CWORD_CWORD_CWORD_CWORD, + /* 252 122 "z" */ CWORD_CWORD_CWORD_CWORD, + /* 253 123 "{" */ CWORD_CWORD_CWORD_CWORD, + /* 254 124 "|" */ CSPCL_CWORD_CWORD_CWORD, + /* 255 125 "}" */ CENDVAR_CENDVAR_CWORD_CENDVAR, + /* 256 126 "~" */ CWORD_CCTL_CCTL_CWORD, + /* 257 127 */ CWORD_CWORD_CWORD_CWORD, +}; + +#endif /* USE_SIT_FUNCTION */ + +/* alias.c */ + + +#define ATABSIZE 39 + +static int funcblocksize; /* size of structures in function */ +static int funcstringsize; /* size of strings in node */ +static pointer funcblock; /* block to allocate function from */ +static char *funcstring; /* block to allocate strings from */ + +static const short nodesize[26] = { + SHELL_ALIGN(sizeof (struct ncmd)), + SHELL_ALIGN(sizeof (struct npipe)), + SHELL_ALIGN(sizeof (struct nredir)), + SHELL_ALIGN(sizeof (struct nredir)), + SHELL_ALIGN(sizeof (struct nredir)), + SHELL_ALIGN(sizeof (struct nbinary)), + SHELL_ALIGN(sizeof (struct nbinary)), + SHELL_ALIGN(sizeof (struct nbinary)), + SHELL_ALIGN(sizeof (struct nif)), + SHELL_ALIGN(sizeof (struct nbinary)), + SHELL_ALIGN(sizeof (struct nbinary)), + SHELL_ALIGN(sizeof (struct nfor)), + SHELL_ALIGN(sizeof (struct ncase)), + SHELL_ALIGN(sizeof (struct nclist)), + SHELL_ALIGN(sizeof (struct narg)), + SHELL_ALIGN(sizeof (struct narg)), + SHELL_ALIGN(sizeof (struct nfile)), + SHELL_ALIGN(sizeof (struct nfile)), + SHELL_ALIGN(sizeof (struct nfile)), + SHELL_ALIGN(sizeof (struct nfile)), + SHELL_ALIGN(sizeof (struct nfile)), + SHELL_ALIGN(sizeof (struct ndup)), + SHELL_ALIGN(sizeof (struct ndup)), + SHELL_ALIGN(sizeof (struct nhere)), + SHELL_ALIGN(sizeof (struct nhere)), + SHELL_ALIGN(sizeof (struct nnot)), +}; + + +static void calcsize(union node *); +static void sizenodelist(struct nodelist *); +static union node *copynode(union node *); +static struct nodelist *copynodelist(struct nodelist *); +static char *nodesavestr(char *); + + +static int evalstring(char *, int mask); +union node; /* BLETCH for ansi C */ +static void evaltree(union node *, int); +static void evalbackcmd(union node *, struct backcmd *); + +static int evalskip; /* set if we are skipping commands */ +static int skipcount; /* number of levels to skip */ +static int funcnest; /* depth of function calls */ + +/* reasons for skipping commands (see comment on breakcmd routine) */ +#define SKIPBREAK (1 << 0) +#define SKIPCONT (1 << 1) +#define SKIPFUNC (1 << 2) +#define SKIPFILE (1 << 3) +#define SKIPEVAL (1 << 4) + +/* + * This file was generated by the mkbuiltins program. + */ + +#if JOBS +static int bgcmd(int, char **); +#endif +static int breakcmd(int, char **); +static int cdcmd(int, char **); +#ifdef CONFIG_ASH_CMDCMD +static int commandcmd(int, char **); +#endif +static int dotcmd(int, char **); +static int evalcmd(int, char **); +#ifdef CONFIG_ASH_BUILTIN_ECHO +static int echocmd(int, char **); +#endif +#ifdef CONFIG_ASH_BUILTIN_TEST +static int testcmd(int, char **); +#endif +static int execcmd(int, char **); +static int exitcmd(int, char **); +static int exportcmd(int, char **); +static int falsecmd(int, char **); +#if JOBS +static int fgcmd(int, char **); +#endif +#ifdef CONFIG_ASH_GETOPTS +static int getoptscmd(int, char **); +#endif +static int hashcmd(int, char **); +#ifndef CONFIG_FEATURE_SH_EXTRA_QUIET +static int helpcmd(int argc, char **argv); +#endif +#if JOBS +static int jobscmd(int, char **); +#endif +#ifdef CONFIG_ASH_MATH_SUPPORT +static int letcmd(int, char **); +#endif +static int localcmd(int, char **); +static int pwdcmd(int, char **); +static int readcmd(int, char **); +static int returncmd(int, char **); +static int setcmd(int, char **); +static int shiftcmd(int, char **); +static int timescmd(int, char **); +static int trapcmd(int, char **); +static int truecmd(int, char **); +static int typecmd(int, char **); +static int umaskcmd(int, char **); +static int unsetcmd(int, char **); +static int waitcmd(int, char **); +static int ulimitcmd(int, char **); +#if JOBS +static int killcmd(int, char **); +#endif + +/* mail.h */ + +#ifdef CONFIG_ASH_MAIL +static void chkmail(void); +static void changemail(const char *); +#endif + +/* exec.h */ + +/* values of cmdtype */ +#define CMDUNKNOWN -1 /* no entry in table for command */ +#define CMDNORMAL 0 /* command is an executable program */ +#define CMDFUNCTION 1 /* command is a shell function */ +#define CMDBUILTIN 2 /* command is a shell builtin */ + +struct builtincmd { + const char *name; + int (*builtin)(int, char **); + /* unsigned flags; */ +}; + + +#define COMMANDCMD (builtincmd + 5 + \ + 2 * ENABLE_ASH_BUILTIN_TEST + \ + ENABLE_ASH_ALIAS + \ + ENABLE_ASH_JOB_CONTROL) +#define EXECCMD (builtincmd + 7 + \ + 2 * ENABLE_ASH_BUILTIN_TEST + \ + ENABLE_ASH_ALIAS + \ + ENABLE_ASH_JOB_CONTROL + \ + ENABLE_ASH_CMDCMD + \ + ENABLE_ASH_BUILTIN_ECHO) + +#define BUILTIN_NOSPEC "0" +#define BUILTIN_SPECIAL "1" +#define BUILTIN_REGULAR "2" +#define BUILTIN_SPEC_REG "3" +#define BUILTIN_ASSIGN "4" +#define BUILTIN_SPEC_ASSG "5" +#define BUILTIN_REG_ASSG "6" +#define BUILTIN_SPEC_REG_ASSG "7" + +#define IS_BUILTIN_SPECIAL(builtincmd) ((builtincmd)->name[0] & 1) +#define IS_BUILTIN_REGULAR(builtincmd) ((builtincmd)->name[0] & 2) +#define IS_BUILTIN_ASSIGN(builtincmd) ((builtincmd)->name[0] & 4) + +/* make sure to keep these in proper order since it is searched via bsearch() */ +static const struct builtincmd builtincmd[] = { + { BUILTIN_SPEC_REG ".", dotcmd }, + { BUILTIN_SPEC_REG ":", truecmd }, +#ifdef CONFIG_ASH_BUILTIN_TEST + { BUILTIN_REGULAR "[", testcmd }, + { BUILTIN_REGULAR "[[", testcmd }, +#endif +#ifdef CONFIG_ASH_ALIAS + { BUILTIN_REG_ASSG "alias", aliascmd }, +#endif +#if JOBS + { BUILTIN_REGULAR "bg", bgcmd }, +#endif + { BUILTIN_SPEC_REG "break", breakcmd }, + { BUILTIN_REGULAR "cd", cdcmd }, + { BUILTIN_NOSPEC "chdir", cdcmd }, +#ifdef CONFIG_ASH_CMDCMD + { BUILTIN_REGULAR "command", commandcmd }, +#endif + { BUILTIN_SPEC_REG "continue", breakcmd }, +#ifdef CONFIG_ASH_BUILTIN_ECHO + { BUILTIN_REGULAR "echo", echocmd }, +#endif + { BUILTIN_SPEC_REG "eval", evalcmd }, + { BUILTIN_SPEC_REG "exec", execcmd }, + { BUILTIN_SPEC_REG "exit", exitcmd }, + { BUILTIN_SPEC_REG_ASSG "export", exportcmd }, + { BUILTIN_REGULAR "false", falsecmd }, +#if JOBS + { BUILTIN_REGULAR "fg", fgcmd }, +#endif +#ifdef CONFIG_ASH_GETOPTS + { BUILTIN_REGULAR "getopts", getoptscmd }, +#endif + { BUILTIN_NOSPEC "hash", hashcmd }, +#ifndef CONFIG_FEATURE_SH_EXTRA_QUIET + { BUILTIN_NOSPEC "help", helpcmd }, +#endif +#if JOBS + { BUILTIN_REGULAR "jobs", jobscmd }, + { BUILTIN_REGULAR "kill", killcmd }, +#endif +#ifdef CONFIG_ASH_MATH_SUPPORT + { BUILTIN_NOSPEC "let", letcmd }, +#endif + { BUILTIN_ASSIGN "local", localcmd }, + { BUILTIN_NOSPEC "pwd", pwdcmd }, + { BUILTIN_REGULAR "read", readcmd }, + { BUILTIN_SPEC_REG_ASSG "readonly", exportcmd }, + { BUILTIN_SPEC_REG "return", returncmd }, + { BUILTIN_SPEC_REG "set", setcmd }, + { BUILTIN_SPEC_REG "shift", shiftcmd }, + { BUILTIN_SPEC_REG "source", dotcmd }, +#ifdef CONFIG_ASH_BUILTIN_TEST + { BUILTIN_REGULAR "test", testcmd }, +#endif + { BUILTIN_SPEC_REG "times", timescmd }, + { BUILTIN_SPEC_REG "trap", trapcmd }, + { BUILTIN_REGULAR "true", truecmd }, + { BUILTIN_NOSPEC "type", typecmd }, + { BUILTIN_NOSPEC "ulimit", ulimitcmd }, + { BUILTIN_REGULAR "umask", umaskcmd }, +#ifdef CONFIG_ASH_ALIAS + { BUILTIN_REGULAR "unalias", unaliascmd }, +#endif + { BUILTIN_SPEC_REG "unset", unsetcmd }, + { BUILTIN_REGULAR "wait", waitcmd }, +}; + +#define NUMBUILTINS (sizeof (builtincmd) / sizeof (struct builtincmd) ) + +static const char *safe_applets[] = { + "[", "test", "echo", "cat", + "ln", "cp", "touch", "mkdir", "rm", + "cut", "hexdump", "awk", "sort", + "find", "xargs", "ls", "dd", + "chown", "chmod" +}; + + +struct cmdentry { + int cmdtype; + union param { + int index; + const struct builtincmd *cmd; + struct funcnode *func; + } u; +}; + + +/* action to find_command() */ +#define DO_ERR 0x01 /* prints errors */ +#define DO_ABS 0x02 /* checks absolute paths */ +#define DO_NOFUNC 0x04 /* don't return shell functions, for command */ +#define DO_ALTPATH 0x08 /* using alternate path */ +#define DO_ALTBLTIN 0x20 /* %builtin in alt. path */ + +static const char *pathopt; /* set by padvance */ + +static void shellexec(char **, const char *, int) + ATTRIBUTE_NORETURN; +static char *padvance(const char **, const char *); +static void find_command(char *, struct cmdentry *, int, const char *); +static struct builtincmd *find_builtin(const char *); +static void hashcd(void); +static void changepath(const char *); +static void defun(char *, union node *); +static void unsetfunc(const char *); + +#ifdef CONFIG_ASH_MATH_SUPPORT_64 +typedef int64_t arith_t; +#define arith_t_type (long long) +#else +typedef long arith_t; +#define arith_t_type (long) +#endif + +#ifdef CONFIG_ASH_MATH_SUPPORT +static arith_t dash_arith(const char *); +static arith_t arith(const char *expr, int *perrcode); +#endif + +#ifdef CONFIG_ASH_RANDOM_SUPPORT +static unsigned long rseed; +static void change_random(const char *); +# ifndef DYNAMIC_VAR +# define DYNAMIC_VAR +# endif +#endif + +/* init.h */ + +static void reset(void); + +/* var.h */ + +/* + * Shell variables. + */ + +/* flags */ +#define VEXPORT 0x01 /* variable is exported */ +#define VREADONLY 0x02 /* variable cannot be modified */ +#define VSTRFIXED 0x04 /* variable struct is statically allocated */ +#define VTEXTFIXED 0x08 /* text is statically allocated */ +#define VSTACK 0x10 /* text is allocated on the stack */ +#define VUNSET 0x20 /* the variable is not set */ +#define VNOFUNC 0x40 /* don't call the callback function */ +#define VNOSET 0x80 /* do not set variable - just readonly test */ +#define VNOSAVE 0x100 /* when text is on the heap before setvareq */ +#ifdef DYNAMIC_VAR +# define VDYNAMIC 0x200 /* dynamic variable */ +# else +# define VDYNAMIC 0 +#endif + +struct var { + struct var *next; /* next entry in hash list */ + int flags; /* flags are defined above */ + const char *text; /* name=value */ + void (*func)(const char *); /* function to be called when */ + /* the variable gets set/unset */ +}; + +struct localvar { + struct localvar *next; /* next local variable in list */ + struct var *vp; /* the variable that was made local */ + int flags; /* saved flags */ + const char *text; /* saved text */ +}; + + +static struct localvar *localvars; + +/* + * Shell variables. + */ + +#ifdef CONFIG_ASH_GETOPTS +static void getoptsreset(const char *); +#endif + +#ifdef CONFIG_LOCALE_SUPPORT +static void change_lc_all(const char *value); +static void change_lc_ctype(const char *value); +#endif + + +#define VTABSIZE 39 + +static const char defpathvar[] = "PATH=/usr/local/bin:/usr/bin:/sbin:/bin"; +#ifdef IFS_BROKEN +static const char defifsvar[] = "IFS= \t\n"; +#define defifs (defifsvar + 4) +#else +static const char defifs[] = " \t\n"; +#endif + + +static struct var varinit[] = { +#ifdef IFS_BROKEN + { 0, VSTRFIXED|VTEXTFIXED, defifsvar, 0 }, +#else + { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "IFS\0", 0 }, +#endif + +#ifdef CONFIG_ASH_MAIL + { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "MAIL\0", changemail }, + { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "MAILPATH\0", changemail }, +#endif + + { 0, VSTRFIXED|VTEXTFIXED, defpathvar, changepath }, + { 0, VSTRFIXED|VTEXTFIXED, "PS1=$ ", 0 }, + { 0, VSTRFIXED|VTEXTFIXED, "PS2=> ", 0 }, + { 0, VSTRFIXED|VTEXTFIXED, "PS4=+ ", 0 }, +#ifdef CONFIG_ASH_GETOPTS + { 0, VSTRFIXED|VTEXTFIXED, "OPTIND=1", getoptsreset }, +#endif +#ifdef CONFIG_ASH_RANDOM_SUPPORT + {0, VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM\0", change_random }, +#endif +#ifdef CONFIG_LOCALE_SUPPORT + {0, VSTRFIXED | VTEXTFIXED | VUNSET, "LC_ALL\0", change_lc_all }, + {0, VSTRFIXED | VTEXTFIXED | VUNSET, "LC_CTYPE\0", change_lc_ctype }, +#endif +#ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY + {0, VSTRFIXED | VTEXTFIXED | VUNSET, "HISTFILE\0", NULL }, +#endif +}; + +#define vifs varinit[0] +#ifdef CONFIG_ASH_MAIL +#define vmail (&vifs)[1] +#define vmpath (&vmail)[1] +#else +#define vmpath vifs +#endif +#define vpath (&vmpath)[1] +#define vps1 (&vpath)[1] +#define vps2 (&vps1)[1] +#define vps4 (&vps2)[1] +#define voptind (&vps4)[1] +#ifdef CONFIG_ASH_GETOPTS +#define vrandom (&voptind)[1] +#else +#define vrandom (&vps4)[1] +#endif +#define defpath (defpathvar + 5) + +/* + * The following macros access the values of the above variables. + * They have to skip over the name. They return the null string + * for unset variables. + */ + +#define ifsval() (vifs.text + 4) +#define ifsset() ((vifs.flags & VUNSET) == 0) +#define mailval() (vmail.text + 5) +#define mpathval() (vmpath.text + 9) +#define pathval() (vpath.text + 5) +#define ps1val() (vps1.text + 4) +#define ps2val() (vps2.text + 4) +#define ps4val() (vps4.text + 4) +#define optindval() (voptind.text + 7) + +#define mpathset() ((vmpath.flags & VUNSET) == 0) + +static void setvar(const char *, const char *, int); +static void setvareq(char *, int); +static void listsetvar(struct strlist *, int); +static char *lookupvar(const char *); +static char *bltinlookup(const char *); +static char **listvars(int, int, char ***); +#define environment() listvars(VEXPORT, VUNSET, 0) +static int showvars(const char *, int, int); +static void poplocalvars(void); +static int unsetvar(const char *); +#ifdef CONFIG_ASH_GETOPTS +static int setvarsafe(const char *, const char *, int); +#endif +static int varcmp(const char *, const char *); +static struct var **hashvar(const char *); + + +static int varequal(const char *a, const char *b) { + return !varcmp(a, b); +} + + +static int loopnest; /* current loop nesting level */ + +/* + * The parsefile structure pointed to by the global variable parsefile + * contains information about the current file being read. + */ + + +struct redirtab { + struct redirtab *next; + int renamed[10]; + int nullredirs; +}; + +static struct redirtab *redirlist; +static int nullredirs; + +extern char **environ; + +/* output.h */ + + +static void outstr(const char *, FILE *); +static void outcslow(int, FILE *); +static void flushall(void); +static void flusherr(void); +static int out1fmt(const char *, ...) + __attribute__((__format__(__printf__,1,2))); +static int fmtstr(char *, size_t, const char *, ...) + __attribute__((__format__(__printf__,3,4))); + +static int preverrout_fd; /* save fd2 before print debug if xflag is set. */ + + +static void out1str(const char *p) +{ + outstr(p, stdout); +} + +static void out2str(const char *p) +{ + outstr(p, stderr); + flusherr(); +} + +/* + * Initialization code. + */ + +/* + * This routine initializes the builtin variables. + */ + +static void initvar(void) +{ + struct var *vp; + struct var *end; + struct var **vpp; + + /* + * PS1 depends on uid + */ +#if defined(CONFIG_FEATURE_COMMAND_EDITING) && defined(CONFIG_FEATURE_SH_FANCY_PROMPT) + vps1.text = "PS1=\\w \\$ "; +#else + if (!geteuid()) + vps1.text = "PS1=# "; +#endif + vp = varinit; + end = vp + sizeof(varinit) / sizeof(varinit[0]); + do { + vpp = hashvar(vp->text); + vp->next = *vpp; + *vpp = vp; + } while (++vp < end); +} + +static void init(void) +{ + + /* from input.c: */ + { + basepf.nextc = basepf.buf = basebuf; + } + + /* from trap.c: */ + { + signal(SIGCHLD, SIG_DFL); + } + + /* from var.c: */ + { + char **envp; + char ppid[32]; + const char *p; + struct stat st1, st2; + + initvar(); + for (envp = environ ; envp && *envp ; envp++) { + if (strchr(*envp, '=')) { + setvareq(*envp, VEXPORT|VTEXTFIXED); + } + } + + snprintf(ppid, sizeof(ppid), "%d", (int) getppid()); + setvar("PPID", ppid, 0); + + p = lookupvar("PWD"); + if (p) + if (*p != '/' || stat(p, &st1) || stat(".", &st2) || + st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino) + p = 0; + setpwd(p, 0); + } +} + +/* PEOF (the end of file marker) */ + +enum { + INPUT_PUSH_FILE = 1, + INPUT_NOFILE_OK = 2, +}; + +/* + * The input line number. Input.c just defines this variable, and saves + * and restores it when files are pushed and popped. The user of this + * package must set its value. + */ + +static int pgetc(void); +static int pgetc2(void); +static int preadbuffer(void); +static void pungetc(void); +static void pushstring(char *, void *); +static void popstring(void); +static void setinputfd(int, int); +static void setinputstring(char *); +static void popfile(void); +static void popallfiles(void); +static void closescript(void); + + +/* jobs.h */ + + +/* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */ +#define FORK_FG 0 +#define FORK_BG 1 +#define FORK_NOJOB 2 + +/* mode flags for showjob(s) */ +#define SHOW_PGID 0x01 /* only show pgid - for jobs -p */ +#define SHOW_PID 0x04 /* include process pid */ +#define SHOW_CHANGED 0x08 /* only jobs whose state has changed */ + + +/* + * A job structure contains information about a job. A job is either a + * single process or a set of processes contained in a pipeline. In the + * latter case, pidlist will be non-NULL, and will point to a -1 terminated + * array of pids. + */ + +struct procstat { + pid_t pid; /* process id */ + int status; /* last process status from wait() */ + char *cmd; /* text of command being run */ +}; + +struct job { + struct procstat ps0; /* status of process */ + struct procstat *ps; /* status or processes when more than one */ +#if JOBS + int stopstatus; /* status of a stopped job */ +#endif + uint32_t + nprocs: 16, /* number of processes */ + state: 8, +#define JOBRUNNING 0 /* at least one proc running */ +#define JOBSTOPPED 1 /* all procs are stopped */ +#define JOBDONE 2 /* all procs are completed */ +#if JOBS + sigint: 1, /* job was killed by SIGINT */ + jobctl: 1, /* job running under job control */ +#endif + waited: 1, /* true if this entry has been waited for */ + used: 1, /* true if this entry is in used */ + changed: 1; /* true if status has changed */ + struct job *prev_job; /* previous job */ +}; + +static pid_t backgndpid; /* pid of last background process */ +static int job_warning; /* user was warned about stopped jobs */ +#if JOBS +static int jobctl; /* true if doing job control */ +#endif + +static struct job *makejob(union node *, int); +static int forkshell(struct job *, union node *, int); +static int waitforjob(struct job *); +static int stoppedjobs(void); + +#if ! JOBS +#define setjobctl(on) /* do nothing */ +#else +static void setjobctl(int); +static void showjobs(FILE *, int); +#endif + +/* main.h */ + + +/* pid of main shell */ +static int rootpid; +/* shell level: 0 for the main shell, 1 for its children, and so on */ +static int shlvl; +#define rootshell (!shlvl) + +static void readcmdfile(char *); +static int cmdloop(int); + +/* memalloc.h */ + + +struct stackmark { + struct stack_block *stackp; + char *stacknxt; + size_t stacknleft; + struct stackmark *marknext; +}; + +/* minimum size of a block */ +#define MINSIZE SHELL_ALIGN(504) + +struct stack_block { + struct stack_block *prev; + char space[MINSIZE]; +}; + +static struct stack_block stackbase; +static struct stack_block *stackp = &stackbase; +static struct stackmark *markp; +static char *stacknxt = stackbase.space; +static size_t stacknleft = MINSIZE; +static char *sstrend = stackbase.space + MINSIZE; +static int herefd = -1; + + +static pointer ckmalloc(size_t); +static pointer ckrealloc(pointer, size_t); +static char *savestr(const char *); +static pointer stalloc(size_t); +static void stunalloc(pointer); +static void setstackmark(struct stackmark *); +static void popstackmark(struct stackmark *); +static void growstackblock(void); +static void *growstackstr(void); +static char *makestrspace(size_t, char *); +static char *stnputs(const char *, size_t, char *); +static char *stputs(const char *, char *); + + +static char *_STPUTC(int c, char *p) { + if (p == sstrend) + p = growstackstr(); + *p++ = c; + return p; +} + +#define stackblock() ((void *)stacknxt) +#define stackblocksize() stacknleft +#define STARTSTACKSTR(p) ((p) = stackblock()) +#define STPUTC(c, p) ((p) = _STPUTC((c), (p))) +#define CHECKSTRSPACE(n, p) \ + ({ \ + char *q = (p); \ + size_t l = (n); \ + size_t m = sstrend - q; \ + if (l > m) \ + (p) = makestrspace(l, q); \ + 0; \ + }) +#define USTPUTC(c, p) (*p++ = (c)) +#define STACKSTRNUL(p) ((p) == sstrend? (p = growstackstr(), *p = '\0') : (*p = '\0')) +#define STUNPUTC(p) (--p) +#define STTOPC(p) p[-1] +#define STADJUST(amount, p) (p += (amount)) + +#define grabstackstr(p) stalloc((char *)(p) - (char *)stackblock()) +#define ungrabstackstr(s, p) stunalloc((s)) +#define stackstrend() ((void *)sstrend) + +#define ckfree(p) free((pointer)(p)) + +/* mystring.h */ + + +#define DOLATSTRLEN 4 + +static char *prefix(const char *, const char *); +static int number(const char *); +static int is_number(const char *); +static char *single_quote(const char *); +static char *sstrdup(const char *); + +#define equal(s1, s2) (strcmp(s1, s2) == 0) +#define scopy(s1, s2) ((void)strcpy(s2, s1)) + +/* options.h */ + +struct shparam { + int nparam; /* # of positional parameters (without $0) */ + unsigned char malloc; /* if parameter list dynamically allocated */ + char **p; /* parameter list */ +#ifdef CONFIG_ASH_GETOPTS + int optind; /* next parameter to be processed by getopts */ + int optoff; /* used by getopts */ +#endif +}; + + +#define eflag optlist[0] +#define fflag optlist[1] +#define Iflag optlist[2] +#define iflag optlist[3] +#define mflag optlist[4] +#define nflag optlist[5] +#define sflag optlist[6] +#define xflag optlist[7] +#define vflag optlist[8] +#define Cflag optlist[9] +#define aflag optlist[10] +#define bflag optlist[11] +#define uflag optlist[12] +#define viflag optlist[13] + +#if DEBUG +#define nolog optlist[14] +#define debug optlist[15] +#endif + +#ifndef CONFIG_FEATURE_COMMAND_EDITING_VI +#define setvimode(on) viflag = 0 /* forcibly keep the option off */ +#endif + +/* options.c */ + + +static const char *const optletters_optnames[] = { + "e" "errexit", + "f" "noglob", + "I" "ignoreeof", + "i" "interactive", + "m" "monitor", + "n" "noexec", + "s" "stdin", + "x" "xtrace", + "v" "verbose", + "C" "noclobber", + "a" "allexport", + "b" "notify", + "u" "nounset", + "\0" "vi", +#if DEBUG + "\0" "nolog", + "\0" "debug", +#endif +}; + +#define optletters(n) optletters_optnames[(n)][0] +#define optnames(n) (&optletters_optnames[(n)][1]) + +#define NOPTS (sizeof(optletters_optnames)/sizeof(optletters_optnames[0])) + +static char optlist[NOPTS]; + + +static char *arg0; /* value of $0 */ +static struct shparam shellparam; /* $@ current positional parameters */ +static char **argptr; /* argument list for builtin commands */ +static char *optionarg; /* set by nextopt (like getopt) */ +static char *optptr; /* used by nextopt */ + +static char *minusc; /* argument to -c option */ + + +static void procargs(int, char **); +static void optschanged(void); +static void setparam(char **); +static void freeparam(volatile struct shparam *); +static int shiftcmd(int, char **); +static int setcmd(int, char **); +static int nextopt(const char *); + +/* redir.h */ + +/* flags passed to redirect */ +#define REDIR_PUSH 01 /* save previous values of file descriptors */ +#define REDIR_SAVEFD2 03 /* set preverrout */ + +union node; +static void redirect(union node *, int); +static void popredir(int); +static void clearredir(int); +static int copyfd(int, int); +static int redirectsafe(union node *, int); + +/* show.h */ + + +#if DEBUG +static void showtree(union node *); +static void trace(const char *, ...); +static void tracev(const char *, va_list); +static void trargs(char **); +static void trputc(int); +static void trputs(const char *); +static void opentrace(void); +#endif + +/* trap.h */ + + +/* trap handler commands */ +static char *trap[NSIG]; +/* current value of signal */ +static char sigmode[NSIG - 1]; +/* indicates specified signal received */ +static char gotsig[NSIG - 1]; + +static void clear_traps(void); +static void setsignal(int); +static void ignoresig(int); +static void onsig(int); +static int dotrap(void); +static void setinteractive(int); +static void exitshell(void) ATTRIBUTE_NORETURN; + + +static int is_safe_applet(char *name) +{ + int n = sizeof(safe_applets) / sizeof(char *); + int i; + for (i = 0; i < n; i++) + if (strcmp(safe_applets[i], name) == 0) + return 1; + + return 0; +} + + +/* + * This routine is called when an error or an interrupt occurs in an + * interactive shell and control is returned to the main command loop. + */ + +static void +reset(void) +{ + /* from eval.c: */ + { + evalskip = 0; + loopnest = 0; + } + + /* from input.c: */ + { + parselleft = parsenleft = 0; /* clear input buffer */ + popallfiles(); + } + + /* from parser.c: */ + { + tokpushback = 0; + checkkwd = 0; + } + + /* from redir.c: */ + { + clearredir(0); + } + +} + +#ifdef CONFIG_ASH_ALIAS +static struct alias *atab[ATABSIZE]; + +static void setalias(const char *, const char *); +static struct alias *freealias(struct alias *); +static struct alias **__lookupalias(const char *); + +static void +setalias(const char *name, const char *val) +{ + struct alias *ap, **app; + + app = __lookupalias(name); + ap = *app; + INTOFF; + if (ap) { + if (!(ap->flag & ALIASINUSE)) { + ckfree(ap->val); + } + ap->val = savestr(val); + ap->flag &= ~ALIASDEAD; + } else { + /* not found */ + ap = ckmalloc(sizeof (struct alias)); + ap->name = savestr(name); + ap->val = savestr(val); + ap->flag = 0; + ap->next = 0; + *app = ap; + } + INTON; +} + +static int +unalias(const char *name) +{ + struct alias **app; + + app = __lookupalias(name); + + if (*app) { + INTOFF; + *app = freealias(*app); + INTON; + return 0; + } + + return 1; +} + +static void +rmaliases(void) +{ + struct alias *ap, **app; + int i; + + INTOFF; + for (i = 0; i < ATABSIZE; i++) { + app = &atab[i]; + for (ap = *app; ap; ap = *app) { + *app = freealias(*app); + if (ap == *app) { + app = &ap->next; + } + } + } + INTON; +} + +static struct alias * +lookupalias(const char *name, int check) +{ + struct alias *ap = *__lookupalias(name); + + if (check && ap && (ap->flag & ALIASINUSE)) + return NULL; + return ap; +} + +/* + * TODO - sort output + */ +static int +aliascmd(int argc, char **argv) +{ + char *n, *v; + int ret = 0; + struct alias *ap; + + if (argc == 1) { + int i; + + for (i = 0; i < ATABSIZE; i++) + for (ap = atab[i]; ap; ap = ap->next) { + printalias(ap); + } + return 0; + } + while ((n = *++argv) != NULL) { + if ((v = strchr(n+1, '=')) == NULL) { /* n+1: funny ksh stuff */ + if ((ap = *__lookupalias(n)) == NULL) { + fprintf(stderr, "%s: %s not found\n", "alias", n); + ret = 1; + } else + printalias(ap); + } else { + *v++ = '\0'; + setalias(n, v); + } + } + + return ret; +} + +static int +unaliascmd(int argc, char **argv) +{ + int i; + + while ((i = nextopt("a")) != '\0') { + if (i == 'a') { + rmaliases(); + return 0; + } + } + for (i = 0; *argptr; argptr++) { + if (unalias(*argptr)) { + fprintf(stderr, "%s: %s not found\n", "unalias", *argptr); + i = 1; + } + } + + return i; +} + +static struct alias * +freealias(struct alias *ap) { + struct alias *next; + + if (ap->flag & ALIASINUSE) { + ap->flag |= ALIASDEAD; + return ap; + } + + next = ap->next; + ckfree(ap->name); + ckfree(ap->val); + ckfree(ap); + return next; +} + +static void +printalias(const struct alias *ap) { + out1fmt("%s=%s\n", ap->name, single_quote(ap->val)); +} + +static struct alias ** +__lookupalias(const char *name) { + unsigned int hashval; + struct alias **app; + const char *p; + unsigned int ch; + + p = name; + + ch = (unsigned char)*p; + hashval = ch << 4; + while (ch) { + hashval += ch; + ch = (unsigned char)*++p; + } + app = &atab[hashval % ATABSIZE]; + + for (; *app; app = &(*app)->next) { + if (equal(name, (*app)->name)) { + break; + } + } + + return app; +} +#endif /* CONFIG_ASH_ALIAS */ + + +/* cd.c */ + +/* + * The cd and pwd commands. + */ + +#define CD_PHYSICAL 1 +#define CD_PRINT 2 + +static int docd(const char *, int); +static int cdopt(void); + +static char *curdir = nullstr; /* current working directory */ +static char *physdir = nullstr; /* physical working directory */ + +static int +cdopt(void) +{ + int flags = 0; + int i, j; + + j = 'L'; + while ((i = nextopt("LP"))) { + if (i != j) { + flags ^= CD_PHYSICAL; + j = i; + } + } + + return flags; +} + +static int +cdcmd(int argc, char **argv) +{ + const char *dest; + const char *path; + const char *p; + char c; + struct stat statb; + int flags; + + flags = cdopt(); + dest = *argptr; + if (!dest) + dest = bltinlookup(homestr); + else if (dest[0] == '-' && dest[1] == '\0') { + dest = bltinlookup("OLDPWD"); + flags |= CD_PRINT; + } + if (!dest) + dest = nullstr; + if (*dest == '/') + goto step7; + if (*dest == '.') { + c = dest[1]; +dotdot: + switch (c) { + case '\0': + case '/': + goto step6; + case '.': + c = dest[2]; + if (c != '.') + goto dotdot; + } + } + if (!*dest) + dest = "."; + if (!(path = bltinlookup("CDPATH"))) { +step6: +step7: + p = dest; + goto docd; + } + do { + c = *path; + p = padvance(&path, dest); + if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) { + if (c && c != ':') + flags |= CD_PRINT; +docd: + if (!docd(p, flags)) + goto out; + break; + } + } while (path); + sh_error("can't cd to %s", dest); + /* NOTREACHED */ +out: + if (flags & CD_PRINT) + out1fmt(snlfmt, curdir); + return 0; +} + + +/* + * Update curdir (the name of the current directory) in response to a + * cd command. + */ + +static const char * updatepwd(const char *dir) +{ + char *new; + char *p; + char *cdcomppath; + const char *lim; + + cdcomppath = sstrdup(dir); + STARTSTACKSTR(new); + if (*dir != '/') { + if (curdir == nullstr) + return 0; + new = stputs(curdir, new); + } + new = makestrspace(strlen(dir) + 2, new); + lim = stackblock() + 1; + if (*dir != '/') { + if (new[-1] != '/') + USTPUTC('/', new); + if (new > lim && *lim == '/') + lim++; + } else { + USTPUTC('/', new); + cdcomppath++; + if (dir[1] == '/' && dir[2] != '/') { + USTPUTC('/', new); + cdcomppath++; + lim++; + } + } + p = strtok(cdcomppath, "/"); + while (p) { + switch(*p) { + case '.': + if (p[1] == '.' && p[2] == '\0') { + while (new > lim) { + STUNPUTC(new); + if (new[-1] == '/') + break; + } + break; + } else if (p[1] == '\0') + break; + /* fall through */ + default: + new = stputs(p, new); + USTPUTC('/', new); + } + p = strtok(0, "/"); + } + if (new > lim) + STUNPUTC(new); + *new = 0; + return stackblock(); +} + +/* + * Actually do the chdir. We also call hashcd to let the routines in exec.c + * know that the current directory has changed. + */ + +static int +docd(const char *dest, int flags) +{ + const char *dir = 0; + int err; + + TRACE(("docd(\"%s\", %d) called\n", dest, flags)); + + INTOFF; + if (!(flags & CD_PHYSICAL)) { + dir = updatepwd(dest); + if (dir) + dest = dir; + } + err = chdir(dest); + if (err) + goto out; + setpwd(dir, 1); + hashcd(); +out: + INTON; + return err; +} + +/* + * Find out what the current directory is. If we already know the current + * directory, this routine returns immediately. + */ +static char * getpwd(void) +{ + char *dir = getcwd(0, 0); + return dir ? dir : nullstr; +} + +static int +pwdcmd(int argc, char **argv) +{ + int flags; + const char *dir = curdir; + + flags = cdopt(); + if (flags) { + if (physdir == nullstr) + setpwd(dir, 0); + dir = physdir; + } + out1fmt(snlfmt, dir); + return 0; +} + +static void +setpwd(const char *val, int setold) +{ + char *oldcur, *dir; + + oldcur = dir = curdir; + + if (setold) { + setvar("OLDPWD", oldcur, VEXPORT); + } + INTOFF; + if (physdir != nullstr) { + if (physdir != oldcur) + free(physdir); + physdir = nullstr; + } + if (oldcur == val || !val) { + char *s = getpwd(); + physdir = s; + if (!val) + dir = s; + } else + dir = savestr(val); + if (oldcur != dir && oldcur != nullstr) { + free(oldcur); + } + curdir = dir; + INTON; + setvar("PWD", dir, VEXPORT); +} + +/* error.c */ + +/* + * Errors and exceptions. + */ + +/* + * Code to handle exceptions in C. + */ + + + +static void exverror(int, const char *, va_list) + ATTRIBUTE_NORETURN; + +/* + * Called to raise an exception. Since C doesn't include exceptions, we + * just do a longjmp to the exception handler. The type of exception is + * stored in the global variable "exception". + */ + +static void +exraise(int e) +{ +#if DEBUG + if (handler == NULL) + abort(); +#endif + INTOFF; + + exception = e; + longjmp(handler->loc, 1); +} + + +/* + * Called from trap.c when a SIGINT is received. (If the user specifies + * that SIGINT is to be trapped or ignored using the trap builtin, then + * this routine is not called.) Suppressint is nonzero when interrupts + * are held using the INTOFF macro. (The test for iflag is just + * defensive programming.) + */ + +static void +onint(void) { + int i; + + intpending = 0; + i = EXSIG; + if (gotsig[SIGINT - 1] && !trap[SIGINT]) { + if (!(rootshell && iflag)) { + signal(SIGINT, SIG_DFL); + raise(SIGINT); + } + i = EXINT; + } + exraise(i); + /* NOTREACHED */ +} + +static void +exvwarning(const char *msg, va_list ap) +{ + FILE *errs; + + errs = stderr; + fprintf(errs, "%s: ", arg0); + if (commandname) { + const char *fmt = (!iflag || parsefile->fd) ? + "%s: %d: " : "%s: "; + fprintf(errs, fmt, commandname, startlinno); + } + vfprintf(errs, msg, ap); + outcslow('\n', errs); +} + +/* + * Exverror is called to raise the error exception. If the second argument + * is not NULL then error prints an error message using printf style + * formatting. It then raises the error exception. + */ +static void +exverror(int cond, const char *msg, va_list ap) +{ +#if DEBUG + if (msg) { + TRACE(("exverror(%d, \"", cond)); + TRACEV((msg, ap)); + TRACE(("\") pid=%d\n", getpid())); + } else + TRACE(("exverror(%d, NULL) pid=%d\n", cond, getpid())); + if (msg) +#endif + exvwarning(msg, ap); + + flushall(); + exraise(cond); + /* NOTREACHED */ +} + + +static void +sh_error(const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + exverror(EXERROR, msg, ap); + /* NOTREACHED */ + va_end(ap); +} + + +static void +exerror(int cond, const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + exverror(cond, msg, ap); + /* NOTREACHED */ + va_end(ap); +} + +/* + * error/warning routines for external builtins + */ + +static void +sh_warnx(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + exvwarning(fmt, ap); + va_end(ap); +} + + +/* + * Return a string describing an error. The returned string may be a + * pointer to a static buffer that will be overwritten on the next call. + * Action describes the operation that got the error. + */ + +static const char * +errmsg(int e, const char *em) +{ + if(e == ENOENT || e == ENOTDIR) { + + return em; + } + return strerror(e); +} + + +/* eval.c */ + +/* + * Evaluate a command. + */ + +/* flags in argument to evaltree */ +#define EV_EXIT 01 /* exit after evaluating tree */ +#define EV_TESTED 02 /* exit status is checked; ignore -e flag */ +#define EV_BACKCMD 04 /* command executing within back quotes */ + + +static void evalloop(union node *, int); +static void evalfor(union node *, int); +static void evalcase(union node *, int); +static void evalsubshell(union node *, int); +static void expredir(union node *); +static void evalpipe(union node *, int); +static void evalcommand(union node *, int); +static int evalbltin(const struct builtincmd *, int, char **); +static int evalfun(struct funcnode *, int, char **, int); +static void prehash(union node *); +static int bltincmd(int, char **); + + +static const struct builtincmd bltin = { + "\0\0", bltincmd +}; + + +/* + * Called to reset things after an exception. + */ + +/* + * The eval command. + */ + +static int +evalcmd(int argc, char **argv) +{ + char *p; + char *concat; + char **ap; + + if (argc > 1) { + p = argv[1]; + if (argc > 2) { + STARTSTACKSTR(concat); + ap = argv + 2; + for (;;) { + concat = stputs(p, concat); + if ((p = *ap++) == NULL) + break; + STPUTC(' ', concat); + } + STPUTC('\0', concat); + p = grabstackstr(concat); + } + evalstring(p, ~SKIPEVAL); + + } + return exitstatus; +} + + +/* + * Execute a command or commands contained in a string. + */ + +static int +evalstring(char *s, int mask) +{ + union node *n; + struct stackmark smark; + int skip; + + setinputstring(s); + setstackmark(&smark); + + skip = 0; + while ((n = parsecmd(0)) != NEOF) { + evaltree(n, 0); + popstackmark(&smark); + skip = evalskip; + if (skip) + break; + } + popfile(); + + skip &= mask; + evalskip = skip; + return skip; +} + + + +/* + * Evaluate a parse tree. The value is left in the global variable + * exitstatus. + */ + +static void +evaltree(union node *n, int flags) +{ + int checkexit = 0; + void (*evalfn)(union node *, int); + unsigned isor; + int status; + if (n == NULL) { + TRACE(("evaltree(NULL) called\n")); + goto out; + } + TRACE(("pid %d, evaltree(%p: %d, %d) called\n", + getpid(), n, n->type, flags)); + switch (n->type) { + default: +#if DEBUG + out1fmt("Node type = %d\n", n->type); + fflush(stdout); + break; +#endif + case NNOT: + evaltree(n->nnot.com, EV_TESTED); + status = !exitstatus; + goto setstatus; + case NREDIR: + expredir(n->nredir.redirect); + status = redirectsafe(n->nredir.redirect, REDIR_PUSH); + if (!status) { + evaltree(n->nredir.n, flags & EV_TESTED); + status = exitstatus; + } + popredir(0); + goto setstatus; + case NCMD: + evalfn = evalcommand; +checkexit: + if (eflag && !(flags & EV_TESTED)) + checkexit = ~0; + goto calleval; + case NFOR: + evalfn = evalfor; + goto calleval; + case NWHILE: + case NUNTIL: + evalfn = evalloop; + goto calleval; + case NSUBSHELL: + case NBACKGND: + evalfn = evalsubshell; + goto calleval; + case NPIPE: + evalfn = evalpipe; + goto checkexit; + case NCASE: + evalfn = evalcase; + goto calleval; + case NAND: + case NOR: + case NSEMI: +#if NAND + 1 != NOR +#error NAND + 1 != NOR +#endif +#if NOR + 1 != NSEMI +#error NOR + 1 != NSEMI +#endif + isor = n->type - NAND; + evaltree( + n->nbinary.ch1, + (flags | ((isor >> 1) - 1)) & EV_TESTED + ); + if (!exitstatus == isor) + break; + if (!evalskip) { + n = n->nbinary.ch2; +evaln: + evalfn = evaltree; +calleval: + evalfn(n, flags); + break; + } + break; + case NIF: + evaltree(n->nif.test, EV_TESTED); + if (evalskip) + break; + if (exitstatus == 0) { + n = n->nif.ifpart; + goto evaln; + } else if (n->nif.elsepart) { + n = n->nif.elsepart; + goto evaln; + } + goto success; + case NDEFUN: + defun(n->narg.text, n->narg.next); +success: + status = 0; +setstatus: + exitstatus = status; + break; + } +out: + if ((checkexit & exitstatus)) + evalskip |= SKIPEVAL; + else if (pendingsigs && dotrap()) + goto exexit; + + if (flags & EV_EXIT) { +exexit: + exraise(EXEXIT); + } +} + + +#if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3) +static +#endif +void evaltreenr(union node *, int) __attribute__ ((alias("evaltree"),__noreturn__)); + + +static void +evalloop(union node *n, int flags) +{ + int status; + + loopnest++; + status = 0; + flags &= EV_TESTED; + for (;;) { + int i; + + evaltree(n->nbinary.ch1, EV_TESTED); + if (evalskip) { +skipping: if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = 0; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = 0; + break; + } + i = exitstatus; + if (n->type != NWHILE) + i = !i; + if (i != 0) + break; + evaltree(n->nbinary.ch2, flags); + status = exitstatus; + if (evalskip) + goto skipping; + } + loopnest--; + exitstatus = status; +} + + + +static void +evalfor(union node *n, int flags) +{ + struct arglist arglist; + union node *argp; + struct strlist *sp; + struct stackmark smark; + + setstackmark(&smark); + arglist.lastp = &arglist.list; + for (argp = n->nfor.args ; argp ; argp = argp->narg.next) { + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE | EXP_RECORD); + /* XXX */ + if (evalskip) + goto out; + } + *arglist.lastp = NULL; + + exitstatus = 0; + loopnest++; + flags &= EV_TESTED; + for (sp = arglist.list ; sp ; sp = sp->next) { + setvar(n->nfor.var, sp->text, 0); + evaltree(n->nfor.body, flags); + if (evalskip) { + if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = 0; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = 0; + break; + } + } + loopnest--; +out: + popstackmark(&smark); +} + + + +static void +evalcase(union node *n, int flags) +{ + union node *cp; + union node *patp; + struct arglist arglist; + struct stackmark smark; + + setstackmark(&smark); + arglist.lastp = &arglist.list; + expandarg(n->ncase.expr, &arglist, EXP_TILDE); + exitstatus = 0; + for (cp = n->ncase.cases ; cp && evalskip == 0 ; cp = cp->nclist.next) { + for (patp = cp->nclist.pattern ; patp ; patp = patp->narg.next) { + if (casematch(patp, arglist.list->text)) { + if (evalskip == 0) { + evaltree(cp->nclist.body, flags); + } + goto out; + } + } + } +out: + popstackmark(&smark); +} + + + +/* + * Kick off a subshell to evaluate a tree. + */ + +static void +evalsubshell(union node *n, int flags) +{ + struct job *jp; + int backgnd = (n->type == NBACKGND); + int status; + + expredir(n->nredir.redirect); + if (!backgnd && flags & EV_EXIT && !trap[0]) + goto nofork; + INTOFF; + jp = makejob(n, 1); + if (forkshell(jp, n, backgnd) == 0) { + INTON; + flags |= EV_EXIT; + if (backgnd) + flags &=~ EV_TESTED; +nofork: + redirect(n->nredir.redirect, 0); + evaltreenr(n->nredir.n, flags); + /* never returns */ + } + status = 0; + if (! backgnd) + status = waitforjob(jp); + exitstatus = status; + INTON; +} + + + +/* + * Compute the names of the files in a redirection list. + */ + +static void +expredir(union node *n) +{ + union node *redir; + + for (redir = n ; redir ; redir = redir->nfile.next) { + struct arglist fn; + memset(&fn, 0, sizeof(struct arglist)); + fn.lastp = &fn.list; + switch (redir->type) { + case NFROMTO: + case NFROM: + case NTO: + case NCLOBBER: + case NAPPEND: + expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR); + redir->nfile.expfname = fn.list->text; + break; + case NFROMFD: + case NTOFD: + if (redir->ndup.vname) { + expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE); + if (fn.list != NULL) + fixredir(redir, fn.list->text, 1); + else + sh_error("redir error"); + } + break; + } + } +} + + + +/* + * Evaluate a pipeline. All the processes in the pipeline are children + * of the process creating the pipeline. (This differs from some versions + * of the shell, which make the last process in a pipeline the parent + * of all the rest.) + */ + +static void +evalpipe(union node *n, int flags) +{ + struct job *jp; + struct nodelist *lp; + int pipelen; + int prevfd; + int pip[2]; + + TRACE(("evalpipe(0x%lx) called\n", (long)n)); + pipelen = 0; + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) + pipelen++; + flags |= EV_EXIT; + INTOFF; + jp = makejob(n, pipelen); + prevfd = -1; + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { + prehash(lp->n); + pip[1] = -1; + if (lp->next) { + if (pipe(pip) < 0) { + close(prevfd); + sh_error("Pipe call failed"); + } + } + if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) { + INTON; + if (pip[1] >= 0) { + close(pip[0]); + } + if (prevfd > 0) { + dup2(prevfd, 0); + close(prevfd); + } + if (pip[1] > 1) { + dup2(pip[1], 1); + close(pip[1]); + } + evaltreenr(lp->n, flags); + /* never returns */ + } + if (prevfd >= 0) + close(prevfd); + prevfd = pip[0]; + close(pip[1]); + } + if (n->npipe.backgnd == 0) { + exitstatus = waitforjob(jp); + TRACE(("evalpipe: job done exit status %d\n", exitstatus)); + } + INTON; +} + + + +/* + * Execute a command inside back quotes. If it's a builtin command, we + * want to save its output in a block obtained from malloc. Otherwise + * we fork off a subprocess and get the output of the command via a pipe. + * Should be called with interrupts off. + */ + +static void +evalbackcmd(union node *n, struct backcmd *result) +{ + int saveherefd; + + result->fd = -1; + result->buf = NULL; + result->nleft = 0; + result->jp = NULL; + if (n == NULL) { + goto out; + } + + saveherefd = herefd; + herefd = -1; + + { + int pip[2]; + struct job *jp; + + if (pipe(pip) < 0) + sh_error("Pipe call failed"); + jp = makejob(n, 1); + if (forkshell(jp, n, FORK_NOJOB) == 0) { + FORCEINTON; + close(pip[0]); + if (pip[1] != 1) { + close(1); + copyfd(pip[1], 1); + close(pip[1]); + } + eflag = 0; + evaltreenr(n, EV_EXIT); + /* NOTREACHED */ + } + close(pip[1]); + result->fd = pip[0]; + result->jp = jp; + } + herefd = saveherefd; +out: + TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n", + result->fd, result->buf, result->nleft, result->jp)); +} + +#ifdef CONFIG_ASH_CMDCMD +static char ** parse_command_args(char **argv, const char **path) +{ + char *cp, c; + + for (;;) { + cp = *++argv; + if (!cp) + return 0; + if (*cp++ != '-') + break; + if (!(c = *cp++)) + break; + if (c == '-' && !*cp) { + argv++; + break; + } + do { + switch (c) { + case 'p': + *path = defpath; + break; + default: + /* run 'typecmd' for other options */ + return 0; + } + } while ((c = *cp++)); + } + return argv; +} +#endif + +static int isassignment(const char *p) +{ + const char *q = endofname(p); + if (p == q) + return 0; + return *q == '='; +} + +#ifdef CONFIG_ASH_EXPAND_PRMT +static const char *expandstr(const char *ps); +#else +#define expandstr(s) s +#endif + +/* + * Execute a simple command. + */ + +static void +evalcommand(union node *cmd, int flags) +{ + struct stackmark smark; + union node *argp; + struct arglist arglist; + struct arglist varlist; + char **argv; + int argc; + const struct strlist *sp; + struct cmdentry cmdentry; + struct job *jp; + char *lastarg; + const char *path; + int spclbltin; + int cmd_is_exec; + int status; + char **nargv; + struct builtincmd *bcmd; + int pseudovarflag = 0; + + /* First expand the arguments. */ + TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); + setstackmark(&smark); + back_exitstatus = 0; + + cmdentry.cmdtype = CMDBUILTIN; + cmdentry.u.cmd = &bltin; + varlist.lastp = &varlist.list; + *varlist.lastp = NULL; + arglist.lastp = &arglist.list; + *arglist.lastp = NULL; + + argc = 0; + if (cmd->ncmd.args) + { + bcmd = find_builtin(cmd->ncmd.args->narg.text); + pseudovarflag = bcmd && IS_BUILTIN_ASSIGN(bcmd); + } + + for (argp = cmd->ncmd.args; argp; argp = argp->narg.next) { + struct strlist **spp; + + spp = arglist.lastp; + if (pseudovarflag && isassignment(argp->narg.text)) + expandarg(argp, &arglist, EXP_VARTILDE); + else + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); + + for (sp = *spp; sp; sp = sp->next) + argc++; + } + + argv = nargv = stalloc(sizeof (char *) * (argc + 1)); + for (sp = arglist.list ; sp ; sp = sp->next) { + TRACE(("evalcommand arg: %s\n", sp->text)); + *nargv++ = sp->text; + } + *nargv = NULL; + + lastarg = NULL; + if (iflag && funcnest == 0 && argc > 0) + lastarg = nargv[-1]; + + preverrout_fd = 2; + expredir(cmd->ncmd.redirect); + status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH|REDIR_SAVEFD2); + + path = vpath.text; + for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) { + struct strlist **spp; + char *p; + + spp = varlist.lastp; + expandarg(argp, &varlist, EXP_VARTILDE); + + /* + * Modify the command lookup path, if a PATH= assignment + * is present + */ + p = (*spp)->text; + if (varequal(p, path)) + path = p; + } + + /* Print the command if xflag is set. */ + if (xflag) { + int n; + const char *p = " %s"; + + p++; + dprintf(preverrout_fd, p, expandstr(ps4val())); + + sp = varlist.list; + for(n = 0; n < 2; n++) { + while (sp) { + dprintf(preverrout_fd, p, sp->text); + sp = sp->next; + if(*p == '%') { + p--; + } + } + sp = arglist.list; + } + full_write(preverrout_fd, "\n", 1); + } + + cmd_is_exec = 0; + spclbltin = -1; + + /* Now locate the command. */ + if (argc) { + const char *oldpath; + int cmd_flag = DO_ERR; + + path += 5; + oldpath = path; + for (;;) { + find_command(argv[0], &cmdentry, cmd_flag, path); + if (cmdentry.cmdtype == CMDUNKNOWN) { + status = 127; + flusherr(); + goto bail; + } + + /* implement bltin and command here */ + if (cmdentry.cmdtype != CMDBUILTIN) + break; + if (spclbltin < 0) + spclbltin = IS_BUILTIN_SPECIAL(cmdentry.u.cmd); + if (cmdentry.u.cmd == EXECCMD) + cmd_is_exec++; +#ifdef CONFIG_ASH_CMDCMD + if (cmdentry.u.cmd == COMMANDCMD) { + + path = oldpath; + nargv = parse_command_args(argv, &path); + if (!nargv) + break; + argc -= nargv - argv; + argv = nargv; + cmd_flag |= DO_NOFUNC; + } else +#endif + break; + } + } + + if (status) { + /* We have a redirection error. */ + if (spclbltin > 0) + exraise(EXERROR); +bail: + exitstatus = status; + goto out; + } + + /* Execute the command. */ + switch (cmdentry.cmdtype) { + default: + /* Fork off a child process if necessary. */ + if (!(flags & EV_EXIT) || trap[0]) { + INTOFF; + jp = makejob(cmd, 1); + if (forkshell(jp, cmd, FORK_FG) != 0) { + exitstatus = waitforjob(jp); + INTON; + break; + } + FORCEINTON; + } + listsetvar(varlist.list, VEXPORT|VSTACK); + shellexec(argv, path, cmdentry.u.index); + /* NOTREACHED */ + + case CMDBUILTIN: + cmdenviron = varlist.list; + if (cmdenviron) { + struct strlist *list = cmdenviron; + int i = VNOSET; + if (spclbltin > 0 || argc == 0) { + i = 0; + if (cmd_is_exec && argc > 1) + i = VEXPORT; + } + listsetvar(list, i); + } + if (evalbltin(cmdentry.u.cmd, argc, argv)) { + int exit_status; + int i, j; + + i = exception; + if (i == EXEXIT) + goto raise; + + exit_status = 2; + j = 0; + if (i == EXINT) + j = SIGINT; + if (i == EXSIG) + j = pendingsigs; + if (j) + exit_status = j + 128; + exitstatus = exit_status; + + if (i == EXINT || spclbltin > 0) { +raise: + longjmp(handler->loc, 1); + } + FORCEINTON; + } + break; + + case CMDFUNCTION: + listsetvar(varlist.list, 0); + if (evalfun(cmdentry.u.func, argc, argv, flags)) + goto raise; + break; + } + +out: + popredir(cmd_is_exec); + if (lastarg) + /* dsl: I think this is intended to be used to support + * '_' in 'vi' command mode during line editing... + * However I implemented that within libedit itself. + */ + setvar("_", lastarg, 0); + popstackmark(&smark); +} + +static int +evalbltin(const struct builtincmd *cmd, int argc, char **argv) { + char *volatile savecmdname; + struct jmploc *volatile savehandler; + struct jmploc jmploc; + int i; + + savecmdname = commandname; + if ((i = setjmp(jmploc.loc))) + goto cmddone; + savehandler = handler; + handler = &jmploc; + commandname = argv[0]; + argptr = argv + 1; + optptr = NULL; /* initialize nextopt */ + exitstatus = (*cmd->builtin)(argc, argv); + flushall(); +cmddone: + exitstatus |= ferror(stdout); + clearerr(stdout); + commandname = savecmdname; + exsig = 0; + handler = savehandler; + + return i; +} + +static int +evalfun(struct funcnode *func, int argc, char **argv, int flags) +{ + volatile struct shparam saveparam; + struct localvar *volatile savelocalvars; + struct jmploc *volatile savehandler; + struct jmploc jmploc; + int e; + + saveparam = shellparam; + savelocalvars = localvars; + if ((e = setjmp(jmploc.loc))) { + goto funcdone; + } + INTOFF; + savehandler = handler; + handler = &jmploc; + localvars = NULL; + shellparam.malloc = 0; + func->count++; + funcnest++; + INTON; + shellparam.nparam = argc - 1; + shellparam.p = argv + 1; +#ifdef CONFIG_ASH_GETOPTS + shellparam.optind = 1; + shellparam.optoff = -1; +#endif + evaltree(&func->n, flags & EV_TESTED); +funcdone: + INTOFF; + funcnest--; + freefunc(func); + poplocalvars(); + localvars = savelocalvars; + freeparam(&shellparam); + shellparam = saveparam; + handler = savehandler; + INTON; + evalskip &= ~SKIPFUNC; + return e; +} + + +static int goodname(const char *p) +{ + return !*endofname(p); +} + +/* + * Search for a command. This is called before we fork so that the + * location of the command will be available in the parent as well as + * the child. The check for "goodname" is an overly conservative + * check that the name will not be subject to expansion. + */ + +static void +prehash(union node *n) +{ + struct cmdentry entry; + + if (n->type == NCMD && n->ncmd.args) + if (goodname(n->ncmd.args->narg.text)) + find_command(n->ncmd.args->narg.text, &entry, 0, + pathval()); +} + + + +/* + * Builtin commands. Builtin commands whose functions are closely + * tied to evaluation are implemented here. + */ + +/* + * No command given. + */ + +static int +bltincmd(int argc, char **argv) +{ + /* + * Preserve exitstatus of a previous possible redirection + * as POSIX mandates + */ + return back_exitstatus; +} + + +/* + * Handle break and continue commands. Break, continue, and return are + * all handled by setting the evalskip flag. The evaluation routines + * above all check this flag, and if it is set they start skipping + * commands rather than executing them. The variable skipcount is + * the number of loops to break/continue, or the number of function + * levels to return. (The latter is always 1.) It should probably + * be an error to break out of more loops than exist, but it isn't + * in the standard shell so we don't make it one here. + */ + +static int +breakcmd(int argc, char **argv) +{ + int n = argc > 1 ? number(argv[1]) : 1; + + if (n <= 0) + sh_error(illnum, argv[1]); + if (n > loopnest) + n = loopnest; + if (n > 0) { + evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK; + skipcount = n; + } + return 0; +} + + +/* + * The return command. + */ + +static int +returncmd(int argc, char **argv) +{ + /* + * If called outside a function, do what ksh does; + * skip the rest of the file. + */ + evalskip = funcnest ? SKIPFUNC : SKIPFILE; + return argv[1] ? number(argv[1]) : exitstatus; +} + + +static int +falsecmd(int argc, char **argv) +{ + return 1; +} + + +static int +truecmd(int argc, char **argv) +{ + return 0; +} + + +static int +execcmd(int argc, char **argv) +{ + if (argc > 1) { + iflag = 0; /* exit on error */ + mflag = 0; + optschanged(); + shellexec(argv + 1, pathval(), 0); + } + return 0; +} + + +/* exec.c */ + +/* + * When commands are first encountered, they are entered in a hash table. + * This ensures that a full path search will not have to be done for them + * on each invocation. + * + * We should investigate converting to a linear search, even though that + * would make the command name "hash" a misnomer. + */ + +#define CMDTABLESIZE 31 /* should be prime */ +#define ARB 1 /* actual size determined at run time */ + + + +struct tblentry { + struct tblentry *next; /* next entry in hash chain */ + union param param; /* definition of builtin function */ + short cmdtype; /* index identifying command */ + char rehash; /* if set, cd done since entry created */ + char cmdname[ARB]; /* name of command */ +}; + + +static struct tblentry *cmdtable[CMDTABLESIZE]; +static int builtinloc = -1; /* index in path of %builtin, or -1 */ + + +static void tryexec(char *, char **, char **); +static void clearcmdentry(int); +static struct tblentry *cmdlookup(const char *, int); +static void delete_cmd_entry(void); + + +/* + * Exec a program. Never returns. If you change this routine, you may + * have to change the find_command routine as well. + */ + +static void +shellexec(char **argv, const char *path, int idx) +{ + char *cmdname; + int e; + char **envp; + int exerrno; + + clearredir(1); + envp = environment(); + if (strchr(argv[0], '/') != NULL + || is_safe_applet(argv[0]) +#ifdef CONFIG_FEATURE_SH_STANDALONE_SHELL + || find_applet_by_name(argv[0]) +#endif + ) { + tryexec(argv[0], argv, envp); + e = errno; + } else { + e = ENOENT; + while ((cmdname = padvance(&path, argv[0])) != NULL) { + if (--idx < 0 && pathopt == NULL) { + tryexec(cmdname, argv, envp); + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + } + stunalloc(cmdname); + } + } + + /* Map to POSIX errors */ + switch (e) { + case EACCES: + exerrno = 126; + break; + case ENOENT: + exerrno = 127; + break; + default: + exerrno = 2; + break; + } + exitstatus = exerrno; + TRACE(("shellexec failed for %s, errno %d, suppressint %d\n", + argv[0], e, suppressint )); + exerror(EXEXEC, "%s: %s", argv[0], errmsg(e, E_EXEC)); + /* NOTREACHED */ +} + + +static void +tryexec(char *cmd, char **argv, char **envp) +{ + int repeated = 0; + struct BB_applet *a; + int argc = 0; + char **c; + + if(strchr(cmd, '/') == NULL && is_safe_applet(cmd) && (a = find_applet_by_name(cmd)) != NULL) { + c = argv; + while (*c != NULL) { + c++; argc++; + } + applet_name = cmd; + exit(a->main(argc, argv)); + } +#ifdef CONFIG_FEATURE_SH_STANDALONE_SHELL + if(find_applet_by_name(cmd) != NULL) { + /* re-exec ourselves with the new arguments */ + execve(CONFIG_BUSYBOX_EXEC_PATH,argv,envp); + /* If they called chroot or otherwise made the binary no longer + * executable, fall through */ + } +#endif + +repeat: +#ifdef SYSV + do { + execve(cmd, argv, envp); + } while (errno == EINTR); +#else + execve(cmd, argv, envp); +#endif + if (repeated++) { + ckfree(argv); + } else if (errno == ENOEXEC) { + char **ap; + char **new; + + for (ap = argv; *ap; ap++) + ; + ap = new = ckmalloc((ap - argv + 2) * sizeof(char *)); + ap[1] = cmd; + *ap = cmd = (char *)DEFAULT_SHELL; + ap += 2; + argv++; + while ((*ap++ = *argv++)) + ; + argv = new; + goto repeat; + } +} + + + +/* + * Do a path search. The variable path (passed by reference) should be + * set to the start of the path before the first call; padvance will update + * this value as it proceeds. Successive calls to padvance will return + * the possible path expansions in sequence. If an option (indicated by + * a percent sign) appears in the path entry then the global variable + * pathopt will be set to point to it; otherwise pathopt will be set to + * NULL. + */ + +static char * +padvance(const char **path, const char *name) +{ + const char *p; + char *q; + const char *start; + size_t len; + + if (*path == NULL) + return NULL; + start = *path; + for (p = start ; *p && *p != ':' && *p != '%' ; p++); + len = p - start + strlen(name) + 2; /* "2" is for '/' and '\0' */ + while (stackblocksize() < len) + growstackblock(); + q = stackblock(); + if (p != start) { + memcpy(q, start, p - start); + q += p - start; + *q++ = '/'; + } + strcpy(q, name); + pathopt = NULL; + if (*p == '%') { + pathopt = ++p; + while (*p && *p != ':') p++; + } + if (*p == ':') + *path = p + 1; + else + *path = NULL; + return stalloc(len); +} + + +/*** Command hashing code ***/ + +static void +printentry(struct tblentry *cmdp) +{ + int idx; + const char *path; + char *name; + + idx = cmdp->param.index; + path = pathval(); + do { + name = padvance(&path, cmdp->cmdname); + stunalloc(name); + } while (--idx >= 0); + out1fmt("%s%s\n", name, (cmdp->rehash ? "*" : nullstr)); +} + + +static int +hashcmd(int argc, char **argv) +{ + struct tblentry **pp; + struct tblentry *cmdp; + int c; + struct cmdentry entry; + char *name; + + while ((c = nextopt("r")) != '\0') { + clearcmdentry(0); + return 0; + } + if (*argptr == NULL) { + for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) { + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + if (cmdp->cmdtype == CMDNORMAL) + printentry(cmdp); + } + } + return 0; + } + c = 0; + while ((name = *argptr) != NULL) { + if ((cmdp = cmdlookup(name, 0)) != NULL + && (cmdp->cmdtype == CMDNORMAL + || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0))) + delete_cmd_entry(); + find_command(name, &entry, DO_ERR, pathval()); + if (entry.cmdtype == CMDUNKNOWN) + c = 1; + argptr++; + } + return c; +} + + +/* + * Resolve a command name. If you change this routine, you may have to + * change the shellexec routine as well. + */ + +static void +find_command(char *name, struct cmdentry *entry, int act, const char *path) +{ + struct tblentry *cmdp; + int idx; + int prev; + char *fullname; + struct stat statb; + int e; + int updatetbl; + struct builtincmd *bcmd; + + /* If name contains a slash, don't use PATH or hash table */ + if (strchr(name, '/') != NULL) { + entry->u.index = -1; + if (act & DO_ABS) { + while (stat(name, &statb) < 0) { +#ifdef SYSV + if (errno == EINTR) + continue; +#endif + entry->cmdtype = CMDUNKNOWN; + return; + } + } + entry->cmdtype = CMDNORMAL; + return; + } + +#ifdef CONFIG_FEATURE_SH_STANDALONE_SHELL + if (find_applet_by_name(name)) { + entry->cmdtype = CMDNORMAL; + entry->u.index = -1; + return; + } +#endif + + if (is_safe_applet(name)) { + entry->cmdtype = CMDNORMAL; + entry->u.index = -1; + return; + } + + updatetbl = (path == pathval()); + if (!updatetbl) { + act |= DO_ALTPATH; + if (strstr(path, "%builtin") != NULL) + act |= DO_ALTBLTIN; + } + + /* If name is in the table, check answer will be ok */ + if ((cmdp = cmdlookup(name, 0)) != NULL) { + int bit; + + switch (cmdp->cmdtype) { + default: +#if DEBUG + abort(); +#endif + case CMDNORMAL: + bit = DO_ALTPATH; + break; + case CMDFUNCTION: + bit = DO_NOFUNC; + break; + case CMDBUILTIN: + bit = DO_ALTBLTIN; + break; + } + if (act & bit) { + updatetbl = 0; + cmdp = NULL; + } else if (cmdp->rehash == 0) + /* if not invalidated by cd, we're done */ + goto success; + } + + /* If %builtin not in path, check for builtin next */ + bcmd = find_builtin(name); + if (bcmd && (IS_BUILTIN_REGULAR(bcmd) || ( + act & DO_ALTPATH ? !(act & DO_ALTBLTIN) : builtinloc <= 0 + ))) + goto builtin_success; + + /* We have to search path. */ + prev = -1; /* where to start */ + if (cmdp && cmdp->rehash) { /* doing a rehash */ + if (cmdp->cmdtype == CMDBUILTIN) + prev = builtinloc; + else + prev = cmdp->param.index; + } + + e = ENOENT; + idx = -1; +loop: + while ((fullname = padvance(&path, name)) != NULL) { + stunalloc(fullname); + idx++; + if (pathopt) { + if (prefix(pathopt, "builtin")) { + if (bcmd) + goto builtin_success; + continue; + } else if (!(act & DO_NOFUNC) && + prefix(pathopt, "func")) { + /* handled below */ + } else { + /* ignore unimplemented options */ + continue; + } + } + /* if rehash, don't redo absolute path names */ + if (fullname[0] == '/' && idx <= prev) { + if (idx < prev) + continue; + TRACE(("searchexec \"%s\": no change\n", name)); + goto success; + } + while (stat(fullname, &statb) < 0) { +#ifdef SYSV + if (errno == EINTR) + continue; +#endif + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + goto loop; + } + e = EACCES; /* if we fail, this will be the error */ + if (!S_ISREG(statb.st_mode)) + continue; + if (pathopt) { /* this is a %func directory */ + stalloc(strlen(fullname) + 1); + readcmdfile(fullname); + if ((cmdp = cmdlookup(name, 0)) == NULL || + cmdp->cmdtype != CMDFUNCTION) + sh_error("%s not defined in %s", name, fullname); + stunalloc(fullname); + goto success; + } + TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname)); + if (!updatetbl) { + entry->cmdtype = CMDNORMAL; + entry->u.index = idx; + return; + } + INTOFF; + cmdp = cmdlookup(name, 1); + cmdp->cmdtype = CMDNORMAL; + cmdp->param.index = idx; + INTON; + goto success; + } + + /* We failed. If there was an entry for this command, delete it */ + if (cmdp && updatetbl) + delete_cmd_entry(); + if (act & DO_ERR) + sh_warnx("%s: %s", name, errmsg(e, E_EXEC)); + entry->cmdtype = CMDUNKNOWN; + return; + +builtin_success: + if (!updatetbl) { + entry->cmdtype = CMDBUILTIN; + entry->u.cmd = bcmd; + return; + } + INTOFF; + cmdp = cmdlookup(name, 1); + cmdp->cmdtype = CMDBUILTIN; + cmdp->param.cmd = bcmd; + INTON; +success: + cmdp->rehash = 0; + entry->cmdtype = cmdp->cmdtype; + entry->u = cmdp->param; +} + + +/* + * Wrapper around strcmp for qsort/bsearch/... + */ +static int pstrcmp(const void *a, const void *b) +{ + return strcmp((const char *) a, (*(const char *const *) b) + 1); +} + +/* + * Search the table of builtin commands. + */ + +static struct builtincmd * +find_builtin(const char *name) +{ + struct builtincmd *bp; + + bp = bsearch( + name, builtincmd, NUMBUILTINS, sizeof(struct builtincmd), + pstrcmp + ); + return bp; +} + + + +/* + * Called when a cd is done. Marks all commands so the next time they + * are executed they will be rehashed. + */ + +static void +hashcd(void) +{ + struct tblentry **pp; + struct tblentry *cmdp; + + for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) { + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + if (cmdp->cmdtype == CMDNORMAL || ( + cmdp->cmdtype == CMDBUILTIN && + !(IS_BUILTIN_REGULAR(cmdp->param.cmd)) && + builtinloc > 0 + )) + cmdp->rehash = 1; + } + } +} + + + +/* + * Fix command hash table when PATH changed. + * Called before PATH is changed. The argument is the new value of PATH; + * pathval() still returns the old value at this point. + * Called with interrupts off. + */ + +static void +changepath(const char *newval) +{ + const char *old, *new; + int idx; + int firstchange; + int idx_bltin; + + old = pathval(); + new = newval; + firstchange = 9999; /* assume no change */ + idx = 0; + idx_bltin = -1; + for (;;) { + if (*old != *new) { + firstchange = idx; + if ((*old == '\0' && *new == ':') + || (*old == ':' && *new == '\0')) + firstchange++; + old = new; /* ignore subsequent differences */ + } + if (*new == '\0') + break; + if (*new == '%' && idx_bltin < 0 && prefix(new + 1, "builtin")) + idx_bltin = idx; + if (*new == ':') { + idx++; + } + new++, old++; + } + if (builtinloc < 0 && idx_bltin >= 0) + builtinloc = idx_bltin; /* zap builtins */ + if (builtinloc >= 0 && idx_bltin < 0) + firstchange = 0; + clearcmdentry(firstchange); + builtinloc = idx_bltin; +} + + +/* + * Clear out command entries. The argument specifies the first entry in + * PATH which has changed. + */ + +static void +clearcmdentry(int firstchange) +{ + struct tblentry **tblp; + struct tblentry **pp; + struct tblentry *cmdp; + + INTOFF; + for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) { + pp = tblp; + while ((cmdp = *pp) != NULL) { + if ((cmdp->cmdtype == CMDNORMAL && + cmdp->param.index >= firstchange) + || (cmdp->cmdtype == CMDBUILTIN && + builtinloc >= firstchange)) { + *pp = cmdp->next; + ckfree(cmdp); + } else { + pp = &cmdp->next; + } + } + } + INTON; +} + + + +/* + * Locate a command in the command hash table. If "add" is nonzero, + * add the command to the table if it is not already present. The + * variable "lastcmdentry" is set to point to the address of the link + * pointing to the entry, so that delete_cmd_entry can delete the + * entry. + * + * Interrupts must be off if called with add != 0. + */ + +static struct tblentry **lastcmdentry; + + +static struct tblentry * +cmdlookup(const char *name, int add) +{ + unsigned int hashval; + const char *p; + struct tblentry *cmdp; + struct tblentry **pp; + + p = name; + hashval = (unsigned char)*p << 4; + while (*p) + hashval += (unsigned char)*p++; + hashval &= 0x7FFF; + pp = &cmdtable[hashval % CMDTABLESIZE]; + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + if (equal(cmdp->cmdname, name)) + break; + pp = &cmdp->next; + } + if (add && cmdp == NULL) { + cmdp = *pp = ckmalloc(sizeof (struct tblentry) - ARB + + strlen(name) + 1); + cmdp->next = NULL; + cmdp->cmdtype = CMDUNKNOWN; + strcpy(cmdp->cmdname, name); + } + lastcmdentry = pp; + return cmdp; +} + +/* + * Delete the command entry returned on the last lookup. + */ + +static void +delete_cmd_entry(void) +{ + struct tblentry *cmdp; + + INTOFF; + cmdp = *lastcmdentry; + *lastcmdentry = cmdp->next; + if (cmdp->cmdtype == CMDFUNCTION) + freefunc(cmdp->param.func); + ckfree(cmdp); + INTON; +} + + +/* + * Add a new command entry, replacing any existing command entry for + * the same name - except special builtins. + */ + +static void addcmdentry(char *name, struct cmdentry *entry) +{ + struct tblentry *cmdp; + + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) { + freefunc(cmdp->param.func); + } + cmdp->cmdtype = entry->cmdtype; + cmdp->param = entry->u; + cmdp->rehash = 0; +} + +/* + * Make a copy of a parse tree. + */ + +static struct funcnode * copyfunc(union node *n) +{ + struct funcnode *f; + size_t blocksize; + + funcblocksize = offsetof(struct funcnode, n); + funcstringsize = 0; + calcsize(n); + blocksize = funcblocksize; + f = ckmalloc(blocksize + funcstringsize); + funcblock = (char *) f + offsetof(struct funcnode, n); + funcstring = (char *) f + blocksize; + copynode(n); + f->count = 0; + return f; +} + +/* + * Define a shell function. + */ + +static void +defun(char *name, union node *func) +{ + struct cmdentry entry; + + INTOFF; + entry.cmdtype = CMDFUNCTION; + entry.u.func = copyfunc(func); + addcmdentry(name, &entry); + INTON; +} + + +/* + * Delete a function if it exists. + */ + +static void +unsetfunc(const char *name) +{ + struct tblentry *cmdp; + + if ((cmdp = cmdlookup(name, 0)) != NULL && + cmdp->cmdtype == CMDFUNCTION) + delete_cmd_entry(); +} + +/* + * Locate and print what a word is... + */ + + +#ifdef CONFIG_ASH_CMDCMD +static int +describe_command(char *command, int describe_command_verbose) +#else +#define describe_command_verbose 1 +static int +describe_command(char *command) +#endif +{ + struct cmdentry entry; + struct tblentry *cmdp; +#ifdef CONFIG_ASH_ALIAS + const struct alias *ap; +#endif + const char *path = pathval(); + + if (describe_command_verbose) { + out1str(command); + } + + /* First look at the keywords */ + if (findkwd(command)) { + out1str(describe_command_verbose ? " is a shell keyword" : command); + goto out; + } + +#ifdef CONFIG_ASH_ALIAS + /* Then look at the aliases */ + if ((ap = lookupalias(command, 0)) != NULL) { + if (describe_command_verbose) { + out1fmt(" is an alias for %s", ap->val); + } else { + out1str("alias "); + printalias(ap); + return 0; + } + goto out; + } +#endif + /* Then check if it is a tracked alias */ + if ((cmdp = cmdlookup(command, 0)) != NULL) { + entry.cmdtype = cmdp->cmdtype; + entry.u = cmdp->param; + } else { + /* Finally use brute force */ + find_command(command, &entry, DO_ABS, path); + } + + switch (entry.cmdtype) { + case CMDNORMAL: { + int j = entry.u.index; + char *p; + if (j == -1) { + p = command; + } else { + do { + p = padvance(&path, command); + stunalloc(p); + } while (--j >= 0); + } + if (describe_command_verbose) { + out1fmt(" is%s %s", + (cmdp ? " a tracked alias for" : nullstr), p + ); + } else { + out1str(p); + } + break; + } + + case CMDFUNCTION: + if (describe_command_verbose) { + out1str(" is a shell function"); + } else { + out1str(command); + } + break; + + case CMDBUILTIN: + if (describe_command_verbose) { + out1fmt(" is a %sshell builtin", + IS_BUILTIN_SPECIAL(entry.u.cmd) ? + "special " : nullstr + ); + } else { + out1str(command); + } + break; + + default: + if (describe_command_verbose) { + out1str(": not found\n"); + } + return 127; + } + +out: + outstr("\n", stdout); + return 0; +} + +static int +typecmd(int argc, char **argv) +{ + int i; + int err = 0; + + for (i = 1; i < argc; i++) { +#ifdef CONFIG_ASH_CMDCMD + err |= describe_command(argv[i], 1); +#else + err |= describe_command(argv[i]); +#endif + } + return err; +} + +#ifdef CONFIG_ASH_CMDCMD +static int +commandcmd(int argc, char **argv) +{ + int c; + enum { + VERIFY_BRIEF = 1, + VERIFY_VERBOSE = 2, + } verify = 0; + + while ((c = nextopt("pvV")) != '\0') + if (c == 'V') + verify |= VERIFY_VERBOSE; + else if (c == 'v') + verify |= VERIFY_BRIEF; +#if DEBUG + else if (c != 'p') + abort(); +#endif + if (verify) + return describe_command(*argptr, verify - VERIFY_BRIEF); + + return 0; +} +#endif + +/* expand.c */ + +/* + * Routines to expand arguments to commands. We have to deal with + * backquotes, shell variables, and file metacharacters. + */ + +/* + * _rmescape() flags + */ +#define RMESCAPE_ALLOC 0x1 /* Allocate a new string */ +#define RMESCAPE_GLOB 0x2 /* Add backslashes for glob */ +#define RMESCAPE_QUOTED 0x4 /* Remove CTLESC unless in quotes */ +#define RMESCAPE_GROW 0x8 /* Grow strings instead of stalloc */ +#define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */ + +/* + * Structure specifying which parts of the string should be searched + * for IFS characters. + */ + +struct ifsregion { + struct ifsregion *next; /* next region in list */ + int begoff; /* offset of start of region */ + int endoff; /* offset of end of region */ + int nulonly; /* search for nul bytes only */ +}; + +/* output of current string */ +static char *expdest; +/* list of back quote expressions */ +static struct nodelist *argbackq; +/* first struct in list of ifs regions */ +static struct ifsregion ifsfirst; +/* last struct in list */ +static struct ifsregion *ifslastp; +/* holds expanded arg list */ +static struct arglist exparg; + +static void argstr(char *, int); +static char *exptilde(char *, char *, int); +static void expbackq(union node *, int, int); +static const char *subevalvar(char *, char *, int, int, int, int, int); +static char *evalvar(char *, int); +static void strtodest(const char *, int, int); +static void memtodest(const char *p, size_t len, int syntax, int quotes); +static ssize_t varvalue(char *, int, int); +static void recordregion(int, int, int); +static void removerecordregions(int); +static void ifsbreakup(char *, struct arglist *); +static void ifsfree(void); +static void expandmeta(struct strlist *, int); +static int patmatch(char *, const char *); + +static int cvtnum(arith_t); +static size_t esclen(const char *, const char *); +static char *scanleft(char *, char *, char *, char *, int, int); +static char *scanright(char *, char *, char *, char *, int, int); +static void varunset(const char *, const char *, const char *, int) + ATTRIBUTE_NORETURN; + + +#define pmatch(a, b) !fnmatch((a), (b), 0) +/* + * Prepare a pattern for a expmeta (internal glob(3)) call. + * + * Returns an stalloced string. + */ + +static char * preglob(const char *pattern, int quoted, int flag) { + flag |= RMESCAPE_GLOB; + if (quoted) { + flag |= RMESCAPE_QUOTED; + } + return _rmescapes((char *)pattern, flag); +} + + +static size_t +esclen(const char *start, const char *p) { + size_t esc = 0; + + while (p > start && *--p == CTLESC) { + esc++; + } + return esc; +} + + +/* + * Expand shell variables and backquotes inside a here document. + */ + +static void expandhere(union node *arg, int fd) +{ + herefd = fd; + expandarg(arg, (struct arglist *)NULL, 0); + full_write(fd, stackblock(), expdest - (char *)stackblock()); +} + + +/* + * Perform variable substitution and command substitution on an argument, + * placing the resulting list of arguments in arglist. If EXP_FULL is true, + * perform splitting and file name expansion. When arglist is NULL, perform + * here document expansion. + */ + +void +expandarg(union node *arg, struct arglist *arglist, int flag) +{ + struct strlist *sp; + char *p; + + argbackq = arg->narg.backquote; + STARTSTACKSTR(expdest); + ifsfirst.next = NULL; + ifslastp = NULL; + argstr(arg->narg.text, flag); + p = _STPUTC('\0', expdest); + expdest = p - 1; + if (arglist == NULL) { + return; /* here document expanded */ + } + p = grabstackstr(p); + exparg.lastp = &exparg.list; + /* + * TODO - EXP_REDIR + */ + if (flag & EXP_FULL) { + ifsbreakup(p, &exparg); + *exparg.lastp = NULL; + exparg.lastp = &exparg.list; + expandmeta(exparg.list, flag); + } else { + if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */ + rmescapes(p); + sp = (struct strlist *)stalloc(sizeof (struct strlist)); + sp->text = p; + *exparg.lastp = sp; + exparg.lastp = &sp->next; + } + if (ifsfirst.next) + ifsfree(); + *exparg.lastp = NULL; + if (exparg.list) { + *arglist->lastp = exparg.list; + arglist->lastp = exparg.lastp; + } +} + + +/* + * Perform variable and command substitution. If EXP_FULL is set, output CTLESC + * characters to allow for further processing. Otherwise treat + * $@ like $* since no splitting will be performed. + */ + +static void +argstr(char *p, int flag) +{ + static const char spclchars[] = { + '=', + ':', + CTLQUOTEMARK, + CTLENDVAR, + CTLESC, + CTLVAR, + CTLBACKQ, + CTLBACKQ | CTLQUOTE, +#ifdef CONFIG_ASH_MATH_SUPPORT + CTLENDARI, +#endif + 0 + }; + const char *reject = spclchars; + int c; + int quotes = flag & (EXP_FULL | EXP_CASE); /* do CTLESC */ + int breakall = flag & EXP_WORD; + int inquotes; + size_t length; + int startloc; + + if (!(flag & EXP_VARTILDE)) { + reject += 2; + } else if (flag & EXP_VARTILDE2) { + reject++; + } + inquotes = 0; + length = 0; + if (flag & EXP_TILDE) { + char *q; + + flag &= ~EXP_TILDE; +tilde: + q = p; + if (*q == CTLESC && (flag & EXP_QWORD)) + q++; + if (*q == '~') + p = exptilde(p, q, flag); + } +start: + startloc = expdest - (char *)stackblock(); + for (;;) { + length += strcspn(p + length, reject); + c = p[length]; + if (c && (!(c & 0x80) +#ifdef CONFIG_ASH_MATH_SUPPORT + || c == CTLENDARI +#endif + )) { + /* c == '=' || c == ':' || c == CTLENDARI */ + length++; + } + if (length > 0) { + int newloc; + expdest = stnputs(p, length, expdest); + newloc = expdest - (char *)stackblock(); + if (breakall && !inquotes && newloc > startloc) { + recordregion(startloc, newloc, 0); + } + startloc = newloc; + } + p += length + 1; + length = 0; + + switch (c) { + case '\0': + goto breakloop; + case '=': + if (flag & EXP_VARTILDE2) { + p--; + continue; + } + flag |= EXP_VARTILDE2; + reject++; + /* fall through */ + case ':': + /* + * sort of a hack - expand tildes in variable + * assignments (after the first '=' and after ':'s). + */ + if (*--p == '~') { + goto tilde; + } + continue; + } + + switch (c) { + case CTLENDVAR: /* ??? */ + goto breakloop; + case CTLQUOTEMARK: + /* "$@" syntax adherence hack */ + if ( + !inquotes && + !memcmp(p, dolatstr, DOLATSTRLEN) && + (p[4] == CTLQUOTEMARK || ( + p[4] == CTLENDVAR && + p[5] == CTLQUOTEMARK + )) + ) { + p = evalvar(p + 1, flag) + 1; + goto start; + } + inquotes = !inquotes; +addquote: + if (quotes) { + p--; + length++; + startloc++; + } + break; + case CTLESC: + startloc++; + length++; + goto addquote; + case CTLVAR: + p = evalvar(p, flag); + goto start; + case CTLBACKQ: + c = 0; + case CTLBACKQ|CTLQUOTE: + expbackq(argbackq->n, c, quotes); + argbackq = argbackq->next; + goto start; +#ifdef CONFIG_ASH_MATH_SUPPORT + case CTLENDARI: + p--; + expari(quotes); + goto start; +#endif + } + } +breakloop: + ; +} + +static char * +exptilde(char *startp, char *p, int flag) +{ + char c; + char *name; + struct passwd *pw; + const char *home; + int quotes = flag & (EXP_FULL | EXP_CASE); + int startloc; + + name = p + 1; + + while ((c = *++p) != '\0') { + switch(c) { + case CTLESC: + return startp; + case CTLQUOTEMARK: + return startp; + case ':': + if (flag & EXP_VARTILDE) + goto done; + break; + case '/': + case CTLENDVAR: + goto done; + } + } +done: + *p = '\0'; + if (*name == '\0') { + home = lookupvar(homestr); + } else { + if ((pw = getpwnam(name)) == NULL) + goto lose; + home = pw->pw_dir; + } + if (!home || !*home) + goto lose; + *p = c; + startloc = expdest - (char *)stackblock(); + strtodest(home, SQSYNTAX, quotes); + recordregion(startloc, expdest - (char *)stackblock(), 0); + return p; +lose: + *p = c; + return startp; +} + + +static void +removerecordregions(int endoff) +{ + if (ifslastp == NULL) + return; + + if (ifsfirst.endoff > endoff) { + while (ifsfirst.next != NULL) { + struct ifsregion *ifsp; + INTOFF; + ifsp = ifsfirst.next->next; + ckfree(ifsfirst.next); + ifsfirst.next = ifsp; + INTON; + } + if (ifsfirst.begoff > endoff) + ifslastp = NULL; + else { + ifslastp = &ifsfirst; + ifsfirst.endoff = endoff; + } + return; + } + + ifslastp = &ifsfirst; + while (ifslastp->next && ifslastp->next->begoff < endoff) + ifslastp=ifslastp->next; + while (ifslastp->next != NULL) { + struct ifsregion *ifsp; + INTOFF; + ifsp = ifslastp->next->next; + ckfree(ifslastp->next); + ifslastp->next = ifsp; + INTON; + } + if (ifslastp->endoff > endoff) + ifslastp->endoff = endoff; +} + + +#ifdef CONFIG_ASH_MATH_SUPPORT +/* + * Expand arithmetic expression. Backup to start of expression, + * evaluate, place result in (backed up) result, adjust string position. + */ +void +expari(int quotes) +{ + char *p, *start; + int begoff; + int flag; + int len; + + /* ifsfree(); */ + + /* + * This routine is slightly over-complicated for + * efficiency. Next we scan backwards looking for the + * start of arithmetic. + */ + start = stackblock(); + p = expdest - 1; + *p = '\0'; + p--; + do { + int esc; + + while (*p != CTLARI) { + p--; +#if DEBUG + if (p < start) { + sh_error("missing CTLARI (shouldn't happen)"); + } +#endif + } + + esc = esclen(start, p); + if (!(esc % 2)) { + break; + } + + p -= esc + 1; + } while (1); + + begoff = p - start; + + removerecordregions(begoff); + + flag = p[1]; + + expdest = p; + + if (quotes) + rmescapes(p + 2); + + len = cvtnum(dash_arith(p + 2)); + + if (flag != '"') + recordregion(begoff, begoff + len, 0); +} +#endif + +/* + * Expand stuff in backwards quotes. + */ + +static void +expbackq(union node *cmd, int quoted, int quotes) +{ + struct backcmd in; + int i; + char buf[128]; + char *p; + char *dest; + int startloc; + int syntax = quoted? DQSYNTAX : BASESYNTAX; + struct stackmark smark; + + INTOFF; + setstackmark(&smark); + dest = expdest; + startloc = dest - (char *)stackblock(); + grabstackstr(dest); + evalbackcmd(cmd, (struct backcmd *) &in); + popstackmark(&smark); + + p = in.buf; + i = in.nleft; + if (i == 0) + goto read; + for (;;) { + memtodest(p, i, syntax, quotes); +read: + if (in.fd < 0) + break; + i = safe_read(in.fd, buf, sizeof buf); + TRACE(("expbackq: read returns %d\n", i)); + if (i <= 0) + break; + p = buf; + } + + if (in.buf) + ckfree(in.buf); + if (in.fd >= 0) { + close(in.fd); + back_exitstatus = waitforjob(in.jp); + } + INTON; + + /* Eat all trailing newlines */ + dest = expdest; + for (; dest > (char *)stackblock() && dest[-1] == '\n';) + STUNPUTC(dest); + expdest = dest; + + if (quoted == 0) + recordregion(startloc, dest - (char *)stackblock(), 0); + TRACE(("evalbackq: size=%d: \"%.*s\"\n", + (dest - (char *)stackblock()) - startloc, + (dest - (char *)stackblock()) - startloc, + stackblock() + startloc)); +} + + +static char * +scanleft(char *startp, char *rmesc, char *rmescend, char *str, int quotes, + int zero) +{ + char *loc; + char *loc2; + char c; + + loc = startp; + loc2 = rmesc; + do { + int match; + const char *s = loc2; + c = *loc2; + if (zero) { + *loc2 = '\0'; + s = rmesc; + } + match = pmatch(str, s); + *loc2 = c; + if (match) + return loc; + if (quotes && *loc == CTLESC) + loc++; + loc++; + loc2++; + } while (c); + return 0; +} + + +static char * +scanright(char *startp, char *rmesc, char *rmescend, char *str, int quotes, + int zero) +{ + int esc = 0; + char *loc; + char *loc2; + + for (loc = str - 1, loc2 = rmescend; loc >= startp; loc2--) { + int match; + char c = *loc2; + const char *s = loc2; + if (zero) { + *loc2 = '\0'; + s = rmesc; + } + match = pmatch(str, s); + *loc2 = c; + if (match) + return loc; + loc--; + if (quotes) { + if (--esc < 0) { + esc = esclen(startp, loc); + } + if (esc % 2) { + esc--; + loc--; + } + } + } + return 0; +} + +static const char * +subevalvar(char *p, char *str, int strloc, int subtype, int startloc, int varflags, int quotes) +{ + char *startp; + char *loc; + int saveherefd = herefd; + struct nodelist *saveargbackq = argbackq; + int amount; + char *rmesc, *rmescend; + int zero; + char *(*scan)(char *, char *, char *, char *, int , int); + + herefd = -1; + argstr(p, subtype != VSASSIGN && subtype != VSQUESTION ? EXP_CASE : 0); + STPUTC('\0', expdest); + herefd = saveherefd; + argbackq = saveargbackq; + startp = stackblock() + startloc; + + switch (subtype) { + case VSASSIGN: + setvar(str, startp, 0); + amount = startp - expdest; + STADJUST(amount, expdest); + return startp; + + case VSQUESTION: + varunset(p, str, startp, varflags); + /* NOTREACHED */ + } + + subtype -= VSTRIMRIGHT; +#if DEBUG + if (subtype < 0 || subtype > 3) + abort(); +#endif + + rmesc = startp; + rmescend = stackblock() + strloc; + if (quotes) { + rmesc = _rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW); + if (rmesc != startp) { + rmescend = expdest; + startp = stackblock() + startloc; + } + } + rmescend--; + str = stackblock() + strloc; + preglob(str, varflags & VSQUOTE, 0); + + /* zero = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX */ + zero = subtype >> 1; + /* VSTRIMLEFT/VSTRIMRIGHTMAX -> scanleft */ + scan = (subtype & 1) ^ zero ? scanleft : scanright; + + loc = scan(startp, rmesc, rmescend, str, quotes, zero); + if (loc) { + if (zero) { + memmove(startp, loc, str - loc); + loc = startp + (str - loc) - 1; + } + *loc = '\0'; + amount = loc - expdest; + STADJUST(amount, expdest); + } + return loc; +} + + +/* + * Expand a variable, and return a pointer to the next character in the + * input string. + */ +static char * +evalvar(char *p, int flag) +{ + int subtype; + int varflags; + char *var; + int patloc; + int c; + int startloc; + ssize_t varlen; + int easy; + int quotes; + int quoted; + + quotes = flag & (EXP_FULL | EXP_CASE); + varflags = *p++; + subtype = varflags & VSTYPE; + quoted = varflags & VSQUOTE; + var = p; + easy = (!quoted || (*var == '@' && shellparam.nparam)); + startloc = expdest - (char *)stackblock(); + p = strchr(p, '=') + 1; + +again: + varlen = varvalue(var, varflags, flag); + if (varflags & VSNUL) + varlen--; + + if (subtype == VSPLUS) { + varlen = -1 - varlen; + goto vsplus; + } + + if (subtype == VSMINUS) { +vsplus: + if (varlen < 0) { + argstr( + p, flag | EXP_TILDE | + (quoted ? EXP_QWORD : EXP_WORD) + ); + goto end; + } + if (easy) + goto record; + goto end; + } + + if (subtype == VSASSIGN || subtype == VSQUESTION) { + if (varlen < 0) { + if (subevalvar(p, var, 0, subtype, startloc, + varflags, 0)) { + varflags &= ~VSNUL; + /* + * Remove any recorded regions beyond + * start of variable + */ + removerecordregions(startloc); + goto again; + } + goto end; + } + if (easy) + goto record; + goto end; + } + + if (varlen < 0 && uflag) + varunset(p, var, 0, 0); + + if (subtype == VSLENGTH) { + cvtnum(varlen > 0 ? varlen : 0); + goto record; + } + + if (subtype == VSNORMAL) { + if (!easy) + goto end; +record: + recordregion(startloc, expdest - (char *)stackblock(), quoted); + goto end; + } + +#if DEBUG + switch (subtype) { + case VSTRIMLEFT: + case VSTRIMLEFTMAX: + case VSTRIMRIGHT: + case VSTRIMRIGHTMAX: + break; + default: + abort(); + } +#endif + + if (varlen >= 0) { + /* + * Terminate the string and start recording the pattern + * right after it + */ + STPUTC('\0', expdest); + patloc = expdest - (char *)stackblock(); + if (subevalvar(p, NULL, patloc, subtype, + startloc, varflags, quotes) == 0) { + int amount = expdest - ( + (char *)stackblock() + patloc - 1 + ); + STADJUST(-amount, expdest); + } + /* Remove any recorded regions beyond start of variable */ + removerecordregions(startloc); + goto record; + } + +end: + if (subtype != VSNORMAL) { /* skip to end of alternative */ + int nesting = 1; + for (;;) { + if ((c = *p++) == CTLESC) + p++; + else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) { + if (varlen >= 0) + argbackq = argbackq->next; + } else if (c == CTLVAR) { + if ((*p++ & VSTYPE) != VSNORMAL) + nesting++; + } else if (c == CTLENDVAR) { + if (--nesting == 0) + break; + } + } + } + return p; +} + + +/* + * Put a string on the stack. + */ + +static void +memtodest(const char *p, size_t len, int syntax, int quotes) { + char *q = expdest; + + q = makestrspace(len * 2, q); + + while (len--) { + int c = SC2INT(*p++); + if (!c) + continue; + if (quotes && (SIT(c, syntax) == CCTL || SIT(c, syntax) == CBACK)) + USTPUTC(CTLESC, q); + USTPUTC(c, q); + } + + expdest = q; +} + + +static void +strtodest(const char *p, int syntax, int quotes) +{ + memtodest(p, strlen(p), syntax, quotes); +} + + +/* + * Add the value of a specialized variable to the stack string. + */ + +static ssize_t +varvalue(char *name, int varflags, int flags) +{ + int num; + char *p; + int i; + int sep = 0; + int sepq = 0; + ssize_t len = 0; + char **ap; + int syntax; + int quoted = varflags & VSQUOTE; + int subtype = varflags & VSTYPE; + int quotes = flags & (EXP_FULL | EXP_CASE); + + if (quoted && (flags & EXP_FULL)) + sep = 1 << CHAR_BIT; + + syntax = quoted ? DQSYNTAX : BASESYNTAX; + switch (*name) { + case '$': + num = rootpid; + goto numvar; + case '?': + num = exitstatus; + goto numvar; + case '#': + num = shellparam.nparam; + goto numvar; + case '!': + num = backgndpid; + if (num == 0) + return -1; +numvar: + len = cvtnum(num); + break; + case '-': + p = makestrspace(NOPTS, expdest); + for (i = NOPTS - 1; i >= 0; i--) { + if (optlist[i]) { + USTPUTC(optletters(i), p); + len++; + } + } + expdest = p; + break; + case '@': + if (sep) + goto param; + /* fall through */ + case '*': + sep = ifsset() ? SC2INT(ifsval()[0]) : ' '; + if (quotes && (SIT(sep, syntax) == CCTL || SIT(sep, syntax) == CBACK)) + sepq = 1; +param: + if (!(ap = shellparam.p)) + return -1; + while ((p = *ap++)) { + size_t partlen; + + partlen = strlen(p); + len += partlen; + + if (!(subtype == VSPLUS || subtype == VSLENGTH)) + memtodest(p, partlen, syntax, quotes); + + if (*ap && sep) { + char *q; + + len++; + if (subtype == VSPLUS || subtype == VSLENGTH) { + continue; + } + q = expdest; + if (sepq) + STPUTC(CTLESC, q); + STPUTC(sep, q); + expdest = q; + } + } + return len; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + num = atoi(name); + if (num < 0 || num > shellparam.nparam) + return -1; + p = num ? shellparam.p[num - 1] : arg0; + goto value; + default: + p = lookupvar(name); +value: + if (!p) + return -1; + + len = strlen(p); + if (!(subtype == VSPLUS || subtype == VSLENGTH)) + memtodest(p, len, syntax, quotes); + return len; + } + + if (subtype == VSPLUS || subtype == VSLENGTH) + STADJUST(-len, expdest); + return len; +} + + +/* + * Record the fact that we have to scan this region of the + * string for IFS characters. + */ + +static void +recordregion(int start, int end, int nulonly) +{ + struct ifsregion *ifsp; + + if (ifslastp == NULL) { + ifsp = &ifsfirst; + } else { + INTOFF; + ifsp = (struct ifsregion *)ckmalloc(sizeof (struct ifsregion)); + ifsp->next = NULL; + ifslastp->next = ifsp; + INTON; + } + ifslastp = ifsp; + ifslastp->begoff = start; + ifslastp->endoff = end; + ifslastp->nulonly = nulonly; +} + + +/* + * Break the argument string into pieces based upon IFS and add the + * strings to the argument list. The regions of the string to be + * searched for IFS characters have been stored by recordregion. + */ +static void +ifsbreakup(char *string, struct arglist *arglist) +{ + struct ifsregion *ifsp; + struct strlist *sp; + char *start; + char *p; + char *q; + const char *ifs, *realifs; + int ifsspc; + int nulonly; + + + start = string; + if (ifslastp != NULL) { + ifsspc = 0; + nulonly = 0; + realifs = ifsset() ? ifsval() : defifs; + ifsp = &ifsfirst; + do { + p = string + ifsp->begoff; + nulonly = ifsp->nulonly; + ifs = nulonly ? nullstr : realifs; + ifsspc = 0; + while (p < string + ifsp->endoff) { + q = p; + if (*p == CTLESC) + p++; + if (strchr(ifs, *p)) { + if (!nulonly) + ifsspc = (strchr(defifs, *p) != NULL); + /* Ignore IFS whitespace at start */ + if (q == start && ifsspc) { + p++; + start = p; + continue; + } + *q = '\0'; + sp = (struct strlist *)stalloc(sizeof *sp); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; + p++; + if (!nulonly) { + for (;;) { + if (p >= string + ifsp->endoff) { + break; + } + q = p; + if (*p == CTLESC) + p++; + if (strchr(ifs, *p) == NULL ) { + p = q; + break; + } else if (strchr(defifs, *p) == NULL) { + if (ifsspc) { + p++; + ifsspc = 0; + } else { + p = q; + break; + } + } else + p++; + } + } + start = p; + } else + p++; + } + } while ((ifsp = ifsp->next) != NULL); + if (nulonly) + goto add; + } + + if (!*start) + return; + +add: + sp = (struct strlist *)stalloc(sizeof *sp); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; +} + +static void +ifsfree(void) +{ + struct ifsregion *p; + + INTOFF; + p = ifsfirst.next; + do { + struct ifsregion *ifsp; + ifsp = p->next; + ckfree(p); + p = ifsp; + } while (p); + ifslastp = NULL; + ifsfirst.next = NULL; + INTON; +} + +static void expmeta(char *, char *); +static struct strlist *expsort(struct strlist *); +static struct strlist *msort(struct strlist *, int); + +static char *expdir; + + +static void +expandmeta(struct strlist *str, int flag) +{ + static const char metachars[] = { + '*', '?', '[', 0 + }; + /* TODO - EXP_REDIR */ + + while (str) { + struct strlist **savelastp; + struct strlist *sp; + char *p; + + if (fflag) + goto nometa; + if (!strpbrk(str->text, metachars)) + goto nometa; + savelastp = exparg.lastp; + + INTOFF; + p = preglob(str->text, 0, RMESCAPE_ALLOC | RMESCAPE_HEAP); + { + int i = strlen(str->text); + expdir = ckmalloc(i < 2048 ? 2048 : i); /* XXX */ + } + + expmeta(expdir, p); + ckfree(expdir); + if (p != str->text) + ckfree(p); + INTON; + if (exparg.lastp == savelastp) { + /* + * no matches + */ +nometa: + *exparg.lastp = str; + rmescapes(str->text); + exparg.lastp = &str->next; + } else { + *exparg.lastp = NULL; + *savelastp = sp = expsort(*savelastp); + while (sp->next != NULL) + sp = sp->next; + exparg.lastp = &sp->next; + } + str = str->next; + } +} + +/* + * Add a file name to the list. + */ + +static void +addfname(const char *name) +{ + struct strlist *sp; + + sp = (struct strlist *)stalloc(sizeof *sp); + sp->text = sstrdup(name); + *exparg.lastp = sp; + exparg.lastp = &sp->next; +} + + +/* + * Do metacharacter (i.e. *, ?, [...]) expansion. + */ + +static void +expmeta(char *enddir, char *name) +{ + char *p; + const char *cp; + char *start; + char *endname; + int metaflag; + struct stat statb; + DIR *dirp; + struct dirent *dp; + int atend; + int matchdot; + + metaflag = 0; + start = name; + for (p = name; *p; p++) { + if (*p == '*' || *p == '?') + metaflag = 1; + else if (*p == '[') { + char *q = p + 1; + if (*q == '!') + q++; + for (;;) { + if (*q == '\\') + q++; + if (*q == '/' || *q == '\0') + break; + if (*++q == ']') { + metaflag = 1; + break; + } + } + } else if (*p == '\\') + p++; + else if (*p == '/') { + if (metaflag) + goto out; + start = p + 1; + } + } +out: + if (metaflag == 0) { /* we've reached the end of the file name */ + if (enddir != expdir) + metaflag++; + p = name; + do { + if (*p == '\\') + p++; + *enddir++ = *p; + } while (*p++); + if (metaflag == 0 || lstat(expdir, &statb) >= 0) + addfname(expdir); + return; + } + endname = p; + if (name < start) { + p = name; + do { + if (*p == '\\') + p++; + *enddir++ = *p++; + } while (p < start); + } + if (enddir == expdir) { + cp = "."; + } else if (enddir == expdir + 1 && *expdir == '/') { + cp = "/"; + } else { + cp = expdir; + enddir[-1] = '\0'; + } + if ((dirp = opendir(cp)) == NULL) + return; + if (enddir != expdir) + enddir[-1] = '/'; + if (*endname == 0) { + atend = 1; + } else { + atend = 0; + *endname++ = '\0'; + } + matchdot = 0; + p = start; + if (*p == '\\') + p++; + if (*p == '.') + matchdot++; + while (! intpending && (dp = readdir(dirp)) != NULL) { + if (dp->d_name[0] == '.' && ! matchdot) + continue; + if (pmatch(start, dp->d_name)) { + if (atend) { + scopy(dp->d_name, enddir); + addfname(expdir); + } else { + for (p = enddir, cp = dp->d_name; + (*p++ = *cp++) != '\0';) + continue; + p[-1] = '/'; + expmeta(p, endname); + } + } + } + closedir(dirp); + if (! atend) + endname[-1] = '/'; +} + +/* + * Sort the results of file name expansion. It calculates the number of + * strings to sort and then calls msort (short for merge sort) to do the + * work. + */ + +static struct strlist * +expsort(struct strlist *str) +{ + int len; + struct strlist *sp; + + len = 0; + for (sp = str ; sp ; sp = sp->next) + len++; + return msort(str, len); +} + + +static struct strlist * +msort(struct strlist *list, int len) +{ + struct strlist *p, *q = NULL; + struct strlist **lpp; + int half; + int n; + + if (len <= 1) + return list; + half = len >> 1; + p = list; + for (n = half ; --n >= 0 ; ) { + q = p; + p = p->next; + } + q->next = NULL; /* terminate first half of list */ + q = msort(list, half); /* sort first half of list */ + p = msort(p, len - half); /* sort second half */ + lpp = &list; + for (;;) { +#ifdef CONFIG_LOCALE_SUPPORT + if (strcoll(p->text, q->text) < 0) +#else + if (strcmp(p->text, q->text) < 0) +#endif + { + *lpp = p; + lpp = &p->next; + if ((p = *lpp) == NULL) { + *lpp = q; + break; + } + } else { + *lpp = q; + lpp = &q->next; + if ((q = *lpp) == NULL) { + *lpp = p; + break; + } + } + } + return list; +} + + +/* + * Returns true if the pattern matches the string. + */ + +static int patmatch(char *pattern, const char *string) +{ + return pmatch(preglob(pattern, 0, 0), string); +} + + +/* + * Remove any CTLESC characters from a string. + */ + +static char * +_rmescapes(char *str, int flag) +{ + char *p, *q, *r; + static const char qchars[] = { CTLESC, CTLQUOTEMARK, 0 }; + unsigned inquotes; + int notescaped; + int globbing; + + p = strpbrk(str, qchars); + if (!p) { + return str; + } + q = p; + r = str; + if (flag & RMESCAPE_ALLOC) { + size_t len = p - str; + size_t fulllen = len + strlen(p) + 1; + + if (flag & RMESCAPE_GROW) { + r = makestrspace(fulllen, expdest); + } else if (flag & RMESCAPE_HEAP) { + r = ckmalloc(fulllen); + } else { + r = stalloc(fulllen); + } + q = r; + if (len > 0) { + q = mempcpy(q, str, len); + } + } + inquotes = (flag & RMESCAPE_QUOTED) ^ RMESCAPE_QUOTED; + globbing = flag & RMESCAPE_GLOB; + notescaped = globbing; + while (*p) { + if (*p == CTLQUOTEMARK) { + inquotes = ~inquotes; + p++; + notescaped = globbing; + continue; + } + if (*p == '\\') { + /* naked back slash */ + notescaped = 0; + goto copy; + } + if (*p == CTLESC) { + p++; + if (notescaped && inquotes && *p != '/') { + *q++ = '\\'; + } + } + notescaped = globbing; +copy: + *q++ = *p++; + } + *q = '\0'; + if (flag & RMESCAPE_GROW) { + expdest = r; + STADJUST(q - r + 1, expdest); + } + return r; +} + + +/* + * See if a pattern matches in a case statement. + */ + +int +casematch(union node *pattern, char *val) +{ + struct stackmark smark; + int result; + + setstackmark(&smark); + argbackq = pattern->narg.backquote; + STARTSTACKSTR(expdest); + ifslastp = NULL; + argstr(pattern->narg.text, EXP_TILDE | EXP_CASE); + STACKSTRNUL(expdest); + result = patmatch(stackblock(), val); + popstackmark(&smark); + return result; +} + +/* + * Our own itoa(). + */ + +static int +cvtnum(arith_t num) +{ + int len; + + expdest = makestrspace(32, expdest); +#ifdef CONFIG_ASH_MATH_SUPPORT_64 + len = fmtstr(expdest, 32, "%lld", (long long) num); +#else + len = fmtstr(expdest, 32, "%ld", num); +#endif + STADJUST(len, expdest); + return len; +} + +static void +varunset(const char *end, const char *var, const char *umsg, int varflags) +{ + const char *msg; + const char *tail; + + tail = nullstr; + msg = "parameter not set"; + if (umsg) { + if (*end == CTLENDVAR) { + if (varflags & VSNUL) + tail = " or null"; + } else + msg = umsg; + } + sh_error("%.*s: %s%s", end - var - 1, var, msg, tail); +} + + +/* input.c */ + +/* + * This implements the input routines used by the parser. + */ + +#define EOF_NLEFT -99 /* value of parsenleft when EOF pushed back */ + +static void pushfile(void); + +/* + * Read a character from the script, returning PEOF on end of file. + * Nul characters in the input are silently discarded. + */ + + +#define pgetc_as_macro() (--parsenleft >= 0? SC2INT(*parsenextc++) : preadbuffer()) + +#ifdef CONFIG_ASH_OPTIMIZE_FOR_SIZE +#define pgetc_macro() pgetc() +static int +pgetc(void) +{ + return pgetc_as_macro(); +} +#else +#define pgetc_macro() pgetc_as_macro() +static int +pgetc(void) +{ + return pgetc_macro(); +} +#endif + + +/* + * Same as pgetc(), but ignores PEOA. + */ +#ifdef CONFIG_ASH_ALIAS +static int pgetc2(void) +{ + int c; + + do { + c = pgetc_macro(); + } while (c == PEOA); + return c; +} +#else +static int pgetc2(void) +{ + return pgetc_macro(); +} +#endif + +/* + * Read a line from the script. + */ + +static char * pfgets(char *line, int len) +{ + char *p = line; + int nleft = len; + int c; + + while (--nleft > 0) { + c = pgetc2(); + if (c == PEOF) { + if (p == line) + return NULL; + break; + } + *p++ = c; + if (c == '\n') + break; + } + *p = '\0'; + return line; +} + + + +#ifdef CONFIG_FEATURE_COMMAND_EDITING +#ifdef CONFIG_ASH_EXPAND_PRMT +static char *cmdedit_prompt; +#else +static const char *cmdedit_prompt; +#endif +static void putprompt(const char *s) +{ +#ifdef CONFIG_ASH_EXPAND_PRMT + free(cmdedit_prompt); + cmdedit_prompt = xstrdup(s); +#else + cmdedit_prompt = s; +#endif +} +#else +static void putprompt(const char *s) +{ + out2str(s); +} +#endif + +static int preadfd(void) +{ + int nr; + char *buf = parsefile->buf; + parsenextc = buf; + +retry: +#ifdef CONFIG_FEATURE_COMMAND_EDITING + if (!iflag || parsefile->fd) + nr = safe_read(parsefile->fd, buf, BUFSIZ - 1); + else { +#ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION + cmdedit_path_lookup = pathval(); +#endif + nr = cmdedit_read_input((char *) cmdedit_prompt, buf); + if(nr == 0) { + /* Ctrl+C presend */ + if(trap[SIGINT]) { + buf[0] = '\n'; + buf[1] = 0; + raise(SIGINT); + return 1; + } + goto retry; + } + if(nr < 0 && errno == 0) { + /* Ctrl+D presend */ + nr = 0; + } + } +#else + nr = safe_read(parsefile->fd, buf, BUFSIZ - 1); +#endif + + if (nr < 0) { + if (parsefile->fd == 0 && errno == EWOULDBLOCK) { + int flags = fcntl(0, F_GETFL, 0); + if (flags >= 0 && flags & O_NONBLOCK) { + flags &=~ O_NONBLOCK; + if (fcntl(0, F_SETFL, flags) >= 0) { + out2str("sh: turning off NDELAY mode\n"); + goto retry; + } + } + } + } + return nr; +} + +/* + * Refill the input buffer and return the next input character: + * + * 1) If a string was pushed back on the input, pop it; + * 2) If an EOF was pushed back (parsenleft == EOF_NLEFT) or we are reading + * from a string so we can't refill the buffer, return EOF. + * 3) If the is more stuff in this buffer, use it else call read to fill it. + * 4) Process input up to the next newline, deleting nul characters. + */ + +int +preadbuffer(void) +{ + char *q; + int more; + char savec; + + while (parsefile->strpush) { +#ifdef CONFIG_ASH_ALIAS + if (parsenleft == -1 && parsefile->strpush->ap && + parsenextc[-1] != ' ' && parsenextc[-1] != '\t') { + return PEOA; + } +#endif + popstring(); + if (--parsenleft >= 0) + return SC2INT(*parsenextc++); + } + if (parsenleft == EOF_NLEFT || parsefile->buf == NULL) + return PEOF; + flushall(); + + more = parselleft; + if (more <= 0) { +again: + if ((more = preadfd()) <= 0) { + parselleft = parsenleft = EOF_NLEFT; + return PEOF; + } + } + + q = parsenextc; + + /* delete nul characters */ + for (;;) { + int c; + + more--; + c = *q; + + if (!c) + memmove(q, q + 1, more); + else { + q++; + if (c == '\n') { + parsenleft = q - parsenextc - 1; + break; + } + } + + if (more <= 0) { + parsenleft = q - parsenextc - 1; + if (parsenleft < 0) + goto again; + break; + } + } + parselleft = more; + + savec = *q; + *q = '\0'; + + if (vflag) { + out2str(parsenextc); + } + + *q = savec; + + return SC2INT(*parsenextc++); +} + +/* + * Undo the last call to pgetc. Only one character may be pushed back. + * PEOF may be pushed back. + */ + +void +pungetc(void) +{ + parsenleft++; + parsenextc--; +} + +/* + * Push a string back onto the input at this current parsefile level. + * We handle aliases this way. + */ +void +pushstring(char *s, void *ap) +{ + struct strpush *sp; + size_t len; + + len = strlen(s); + INTOFF; +/*dprintf("*** calling pushstring: %s, %d\n", s, len);*/ + if (parsefile->strpush) { + sp = ckmalloc(sizeof (struct strpush)); + sp->prev = parsefile->strpush; + parsefile->strpush = sp; + } else + sp = parsefile->strpush = &(parsefile->basestrpush); + sp->prevstring = parsenextc; + sp->prevnleft = parsenleft; +#ifdef CONFIG_ASH_ALIAS + sp->ap = (struct alias *)ap; + if (ap) { + ((struct alias *)ap)->flag |= ALIASINUSE; + sp->string = s; + } +#endif + parsenextc = s; + parsenleft = len; + INTON; +} + +void +popstring(void) +{ + struct strpush *sp = parsefile->strpush; + + INTOFF; +#ifdef CONFIG_ASH_ALIAS + if (sp->ap) { + if (parsenextc[-1] == ' ' || parsenextc[-1] == '\t') { + checkkwd |= CHKALIAS; + } + if (sp->string != sp->ap->val) { + ckfree(sp->string); + } + sp->ap->flag &= ~ALIASINUSE; + if (sp->ap->flag & ALIASDEAD) { + unalias(sp->ap->name); + } + } +#endif + parsenextc = sp->prevstring; + parsenleft = sp->prevnleft; +/*dprintf("*** calling popstring: restoring to '%s'\n", parsenextc);*/ + parsefile->strpush = sp->prev; + if (sp != &(parsefile->basestrpush)) + ckfree(sp); + INTON; +} + +/* + * Set the input to take input from a file. If push is set, push the + * old input onto the stack first. + */ + +static int +setinputfile(const char *fname, int flags) +{ + int fd; + int fd2; + + INTOFF; + if ((fd = open(fname, O_RDONLY)) < 0) { + if (flags & INPUT_NOFILE_OK) + goto out; + sh_error("Can't open %s", fname); + } + if (fd < 10) { + fd2 = copyfd(fd, 10); + close(fd); + if (fd2 < 0) + sh_error("Out of file descriptors"); + fd = fd2; + } + setinputfd(fd, flags & INPUT_PUSH_FILE); +out: + INTON; + return fd; +} + + +/* + * Like setinputfile, but takes an open file descriptor. Call this with + * interrupts off. + */ + +static void +setinputfd(int fd, int push) +{ + (void) fcntl(fd, F_SETFD, FD_CLOEXEC); + if (push) { + pushfile(); + parsefile->buf = 0; + } + parsefile->fd = fd; + if (parsefile->buf == NULL) + parsefile->buf = ckmalloc(IBUFSIZ); + parselleft = parsenleft = 0; + plinno = 1; +} + + +/* + * Like setinputfile, but takes input from a string. + */ + +static void +setinputstring(char *string) +{ + INTOFF; + pushfile(); + parsenextc = string; + parsenleft = strlen(string); + parsefile->buf = NULL; + plinno = 1; + INTON; +} + + +/* + * To handle the "." command, a stack of input files is used. Pushfile + * adds a new entry to the stack and popfile restores the previous level. + */ + +static void +pushfile(void) +{ + struct parsefile *pf; + + parsefile->nleft = parsenleft; + parsefile->lleft = parselleft; + parsefile->nextc = parsenextc; + parsefile->linno = plinno; + pf = (struct parsefile *)ckmalloc(sizeof (struct parsefile)); + pf->prev = parsefile; + pf->fd = -1; + pf->strpush = NULL; + pf->basestrpush.prev = NULL; + parsefile = pf; +} + + +static void +popfile(void) +{ + struct parsefile *pf = parsefile; + + INTOFF; + if (pf->fd >= 0) + close(pf->fd); + if (pf->buf) + ckfree(pf->buf); + while (pf->strpush) + popstring(); + parsefile = pf->prev; + ckfree(pf); + parsenleft = parsefile->nleft; + parselleft = parsefile->lleft; + parsenextc = parsefile->nextc; + plinno = parsefile->linno; + INTON; +} + + +/* + * Return to top level. + */ + +static void +popallfiles(void) +{ + while (parsefile != &basepf) + popfile(); +} + + +/* + * Close the file(s) that the shell is reading commands from. Called + * after a fork is done. + */ + +static void +closescript(void) +{ + popallfiles(); + if (parsefile->fd > 0) { + close(parsefile->fd); + parsefile->fd = 0; + } +} + +/* jobs.c */ + +/* mode flags for set_curjob */ +#define CUR_DELETE 2 +#define CUR_RUNNING 1 +#define CUR_STOPPED 0 + +/* mode flags for dowait */ +#define DOWAIT_NORMAL 0 +#define DOWAIT_BLOCK 1 + +/* array of jobs */ +static struct job *jobtab; +/* size of array */ +static unsigned njobs; +#if JOBS +/* pgrp of shell on invocation */ +static int initialpgrp; +static int ttyfd = -1; +#endif +/* current job */ +static struct job *curjob; +/* number of presumed living untracked jobs */ +static int jobless; + +static void set_curjob(struct job *, unsigned); +#if JOBS +static int restartjob(struct job *, int); +static void xtcsetpgrp(int, pid_t); +static char *commandtext(union node *); +static void cmdlist(union node *, int); +static void cmdtxt(union node *); +static void cmdputs(const char *); +static void showpipe(struct job *, FILE *); +#endif +static int sprint_status(char *, int, int); +static void freejob(struct job *); +static struct job *getjob(const char *, int); +static struct job *growjobtab(void); +static void forkchild(struct job *, union node *, int); +static void forkparent(struct job *, union node *, int, pid_t); +static int dowait(int, struct job *); +static int getstatus(struct job *); + +static void +set_curjob(struct job *jp, unsigned mode) +{ + struct job *jp1; + struct job **jpp, **curp; + + /* first remove from list */ + jpp = curp = &curjob; + do { + jp1 = *jpp; + if (jp1 == jp) + break; + jpp = &jp1->prev_job; + } while (1); + *jpp = jp1->prev_job; + + /* Then re-insert in correct position */ + jpp = curp; + switch (mode) { + default: +#if DEBUG + abort(); +#endif + case CUR_DELETE: + /* job being deleted */ + break; + case CUR_RUNNING: + /* newly created job or backgrounded job, + put after all stopped jobs. */ + do { + jp1 = *jpp; +#if JOBS + if (!jp1 || jp1->state != JOBSTOPPED) +#endif + break; + jpp = &jp1->prev_job; + } while (1); + /* FALLTHROUGH */ +#if JOBS + case CUR_STOPPED: +#endif + /* newly stopped job - becomes curjob */ + jp->prev_job = *jpp; + *jpp = jp; + break; + } +} + +#if JOBS +/* + * Turn job control on and off. + * + * Note: This code assumes that the third arg to ioctl is a character + * pointer, which is true on Berkeley systems but not System V. Since + * System V doesn't have job control yet, this isn't a problem now. + * + * Called with interrupts off. + */ + +void +setjobctl(int on) +{ + int fd; + int pgrp; + + if (on == jobctl || rootshell == 0) + return; + if (on) { + int ofd; + ofd = fd = open(_PATH_TTY, O_RDWR); + if (fd < 0) { + fd += 3; + while (!isatty(fd) && --fd >= 0) + ; + } + fd = fcntl(fd, F_DUPFD, 10); + close(ofd); + if (fd < 0) + goto out; + fcntl(fd, F_SETFD, FD_CLOEXEC); + do { /* while we are in the background */ + if ((pgrp = tcgetpgrp(fd)) < 0) { +out: + sh_warnx("can't access tty; job control turned off"); + mflag = on = 0; + goto close; + } + if (pgrp == getpgrp()) + break; + killpg(0, SIGTTIN); + } while (1); + initialpgrp = pgrp; + + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); + pgrp = rootpid; + setpgid(0, pgrp); + xtcsetpgrp(fd, pgrp); + } else { + /* turning job control off */ + fd = ttyfd; + pgrp = initialpgrp; + xtcsetpgrp(fd, pgrp); + setpgid(0, pgrp); + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); +close: + close(fd); + fd = -1; + } + ttyfd = fd; + jobctl = on; +} + +static int +killcmd(int argc, char **argv) +{ + int signo = -1; + int list = 0; + int i; + pid_t pid; + struct job *jp; + + if (argc <= 1) { +usage: + sh_error( +"Usage: kill [-s sigspec | -signum | -sigspec] [pid | job]... or\n" +"kill -l [exitstatus]" + ); + } + + if (**++argv == '-') { + signo = get_signum(*argv + 1); + if (signo < 0) { + int c; + + while ((c = nextopt("ls:")) != '\0') + switch (c) { + default: +#if DEBUG + abort(); +#endif + case 'l': + list = 1; + break; + case 's': + signo = get_signum(optionarg); + if (signo < 0) { + sh_error( + "invalid signal number or name: %s", + optionarg + ); + } + break; + } + argv = argptr; + } else + argv++; + } + + if (!list && signo < 0) + signo = SIGTERM; + + if ((signo < 0 || !*argv) ^ list) { + goto usage; + } + + if (list) { + const char *name; + + if (!*argv) { + for (i = 1; i < NSIG; i++) { + name = get_signame(i); + if (isdigit(*name)) + out1fmt(snlfmt, name); + } + return 0; + } + name = get_signame(signo); + if (isdigit(*name)) + out1fmt(snlfmt, name); + else + sh_error("invalid signal number or exit status: %s", *argptr); + return 0; + } + + i = 0; + do { + if (**argv == '%') { + jp = getjob(*argv, 0); + pid = -jp->ps[0].pid; + } else { + pid = **argv == '-' ? + -number(*argv + 1) : number(*argv); + } + if (kill(pid, signo) != 0) { + sh_warnx("(%d) - %m", pid); + i = 1; + } + } while (*++argv); + + return i; +} +#endif /* JOBS */ + +#if defined(JOBS) || DEBUG +static int +jobno(const struct job *jp) +{ + return jp - jobtab + 1; +} +#endif + +#if JOBS +static int +fgcmd(int argc, char **argv) +{ + struct job *jp; + FILE *out; + int mode; + int retval; + + mode = (**argv == 'f') ? FORK_FG : FORK_BG; + nextopt(nullstr); + argv = argptr; + out = stdout; + do { + jp = getjob(*argv, 1); + if (mode == FORK_BG) { + set_curjob(jp, CUR_RUNNING); + fprintf(out, "[%d] ", jobno(jp)); + } + outstr(jp->ps->cmd, out); + showpipe(jp, out); + retval = restartjob(jp, mode); + } while (*argv && *++argv); + return retval; +} + +static int bgcmd(int, char **) __attribute__((__alias__("fgcmd"))); + + +static int +restartjob(struct job *jp, int mode) +{ + struct procstat *ps; + int i; + int status; + pid_t pgid; + + INTOFF; + if (jp->state == JOBDONE) + goto out; + jp->state = JOBRUNNING; + pgid = jp->ps->pid; + if (mode == FORK_FG) + xtcsetpgrp(ttyfd, pgid); + killpg(pgid, SIGCONT); + ps = jp->ps; + i = jp->nprocs; + do { + if (WIFSTOPPED(ps->status)) { + ps->status = -1; + } + } while (ps++, --i); +out: + status = (mode == FORK_FG) ? waitforjob(jp) : 0; + INTON; + return status; +} +#endif + +static int +sprint_status(char *s, int status, int sigonly) +{ + int col; + int st; + + col = 0; + if (!WIFEXITED(status)) { +#if JOBS + if (WIFSTOPPED(status)) + st = WSTOPSIG(status); + else +#endif + st = WTERMSIG(status); + if (sigonly) { + if (st == SIGINT || st == SIGPIPE) + goto out; +#if JOBS + if (WIFSTOPPED(status)) + goto out; +#endif + } + st &= 0x7f; + col = fmtstr(s, 32, strsignal(st)); + if (WCOREDUMP(status)) { + col += fmtstr(s + col, 16, " (core dumped)"); + } + } else if (!sigonly) { + st = WEXITSTATUS(status); + if (st) + col = fmtstr(s, 16, "Done(%d)", st); + else + col = fmtstr(s, 16, "Done"); + } + +out: + return col; +} + +#if JOBS +static void +showjob(FILE *out, struct job *jp, int mode) +{ + struct procstat *ps; + struct procstat *psend; + int col; + int indent; + char s[80]; + + ps = jp->ps; + + if (mode & SHOW_PGID) { + /* just output process (group) id of pipeline */ + fprintf(out, "%d\n", ps->pid); + return; + } + + col = fmtstr(s, 16, "[%d] ", jobno(jp)); + indent = col; + + if (jp == curjob) + s[col - 2] = '+'; + else if (curjob && jp == curjob->prev_job) + s[col - 2] = '-'; + + if (mode & SHOW_PID) + col += fmtstr(s + col, 16, "%d ", ps->pid); + + psend = ps + jp->nprocs; + + if (jp->state == JOBRUNNING) { + scopy("Running", s + col); + col += strlen("Running"); + } else { + int status = psend[-1].status; +#if JOBS + if (jp->state == JOBSTOPPED) + status = jp->stopstatus; +#endif + col += sprint_status(s + col, status, 0); + } + + goto start; + + do { + /* for each process */ + col = fmtstr(s, 48, " |\n%*c%d ", indent, ' ', ps->pid) - 3; + +start: + fprintf(out, "%s%*c%s", + s, 33 - col >= 0 ? 33 - col : 0, ' ', ps->cmd + ); + if (!(mode & SHOW_PID)) { + showpipe(jp, out); + break; + } + if (++ps == psend) { + outcslow('\n', out); + break; + } + } while (1); + + jp->changed = 0; + + if (jp->state == JOBDONE) { + TRACE(("showjob: freeing job %d\n", jobno(jp))); + freejob(jp); + } +} + + +static int +jobscmd(int argc, char **argv) +{ + int mode, m; + FILE *out; + + mode = 0; + while ((m = nextopt("lp"))) + if (m == 'l') + mode = SHOW_PID; + else + mode = SHOW_PGID; + + out = stdout; + argv = argptr; + if (*argv) + do + showjob(out, getjob(*argv,0), mode); + while (*++argv); + else + showjobs(out, mode); + + return 0; +} + + +/* + * Print a list of jobs. If "change" is nonzero, only print jobs whose + * statuses have changed since the last call to showjobs. + */ + +static void +showjobs(FILE *out, int mode) +{ + struct job *jp; + + TRACE(("showjobs(%x) called\n", mode)); + + /* If not even one one job changed, there is nothing to do */ + while (dowait(DOWAIT_NORMAL, NULL) > 0) + continue; + + for (jp = curjob; jp; jp = jp->prev_job) { + if (!(mode & SHOW_CHANGED) || jp->changed) + showjob(out, jp, mode); + } +} +#endif /* JOBS */ + +/* + * Mark a job structure as unused. + */ + +static void +freejob(struct job *jp) +{ + struct procstat *ps; + int i; + + INTOFF; + for (i = jp->nprocs, ps = jp->ps ; --i >= 0 ; ps++) { + if (ps->cmd != nullstr) + ckfree(ps->cmd); + } + if (jp->ps != &jp->ps0) + ckfree(jp->ps); + jp->used = 0; + set_curjob(jp, CUR_DELETE); + INTON; +} + + +static int +waitcmd(int argc, char **argv) +{ + struct job *job; + int retval; + struct job *jp; + + EXSIGON(); + + nextopt(nullstr); + retval = 0; + + argv = argptr; + if (!*argv) { + /* wait for all jobs */ + for (;;) { + jp = curjob; + while (1) { + if (!jp) { + /* no running procs */ + goto out; + } + if (jp->state == JOBRUNNING) + break; + jp->waited = 1; + jp = jp->prev_job; + } + dowait(DOWAIT_BLOCK, 0); + } + } + + retval = 127; + do { + if (**argv != '%') { + pid_t pid = number(*argv); + job = curjob; + goto start; + do { + if (job->ps[job->nprocs - 1].pid == pid) + break; + job = job->prev_job; +start: + if (!job) + goto repeat; + } while (1); + } else + job = getjob(*argv, 0); + /* loop until process terminated or stopped */ + while (job->state == JOBRUNNING) + dowait(DOWAIT_BLOCK, 0); + job->waited = 1; + retval = getstatus(job); +repeat: + ; + } while (*++argv); + +out: + return retval; +} + + +/* + * Convert a job name to a job structure. + */ + +static struct job * +getjob(const char *name, int getctl) +{ + struct job *jp; + struct job *found; + const char *err_msg = "No such job: %s"; + unsigned num; + int c; + const char *p; + char *(*match)(const char *, const char *); + + jp = curjob; + p = name; + if (!p) + goto currentjob; + + if (*p != '%') + goto err; + + c = *++p; + if (!c) + goto currentjob; + + if (!p[1]) { + if (c == '+' || c == '%') { +currentjob: + err_msg = "No current job"; + goto check; + } else if (c == '-') { + if (jp) + jp = jp->prev_job; + err_msg = "No previous job"; +check: + if (!jp) + goto err; + goto gotit; + } + } + + if (is_number(p)) { + num = atoi(p); + if (num < njobs) { + jp = jobtab + num - 1; + if (jp->used) + goto gotit; + goto err; + } + } + + match = prefix; + if (*p == '?') { + match = strstr; + p++; + } + + found = 0; + while (1) { + if (!jp) + goto err; + if (match(jp->ps[0].cmd, p)) { + if (found) + goto err; + found = jp; + err_msg = "%s: ambiguous"; + } + jp = jp->prev_job; + } + +gotit: +#if JOBS + err_msg = "job %s not created under job control"; + if (getctl && jp->jobctl == 0) + goto err; +#endif + return jp; +err: + sh_error(err_msg, name); +} + + +/* + * Return a new job structure. + * Called with interrupts off. + */ + +static struct job * +makejob(union node *node, int nprocs) +{ + int i; + struct job *jp; + + for (i = njobs, jp = jobtab ; ; jp++) { + if (--i < 0) { + jp = growjobtab(); + break; + } + if (jp->used == 0) + break; + if (jp->state != JOBDONE || !jp->waited) + continue; +#if JOBS + if (jobctl) + continue; +#endif + freejob(jp); + break; + } + memset(jp, 0, sizeof(*jp)); +#if JOBS + if (jobctl) + jp->jobctl = 1; +#endif + jp->prev_job = curjob; + curjob = jp; + jp->used = 1; + jp->ps = &jp->ps0; + if (nprocs > 1) { + jp->ps = ckmalloc(nprocs * sizeof (struct procstat)); + } + TRACE(("makejob(0x%lx, %d) returns %%%d\n", (long)node, nprocs, + jobno(jp))); + return jp; +} + +static struct job * +growjobtab(void) +{ + size_t len; + ptrdiff_t offset; + struct job *jp, *jq; + + len = njobs * sizeof(*jp); + jq = jobtab; + jp = ckrealloc(jq, len + 4 * sizeof(*jp)); + + offset = (char *)jp - (char *)jq; + if (offset) { + /* Relocate pointers */ + size_t l = len; + + jq = (struct job *)((char *)jq + l); + while (l) { + l -= sizeof(*jp); + jq--; +#define joff(p) ((struct job *)((char *)(p) + l)) +#define jmove(p) (p) = (void *)((char *)(p) + offset) + if (xlikely(joff(jp)->ps == &jq->ps0)) + jmove(joff(jp)->ps); + if (joff(jp)->prev_job) + jmove(joff(jp)->prev_job); + } + if (curjob) + jmove(curjob); +#undef joff +#undef jmove + } + + njobs += 4; + jobtab = jp; + jp = (struct job *)((char *)jp + len); + jq = jp + 3; + do { + jq->used = 0; + } while (--jq >= jp); + return jp; +} + + +/* + * Fork off a subshell. If we are doing job control, give the subshell its + * own process group. Jp is a job structure that the job is to be added to. + * N is the command that will be evaluated by the child. Both jp and n may + * be NULL. The mode parameter can be one of the following: + * FORK_FG - Fork off a foreground process. + * FORK_BG - Fork off a background process. + * FORK_NOJOB - Like FORK_FG, but don't give the process its own + * process group even if job control is on. + * + * When job control is turned off, background processes have their standard + * input redirected to /dev/null (except for the second and later processes + * in a pipeline). + * + * Called with interrupts off. + */ + +static void forkchild(struct job *jp, union node *n, int mode) +{ + int oldlvl; + + TRACE(("Child shell %d\n", getpid())); + oldlvl = shlvl; + shlvl++; + + closescript(); + clear_traps(); +#if JOBS + /* do job control only in root shell */ + jobctl = 0; + if (mode != FORK_NOJOB && jp->jobctl && !oldlvl) { + pid_t pgrp; + + if (jp->nprocs == 0) + pgrp = getpid(); + else + pgrp = jp->ps[0].pid; + /* This can fail because we are doing it in the parent also */ + (void)setpgid(0, pgrp); + if (mode == FORK_FG) + xtcsetpgrp(ttyfd, pgrp); + setsignal(SIGTSTP); + setsignal(SIGTTOU); + } else +#endif + if (mode == FORK_BG) { + ignoresig(SIGINT); + ignoresig(SIGQUIT); + if (jp->nprocs == 0) { + close(0); + if (open(bb_dev_null, O_RDONLY) != 0) + sh_error("Can't open %s", bb_dev_null); + } + } + if (!oldlvl && iflag) { + setsignal(SIGINT); + setsignal(SIGQUIT); + setsignal(SIGTERM); + } + for (jp = curjob; jp; jp = jp->prev_job) + freejob(jp); + jobless = 0; +} + +static void forkparent(struct job *jp, union node *n, int mode, pid_t pid) +{ + TRACE(("In parent shell: child = %d\n", pid)); + if (!jp) { + while (jobless && dowait(DOWAIT_NORMAL, 0) > 0); + jobless++; + return; + } +#if JOBS + if (mode != FORK_NOJOB && jp->jobctl) { + int pgrp; + + if (jp->nprocs == 0) + pgrp = pid; + else + pgrp = jp->ps[0].pid; + /* This can fail because we are doing it in the child also */ + (void)setpgid(pid, pgrp); + } +#endif + if (mode == FORK_BG) { + backgndpid = pid; /* set $! */ + set_curjob(jp, CUR_RUNNING); + } + if (jp) { + struct procstat *ps = &jp->ps[jp->nprocs++]; + ps->pid = pid; + ps->status = -1; + ps->cmd = nullstr; +#if JOBS + if (jobctl && n) + ps->cmd = commandtext(n); +#endif + } +} + +static int +forkshell(struct job *jp, union node *n, int mode) +{ + int pid; + + TRACE(("forkshell(%%%d, %p, %d) called\n", jobno(jp), n, mode)); + pid = fork(); + if (pid < 0) { + TRACE(("Fork failed, errno=%d", errno)); + if (jp) + freejob(jp); + sh_error("Cannot fork"); + } + if (pid == 0) + forkchild(jp, n, mode); + else + forkparent(jp, n, mode, pid); + return pid; +} + +/* + * Wait for job to finish. + * + * Under job control we have the problem that while a child process is + * running interrupts generated by the user are sent to the child but not + * to the shell. This means that an infinite loop started by an inter- + * active user may be hard to kill. With job control turned off, an + * interactive user may place an interactive program inside a loop. If + * the interactive program catches interrupts, the user doesn't want + * these interrupts to also abort the loop. The approach we take here + * is to have the shell ignore interrupt signals while waiting for a + * foreground process to terminate, and then send itself an interrupt + * signal if the child process was terminated by an interrupt signal. + * Unfortunately, some programs want to do a bit of cleanup and then + * exit on interrupt; unless these processes terminate themselves by + * sending a signal to themselves (instead of calling exit) they will + * confuse this approach. + * + * Called with interrupts off. + */ + +int +waitforjob(struct job *jp) +{ + int st; + + TRACE(("waitforjob(%%%d) called\n", jobno(jp))); + while (jp->state == JOBRUNNING) { + dowait(DOWAIT_BLOCK, jp); + } + st = getstatus(jp); +#if JOBS + if (jp->jobctl) { + xtcsetpgrp(ttyfd, rootpid); + /* + * This is truly gross. + * If we're doing job control, then we did a TIOCSPGRP which + * caused us (the shell) to no longer be in the controlling + * session -- so we wouldn't have seen any ^C/SIGINT. So, we + * intuit from the subprocess exit status whether a SIGINT + * occurred, and if so interrupt ourselves. Yuck. - mycroft + */ + if (jp->sigint) + raise(SIGINT); + } + if (jp->state == JOBDONE) +#endif + freejob(jp); + return st; +} + + +/* + * Do a wait system call. If job control is compiled in, we accept + * stopped processes. If block is zero, we return a value of zero + * rather than blocking. + * + * System V doesn't have a non-blocking wait system call. It does + * have a SIGCLD signal that is sent to a process when one of it's + * children dies. The obvious way to use SIGCLD would be to install + * a handler for SIGCLD which simply bumped a counter when a SIGCLD + * was received, and have waitproc bump another counter when it got + * the status of a process. Waitproc would then know that a wait + * system call would not block if the two counters were different. + * This approach doesn't work because if a process has children that + * have not been waited for, System V will send it a SIGCLD when it + * installs a signal handler for SIGCLD. What this means is that when + * a child exits, the shell will be sent SIGCLD signals continuously + * until is runs out of stack space, unless it does a wait call before + * restoring the signal handler. The code below takes advantage of + * this (mis)feature by installing a signal handler for SIGCLD and + * then checking to see whether it was called. If there are any + * children to be waited for, it will be. + * + * If neither SYSV nor BSD is defined, we don't implement nonblocking + * waits at all. In this case, the user will not be informed when + * a background process until the next time she runs a real program + * (as opposed to running a builtin command or just typing return), + * and the jobs command may give out of date information. + */ + +static int waitproc(int block, int *status) +{ + int flags = 0; + +#if JOBS + if (jobctl) + flags |= WUNTRACED; +#endif + if (block == 0) + flags |= WNOHANG; + return wait3(status, flags, (struct rusage *)NULL); +} + +/* + * Wait for a process to terminate. + */ + +static int +dowait(int block, struct job *job) +{ + int pid; + int status; + struct job *jp; + struct job *thisjob; + int state; + + TRACE(("dowait(%d) called\n", block)); + pid = waitproc(block, &status); + TRACE(("wait returns pid %d, status=%d\n", pid, status)); + if (pid <= 0) + return pid; + INTOFF; + thisjob = NULL; + for (jp = curjob; jp; jp = jp->prev_job) { + struct procstat *sp; + struct procstat *spend; + if (jp->state == JOBDONE) + continue; + state = JOBDONE; + spend = jp->ps + jp->nprocs; + sp = jp->ps; + do { + if (sp->pid == pid) { + TRACE(("Job %d: changing status of proc %d from 0x%x to 0x%x\n", jobno(jp), pid, sp->status, status)); + sp->status = status; + thisjob = jp; + } + if (sp->status == -1) + state = JOBRUNNING; +#if JOBS + if (state == JOBRUNNING) + continue; + if (WIFSTOPPED(sp->status)) { + jp->stopstatus = sp->status; + state = JOBSTOPPED; + } +#endif + } while (++sp < spend); + if (thisjob) + goto gotjob; + } +#if JOBS + if (!WIFSTOPPED(status)) +#endif + + jobless--; + goto out; + +gotjob: + if (state != JOBRUNNING) { + thisjob->changed = 1; + + if (thisjob->state != state) { + TRACE(("Job %d: changing state from %d to %d\n", jobno(thisjob), thisjob->state, state)); + thisjob->state = state; +#if JOBS + if (state == JOBSTOPPED) { + set_curjob(thisjob, CUR_STOPPED); + } +#endif + } + } + +out: + INTON; + + if (thisjob && thisjob == job) { + char s[48 + 1]; + int len; + + len = sprint_status(s, status, 1); + if (len) { + s[len] = '\n'; + s[len + 1] = 0; + out2str(s); + } + } + return pid; +} + + +/* + * return 1 if there are stopped jobs, otherwise 0 + */ + +int +stoppedjobs(void) +{ + struct job *jp; + int retval; + + retval = 0; + if (job_warning) + goto out; + jp = curjob; + if (jp && jp->state == JOBSTOPPED) { + out2str("You have stopped jobs.\n"); + job_warning = 2; + retval++; + } + +out: + return retval; +} + +/* + * Return a string identifying a command (to be printed by the + * jobs command). + */ + +#if JOBS +static char *cmdnextc; + +static char * +commandtext(union node *n) +{ + char *name; + + STARTSTACKSTR(cmdnextc); + cmdtxt(n); + name = stackblock(); + TRACE(("commandtext: name %p, end %p\n\t\"%s\"\n", + name, cmdnextc, cmdnextc)); + return savestr(name); +} + +static void +cmdtxt(union node *n) +{ + union node *np; + struct nodelist *lp; + const char *p; + char s[2]; + + if (!n) + return; + switch (n->type) { + default: +#if DEBUG + abort(); +#endif + case NPIPE: + lp = n->npipe.cmdlist; + for (;;) { + cmdtxt(lp->n); + lp = lp->next; + if (!lp) + break; + cmdputs(" | "); + } + break; + case NSEMI: + p = "; "; + goto binop; + case NAND: + p = " && "; + goto binop; + case NOR: + p = " || "; +binop: + cmdtxt(n->nbinary.ch1); + cmdputs(p); + n = n->nbinary.ch2; + goto donode; + case NREDIR: + case NBACKGND: + n = n->nredir.n; + goto donode; + case NNOT: + cmdputs("!"); + n = n->nnot.com; +donode: + cmdtxt(n); + break; + case NIF: + cmdputs("if "); + cmdtxt(n->nif.test); + cmdputs("; then "); + n = n->nif.ifpart; + if (n->nif.elsepart) { + cmdtxt(n); + cmdputs("; else "); + n = n->nif.elsepart; + } + p = "; fi"; + goto dotail; + case NSUBSHELL: + cmdputs("("); + n = n->nredir.n; + p = ")"; + goto dotail; + case NWHILE: + p = "while "; + goto until; + case NUNTIL: + p = "until "; +until: + cmdputs(p); + cmdtxt(n->nbinary.ch1); + n = n->nbinary.ch2; + p = "; done"; +dodo: + cmdputs("; do "); +dotail: + cmdtxt(n); + goto dotail2; + case NFOR: + cmdputs("for "); + cmdputs(n->nfor.var); + cmdputs(" in "); + cmdlist(n->nfor.args, 1); + n = n->nfor.body; + p = "; done"; + goto dodo; + case NDEFUN: + cmdputs(n->narg.text); + p = "() { ... }"; + goto dotail2; + case NCMD: + cmdlist(n->ncmd.args, 1); + cmdlist(n->ncmd.redirect, 0); + break; + case NARG: + p = n->narg.text; +dotail2: + cmdputs(p); + break; + case NHERE: + case NXHERE: + p = "<<..."; + goto dotail2; + case NCASE: + cmdputs("case "); + cmdputs(n->ncase.expr->narg.text); + cmdputs(" in "); + for (np = n->ncase.cases; np; np = np->nclist.next) { + cmdtxt(np->nclist.pattern); + cmdputs(") "); + cmdtxt(np->nclist.body); + cmdputs(";; "); + } + p = "esac"; + goto dotail2; + case NTO: + p = ">"; + goto redir; + case NCLOBBER: + p = ">|"; + goto redir; + case NAPPEND: + p = ">>"; + goto redir; + case NTOFD: + p = ">&"; + goto redir; + case NFROM: + p = "<"; + goto redir; + case NFROMFD: + p = "<&"; + goto redir; + case NFROMTO: + p = "<>"; +redir: + s[0] = n->nfile.fd + '0'; + s[1] = '\0'; + cmdputs(s); + cmdputs(p); + if (n->type == NTOFD || n->type == NFROMFD) { + s[0] = n->ndup.dupfd + '0'; + p = s; + goto dotail2; + } else { + n = n->nfile.fname; + goto donode; + } + } +} + +static void +cmdlist(union node *np, int sep) +{ + for (; np; np = np->narg.next) { + if (!sep) + cmdputs(spcstr); + cmdtxt(np); + if (sep && np->narg.next) + cmdputs(spcstr); + } +} + +static void +cmdputs(const char *s) +{ + const char *p, *str; + char c, cc[2] = " "; + char *nextc; + int subtype = 0; + int quoted = 0; + static const char vstype[VSTYPE + 1][4] = { + "", "}", "-", "+", "?", "=", + "%", "%%", "#", "##" + }; + nextc = makestrspace((strlen(s) + 1) * 8, cmdnextc); + p = s; + while ((c = *p++) != 0) { + str = 0; + switch (c) { + case CTLESC: + c = *p++; + break; + case CTLVAR: + subtype = *p++; + if ((subtype & VSTYPE) == VSLENGTH) + str = "${#"; + else + str = "${"; + if (!(subtype & VSQUOTE) != !(quoted & 1)) { + quoted ^= 1; + c = '"'; + } else + goto dostr; + break; + case CTLENDVAR: + str = "\"}" + !(quoted & 1); + quoted >>= 1; + subtype = 0; + goto dostr; + case CTLBACKQ: + str = "$(...)"; + goto dostr; + case CTLBACKQ+CTLQUOTE: + str = "\"$(...)\""; + goto dostr; +#ifdef CONFIG_ASH_MATH_SUPPORT + case CTLARI: + str = "$(("; + goto dostr; + case CTLENDARI: + str = "))"; + goto dostr; +#endif + case CTLQUOTEMARK: + quoted ^= 1; + c = '"'; + break; + case '=': + if (subtype == 0) + break; + if ((subtype & VSTYPE) != VSNORMAL) + quoted <<= 1; + str = vstype[subtype & VSTYPE]; + if (subtype & VSNUL) + c = ':'; + else + goto checkstr; + break; + case '\'': + case '\\': + case '"': + case '$': + /* These can only happen inside quotes */ + cc[0] = c; + str = cc; + c = '\\'; + break; + default: + break; + } + USTPUTC(c, nextc); +checkstr: + if (!str) + continue; +dostr: + while ((c = *str++)) { + USTPUTC(c, nextc); + } + } + if (quoted & 1) { + USTPUTC('"', nextc); + } + *nextc = 0; + cmdnextc = nextc; +} + + +static void +showpipe(struct job *jp, FILE *out) +{ + struct procstat *sp; + struct procstat *spend; + + spend = jp->ps + jp->nprocs; + for (sp = jp->ps + 1; sp < spend; sp++) + fprintf(out, " | %s", sp->cmd); + outcslow('\n', out); + flushall(); +} + +static void +xtcsetpgrp(int fd, pid_t pgrp) +{ + if (tcsetpgrp(fd, pgrp)) + sh_error("Cannot set tty process group (%m)"); +} +#endif /* JOBS */ + +static int +getstatus(struct job *job) { + int status; + int retval; + + status = job->ps[job->nprocs - 1].status; + retval = WEXITSTATUS(status); + if (!WIFEXITED(status)) { +#if JOBS + retval = WSTOPSIG(status); + if (!WIFSTOPPED(status)) +#endif + { + /* XXX: limits number of signals */ + retval = WTERMSIG(status); +#if JOBS + if (retval == SIGINT) + job->sigint = 1; +#endif + } + retval += 128; + } + TRACE(("getstatus: job %d, nproc %d, status %x, retval %x\n", + jobno(job), job->nprocs, status, retval)); + return retval; +} + +#ifdef CONFIG_ASH_MAIL +/* mail.c */ + +/* + * Routines to check for mail. (Perhaps make part of main.c?) + */ + +#define MAXMBOXES 10 + +/* times of mailboxes */ +static time_t mailtime[MAXMBOXES]; +/* Set if MAIL or MAILPATH is changed. */ +static int mail_var_path_changed; + + + +/* + * Print appropriate message(s) if mail has arrived. + * If mail_var_path_changed is set, + * then the value of MAIL has mail_var_path_changed, + * so we just update the values. + */ + +static void +chkmail(void) +{ + const char *mpath; + char *p; + char *q; + time_t *mtp; + struct stackmark smark; + struct stat statb; + + setstackmark(&smark); + mpath = mpathset() ? mpathval() : mailval(); + for (mtp = mailtime; mtp < mailtime + MAXMBOXES; mtp++) { + p = padvance(&mpath, nullstr); + if (p == NULL) + break; + if (*p == '\0') + continue; + for (q = p ; *q ; q++); +#if DEBUG + if (q[-1] != '/') + abort(); +#endif + q[-1] = '\0'; /* delete trailing '/' */ + if (stat(p, &statb) < 0) { + *mtp = 0; + continue; + } + if (!mail_var_path_changed && statb.st_mtime != *mtp) { + fprintf( + stderr, snlfmt, + pathopt ? pathopt : "you have mail" + ); + } + *mtp = statb.st_mtime; + } + mail_var_path_changed = 0; + popstackmark(&smark); +} + + +static void +changemail(const char *val) +{ + mail_var_path_changed++; +} + +#endif /* CONFIG_ASH_MAIL */ + +/* main.c */ + + +#if PROFILE +static short profile_buf[16384]; +extern int etext(); +#endif + +static int isloginsh; + +static void read_profile(const char *); + +/* + * Main routine. We initialize things, parse the arguments, execute + * profiles if we're a login shell, and then call cmdloop to execute + * commands. The setjmp call sets up the location to jump to when an + * exception occurs. When an exception occurs the variable "state" + * is used to figure out how far we had gotten. + */ + +int +ash_main(int argc, char **argv) +{ + char *shinit; + volatile int state; + struct jmploc jmploc; + struct stackmark smark; + +#ifdef __GLIBC__ + dash_errno = __errno_location(); +#endif + +#if PROFILE + monitor(4, etext, profile_buf, sizeof profile_buf, 50); +#endif + state = 0; + if (setjmp(jmploc.loc)) { + int e; + int s; + + reset(); + + e = exception; + if (e == EXERROR) + exitstatus = 2; + s = state; + if (e == EXEXIT || s == 0 || iflag == 0 || shlvl) + exitshell(); + + if (e == EXINT) { + outcslow('\n', stderr); + } + popstackmark(&smark); + FORCEINTON; /* enable interrupts */ + if (s == 1) + goto state1; + else if (s == 2) + goto state2; + else if (s == 3) + goto state3; + else + goto state4; + } + handler = &jmploc; +#if DEBUG + opentrace(); + trputs("Shell args: "); trargs(argv); +#endif + rootpid = getpid(); + +#ifdef CONFIG_ASH_RANDOM_SUPPORT + rseed = rootpid + ((time_t)time((time_t *)0)); +#endif + init(); + setstackmark(&smark); + procargs(argc, argv); +#ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY + if ( iflag ) { + const char *hp = lookupvar("HISTFILE"); + + if(hp == NULL ) { + hp = lookupvar("HOME"); + if(hp != NULL) { + char *defhp = concat_path_file(hp, ".ash_history"); + setvar("HISTFILE", defhp, 0); + free(defhp); + } + } + } +#endif + if (argv[0] && argv[0][0] == '-') + isloginsh = 1; + if (isloginsh) { + state = 1; + read_profile("/etc/profile"); +state1: + state = 2; + read_profile(".profile"); + } +state2: + state = 3; + if ( +#ifndef linux + getuid() == geteuid() && getgid() == getegid() && +#endif + iflag + ) { + if ((shinit = lookupvar("ENV")) != NULL && *shinit != '\0') { + read_profile(shinit); + } + } +state3: + state = 4; + if (minusc) + evalstring(minusc, 0); + + if (sflag || minusc == NULL) { +#ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY + if ( iflag ) { + const char *hp = lookupvar("HISTFILE"); + + if(hp != NULL ) + load_history ( hp ); + } +#endif +state4: /* XXX ??? - why isn't this before the "if" statement */ + cmdloop(1); + } +#if PROFILE + monitor(0); +#endif +#ifdef GPROF + { + extern void _mcleanup(void); + _mcleanup(); + } +#endif + exitshell(); + /* NOTREACHED */ +} + + +/* + * Read and execute commands. "Top" is nonzero for the top level command + * loop; it turns on prompting if the shell is interactive. + */ + +static int +cmdloop(int top) +{ + union node *n; + struct stackmark smark; + int inter; + int numeof = 0; + + TRACE(("cmdloop(%d) called\n", top)); + for (;;) { + int skip; + + setstackmark(&smark); +#if JOBS + if (jobctl) + showjobs(stderr, SHOW_CHANGED); +#endif + inter = 0; + if (iflag && top) { + inter++; +#ifdef CONFIG_ASH_MAIL + chkmail(); +#endif + } + n = parsecmd(inter); + /* showtree(n); DEBUG */ + if (n == NEOF) { + if (!top || numeof >= 50) + break; + if (!stoppedjobs()) { + if (!Iflag) + break; + out2str("\nUse \"exit\" to leave shell.\n"); + } + numeof++; + } else if (nflag == 0) { + job_warning = (job_warning == 2) ? 1 : 0; + numeof = 0; + evaltree(n, 0); + } + popstackmark(&smark); + skip = evalskip; + + if (skip) { + evalskip = 0; + return skip & SKIPEVAL; + } + } + + return 0; +} + + +/* + * Read /etc/profile or .profile. Return on error. + */ + +static void +read_profile(const char *name) +{ + int skip; + + if (setinputfile(name, INPUT_PUSH_FILE | INPUT_NOFILE_OK) < 0) + return; + + skip = cmdloop(0); + popfile(); + + if (skip) + exitshell(); +} + + +/* + * Read a file containing shell functions. + */ + +static void +readcmdfile(char *name) +{ + setinputfile(name, INPUT_PUSH_FILE); + cmdloop(0); + popfile(); +} + + +/* + * Take commands from a file. To be compatible we should do a path + * search for the file, which is necessary to find sub-commands. + */ + +static char * find_dot_file(char *name) +{ + char *fullname; + const char *path = pathval(); + struct stat statb; + + /* don't try this for absolute or relative paths */ + if (strchr(name, '/')) + return name; + + while ((fullname = padvance(&path, name)) != NULL) { + if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) { + /* + * Don't bother freeing here, since it will + * be freed by the caller. + */ + return fullname; + } + stunalloc(fullname); + } + + /* not found in the PATH */ + sh_error(not_found_msg, name); + /* NOTREACHED */ +} + +static int dotcmd(int argc, char **argv) +{ + struct strlist *sp; + volatile struct shparam saveparam; + int status = 0; + + for (sp = cmdenviron; sp; sp = sp->next) + setvareq(xstrdup(sp->text), VSTRFIXED | VTEXTFIXED); + + if (argc >= 2) { /* That's what SVR2 does */ + char *fullname; + + fullname = find_dot_file(argv[1]); + + if (argc > 2) { + saveparam = shellparam; + shellparam.malloc = 0; + shellparam.nparam = argc - 2; + shellparam.p = argv + 2; + }; + + setinputfile(fullname, INPUT_PUSH_FILE); + commandname = fullname; + cmdloop(0); + popfile(); + + if (argc > 2) { + freeparam(&shellparam); + shellparam = saveparam; + }; + status = exitstatus; + } + return status; +} + + +static int +exitcmd(int argc, char **argv) +{ + if (stoppedjobs()) + return 0; + if (argc > 1) + exitstatus = number(argv[1]); + exraise(EXEXIT); + /* NOTREACHED */ +} + +#ifdef CONFIG_ASH_BUILTIN_ECHO +static int +echocmd(int argc, char **argv) +{ + return bb_echo(argc, argv); +} +#endif + +#ifdef CONFIG_ASH_BUILTIN_TEST +static int +testcmd(int argc, char **argv) +{ + return bb_test(argc, argv); +} +#endif + +/* memalloc.c */ + +/* + * Same for malloc, realloc, but returns an error when out of space. + */ + +static pointer +ckrealloc(pointer p, size_t nbytes) +{ + p = realloc(p, nbytes); + if (p == NULL) + sh_error(bb_msg_memory_exhausted); + return p; +} + +static pointer +ckmalloc(size_t nbytes) +{ + return ckrealloc(NULL, nbytes); +} + +/* + * Make a copy of a string in safe storage. + */ + +static char * +savestr(const char *s) +{ + char *p = strdup(s); + if (!p) + sh_error(bb_msg_memory_exhausted); + return p; +} + + +/* + * Parse trees for commands are allocated in lifo order, so we use a stack + * to make this more efficient, and also to avoid all sorts of exception + * handling code to handle interrupts in the middle of a parse. + * + * The size 504 was chosen because the Ultrix malloc handles that size + * well. + */ + + +static pointer +stalloc(size_t nbytes) +{ + char *p; + size_t aligned; + + aligned = SHELL_ALIGN(nbytes); + if (aligned > stacknleft) { + size_t len; + size_t blocksize; + struct stack_block *sp; + + blocksize = aligned; + if (blocksize < MINSIZE) + blocksize = MINSIZE; + len = sizeof(struct stack_block) - MINSIZE + blocksize; + if (len < blocksize) + sh_error(bb_msg_memory_exhausted); + INTOFF; + sp = ckmalloc(len); + sp->prev = stackp; + stacknxt = sp->space; + stacknleft = blocksize; + sstrend = stacknxt + blocksize; + stackp = sp; + INTON; + } + p = stacknxt; + stacknxt += aligned; + stacknleft -= aligned; + return p; +} + + +void +stunalloc(pointer p) +{ +#if DEBUG + if (!p || (stacknxt < (char *)p) || ((char *)p < stackp->space)) { + write(2, "stunalloc\n", 10); + abort(); + } +#endif + stacknleft += stacknxt - (char *)p; + stacknxt = p; +} + + +void +setstackmark(struct stackmark *mark) +{ + mark->stackp = stackp; + mark->stacknxt = stacknxt; + mark->stacknleft = stacknleft; + mark->marknext = markp; + markp = mark; +} + + +void +popstackmark(struct stackmark *mark) +{ + struct stack_block *sp; + + INTOFF; + markp = mark->marknext; + while (stackp != mark->stackp) { + sp = stackp; + stackp = sp->prev; + ckfree(sp); + } + stacknxt = mark->stacknxt; + stacknleft = mark->stacknleft; + sstrend = mark->stacknxt + mark->stacknleft; + INTON; +} + + +/* + * When the parser reads in a string, it wants to stick the string on the + * stack and only adjust the stack pointer when it knows how big the + * string is. Stackblock (defined in stack.h) returns a pointer to a block + * of space on top of the stack and stackblocklen returns the length of + * this block. Growstackblock will grow this space by at least one byte, + * possibly moving it (like realloc). Grabstackblock actually allocates the + * part of the block that has been used. + */ + +void +growstackblock(void) +{ + size_t newlen; + + newlen = stacknleft * 2; + if (newlen < stacknleft) + sh_error(bb_msg_memory_exhausted); + if (newlen < 128) + newlen += 128; + + if (stacknxt == stackp->space && stackp != &stackbase) { + struct stack_block *oldstackp; + struct stackmark *xmark; + struct stack_block *sp; + struct stack_block *prevstackp; + size_t grosslen; + + INTOFF; + oldstackp = stackp; + sp = stackp; + prevstackp = sp->prev; + grosslen = newlen + sizeof(struct stack_block) - MINSIZE; + sp = ckrealloc((pointer)sp, grosslen); + sp->prev = prevstackp; + stackp = sp; + stacknxt = sp->space; + stacknleft = newlen; + sstrend = sp->space + newlen; + + /* + * Stack marks pointing to the start of the old block + * must be relocated to point to the new block + */ + xmark = markp; + while (xmark != NULL && xmark->stackp == oldstackp) { + xmark->stackp = stackp; + xmark->stacknxt = stacknxt; + xmark->stacknleft = stacknleft; + xmark = xmark->marknext; + } + INTON; + } else { + char *oldspace = stacknxt; + int oldlen = stacknleft; + char *p = stalloc(newlen); + + /* free the space we just allocated */ + stacknxt = memcpy(p, oldspace, oldlen); + stacknleft += newlen; + } +} + +static void grabstackblock(size_t len) +{ + len = SHELL_ALIGN(len); + stacknxt += len; + stacknleft -= len; +} + +/* + * The following routines are somewhat easier to use than the above. + * The user declares a variable of type STACKSTR, which may be declared + * to be a register. The macro STARTSTACKSTR initializes things. Then + * the user uses the macro STPUTC to add characters to the string. In + * effect, STPUTC(c, p) is the same as *p++ = c except that the stack is + * grown as necessary. When the user is done, she can just leave the + * string there and refer to it using stackblock(). Or she can allocate + * the space for it using grabstackstr(). If it is necessary to allow + * someone else to use the stack temporarily and then continue to grow + * the string, the user should use grabstack to allocate the space, and + * then call ungrabstr(p) to return to the previous mode of operation. + * + * USTPUTC is like STPUTC except that it doesn't check for overflow. + * CHECKSTACKSPACE can be called before USTPUTC to ensure that there + * is space for at least one character. + */ + +void * +growstackstr(void) +{ + size_t len = stackblocksize(); + if (herefd >= 0 && len >= 1024) { + full_write(herefd, stackblock(), len); + return stackblock(); + } + growstackblock(); + return stackblock() + len; +} + +/* + * Called from CHECKSTRSPACE. + */ + +char * +makestrspace(size_t newlen, char *p) +{ + size_t len = p - stacknxt; + size_t size = stackblocksize(); + + for (;;) { + size_t nleft; + + size = stackblocksize(); + nleft = size - len; + if (nleft >= newlen) + break; + growstackblock(); + } + return stackblock() + len; +} + +char * +stnputs(const char *s, size_t n, char *p) +{ + p = makestrspace(n, p); + p = mempcpy(p, s, n); + return p; +} + +char * +stputs(const char *s, char *p) +{ + return stnputs(s, strlen(s), p); +} + +/* mystring.c */ + +/* + * String functions. + * + * number(s) Convert a string of digits to an integer. + * is_number(s) Return true if s is a string of digits. + */ + +/* + * prefix -- see if pfx is a prefix of string. + */ + +char * +prefix(const char *string, const char *pfx) +{ + while (*pfx) { + if (*pfx++ != *string++) + return 0; + } + return (char *) string; +} + + +/* + * Convert a string of digits to an integer, printing an error message on + * failure. + */ + +int +number(const char *s) +{ + + if (! is_number(s)) + sh_error(illnum, s); + return atoi(s); +} + + +/* + * Check for a valid number. This should be elsewhere. + */ + +int +is_number(const char *p) +{ + do { + if (! is_digit(*p)) + return 0; + } while (*++p != '\0'); + return 1; +} + + +/* + * Produce a possibly single quoted string suitable as input to the shell. + * The return string is allocated on the stack. + */ + +char * +single_quote(const char *s) { + char *p; + + STARTSTACKSTR(p); + + do { + char *q; + size_t len; + + len = strchrnul(s, '\'') - s; + + q = p = makestrspace(len + 3, p); + + *q++ = '\''; + q = mempcpy(q, s, len); + *q++ = '\''; + s += len; + + STADJUST(q - p, p); + + len = strspn(s, "'"); + if (!len) + break; + + q = p = makestrspace(len + 3, p); + + *q++ = '"'; + q = mempcpy(q, s, len); + *q++ = '"'; + s += len; + + STADJUST(q - p, p); + } while (*s); + + USTPUTC(0, p); + + return stackblock(); +} + +/* + * Like strdup but works with the ash stack. + */ + +char * +sstrdup(const char *p) +{ + size_t len = strlen(p) + 1; + return memcpy(stalloc(len), p, len); +} + + +static void +calcsize(union node *n) +{ + if (n == NULL) + return; + funcblocksize += nodesize[n->type]; + switch (n->type) { + case NCMD: + calcsize(n->ncmd.redirect); + calcsize(n->ncmd.args); + calcsize(n->ncmd.assign); + break; + case NPIPE: + sizenodelist(n->npipe.cmdlist); + break; + case NREDIR: + case NBACKGND: + case NSUBSHELL: + calcsize(n->nredir.redirect); + calcsize(n->nredir.n); + break; + case NAND: + case NOR: + case NSEMI: + case NWHILE: + case NUNTIL: + calcsize(n->nbinary.ch2); + calcsize(n->nbinary.ch1); + break; + case NIF: + calcsize(n->nif.elsepart); + calcsize(n->nif.ifpart); + calcsize(n->nif.test); + break; + case NFOR: + funcstringsize += strlen(n->nfor.var) + 1; + calcsize(n->nfor.body); + calcsize(n->nfor.args); + break; + case NCASE: + calcsize(n->ncase.cases); + calcsize(n->ncase.expr); + break; + case NCLIST: + calcsize(n->nclist.body); + calcsize(n->nclist.pattern); + calcsize(n->nclist.next); + break; + case NDEFUN: + case NARG: + sizenodelist(n->narg.backquote); + funcstringsize += strlen(n->narg.text) + 1; + calcsize(n->narg.next); + break; + case NTO: + case NCLOBBER: + case NFROM: + case NFROMTO: + case NAPPEND: + calcsize(n->nfile.fname); + calcsize(n->nfile.next); + break; + case NTOFD: + case NFROMFD: + calcsize(n->ndup.vname); + calcsize(n->ndup.next); + break; + case NHERE: + case NXHERE: + calcsize(n->nhere.doc); + calcsize(n->nhere.next); + break; + case NNOT: + calcsize(n->nnot.com); + break; + }; +} + + +static void +sizenodelist(struct nodelist *lp) +{ + while (lp) { + funcblocksize += SHELL_ALIGN(sizeof(struct nodelist)); + calcsize(lp->n); + lp = lp->next; + } +} + + +static union node * +copynode(union node *n) +{ + union node *new; + + if (n == NULL) + return NULL; + new = funcblock; + funcblock = (char *) funcblock + nodesize[n->type]; + switch (n->type) { + case NCMD: + new->ncmd.redirect = copynode(n->ncmd.redirect); + new->ncmd.args = copynode(n->ncmd.args); + new->ncmd.assign = copynode(n->ncmd.assign); + break; + case NPIPE: + new->npipe.cmdlist = copynodelist(n->npipe.cmdlist); + new->npipe.backgnd = n->npipe.backgnd; + break; + case NREDIR: + case NBACKGND: + case NSUBSHELL: + new->nredir.redirect = copynode(n->nredir.redirect); + new->nredir.n = copynode(n->nredir.n); + break; + case NAND: + case NOR: + case NSEMI: + case NWHILE: + case NUNTIL: + new->nbinary.ch2 = copynode(n->nbinary.ch2); + new->nbinary.ch1 = copynode(n->nbinary.ch1); + break; + case NIF: + new->nif.elsepart = copynode(n->nif.elsepart); + new->nif.ifpart = copynode(n->nif.ifpart); + new->nif.test = copynode(n->nif.test); + break; + case NFOR: + new->nfor.var = nodesavestr(n->nfor.var); + new->nfor.body = copynode(n->nfor.body); + new->nfor.args = copynode(n->nfor.args); + break; + case NCASE: + new->ncase.cases = copynode(n->ncase.cases); + new->ncase.expr = copynode(n->ncase.expr); + break; + case NCLIST: + new->nclist.body = copynode(n->nclist.body); + new->nclist.pattern = copynode(n->nclist.pattern); + new->nclist.next = copynode(n->nclist.next); + break; + case NDEFUN: + case NARG: + new->narg.backquote = copynodelist(n->narg.backquote); + new->narg.text = nodesavestr(n->narg.text); + new->narg.next = copynode(n->narg.next); + break; + case NTO: + case NCLOBBER: + case NFROM: + case NFROMTO: + case NAPPEND: + new->nfile.fname = copynode(n->nfile.fname); + new->nfile.fd = n->nfile.fd; + new->nfile.next = copynode(n->nfile.next); + break; + case NTOFD: + case NFROMFD: + new->ndup.vname = copynode(n->ndup.vname); + new->ndup.dupfd = n->ndup.dupfd; + new->ndup.fd = n->ndup.fd; + new->ndup.next = copynode(n->ndup.next); + break; + case NHERE: + case NXHERE: + new->nhere.doc = copynode(n->nhere.doc); + new->nhere.fd = n->nhere.fd; + new->nhere.next = copynode(n->nhere.next); + break; + case NNOT: + new->nnot.com = copynode(n->nnot.com); + break; + }; + new->type = n->type; + return new; +} + + +static struct nodelist * +copynodelist(struct nodelist *lp) +{ + struct nodelist *start; + struct nodelist **lpp; + + lpp = &start; + while (lp) { + *lpp = funcblock; + funcblock = (char *) funcblock + + SHELL_ALIGN(sizeof(struct nodelist)); + (*lpp)->n = copynode(lp->n); + lp = lp->next; + lpp = &(*lpp)->next; + } + *lpp = NULL; + return start; +} + + +static char * +nodesavestr(char *s) +{ + char *rtn = funcstring; + + funcstring = stpcpy(funcstring, s) + 1; + return rtn; +} + + +/* + * Free a parse tree. + */ + +static void +freefunc(struct funcnode *f) +{ + if (f && --f->count < 0) + ckfree(f); +} + + +static void options(int); +static void setoption(int, int); + + +/* + * Process the shell command line arguments. + */ + +void +procargs(int argc, char **argv) +{ + int i; + const char *xminusc; + char **xargv; + + xargv = argv; + arg0 = xargv[0]; + if (argc > 0) + xargv++; + for (i = 0; i < NOPTS; i++) + optlist[i] = 2; + argptr = xargv; + options(1); + xargv = argptr; + xminusc = minusc; + if (*xargv == NULL) { + if (xminusc) + sh_error(bb_msg_requires_arg, "-c"); + sflag = 1; + } + if (iflag == 2 && sflag == 1 && isatty(0) && isatty(1)) + iflag = 1; + if (mflag == 2) + mflag = iflag; + for (i = 0; i < NOPTS; i++) + if (optlist[i] == 2) + optlist[i] = 0; +#if DEBUG == 2 + debug = 1; +#endif + /* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */ + if (xminusc) { + minusc = *xargv++; + if (*xargv) + goto setarg0; + } else if (!sflag) { + setinputfile(*xargv, 0); +setarg0: + arg0 = *xargv++; + commandname = arg0; + } + + shellparam.p = xargv; +#ifdef CONFIG_ASH_GETOPTS + shellparam.optind = 1; + shellparam.optoff = -1; +#endif + /* assert(shellparam.malloc == 0 && shellparam.nparam == 0); */ + while (*xargv) { + shellparam.nparam++; + xargv++; + } + optschanged(); +} + + +void +optschanged(void) +{ +#if DEBUG + opentrace(); +#endif + setinteractive(iflag); + setjobctl(mflag); + setvimode(viflag); +} + +static void minus_o(char *name, int val) +{ + int i; + + if (name == NULL) { + out1str("Current option settings\n"); + for (i = 0; i < NOPTS; i++) + out1fmt("%-16s%s\n", optnames(i), + optlist[i] ? "on" : "off"); + } else { + for (i = 0; i < NOPTS; i++) + if (equal(name, optnames(i))) { + optlist[i] = val; + return; + } + sh_error("Illegal option -o %s", name); + } +} + +/* + * Process shell options. The global variable argptr contains a pointer + * to the argument list; we advance it past the options. + */ + +static void +options(int cmdline) +{ + char *p; + int val; + int c; + + if (cmdline) + minusc = NULL; + while ((p = *argptr) != NULL) { + argptr++; + if ((c = *p++) == '-') { + val = 1; + if (p[0] == '\0' || (p[0] == '-' && p[1] == '\0')) { + if (!cmdline) { + /* "-" means turn off -x and -v */ + if (p[0] == '\0') + xflag = vflag = 0; + /* "--" means reset params */ + else if (*argptr == NULL) + setparam(argptr); + } + break; /* "-" or "--" terminates options */ + } + } else if (c == '+') { + val = 0; + } else { + argptr--; + break; + } + while ((c = *p++) != '\0') { + if (c == 'c' && cmdline) { + minusc = p; /* command is after shell args*/ + } else if (c == 'o') { + minus_o(*argptr, val); + if (*argptr) + argptr++; + } else if (cmdline && (c == '-')) { // long options + if (strcmp(p, "login") == 0) + isloginsh = 1; + break; + } else { + setoption(c, val); + } + } + } +} + + +static void +setoption(int flag, int val) +{ + int i; + + for (i = 0; i < NOPTS; i++) + if (optletters(i) == flag) { + optlist[i] = val; + return; + } + sh_error("Illegal option -%c", flag); + /* NOTREACHED */ +} + + + +/* + * Set the shell parameters. + */ + +void +setparam(char **argv) +{ + char **newparam; + char **ap; + int nparam; + + for (nparam = 0 ; argv[nparam] ; nparam++); + ap = newparam = ckmalloc((nparam + 1) * sizeof *ap); + while (*argv) { + *ap++ = savestr(*argv++); + } + *ap = NULL; + freeparam(&shellparam); + shellparam.malloc = 1; + shellparam.nparam = nparam; + shellparam.p = newparam; +#ifdef CONFIG_ASH_GETOPTS + shellparam.optind = 1; + shellparam.optoff = -1; +#endif +} + + +/* + * Free the list of positional parameters. + */ + +void +freeparam(volatile struct shparam *param) +{ + char **ap; + + if (param->malloc) { + for (ap = param->p ; *ap ; ap++) + ckfree(*ap); + ckfree(param->p); + } +} + + + +/* + * The shift builtin command. + */ + +int +shiftcmd(int argc, char **argv) +{ + int n; + char **ap1, **ap2; + + n = 1; + if (argc > 1) + n = number(argv[1]); + if (n > shellparam.nparam) + sh_error("can't shift that many"); + INTOFF; + shellparam.nparam -= n; + for (ap1 = shellparam.p ; --n >= 0 ; ap1++) { + if (shellparam.malloc) + ckfree(*ap1); + } + ap2 = shellparam.p; + while ((*ap2++ = *ap1++) != NULL); +#ifdef CONFIG_ASH_GETOPTS + shellparam.optind = 1; + shellparam.optoff = -1; +#endif + INTON; + return 0; +} + + + +/* + * The set command builtin. + */ + +int +setcmd(int argc, char **argv) +{ + if (argc == 1) + return showvars(nullstr, 0, VUNSET); + INTOFF; + options(0); + optschanged(); + if (*argptr != NULL) { + setparam(argptr); + } + INTON; + return 0; +} + + +#ifdef CONFIG_ASH_GETOPTS +static void +getoptsreset(const char *value) +{ + shellparam.optind = number(value); + shellparam.optoff = -1; +} +#endif + +#ifdef CONFIG_LOCALE_SUPPORT +static void change_lc_all(const char *value) +{ + if (value != 0 && *value != 0) + setlocale(LC_ALL, value); +} + +static void change_lc_ctype(const char *value) +{ + if (value != 0 && *value != 0) + setlocale(LC_CTYPE, value); +} + +#endif + +#ifdef CONFIG_ASH_RANDOM_SUPPORT +/* Roughly copied from bash.. */ +static void change_random(const char *value) +{ + if(value == NULL) { + /* "get", generate */ + char buf[16]; + + rseed = rseed * 1103515245 + 12345; + sprintf(buf, "%d", (unsigned int)((rseed & 32767))); + /* set without recursion */ + setvar(vrandom.text, buf, VNOFUNC); + vrandom.flags &= ~VNOFUNC; + } else { + /* set/reset */ + rseed = strtoul(value, (char **)NULL, 10); + } +} +#endif + + +#ifdef CONFIG_ASH_GETOPTS +static int +getopts(char *optstr, char *optvar, char **optfirst, int *param_optind, int *optoff) +{ + char *p, *q; + char c = '?'; + int done = 0; + int err = 0; + char s[12]; + char **optnext; + + if(*param_optind < 1) + return 1; + optnext = optfirst + *param_optind - 1; + + if (*param_optind <= 1 || *optoff < 0 || strlen(optnext[-1]) < *optoff) + p = NULL; + else + p = optnext[-1] + *optoff; + if (p == NULL || *p == '\0') { + /* Current word is done, advance */ + p = *optnext; + if (p == NULL || *p != '-' || *++p == '\0') { +atend: + p = NULL; + done = 1; + goto out; + } + optnext++; + if (p[0] == '-' && p[1] == '\0') /* check for "--" */ + goto atend; + } + + c = *p++; + for (q = optstr; *q != c; ) { + if (*q == '\0') { + if (optstr[0] == ':') { + s[0] = c; + s[1] = '\0'; + err |= setvarsafe("OPTARG", s, 0); + } else { + fprintf(stderr, "Illegal option -%c\n", c); + (void) unsetvar("OPTARG"); + } + c = '?'; + goto out; + } + if (*++q == ':') + q++; + } + + if (*++q == ':') { + if (*p == '\0' && (p = *optnext) == NULL) { + if (optstr[0] == ':') { + s[0] = c; + s[1] = '\0'; + err |= setvarsafe("OPTARG", s, 0); + c = ':'; + } else { + fprintf(stderr, "No arg for -%c option\n", c); + (void) unsetvar("OPTARG"); + c = '?'; + } + goto out; + } + + if (p == *optnext) + optnext++; + err |= setvarsafe("OPTARG", p, 0); + p = NULL; + } else + err |= setvarsafe("OPTARG", nullstr, 0); + +out: + *optoff = p ? p - *(optnext - 1) : -1; + *param_optind = optnext - optfirst + 1; + fmtstr(s, sizeof(s), "%d", *param_optind); + err |= setvarsafe("OPTIND", s, VNOFUNC); + s[0] = c; + s[1] = '\0'; + err |= setvarsafe(optvar, s, 0); + if (err) { + *param_optind = 1; + *optoff = -1; + flushall(); + exraise(EXERROR); + } + return done; +} + +/* + * The getopts builtin. Shellparam.optnext points to the next argument + * to be processed. Shellparam.optptr points to the next character to + * be processed in the current argument. If shellparam.optnext is NULL, + * then it's the first time getopts has been called. + */ + +int +getoptscmd(int argc, char **argv) +{ + char **optbase; + + if (argc < 3) + sh_error("Usage: getopts optstring var [arg]"); + else if (argc == 3) { + optbase = shellparam.p; + if (shellparam.optind > shellparam.nparam + 1) { + shellparam.optind = 1; + shellparam.optoff = -1; + } + } + else { + optbase = &argv[3]; + if (shellparam.optind > argc - 2) { + shellparam.optind = 1; + shellparam.optoff = -1; + } + } + + return getopts(argv[1], argv[2], optbase, &shellparam.optind, + &shellparam.optoff); +} +#endif /* CONFIG_ASH_GETOPTS */ + +/* + * XXX - should get rid of. have all builtins use getopt(3). the + * library getopt must have the BSD extension static variable "optreset" + * otherwise it can't be used within the shell safely. + * + * Standard option processing (a la getopt) for builtin routines. The + * only argument that is passed to nextopt is the option string; the + * other arguments are unnecessary. It return the character, or '\0' on + * end of input. + */ + +static int +nextopt(const char *optstring) +{ + char *p; + const char *q; + char c; + + if ((p = optptr) == NULL || *p == '\0') { + p = *argptr; + if (p == NULL || *p != '-' || *++p == '\0') + return '\0'; + argptr++; + if (p[0] == '-' && p[1] == '\0') /* check for "--" */ + return '\0'; + } + c = *p++; + for (q = optstring ; *q != c ; ) { + if (*q == '\0') + sh_error("Illegal option -%c", c); + if (*++q == ':') + q++; + } + if (*++q == ':') { + if (*p == '\0' && (p = *argptr++) == NULL) + sh_error("No arg for -%c option", c); + optionarg = p; + p = NULL; + } + optptr = p; + return c; +} + + +/* output.c */ + +void +outstr(const char *p, FILE *file) +{ + INTOFF; + fputs(p, file); + INTON; +} + +void +flushall(void) +{ + INTOFF; + fflush(stdout); + fflush(stderr); + INTON; +} + +void +flusherr(void) +{ + INTOFF; + fflush(stderr); + INTON; +} + +static void +outcslow(int c, FILE *dest) +{ + INTOFF; + putc(c, dest); + fflush(dest); + INTON; +} + + +static int +out1fmt(const char *fmt, ...) +{ + va_list ap; + int r; + + INTOFF; + va_start(ap, fmt); + r = vprintf(fmt, ap); + va_end(ap); + INTON; + return r; +} + + +int +fmtstr(char *outbuf, size_t length, const char *fmt, ...) +{ + va_list ap; + int ret; + + va_start(ap, fmt); + INTOFF; + ret = vsnprintf(outbuf, length, fmt, ap); + va_end(ap); + INTON; + return ret; +} + + + +/* parser.c */ + + +/* + * Shell command parser. + */ + +#define EOFMARKLEN 79 + + +struct heredoc { + struct heredoc *next; /* next here document in list */ + union node *here; /* redirection node */ + char *eofmark; /* string indicating end of input */ + int striptabs; /* if set, strip leading tabs */ +}; + + + +static struct heredoc *heredoclist; /* list of here documents to read */ + + +static union node *list(int); +static union node *andor(void); +static union node *pipeline(void); +static union node *command(void); +static union node *simplecmd(void); +static union node *makename(void); +static void parsefname(void); +static void parseheredoc(void); +static char peektoken(void); +static int readtoken(void); +static int xxreadtoken(void); +static int readtoken1(int firstc, int syntax, char *eofmark, int striptabs); +static int noexpand(char *); +static void synexpect(int) ATTRIBUTE_NORETURN; +static void synerror(const char *) ATTRIBUTE_NORETURN; +static void setprompt(int); + + + + +/* + * Read and parse a command. Returns NEOF on end of file. (NULL is a + * valid parse tree indicating a blank line.) + */ + +union node * +parsecmd(int interact) +{ + int t; + + tokpushback = 0; + doprompt = interact; + if (doprompt) + setprompt(doprompt); + needprompt = 0; + t = readtoken(); + if (t == TEOF) + return NEOF; + if (t == TNL) + return NULL; + tokpushback++; + return list(1); +} + + +static union node * +list(int nlflag) +{ + union node *n1, *n2, *n3; + int tok; + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (nlflag == 2 && peektoken()) + return NULL; + n1 = NULL; + for (;;) { + n2 = andor(); + tok = readtoken(); + if (tok == TBACKGND) { + if (n2->type == NPIPE) { + n2->npipe.backgnd = 1; + } else { + if (n2->type != NREDIR) { + n3 = stalloc(sizeof(struct nredir)); + n3->nredir.n = n2; + n3->nredir.redirect = NULL; + n2 = n3; + } + n2->type = NBACKGND; + } + } + if (n1 == NULL) { + n1 = n2; + } + else { + n3 = (union node *)stalloc(sizeof (struct nbinary)); + n3->type = NSEMI; + n3->nbinary.ch1 = n1; + n3->nbinary.ch2 = n2; + n1 = n3; + } + switch (tok) { + case TBACKGND: + case TSEMI: + tok = readtoken(); + /* fall through */ + case TNL: + if (tok == TNL) { + parseheredoc(); + if (nlflag == 1) + return n1; + } else { + tokpushback++; + } + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (peektoken()) + return n1; + break; + case TEOF: + if (heredoclist) + parseheredoc(); + else + pungetc(); /* push back EOF on input */ + return n1; + default: + if (nlflag == 1) + synexpect(-1); + tokpushback++; + return n1; + } + } +} + + + +static union node * +andor(void) +{ + union node *n1, *n2, *n3; + int t; + + n1 = pipeline(); + for (;;) { + if ((t = readtoken()) == TAND) { + t = NAND; + } else if (t == TOR) { + t = NOR; + } else { + tokpushback++; + return n1; + } + checkkwd = CHKNL | CHKKWD | CHKALIAS; + n2 = pipeline(); + n3 = (union node *)stalloc(sizeof (struct nbinary)); + n3->type = t; + n3->nbinary.ch1 = n1; + n3->nbinary.ch2 = n2; + n1 = n3; + } +} + + + +static union node * +pipeline(void) +{ + union node *n1, *n2, *pipenode; + struct nodelist *lp, *prev; + int negate; + + negate = 0; + TRACE(("pipeline: entered\n")); + if (readtoken() == TNOT) { + negate = !negate; + checkkwd = CHKKWD | CHKALIAS; + } else + tokpushback++; + n1 = command(); + if (readtoken() == TPIPE) { + pipenode = (union node *)stalloc(sizeof (struct npipe)); + pipenode->type = NPIPE; + pipenode->npipe.backgnd = 0; + lp = (struct nodelist *)stalloc(sizeof (struct nodelist)); + pipenode->npipe.cmdlist = lp; + lp->n = n1; + do { + prev = lp; + lp = (struct nodelist *)stalloc(sizeof (struct nodelist)); + checkkwd = CHKNL | CHKKWD | CHKALIAS; + lp->n = command(); + prev->next = lp; + } while (readtoken() == TPIPE); + lp->next = NULL; + n1 = pipenode; + } + tokpushback++; + if (negate) { + n2 = (union node *)stalloc(sizeof (struct nnot)); + n2->type = NNOT; + n2->nnot.com = n1; + return n2; + } else + return n1; +} + + + +static union node * +command(void) +{ + union node *n1, *n2; + union node *ap, **app; + union node *cp, **cpp; + union node *redir, **rpp; + union node **rpp2; + int t; + + redir = NULL; + rpp2 = &redir; + + switch (readtoken()) { + default: + synexpect(-1); + /* NOTREACHED */ + case TIF: + n1 = (union node *)stalloc(sizeof (struct nif)); + n1->type = NIF; + n1->nif.test = list(0); + if (readtoken() != TTHEN) + synexpect(TTHEN); + n1->nif.ifpart = list(0); + n2 = n1; + while (readtoken() == TELIF) { + n2->nif.elsepart = (union node *)stalloc(sizeof (struct nif)); + n2 = n2->nif.elsepart; + n2->type = NIF; + n2->nif.test = list(0); + if (readtoken() != TTHEN) + synexpect(TTHEN); + n2->nif.ifpart = list(0); + } + if (lasttoken == TELSE) + n2->nif.elsepart = list(0); + else { + n2->nif.elsepart = NULL; + tokpushback++; + } + t = TFI; + break; + case TWHILE: + case TUNTIL: { + int got; + n1 = (union node *)stalloc(sizeof (struct nbinary)); + n1->type = (lasttoken == TWHILE)? NWHILE : NUNTIL; + n1->nbinary.ch1 = list(0); + if ((got=readtoken()) != TDO) { +TRACE(("expecting DO got %s %s\n", tokname(got), got == TWORD ? wordtext : "")); + synexpect(TDO); + } + n1->nbinary.ch2 = list(0); + t = TDONE; + break; + } + case TFOR: + if (readtoken() != TWORD || quoteflag || ! goodname(wordtext)) + synerror("Bad for loop variable"); + n1 = (union node *)stalloc(sizeof (struct nfor)); + n1->type = NFOR; + n1->nfor.var = wordtext; + checkkwd = CHKKWD | CHKALIAS; + if (readtoken() == TIN) { + app = ≈ + while (readtoken() == TWORD) { + n2 = (union node *)stalloc(sizeof (struct narg)); + n2->type = NARG; + n2->narg.text = wordtext; + n2->narg.backquote = backquotelist; + *app = n2; + app = &n2->narg.next; + } + *app = NULL; + n1->nfor.args = ap; + if (lasttoken != TNL && lasttoken != TSEMI) + synexpect(-1); + } else { + n2 = (union node *)stalloc(sizeof (struct narg)); + n2->type = NARG; + n2->narg.text = (char *)dolatstr; + n2->narg.backquote = NULL; + n2->narg.next = NULL; + n1->nfor.args = n2; + /* + * Newline or semicolon here is optional (but note + * that the original Bourne shell only allowed NL). + */ + if (lasttoken != TNL && lasttoken != TSEMI) + tokpushback++; + } + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (readtoken() != TDO) + synexpect(TDO); + n1->nfor.body = list(0); + t = TDONE; + break; + case TCASE: + n1 = (union node *)stalloc(sizeof (struct ncase)); + n1->type = NCASE; + if (readtoken() != TWORD) + synexpect(TWORD); + n1->ncase.expr = n2 = (union node *)stalloc(sizeof (struct narg)); + n2->type = NARG; + n2->narg.text = wordtext; + n2->narg.backquote = backquotelist; + n2->narg.next = NULL; + do { + checkkwd = CHKKWD | CHKALIAS; + } while (readtoken() == TNL); + if (lasttoken != TIN) + synexpect(TIN); + cpp = &n1->ncase.cases; +next_case: + checkkwd = CHKNL | CHKKWD; + t = readtoken(); + while(t != TESAC) { + if (lasttoken == TLP) + readtoken(); + *cpp = cp = (union node *)stalloc(sizeof (struct nclist)); + cp->type = NCLIST; + app = &cp->nclist.pattern; + for (;;) { + *app = ap = (union node *)stalloc(sizeof (struct narg)); + ap->type = NARG; + ap->narg.text = wordtext; + ap->narg.backquote = backquotelist; + if (readtoken() != TPIPE) + break; + app = &ap->narg.next; + readtoken(); + } + ap->narg.next = NULL; + if (lasttoken != TRP) + synexpect(TRP); + cp->nclist.body = list(2); + + cpp = &cp->nclist.next; + + checkkwd = CHKNL | CHKKWD; + if ((t = readtoken()) != TESAC) { + if (t != TENDCASE) + synexpect(TENDCASE); + else + goto next_case; + } + } + *cpp = NULL; + goto redir; + case TLP: + n1 = (union node *)stalloc(sizeof (struct nredir)); + n1->type = NSUBSHELL; + n1->nredir.n = list(0); + n1->nredir.redirect = NULL; + t = TRP; + break; + case TBEGIN: + n1 = list(0); + t = TEND; + break; + case TWORD: + case TREDIR: + tokpushback++; + return simplecmd(); + } + + if (readtoken() != t) + synexpect(t); + +redir: + /* Now check for redirection which may follow command */ + checkkwd = CHKKWD | CHKALIAS; + rpp = rpp2; + while (readtoken() == TREDIR) { + *rpp = n2 = redirnode; + rpp = &n2->nfile.next; + parsefname(); + } + tokpushback++; + *rpp = NULL; + if (redir) { + if (n1->type != NSUBSHELL) { + n2 = (union node *)stalloc(sizeof (struct nredir)); + n2->type = NREDIR; + n2->nredir.n = n1; + n1 = n2; + } + n1->nredir.redirect = redir; + } + + return n1; +} + + +static union node * +simplecmd(void) { + union node *args, **app; + union node *n = NULL; + union node *vars, **vpp; + union node **rpp, *redir; + int savecheckkwd; + + args = NULL; + app = &args; + vars = NULL; + vpp = &vars; + redir = NULL; + rpp = &redir; + + savecheckkwd = CHKALIAS; + for (;;) { + checkkwd = savecheckkwd; + switch (readtoken()) { + case TWORD: + n = (union node *)stalloc(sizeof (struct narg)); + n->type = NARG; + n->narg.text = wordtext; + n->narg.backquote = backquotelist; + if (savecheckkwd && isassignment(wordtext)) { + *vpp = n; + vpp = &n->narg.next; + } else { + *app = n; + app = &n->narg.next; + savecheckkwd = 0; + } + break; + case TREDIR: + *rpp = n = redirnode; + rpp = &n->nfile.next; + parsefname(); /* read name of redirection file */ + break; + case TLP: + if ( + args && app == &args->narg.next && + !vars && !redir + ) { + struct builtincmd *bcmd; + const char *name; + + /* We have a function */ + if (readtoken() != TRP) + synexpect(TRP); + name = n->narg.text; + if ( + !goodname(name) || ( + (bcmd = find_builtin(name)) && + IS_BUILTIN_SPECIAL(bcmd) + ) + ) + synerror("Bad function name"); + n->type = NDEFUN; + checkkwd = CHKNL | CHKKWD | CHKALIAS; + n->narg.next = command(); + return n; + } + /* fall through */ + default: + tokpushback++; + goto out; + } + } +out: + *app = NULL; + *vpp = NULL; + *rpp = NULL; + n = (union node *)stalloc(sizeof (struct ncmd)); + n->type = NCMD; + n->ncmd.args = args; + n->ncmd.assign = vars; + n->ncmd.redirect = redir; + return n; +} + +static union node * +makename(void) +{ + union node *n; + + n = (union node *)stalloc(sizeof (struct narg)); + n->type = NARG; + n->narg.next = NULL; + n->narg.text = wordtext; + n->narg.backquote = backquotelist; + return n; +} + +void fixredir(union node *n, const char *text, int err) +{ + TRACE(("Fix redir %s %d\n", text, err)); + if (!err) + n->ndup.vname = NULL; + + if (is_digit(text[0]) && text[1] == '\0') + n->ndup.dupfd = digit_val(text[0]); + else if (text[0] == '-' && text[1] == '\0') + n->ndup.dupfd = -1; + else { + + if (err) + synerror("Bad fd number"); + else + n->ndup.vname = makename(); + } +} + + +static void +parsefname(void) +{ + union node *n = redirnode; + + if (readtoken() != TWORD) + synexpect(-1); + if (n->type == NHERE) { + struct heredoc *here = heredoc; + struct heredoc *p; + int i; + + if (quoteflag == 0) + n->type = NXHERE; + TRACE(("Here document %d\n", n->type)); + if (! noexpand(wordtext) || (i = strlen(wordtext)) == 0 || i > EOFMARKLEN) + synerror("Illegal eof marker for << redirection"); + rmescapes(wordtext); + here->eofmark = wordtext; + here->next = NULL; + if (heredoclist == NULL) + heredoclist = here; + else { + for (p = heredoclist ; p->next ; p = p->next); + p->next = here; + } + } else if (n->type == NTOFD || n->type == NFROMFD) { + fixredir(n, wordtext, 0); + } else { + n->nfile.fname = makename(); + } +} + + +/* + * Input any here documents. + */ + +static void +parseheredoc(void) +{ + struct heredoc *here; + union node *n; + + here = heredoclist; + heredoclist = 0; + + while (here) { + if (needprompt) { + setprompt(2); + } + readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX, + here->eofmark, here->striptabs); + n = (union node *)stalloc(sizeof (struct narg)); + n->narg.type = NARG; + n->narg.next = NULL; + n->narg.text = wordtext; + n->narg.backquote = backquotelist; + here->here->nhere.doc = n; + here = here->next; + } +} + +static char peektoken(void) +{ + int t; + + t = readtoken(); + tokpushback++; + return tokname_array[t][0]; +} + +static int +readtoken(void) +{ + int t; +#if DEBUG + int alreadyseen = tokpushback; +#endif + +#ifdef CONFIG_ASH_ALIAS +top: +#endif + + t = xxreadtoken(); + + /* + * eat newlines + */ + if (checkkwd & CHKNL) { + while (t == TNL) { + parseheredoc(); + t = xxreadtoken(); + } + } + + if (t != TWORD || quoteflag) { + goto out; + } + + /* + * check for keywords + */ + if (checkkwd & CHKKWD) { + const char *const *pp; + + if ((pp = findkwd(wordtext))) { + lasttoken = t = pp - tokname_array; + TRACE(("keyword %s recognized\n", tokname(t))); + goto out; + } + } + + if (checkkwd & CHKALIAS) { +#ifdef CONFIG_ASH_ALIAS + struct alias *ap; + if ((ap = lookupalias(wordtext, 1)) != NULL) { + if (*ap->val) { + pushstring(ap->val, ap); + } + goto top; + } +#endif + } +out: + checkkwd = 0; +#if DEBUG + if (!alreadyseen) + TRACE(("token %s %s\n", tokname(t), t == TWORD ? wordtext : "")); + else + TRACE(("reread token %s %s\n", tokname(t), t == TWORD ? wordtext : "")); +#endif + return t; +} + + +/* + * Read the next input token. + * If the token is a word, we set backquotelist to the list of cmds in + * backquotes. We set quoteflag to true if any part of the word was + * quoted. + * If the token is TREDIR, then we set redirnode to a structure containing + * the redirection. + * In all cases, the variable startlinno is set to the number of the line + * on which the token starts. + * + * [Change comment: here documents and internal procedures] + * [Readtoken shouldn't have any arguments. Perhaps we should make the + * word parsing code into a separate routine. In this case, readtoken + * doesn't need to have any internal procedures, but parseword does. + * We could also make parseoperator in essence the main routine, and + * have parseword (readtoken1?) handle both words and redirection.] + */ + +#define NEW_xxreadtoken +#ifdef NEW_xxreadtoken + +/* singles must be first! */ +static const char xxreadtoken_chars[7] = { '\n', '(', ')', '&', '|', ';', 0 }; + +static const char xxreadtoken_tokens[] = { + TNL, TLP, TRP, /* only single occurrence allowed */ + TBACKGND, TPIPE, TSEMI, /* if single occurrence */ + TEOF, /* corresponds to trailing nul */ + TAND, TOR, TENDCASE, /* if double occurrence */ +}; + +#define xxreadtoken_doubles \ + (sizeof(xxreadtoken_tokens) - sizeof(xxreadtoken_chars)) +#define xxreadtoken_singles \ + (sizeof(xxreadtoken_chars) - xxreadtoken_doubles - 1) + +static int xxreadtoken(void) +{ + int c; + + if (tokpushback) { + tokpushback = 0; + return lasttoken; + } + if (needprompt) { + setprompt(2); + } + startlinno = plinno; + for (;;) { /* until token or start of word found */ + c = pgetc_macro(); + + if ((c != ' ') && (c != '\t') +#ifdef CONFIG_ASH_ALIAS + && (c != PEOA) +#endif + ) { + if (c == '#') { + while ((c = pgetc()) != '\n' && c != PEOF); + pungetc(); + } else if (c == '\\') { + if (pgetc() != '\n') { + pungetc(); + goto READTOKEN1; + } + startlinno = ++plinno; + if (doprompt) + setprompt(2); + } else { + const char *p + = xxreadtoken_chars + sizeof(xxreadtoken_chars) - 1; + + if (c != PEOF) { + if (c == '\n') { + plinno++; + needprompt = doprompt; + } + + p = strchr(xxreadtoken_chars, c); + if (p == NULL) { + READTOKEN1: + return readtoken1(c, BASESYNTAX, (char *) NULL, 0); + } + + if (p - xxreadtoken_chars >= xxreadtoken_singles) { + if (pgetc() == *p) { /* double occurrence? */ + p += xxreadtoken_doubles + 1; + } else { + pungetc(); + } + } + } + + return lasttoken = xxreadtoken_tokens[p - xxreadtoken_chars]; + } + } + } +} + + +#else +#define RETURN(token) return lasttoken = token + +static int +xxreadtoken(void) +{ + int c; + + if (tokpushback) { + tokpushback = 0; + return lasttoken; + } + if (needprompt) { + setprompt(2); + } + startlinno = plinno; + for (;;) { /* until token or start of word found */ + c = pgetc_macro(); + switch (c) { + case ' ': case '\t': +#ifdef CONFIG_ASH_ALIAS + case PEOA: +#endif + continue; + case '#': + while ((c = pgetc()) != '\n' && c != PEOF); + pungetc(); + continue; + case '\\': + if (pgetc() == '\n') { + startlinno = ++plinno; + if (doprompt) + setprompt(2); + continue; + } + pungetc(); + goto breakloop; + case '\n': + plinno++; + needprompt = doprompt; + RETURN(TNL); + case PEOF: + RETURN(TEOF); + case '&': + if (pgetc() == '&') + RETURN(TAND); + pungetc(); + RETURN(TBACKGND); + case '|': + if (pgetc() == '|') + RETURN(TOR); + pungetc(); + RETURN(TPIPE); + case ';': + if (pgetc() == ';') + RETURN(TENDCASE); + pungetc(); + RETURN(TSEMI); + case '(': + RETURN(TLP); + case ')': + RETURN(TRP); + default: + goto breakloop; + } + } +breakloop: + return readtoken1(c, BASESYNTAX, (char *)NULL, 0); +#undef RETURN +} +#endif /* NEW_xxreadtoken */ + + +/* + * If eofmark is NULL, read a word or a redirection symbol. If eofmark + * is not NULL, read a here document. In the latter case, eofmark is the + * word which marks the end of the document and striptabs is true if + * leading tabs should be stripped from the document. The argument firstc + * is the first character of the input token or document. + * + * Because C does not have internal subroutines, I have simulated them + * using goto's to implement the subroutine linkage. The following macros + * will run code that appears at the end of readtoken1. + */ + +#define CHECKEND() {goto checkend; checkend_return:;} +#define PARSEREDIR() {goto parseredir; parseredir_return:;} +#define PARSESUB() {goto parsesub; parsesub_return:;} +#define PARSEBACKQOLD() {oldstyle = 1; goto parsebackq; parsebackq_oldreturn:;} +#define PARSEBACKQNEW() {oldstyle = 0; goto parsebackq; parsebackq_newreturn:;} +#define PARSEARITH() {goto parsearith; parsearith_return:;} + +static int +readtoken1(int firstc, int syntax, char *eofmark, int striptabs) +{ + int c = firstc; + char *out; + int len; + char line[EOFMARKLEN + 1]; + struct nodelist *bqlist = 0; + int quotef = 0; + int dblquote = 0; + int varnest = 0; /* levels of variables expansion */ + int arinest = 0; /* levels of arithmetic expansion */ + int parenlevel = 0; /* levels of parens in arithmetic */ + int dqvarnest = 0; /* levels of variables expansion within double quotes */ + int oldstyle = 0; + int prevsyntax = 0; /* syntax before arithmetic */ +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) &out; + (void) "ef; + (void) &dblquote; + (void) &varnest; + (void) &arinest; + (void) &parenlevel; + (void) &dqvarnest; + (void) &oldstyle; + (void) &prevsyntax; + (void) &syntax; +#endif + + startlinno = plinno; + dblquote = 0; + if (syntax == DQSYNTAX) + dblquote = 1; + quotef = 0; + bqlist = NULL; + varnest = 0; + arinest = 0; + parenlevel = 0; + dqvarnest = 0; + + STARTSTACKSTR(out); + loop: { /* for each line, until end of word */ + CHECKEND(); /* set c to PEOF if at end of here document */ + for (;;) { /* until end of line or end of word */ + CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */ + switch(SIT(c, syntax)) { + case CNL: /* '\n' */ + if (syntax == BASESYNTAX) + goto endword; /* exit outer loop */ + USTPUTC(c, out); + plinno++; + if (doprompt) + setprompt(2); + c = pgetc(); + goto loop; /* continue outer loop */ + case CWORD: + USTPUTC(c, out); + break; + case CCTL: + if (eofmark == NULL || dblquote) + USTPUTC(CTLESC, out); + USTPUTC(c, out); + break; + case CBACK: /* backslash */ + c = pgetc2(); + if (c == PEOF) { + USTPUTC(CTLESC, out); + USTPUTC('\\', out); + pungetc(); + } else if (c == '\n') { + if (doprompt) + setprompt(2); + } else { + if (dblquote && + c != '\\' && c != '`' && + c != '$' && ( + c != '"' || + eofmark != NULL) + ) { + USTPUTC(CTLESC, out); + USTPUTC('\\', out); + } + if (SIT(c, SQSYNTAX) == CCTL) + USTPUTC(CTLESC, out); + USTPUTC(c, out); + quotef++; + } + break; + case CSQUOTE: + syntax = SQSYNTAX; +quotemark: + if (eofmark == NULL) { + USTPUTC(CTLQUOTEMARK, out); + } + break; + case CDQUOTE: + syntax = DQSYNTAX; + dblquote = 1; + goto quotemark; + case CENDQUOTE: + if (eofmark != NULL && arinest == 0 && + varnest == 0) { + USTPUTC(c, out); + } else { + if (dqvarnest == 0) { + syntax = BASESYNTAX; + dblquote = 0; + } + quotef++; + goto quotemark; + } + break; + case CVAR: /* '$' */ + PARSESUB(); /* parse substitution */ + break; + case CENDVAR: /* '}' */ + if (varnest > 0) { + varnest--; + if (dqvarnest > 0) { + dqvarnest--; + } + USTPUTC(CTLENDVAR, out); + } else { + USTPUTC(c, out); + } + break; +#ifdef CONFIG_ASH_MATH_SUPPORT + case CLP: /* '(' in arithmetic */ + parenlevel++; + USTPUTC(c, out); + break; + case CRP: /* ')' in arithmetic */ + if (parenlevel > 0) { + USTPUTC(c, out); + --parenlevel; + } else { + if (pgetc() == ')') { + if (--arinest == 0) { + USTPUTC(CTLENDARI, out); + syntax = prevsyntax; + if (syntax == DQSYNTAX) + dblquote = 1; + else + dblquote = 0; + } else + USTPUTC(')', out); + } else { + /* + * unbalanced parens + * (don't 2nd guess - no error) + */ + pungetc(); + USTPUTC(')', out); + } + } + break; +#endif + case CBQUOTE: /* '`' */ + PARSEBACKQOLD(); + break; + case CENDFILE: + goto endword; /* exit outer loop */ + case CIGN: + break; + default: + if (varnest == 0) + goto endword; /* exit outer loop */ +#ifdef CONFIG_ASH_ALIAS + if (c != PEOA) +#endif + USTPUTC(c, out); + + } + c = pgetc_macro(); + } + } +endword: +#ifdef CONFIG_ASH_MATH_SUPPORT + if (syntax == ARISYNTAX) + synerror("Missing '))'"); +#endif + if (syntax != BASESYNTAX && ! parsebackquote && eofmark == NULL) + synerror("Unterminated quoted string"); + if (varnest != 0) { + startlinno = plinno; + /* { */ + synerror("Missing '}'"); + } + USTPUTC('\0', out); + len = out - (char *)stackblock(); + out = stackblock(); + if (eofmark == NULL) { + if ((c == '>' || c == '<') + && quotef == 0 + && len <= 2 + && (*out == '\0' || is_digit(*out))) { + PARSEREDIR(); + return lasttoken = TREDIR; + } else { + pungetc(); + } + } + quoteflag = quotef; + backquotelist = bqlist; + grabstackblock(len); + wordtext = out; + return lasttoken = TWORD; +/* end of readtoken routine */ + + + +/* + * Check to see whether we are at the end of the here document. When this + * is called, c is set to the first character of the next input line. If + * we are at the end of the here document, this routine sets the c to PEOF. + */ + +checkend: { + if (eofmark) { +#ifdef CONFIG_ASH_ALIAS + if (c == PEOA) { + c = pgetc2(); + } +#endif + if (striptabs) { + while (c == '\t') { + c = pgetc2(); + } + } + if (c == *eofmark) { + if (pfgets(line, sizeof line) != NULL) { + char *p, *q; + + p = line; + for (q = eofmark + 1 ; *q && *p == *q ; p++, q++); + if (*p == '\n' && *q == '\0') { + c = PEOF; + plinno++; + needprompt = doprompt; + } else { + pushstring(line, NULL); + } + } + } + } + goto checkend_return; +} + + +/* + * Parse a redirection operator. The variable "out" points to a string + * specifying the fd to be redirected. The variable "c" contains the + * first character of the redirection operator. + */ + +parseredir: { + char fd = *out; + union node *np; + + np = (union node *)stalloc(sizeof (struct nfile)); + if (c == '>') { + np->nfile.fd = 1; + c = pgetc(); + if (c == '>') + np->type = NAPPEND; + else if (c == '|') + np->type = NCLOBBER; + else if (c == '&') + np->type = NTOFD; + else { + np->type = NTO; + pungetc(); + } + } else { /* c == '<' */ + np->nfile.fd = 0; + switch (c = pgetc()) { + case '<': + if (sizeof (struct nfile) != sizeof (struct nhere)) { + np = (union node *)stalloc(sizeof (struct nhere)); + np->nfile.fd = 0; + } + np->type = NHERE; + heredoc = (struct heredoc *)stalloc(sizeof (struct heredoc)); + heredoc->here = np; + if ((c = pgetc()) == '-') { + heredoc->striptabs = 1; + } else { + heredoc->striptabs = 0; + pungetc(); + } + break; + + case '&': + np->type = NFROMFD; + break; + + case '>': + np->type = NFROMTO; + break; + + default: + np->type = NFROM; + pungetc(); + break; + } + } + if (fd != '\0') + np->nfile.fd = digit_val(fd); + redirnode = np; + goto parseredir_return; +} + + +/* + * Parse a substitution. At this point, we have read the dollar sign + * and nothing else. + */ + +parsesub: { + int subtype; + int typeloc; + int flags; + char *p; + static const char types[] = "}-+?="; + + c = pgetc(); + if ( + c <= PEOA_OR_PEOF || + (c != '(' && c != '{' && !is_name(c) && !is_special(c)) + ) { + USTPUTC('$', out); + pungetc(); + } else if (c == '(') { /* $(command) or $((arith)) */ + if (pgetc() == '(') { +#ifdef CONFIG_ASH_MATH_SUPPORT + PARSEARITH(); +#else + synerror("We unsupport $((arith))"); +#endif + } else { + pungetc(); + PARSEBACKQNEW(); + } + } else { + USTPUTC(CTLVAR, out); + typeloc = out - (char *)stackblock(); + USTPUTC(VSNORMAL, out); + subtype = VSNORMAL; + if (c == '{') { + c = pgetc(); + if (c == '#') { + if ((c = pgetc()) == '}') + c = '#'; + else + subtype = VSLENGTH; + } + else + subtype = 0; + } + if (c > PEOA_OR_PEOF && is_name(c)) { + do { + STPUTC(c, out); + c = pgetc(); + } while (c > PEOA_OR_PEOF && is_in_name(c)); + } else if (is_digit(c)) { + do { + STPUTC(c, out); + c = pgetc(); + } while (is_digit(c)); + } + else if (is_special(c)) { + USTPUTC(c, out); + c = pgetc(); + } + else +badsub: synerror("Bad substitution"); + + STPUTC('=', out); + flags = 0; + if (subtype == 0) { + switch (c) { + case ':': + flags = VSNUL; + c = pgetc(); + /*FALLTHROUGH*/ + default: + p = strchr(types, c); + if (p == NULL) + goto badsub; + subtype = p - types + VSNORMAL; + break; + case '%': + case '#': + { + int cc = c; + subtype = c == '#' ? VSTRIMLEFT : + VSTRIMRIGHT; + c = pgetc(); + if (c == cc) + subtype++; + else + pungetc(); + break; + } + } + } else { + pungetc(); + } + if (dblquote || arinest) + flags |= VSQUOTE; + *((char *)stackblock() + typeloc) = subtype | flags; + if (subtype != VSNORMAL) { + varnest++; + if (dblquote || arinest) { + dqvarnest++; + } + } + } + goto parsesub_return; +} + + +/* + * Called to parse command substitutions. Newstyle is set if the command + * is enclosed inside $(...); nlpp is a pointer to the head of the linked + * list of commands (passed by reference), and savelen is the number of + * characters on the top of the stack which must be preserved. + */ + +parsebackq: { + struct nodelist **nlpp; + int savepbq; + union node *n; + char *volatile str; + struct jmploc jmploc; + struct jmploc *volatile savehandler; + size_t savelen; + int saveprompt = 0; +#ifdef __GNUC__ + (void) &saveprompt; +#endif + + savepbq = parsebackquote; + if (setjmp(jmploc.loc)) { + if (str) + ckfree(str); + parsebackquote = 0; + handler = savehandler; + longjmp(handler->loc, 1); + } + INTOFF; + str = NULL; + savelen = out - (char *)stackblock(); + if (savelen > 0) { + str = ckmalloc(savelen); + memcpy(str, stackblock(), savelen); + } + savehandler = handler; + handler = &jmploc; + INTON; + if (oldstyle) { + /* We must read until the closing backquote, giving special + treatment to some slashes, and then push the string and + reread it as input, interpreting it normally. */ + char *pout; + int pc; + size_t psavelen; + char *pstr; + + + STARTSTACKSTR(pout); + for (;;) { + if (needprompt) { + setprompt(2); + } + switch (pc = pgetc()) { + case '`': + goto done; + + case '\\': + if ((pc = pgetc()) == '\n') { + plinno++; + if (doprompt) + setprompt(2); + /* + * If eating a newline, avoid putting + * the newline into the new character + * stream (via the STPUTC after the + * switch). + */ + continue; + } + if (pc != '\\' && pc != '`' && pc != '$' + && (!dblquote || pc != '"')) + STPUTC('\\', pout); + if (pc > PEOA_OR_PEOF) { + break; + } + /* fall through */ + + case PEOF: +#ifdef CONFIG_ASH_ALIAS + case PEOA: +#endif + startlinno = plinno; + synerror("EOF in backquote substitution"); + + case '\n': + plinno++; + needprompt = doprompt; + break; + + default: + break; + } + STPUTC(pc, pout); + } +done: + STPUTC('\0', pout); + psavelen = pout - (char *)stackblock(); + if (psavelen > 0) { + pstr = grabstackstr(pout); + setinputstring(pstr); + } + } + nlpp = &bqlist; + while (*nlpp) + nlpp = &(*nlpp)->next; + *nlpp = (struct nodelist *)stalloc(sizeof (struct nodelist)); + (*nlpp)->next = NULL; + parsebackquote = oldstyle; + + if (oldstyle) { + saveprompt = doprompt; + doprompt = 0; + } + + n = list(2); + + if (oldstyle) + doprompt = saveprompt; + else { + if (readtoken() != TRP) + synexpect(TRP); + } + + (*nlpp)->n = n; + if (oldstyle) { + /* + * Start reading from old file again, ignoring any pushed back + * tokens left from the backquote parsing + */ + popfile(); + tokpushback = 0; + } + while (stackblocksize() <= savelen) + growstackblock(); + STARTSTACKSTR(out); + if (str) { + memcpy(out, str, savelen); + STADJUST(savelen, out); + INTOFF; + ckfree(str); + str = NULL; + INTON; + } + parsebackquote = savepbq; + handler = savehandler; + if (arinest || dblquote) + USTPUTC(CTLBACKQ | CTLQUOTE, out); + else + USTPUTC(CTLBACKQ, out); + if (oldstyle) + goto parsebackq_oldreturn; + else + goto parsebackq_newreturn; +} + +#ifdef CONFIG_ASH_MATH_SUPPORT +/* + * Parse an arithmetic expansion (indicate start of one and set state) + */ +parsearith: { + + if (++arinest == 1) { + prevsyntax = syntax; + syntax = ARISYNTAX; + USTPUTC(CTLARI, out); + if (dblquote) + USTPUTC('"',out); + else + USTPUTC(' ',out); + } else { + /* + * we collapse embedded arithmetic expansion to + * parenthesis, which should be equivalent + */ + USTPUTC('(', out); + } + goto parsearith_return; +} +#endif + +} /* end of readtoken */ + + + +/* + * Returns true if the text contains nothing to expand (no dollar signs + * or backquotes). + */ + +static int +noexpand(char *text) +{ + char *p; + char c; + + p = text; + while ((c = *p++) != '\0') { + if (c == CTLQUOTEMARK) + continue; + if (c == CTLESC) + p++; + else if (SIT(c, BASESYNTAX) == CCTL) + return 0; + } + return 1; +} + + +/* + * Return of a legal variable name (a letter or underscore followed by zero or + * more letters, underscores, and digits). + */ + +static char * +endofname(const char *name) +{ + char *p; + + p = (char *) name; + if (! is_name(*p)) + return p; + while (*++p) { + if (! is_in_name(*p)) + break; + } + return p; +} + + +/* + * Called when an unexpected token is read during the parse. The argument + * is the token that is expected, or -1 if more than one type of token can + * occur at this point. + */ + +static void synexpect(int token) +{ + char msg[64]; + int l; + + l = sprintf(msg, "%s unexpected", tokname(lasttoken)); + if (token >= 0) + sprintf(msg + l, " (expecting %s)", tokname(token)); + synerror(msg); + /* NOTREACHED */ +} + +static void +synerror(const char *msg) +{ + sh_error("Syntax error: %s", msg); + /* NOTREACHED */ +} + + +/* + * called by editline -- any expansions to the prompt + * should be added here. + */ + +#ifdef CONFIG_ASH_EXPAND_PRMT +static const char * +expandstr(const char *ps) +{ + union node n; + + /* XXX Fix (char *) cast. */ + setinputstring((char *)ps); + readtoken1(pgetc(), DQSYNTAX, nullstr, 0); + popfile(); + + n.narg.type = NARG; + n.narg.next = NULL; + n.narg.text = wordtext; + n.narg.backquote = backquotelist; + + expandarg(&n, NULL, 0); + return stackblock(); +} +#endif + +static void setprompt(int whichprompt) +{ + const char *prompt; +#ifdef CONFIG_ASH_EXPAND_PRMT + struct stackmark smark; +#endif + + needprompt = 0; + + switch (whichprompt) { + case 1: + prompt = ps1val(); + break; + case 2: + prompt = ps2val(); + break; + default: /* 0 */ + prompt = nullstr; + } +#ifdef CONFIG_ASH_EXPAND_PRMT + setstackmark(&smark); + stalloc(stackblocksize()); +#endif + putprompt(expandstr(prompt)); +#ifdef CONFIG_ASH_EXPAND_PRMT + popstackmark(&smark); +#endif +} + + +static const char *const *findkwd(const char *s) +{ + return bsearch(s, tokname_array + KWDOFFSET, + (sizeof(tokname_array) / sizeof(const char *)) - KWDOFFSET, + sizeof(const char *), pstrcmp); +} + +/* redir.c */ + +/* + * Code for dealing with input/output redirection. + */ + +#define EMPTY -2 /* marks an unused slot in redirtab */ +#ifndef PIPE_BUF +# define PIPESIZE 4096 /* amount of buffering in a pipe */ +#else +# define PIPESIZE PIPE_BUF +#endif + +/* + * Open a file in noclobber mode. + * The code was copied from bash. + */ +static int noclobberopen(const char *fname) +{ + int r, fd; + struct stat finfo, finfo2; + + /* + * If the file exists and is a regular file, return an error + * immediately. + */ + r = stat(fname, &finfo); + if (r == 0 && S_ISREG(finfo.st_mode)) { + errno = EEXIST; + return -1; + } + + /* + * If the file was not present (r != 0), make sure we open it + * exclusively so that if it is created before we open it, our open + * will fail. Make sure that we do not truncate an existing file. + * Note that we don't turn on O_EXCL unless the stat failed -- if the + * file was not a regular file, we leave O_EXCL off. + */ + if (r != 0) + return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666); + fd = open(fname, O_WRONLY|O_CREAT, 0666); + + /* If the open failed, return the file descriptor right away. */ + if (fd < 0) + return fd; + + /* + * OK, the open succeeded, but the file may have been changed from a + * non-regular file to a regular file between the stat and the open. + * We are assuming that the O_EXCL open handles the case where FILENAME + * did not exist and is symlinked to an existing file between the stat + * and open. + */ + + /* + * If we can open it and fstat the file descriptor, and neither check + * revealed that it was a regular file, and the file has not been + * replaced, return the file descriptor. + */ + if (fstat(fd, &finfo2) == 0 && !S_ISREG(finfo2.st_mode) && + finfo.st_dev == finfo2.st_dev && finfo.st_ino == finfo2.st_ino) + return fd; + + /* The file has been replaced. badness. */ + close(fd); + errno = EEXIST; + return -1; +} + +/* + * Handle here documents. Normally we fork off a process to write the + * data to a pipe. If the document is short, we can stuff the data in + * the pipe without forking. + */ + +static int openhere(union node *redir) +{ + int pip[2]; + size_t len = 0; + + if (pipe(pip) < 0) + sh_error("Pipe call failed"); + if (redir->type == NHERE) { + len = strlen(redir->nhere.doc->narg.text); + if (len <= PIPESIZE) { + full_write(pip[1], redir->nhere.doc->narg.text, len); + goto out; + } + } + if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) { + close(pip[0]); + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + signal(SIGHUP, SIG_IGN); +#ifdef SIGTSTP + signal(SIGTSTP, SIG_IGN); +#endif + signal(SIGPIPE, SIG_DFL); + if (redir->type == NHERE) + full_write(pip[1], redir->nhere.doc->narg.text, len); + else + expandhere(redir->nhere.doc, pip[1]); + _exit(0); + } +out: + close(pip[1]); + return pip[0]; +} + +static int +openredirect(union node *redir) +{ + char *fname; + int f; + + switch (redir->nfile.type) { + case NFROM: + fname = redir->nfile.expfname; + if ((f = open(fname, O_RDONLY)) < 0) + goto eopen; + break; + case NFROMTO: + fname = redir->nfile.expfname; + if ((f = open(fname, O_RDWR|O_CREAT|O_TRUNC, 0666)) < 0) + goto ecreate; + break; + case NTO: + /* Take care of noclobber mode. */ + if (Cflag) { + fname = redir->nfile.expfname; + if ((f = noclobberopen(fname)) < 0) + goto ecreate; + break; + } + /* FALLTHROUGH */ + case NCLOBBER: + fname = redir->nfile.expfname; + if ((f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) + goto ecreate; + break; + case NAPPEND: + fname = redir->nfile.expfname; + if ((f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666)) < 0) + goto ecreate; + break; + default: +#if DEBUG + abort(); +#endif + /* Fall through to eliminate warning. */ + case NTOFD: + case NFROMFD: + f = -1; + break; + case NHERE: + case NXHERE: + f = openhere(redir); + break; + } + + return f; +ecreate: + sh_error("cannot create %s: %s", fname, errmsg(errno, E_CREAT)); +eopen: + sh_error("cannot open %s: %s", fname, errmsg(errno, E_OPEN)); +} + +static void dupredirect(union node *redir, int f) +{ + int fd = redir->nfile.fd; + + if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) { + if (redir->ndup.dupfd >= 0) { /* if not ">&-" */ + copyfd(redir->ndup.dupfd, fd); + } + return; + } + + if (f != fd) { + copyfd(f, fd); + close(f); + } + return; +} + +/* + * Process a list of redirection commands. If the REDIR_PUSH flag is set, + * old file descriptors are stashed away so that the redirection can be + * undone by calling popredir. If the REDIR_BACKQ flag is set, then the + * standard output, and the standard error if it becomes a duplicate of + * stdout, is saved in memory. + */ + +static void +redirect(union node *redir, int flags) +{ + union node *n; + struct redirtab *sv; + int i; + int fd; + int newfd; + int *p; + nullredirs++; + if (!redir) { + return; + } + sv = NULL; + INTOFF; + if (flags & REDIR_PUSH) { + struct redirtab *q; + q = ckmalloc(sizeof (struct redirtab)); + q->next = redirlist; + redirlist = q; + q->nullredirs = nullredirs - 1; + for (i = 0 ; i < 10 ; i++) + q->renamed[i] = EMPTY; + nullredirs = 0; + sv = q; + } + n = redir; + do { + fd = n->nfile.fd; + if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) && + n->ndup.dupfd == fd) + continue; /* redirect from/to same file descriptor */ + + newfd = openredirect(n); + if (fd == newfd) + continue; + if (sv && *(p = &sv->renamed[fd]) == EMPTY) { + i = fcntl(fd, F_DUPFD, 10); + + if (i == -1) { + i = errno; + if (i != EBADF) { + close(newfd); + errno = i; + sh_error("%d: %m", fd); + /* NOTREACHED */ + } + } else { + *p = i; + close(fd); + } + } else { + close(fd); + } + dupredirect(n, newfd); + } while ((n = n->nfile.next)); + INTON; + if (flags & REDIR_SAVEFD2 && sv && sv->renamed[2] >= 0) + preverrout_fd = sv->renamed[2]; +} + + +/* + * Undo the effects of the last redirection. + */ + +void +popredir(int drop) +{ + struct redirtab *rp; + int i; + + if (--nullredirs >= 0) + return; + INTOFF; + rp = redirlist; + for (i = 0 ; i < 10 ; i++) { + if (rp->renamed[i] != EMPTY) { + if (!drop) { + close(i); + copyfd(rp->renamed[i], i); + } + close(rp->renamed[i]); + } + } + redirlist = rp->next; + nullredirs = rp->nullredirs; + ckfree(rp); + INTON; +} + +/* + * Undo all redirections. Called on error or interrupt. + */ + +/* + * Discard all saved file descriptors. + */ + +void +clearredir(int drop) +{ + for (;;) { + nullredirs = 0; + if (!redirlist) + break; + popredir(drop); + } +} + + +/* + * Copy a file descriptor to be >= to. Returns -1 + * if the source file descriptor is closed, EMPTY if there are no unused + * file descriptors left. + */ + +int +copyfd(int from, int to) +{ + int newfd; + + newfd = fcntl(from, F_DUPFD, to); + if (newfd < 0) { + if (errno == EMFILE) + return EMPTY; + else + sh_error("%d: %m", from); + } + return newfd; +} + + +int +redirectsafe(union node *redir, int flags) +{ + int err; + volatile int saveint; + struct jmploc *volatile savehandler = handler; + struct jmploc jmploc; + + SAVEINT(saveint); + if (!(err = setjmp(jmploc.loc) * 2)) { + handler = &jmploc; + redirect(redir, flags); + } + handler = savehandler; + if (err && exception != EXERROR) + longjmp(handler->loc, 1); + RESTOREINT(saveint); + return err; +} + +/* show.c */ + +#if DEBUG +static void shtree(union node *, int, char *, FILE*); +static void shcmd(union node *, FILE *); +static void sharg(union node *, FILE *); +static void indent(int, char *, FILE *); +static void trstring(char *); + + +void +showtree(union node *n) +{ + trputs("showtree called\n"); + shtree(n, 1, NULL, stdout); +} + + +static void +shtree(union node *n, int ind, char *pfx, FILE *fp) +{ + struct nodelist *lp; + const char *s; + + if (n == NULL) + return; + + indent(ind, pfx, fp); + switch(n->type) { + case NSEMI: + s = "; "; + goto binop; + case NAND: + s = " && "; + goto binop; + case NOR: + s = " || "; +binop: + shtree(n->nbinary.ch1, ind, NULL, fp); + /* if (ind < 0) */ + fputs(s, fp); + shtree(n->nbinary.ch2, ind, NULL, fp); + break; + case NCMD: + shcmd(n, fp); + if (ind >= 0) + putc('\n', fp); + break; + case NPIPE: + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { + shcmd(lp->n, fp); + if (lp->next) + fputs(" | ", fp); + } + if (n->npipe.backgnd) + fputs(" &", fp); + if (ind >= 0) + putc('\n', fp); + break; + default: + fprintf(fp, "<node type %d>", n->type); + if (ind >= 0) + putc('\n', fp); + break; + } +} + + +static void +shcmd(union node *cmd, FILE *fp) +{ + union node *np; + int first; + const char *s; + int dftfd; + + first = 1; + for (np = cmd->ncmd.args ; np ; np = np->narg.next) { + if (! first) + putchar(' '); + sharg(np, fp); + first = 0; + } + for (np = cmd->ncmd.redirect ; np ; np = np->nfile.next) { + if (! first) + putchar(' '); + switch (np->nfile.type) { + case NTO: s = ">"; dftfd = 1; break; + case NCLOBBER: s = ">|"; dftfd = 1; break; + case NAPPEND: s = ">>"; dftfd = 1; break; + case NTOFD: s = ">&"; dftfd = 1; break; + case NFROM: s = "<"; dftfd = 0; break; + case NFROMFD: s = "<&"; dftfd = 0; break; + case NFROMTO: s = "<>"; dftfd = 0; break; + default: s = "*error*"; dftfd = 0; break; + } + if (np->nfile.fd != dftfd) + fprintf(fp, "%d", np->nfile.fd); + fputs(s, fp); + if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) { + fprintf(fp, "%d", np->ndup.dupfd); + } else { + sharg(np->nfile.fname, fp); + } + first = 0; + } +} + + + +static void +sharg(union node *arg, FILE *fp) +{ + char *p; + struct nodelist *bqlist; + int subtype; + + if (arg->type != NARG) { + out1fmt("<node type %d>\n", arg->type); + abort(); + } + bqlist = arg->narg.backquote; + for (p = arg->narg.text ; *p ; p++) { + switch (*p) { + case CTLESC: + putc(*++p, fp); + break; + case CTLVAR: + putc('$', fp); + putc('{', fp); + subtype = *++p; + if (subtype == VSLENGTH) + putc('#', fp); + + while (*p != '=') + putc(*p++, fp); + + if (subtype & VSNUL) + putc(':', fp); + + switch (subtype & VSTYPE) { + case VSNORMAL: + putc('}', fp); + break; + case VSMINUS: + putc('-', fp); + break; + case VSPLUS: + putc('+', fp); + break; + case VSQUESTION: + putc('?', fp); + break; + case VSASSIGN: + putc('=', fp); + break; + case VSTRIMLEFT: + putc('#', fp); + break; + case VSTRIMLEFTMAX: + putc('#', fp); + putc('#', fp); + break; + case VSTRIMRIGHT: + putc('%', fp); + break; + case VSTRIMRIGHTMAX: + putc('%', fp); + putc('%', fp); + break; + case VSLENGTH: + break; + default: + out1fmt("<subtype %d>", subtype); + } + break; + case CTLENDVAR: + putc('}', fp); + break; + case CTLBACKQ: + case CTLBACKQ|CTLQUOTE: + putc('$', fp); + putc('(', fp); + shtree(bqlist->n, -1, NULL, fp); + putc(')', fp); + break; + default: + putc(*p, fp); + break; + } + } +} + + +static void +indent(int amount, char *pfx, FILE *fp) +{ + int i; + + for (i = 0 ; i < amount ; i++) { + if (pfx && i == amount - 1) + fputs(pfx, fp); + putc('\t', fp); + } +} + + + +/* + * Debugging stuff. + */ + + +FILE *tracefile; + + +void +trputc(int c) +{ + if (debug != 1) + return; + putc(c, tracefile); +} + +void +trace(const char *fmt, ...) +{ + va_list va; + + if (debug != 1) + return; + va_start(va, fmt); + (void) vfprintf(tracefile, fmt, va); + va_end(va); +} + +void +tracev(const char *fmt, va_list va) +{ + if (debug != 1) + return; + (void) vfprintf(tracefile, fmt, va); +} + + +void +trputs(const char *s) +{ + if (debug != 1) + return; + fputs(s, tracefile); +} + + +static void +trstring(char *s) +{ + char *p; + char c; + + if (debug != 1) + return; + putc('"', tracefile); + for (p = s ; *p ; p++) { + switch (*p) { + case '\n': c = 'n'; goto backslash; + case '\t': c = 't'; goto backslash; + case '\r': c = 'r'; goto backslash; + case '"': c = '"'; goto backslash; + case '\\': c = '\\'; goto backslash; + case CTLESC: c = 'e'; goto backslash; + case CTLVAR: c = 'v'; goto backslash; + case CTLVAR+CTLQUOTE: c = 'V'; goto backslash; + case CTLBACKQ: c = 'q'; goto backslash; + case CTLBACKQ+CTLQUOTE: c = 'Q'; goto backslash; +backslash: putc('\\', tracefile); + putc(c, tracefile); + break; + default: + if (*p >= ' ' && *p <= '~') + putc(*p, tracefile); + else { + putc('\\', tracefile); + putc(*p >> 6 & 03, tracefile); + putc(*p >> 3 & 07, tracefile); + putc(*p & 07, tracefile); + } + break; + } + } + putc('"', tracefile); +} + + +void +trargs(char **ap) +{ + if (debug != 1) + return; + while (*ap) { + trstring(*ap++); + if (*ap) + putc(' ', tracefile); + else + putc('\n', tracefile); + } +} + + +void +opentrace(void) +{ + char s[100]; +#ifdef O_APPEND + int flags; +#endif + + if (debug != 1) { + if (tracefile) + fflush(tracefile); + /* leave open because libedit might be using it */ + return; + } + scopy("./trace", s); + if (tracefile) { + if (!freopen(s, "a", tracefile)) { + fprintf(stderr, "Can't re-open %s\n", s); + debug = 0; + return; + } + } else { + if ((tracefile = fopen(s, "a")) == NULL) { + fprintf(stderr, "Can't open %s\n", s); + debug = 0; + return; + } + } +#ifdef O_APPEND + if ((flags = fcntl(fileno(tracefile), F_GETFL, 0)) >= 0) + fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND); +#endif + setlinebuf(tracefile); + fputs("\nTracing started.\n", tracefile); +} +#endif /* DEBUG */ + + +/* trap.c */ + +/* + * Sigmode records the current value of the signal handlers for the various + * modes. A value of zero means that the current handler is not known. + * S_HARD_IGN indicates that the signal was ignored on entry to the shell, + */ + +#define S_DFL 1 /* default signal handling (SIG_DFL) */ +#define S_CATCH 2 /* signal is caught */ +#define S_IGN 3 /* signal is ignored (SIG_IGN) */ +#define S_HARD_IGN 4 /* signal is ignored permenantly */ +#define S_RESET 5 /* temporary - to reset a hard ignored sig */ + + + +/* + * The trap builtin. + */ + +int +trapcmd(int argc, char **argv) +{ + char *action; + char **ap; + int signo; + + nextopt(nullstr); + ap = argptr; + if (!*ap) { + for (signo = 0 ; signo < NSIG ; signo++) { + if (trap[signo] != NULL) { + const char *sn; + + sn = get_signame(signo); + out1fmt("trap -- %s %s\n", + single_quote(trap[signo]), sn); + } + } + return 0; + } + if (!ap[1]) + action = NULL; + else + action = *ap++; + while (*ap) { + if ((signo = get_signum(*ap)) < 0) + sh_error("%s: bad trap", *ap); + INTOFF; + if (action) { + if (action[0] == '-' && action[1] == '\0') + action = NULL; + else + action = savestr(action); + } + if (trap[signo]) + ckfree(trap[signo]); + trap[signo] = action; + if (signo != 0) + setsignal(signo); + INTON; + ap++; + } + return 0; +} + + +/* + * Clear traps on a fork. + */ + +void +clear_traps(void) +{ + char **tp; + + for (tp = trap ; tp < &trap[NSIG] ; tp++) { + if (*tp && **tp) { /* trap not NULL or SIG_IGN */ + INTOFF; + ckfree(*tp); + *tp = NULL; + if (tp != &trap[0]) + setsignal(tp - trap); + INTON; + } + } +} + + +/* + * Set the signal handler for the specified signal. The routine figures + * out what it should be set to. + */ + +void +setsignal(int signo) +{ + int action; + char *t, tsig; + struct sigaction act; + + if ((t = trap[signo]) == NULL) + action = S_DFL; + else if (*t != '\0') + action = S_CATCH; + else + action = S_IGN; + if (rootshell && action == S_DFL) { + switch (signo) { + case SIGINT: + if (iflag || minusc || sflag == 0) + action = S_CATCH; + break; + case SIGQUIT: +#if DEBUG + if (debug) + break; +#endif + /* FALLTHROUGH */ + case SIGTERM: + if (iflag) + action = S_IGN; + break; +#if JOBS + case SIGTSTP: + case SIGTTOU: + if (mflag) + action = S_IGN; + break; +#endif + } + } + + t = &sigmode[signo - 1]; + tsig = *t; + if (tsig == 0) { + /* + * current setting unknown + */ + if (sigaction(signo, 0, &act) == -1) { + /* + * Pretend it worked; maybe we should give a warning + * here, but other shells don't. We don't alter + * sigmode, so that we retry every time. + */ + return; + } + if (act.sa_handler == SIG_IGN) { + if (mflag && (signo == SIGTSTP || + signo == SIGTTIN || signo == SIGTTOU)) { + tsig = S_IGN; /* don't hard ignore these */ + } else + tsig = S_HARD_IGN; + } else { + tsig = S_RESET; /* force to be set */ + } + } + if (tsig == S_HARD_IGN || tsig == action) + return; + switch (action) { + case S_CATCH: + act.sa_handler = onsig; + break; + case S_IGN: + act.sa_handler = SIG_IGN; + break; + default: + act.sa_handler = SIG_DFL; + } + *t = action; + act.sa_flags = 0; + sigfillset(&act.sa_mask); + sigaction(signo, &act, 0); +} + +/* + * Ignore a signal. + */ + +void +ignoresig(int signo) +{ + if (sigmode[signo - 1] != S_IGN && sigmode[signo - 1] != S_HARD_IGN) { + signal(signo, SIG_IGN); + } + sigmode[signo - 1] = S_HARD_IGN; +} + + +/* + * Signal handler. + */ + +void +onsig(int signo) +{ + gotsig[signo - 1] = 1; + pendingsigs = signo; + + if (exsig || (signo == SIGINT && !trap[SIGINT])) { + if (!suppressint) + onint(); + intpending = 1; + } +} + + +/* + * Called to execute a trap. Perhaps we should avoid entering new trap + * handlers while we are executing a trap handler. + */ + +int +dotrap(void) +{ + char *p; + char *q; + int i; + int savestatus; + int skip = 0; + + savestatus = exitstatus; + pendingsigs = 0; + xbarrier(); + + for (i = 0, q = gotsig; i < NSIG - 1; i++, q++) { + if (!*q) + continue; + *q = 0; + + p = trap[i + 1]; + if (!p) + continue; + skip = evalstring(p, SKIPEVAL); + exitstatus = savestatus; + if (skip) + break; + } + + return skip; +} + + +/* + * Controls whether the shell is interactive or not. + */ + +void +setinteractive(int on) +{ + static int is_interactive; + + if (++on == is_interactive) + return; + is_interactive = on; + setsignal(SIGINT); + setsignal(SIGQUIT); + setsignal(SIGTERM); +#ifndef CONFIG_FEATURE_SH_EXTRA_QUIET + if(is_interactive > 1) { + /* Looks like they want an interactive shell */ + static int do_banner; + + if(!do_banner) { + out1fmt( + "\n\n%s Built-in shell (ash)\n" + "Enter 'help' for a list of built-in commands.\n\n", + BB_BANNER); + do_banner++; + } + } +#endif +} + + +#ifndef CONFIG_FEATURE_SH_EXTRA_QUIET +/*** List the available builtins ***/ + +static int helpcmd(int argc, char **argv) +{ + int col, i; + + out1fmt("\nBuilt-in commands:\n-------------------\n"); + for (col = 0, i = 0; i < NUMBUILTINS; i++) { + col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '), + builtincmd[i].name + 1); + if (col > 60) { + out1fmt("\n"); + col = 0; + } + } +#ifdef CONFIG_FEATURE_SH_STANDALONE_SHELL + { + extern const struct BB_applet applets[]; + extern const size_t NUM_APPLETS; + + for (i = 0; i < NUM_APPLETS; i++) { + + col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '), applets[i].name); + if (col > 60) { + out1fmt("\n"); + col = 0; + } + } + } +#endif + out1fmt("\n\n"); + return EXIT_SUCCESS; +} +#endif /* CONFIG_FEATURE_SH_EXTRA_QUIET */ + +/* + * Called to exit the shell. + */ + +void +exitshell(void) +{ + struct jmploc loc; + char *p; + int status; + + status = exitstatus; + TRACE(("pid %d, exitshell(%d)\n", getpid(), status)); + if (setjmp(loc.loc)) { + if (exception == EXEXIT) + _exit(exitstatus); + goto out; + } + handler = &loc; + if ((p = trap[0])) { + trap[0] = NULL; + evalstring(p, 0); + } + flushall(); + setjobctl(0); +#ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY + if (iflag && rootshell) { + const char *hp = lookupvar("HISTFILE"); + + if(hp != NULL ) + save_history ( hp ); + } +#endif +out: + _exit(status); + /* NOTREACHED */ +} + +/* var.c */ + +static struct var *vartab[VTABSIZE]; + +static int vpcmp(const void *, const void *); +static struct var **findvar(struct var **, const char *); + +/* + * Initialize the variable symbol tables and import the environment + */ + + +#ifdef CONFIG_ASH_GETOPTS +/* + * Safe version of setvar, returns 1 on success 0 on failure. + */ + +int +setvarsafe(const char *name, const char *val, int flags) +{ + int err; + volatile int saveint; + struct jmploc *volatile savehandler = handler; + struct jmploc jmploc; + + SAVEINT(saveint); + if (setjmp(jmploc.loc)) + err = 1; + else { + handler = &jmploc; + setvar(name, val, flags); + err = 0; + } + handler = savehandler; + RESTOREINT(saveint); + return err; +} +#endif + +/* + * Set the value of a variable. The flags argument is ored with the + * flags of the variable. If val is NULL, the variable is unset. + */ + +static void +setvar(const char *name, const char *val, int flags) +{ + char *p, *q; + size_t namelen; + char *nameeq; + size_t vallen; + + q = endofname(name); + p = strchrnul(q, '='); + namelen = p - name; + if (!namelen || p != q) + sh_error("%.*s: bad variable name", namelen, name); + vallen = 0; + if (val == NULL) { + flags |= VUNSET; + } else { + vallen = strlen(val); + } + INTOFF; + p = mempcpy(nameeq = ckmalloc(namelen + vallen + 2), name, namelen); + if (val) { + *p++ = '='; + p = mempcpy(p, val, vallen); + } + *p = '\0'; + setvareq(nameeq, flags | VNOSAVE); + INTON; +} + + +/* + * Same as setvar except that the variable and value are passed in + * the first argument as name=value. Since the first argument will + * be actually stored in the table, it should not be a string that + * will go away. + * Called with interrupts off. + */ + +void +setvareq(char *s, int flags) +{ + struct var *vp, **vpp; + + vpp = hashvar(s); + flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1)); + vp = *findvar(vpp, s); + if (vp) { + if ((vp->flags & (VREADONLY|VDYNAMIC)) == VREADONLY) { + const char *n; + + if (flags & VNOSAVE) + free(s); + n = vp->text; + sh_error("%.*s: is read only", strchrnul(n, '=') - n, n); + } + + if (flags & VNOSET) + return; + + if (vp->func && (flags & VNOFUNC) == 0) + (*vp->func)(strchrnul(s, '=') + 1); + + if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(vp->text); + + flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET); + } else { + if (flags & VNOSET) + return; + /* not found */ + vp = ckmalloc(sizeof (*vp)); + vp->next = *vpp; + vp->func = NULL; + *vpp = vp; + } + if (!(flags & (VTEXTFIXED|VSTACK|VNOSAVE))) + s = savestr(s); + vp->text = s; + vp->flags = flags; +} + + +/* + * Process a linked list of variable assignments. + */ + +static void +listsetvar(struct strlist *list_set_var, int flags) +{ + struct strlist *lp = list_set_var; + + if (!lp) + return; + INTOFF; + do { + setvareq(lp->text, flags); + } while ((lp = lp->next)); + INTON; +} + + +/* + * Find the value of a variable. Returns NULL if not set. + */ + +static char * +lookupvar(const char *name) +{ + struct var *v; + + if ((v = *findvar(hashvar(name), name))) { +#ifdef DYNAMIC_VAR + /* + * Dynamic variables are implemented roughly the same way they are + * in bash. Namely, they're "special" so long as they aren't unset. + * As soon as they're unset, they're no longer dynamic, and dynamic + * lookup will no longer happen at that point. -- PFM. + */ + if((v->flags & VDYNAMIC)) + (*v->func)(NULL); +#endif + if(!(v->flags & VUNSET)) + return strchrnul(v->text, '=') + 1; + } + + return NULL; +} + + +/* + * Search the environment of a builtin command. + */ + +static char * +bltinlookup(const char *name) +{ + struct strlist *sp; + + for (sp = cmdenviron ; sp ; sp = sp->next) { + if (varequal(sp->text, name)) + return strchrnul(sp->text, '=') + 1; + } + return lookupvar(name); +} + + +/* + * Generate a list of variables satisfying the given conditions. + */ + +static char ** +listvars(int on, int off, char ***end) +{ + struct var **vpp; + struct var *vp; + char **ep; + int mask; + + STARTSTACKSTR(ep); + vpp = vartab; + mask = on | off; + do { + for (vp = *vpp ; vp ; vp = vp->next) + if ((vp->flags & mask) == on) { + if (ep == stackstrend()) + ep = growstackstr(); + *ep++ = (char *) vp->text; + } + } while (++vpp < vartab + VTABSIZE); + if (ep == stackstrend()) + ep = growstackstr(); + if (end) + *end = ep; + *ep++ = NULL; + return grabstackstr(ep); +} + + +/* + * POSIX requires that 'set' (but not export or readonly) output the + * variables in lexicographic order - by the locale's collating order (sigh). + * Maybe we could keep them in an ordered balanced binary tree + * instead of hashed lists. + * For now just roll 'em through qsort for printing... + */ + +static int +showvars(const char *sep_prefix, int on, int off) +{ + const char *sep; + char **ep, **epend; + + ep = listvars(on, off, &epend); + qsort(ep, epend - ep, sizeof(char *), vpcmp); + + sep = *sep_prefix ? spcstr : sep_prefix; + + for (; ep < epend; ep++) { + const char *p; + const char *q; + + p = strchrnul(*ep, '='); + q = nullstr; + if (*p) + q = single_quote(++p); + + out1fmt("%s%s%.*s%s\n", sep_prefix, sep, (int)(p - *ep), *ep, q); + } + + return 0; +} + + + +/* + * The export and readonly commands. + */ + +static int +exportcmd(int argc, char **argv) +{ + struct var *vp; + char *name; + const char *p; + char **aptr; + int flag = argv[0][0] == 'r'? VREADONLY : VEXPORT; + int notp; + + notp = nextopt("p") - 'p'; + if (notp && ((name = *(aptr = argptr)))) { + do { + if ((p = strchr(name, '=')) != NULL) { + p++; + } else { + if ((vp = *findvar(hashvar(name), name))) { + vp->flags |= flag; + continue; + } + } + setvar(name, p, flag); + } while ((name = *++aptr) != NULL); + } else { + showvars(argv[0], flag, 0); + } + return 0; +} + + +/* + * Make a variable a local variable. When a variable is made local, it's + * value and flags are saved in a localvar structure. The saved values + * will be restored when the shell function returns. We handle the name + * "-" as a special case. + */ + +static void mklocal(char *name) +{ + struct localvar *lvp; + struct var **vpp; + struct var *vp; + + INTOFF; + lvp = ckmalloc(sizeof (struct localvar)); + if (name[0] == '-' && name[1] == '\0') { + char *p; + p = ckmalloc(sizeof(optlist)); + lvp->text = memcpy(p, optlist, sizeof(optlist)); + vp = NULL; + } else { + char *eq; + + vpp = hashvar(name); + vp = *findvar(vpp, name); + eq = strchr(name, '='); + if (vp == NULL) { + if (eq) + setvareq(name, VSTRFIXED); + else + setvar(name, NULL, VSTRFIXED); + vp = *vpp; /* the new variable */ + lvp->flags = VUNSET; + } else { + lvp->text = vp->text; + lvp->flags = vp->flags; + vp->flags |= VSTRFIXED|VTEXTFIXED; + if (eq) + setvareq(name, 0); + } + } + lvp->vp = vp; + lvp->next = localvars; + localvars = lvp; + INTON; +} + +/* + * The "local" command. + */ + +static int +localcmd(int argc, char **argv) +{ + char *name; + + argv = argptr; + while ((name = *argv++) != NULL) { + mklocal(name); + } + return 0; +} + + +/* + * Called after a function returns. + * Interrupts must be off. + */ + +static void +poplocalvars(void) +{ + struct localvar *lvp; + struct var *vp; + + while ((lvp = localvars) != NULL) { + localvars = lvp->next; + vp = lvp->vp; + TRACE(("poplocalvar %s", vp ? vp->text : "-")); + if (vp == NULL) { /* $- saved */ + memcpy(optlist, lvp->text, sizeof(optlist)); + ckfree(lvp->text); + optschanged(); + } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { + unsetvar(vp->text); + } else { + if (vp->func) + (*vp->func)(strchrnul(lvp->text, '=') + 1); + if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(vp->text); + vp->flags = lvp->flags; + vp->text = lvp->text; + } + ckfree(lvp); + } +} + + +/* + * The unset builtin command. We unset the function before we unset the + * variable to allow a function to be unset when there is a readonly variable + * with the same name. + */ + +int +unsetcmd(int argc, char **argv) +{ + char **ap; + int i; + int flag = 0; + int ret = 0; + + while ((i = nextopt("vf")) != '\0') { + flag = i; + } + + for (ap = argptr; *ap ; ap++) { + if (flag != 'f') { + i = unsetvar(*ap); + ret |= i; + if (!(i & 2)) + continue; + } + if (flag != 'v') + unsetfunc(*ap); + } + return ret & 1; +} + + +/* + * Unset the specified variable. + */ + +int +unsetvar(const char *s) +{ + struct var **vpp; + struct var *vp; + int retval; + + vpp = findvar(hashvar(s), s); + vp = *vpp; + retval = 2; + if (vp) { + int flags = vp->flags; + + retval = 1; + if (flags & VREADONLY) + goto out; +#ifdef DYNAMIC_VAR + vp->flags &= ~VDYNAMIC; +#endif + if (flags & VUNSET) + goto ok; + if ((flags & VSTRFIXED) == 0) { + INTOFF; + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(vp->text); + *vpp = vp->next; + ckfree(vp); + INTON; + } else { + setvar(s, 0, 0); + vp->flags &= ~VEXPORT; + } +ok: + retval = 0; + } + +out: + return retval; +} + + + +/* + * Find the appropriate entry in the hash table from the name. + */ + +static struct var ** +hashvar(const char *p) +{ + unsigned int hashval; + + hashval = ((unsigned char) *p) << 4; + while (*p && *p != '=') + hashval += (unsigned char) *p++; + return &vartab[hashval % VTABSIZE]; +} + + + +/* + * Compares two strings up to the first = or '\0'. The first + * string must be terminated by '='; the second may be terminated by + * either '=' or '\0'. + */ + +int +varcmp(const char *p, const char *q) +{ + int c, d; + + while ((c = *p) == (d = *q)) { + if (!c || c == '=') + goto out; + p++; + q++; + } + if (c == '=') + c = 0; + if (d == '=') + d = 0; +out: + return c - d; +} + +static int +vpcmp(const void *a, const void *b) +{ + return varcmp(*(const char **)a, *(const char **)b); +} + +static struct var ** +findvar(struct var **vpp, const char *name) +{ + for (; *vpp; vpp = &(*vpp)->next) { + if (varequal((*vpp)->text, name)) { + break; + } + } + return vpp; +} +/* setmode.c */ + +#include <sys/times.h> + +static const unsigned char timescmd_str[] = { + ' ', offsetof(struct tms, tms_utime), + '\n', offsetof(struct tms, tms_stime), + ' ', offsetof(struct tms, tms_cutime), + '\n', offsetof(struct tms, tms_cstime), + 0 +}; + +static int timescmd(int ac, char **av) +{ + long int clk_tck, s, t; + const unsigned char *p; + struct tms buf; + + clk_tck = sysconf(_SC_CLK_TCK); + times(&buf); + + p = timescmd_str; + do { + t = *(clock_t *)(((char *) &buf) + p[1]); + s = t / clk_tck; + out1fmt("%ldm%ld.%.3lds%c", + s/60, s%60, + ((t - s * clk_tck) * 1000) / clk_tck, + p[0]); + } while (*(p += 2)); + + return 0; +} + +#ifdef CONFIG_ASH_MATH_SUPPORT +static arith_t +dash_arith(const char *s) +{ + arith_t result; + int errcode = 0; + + INTOFF; + result = arith(s, &errcode); + if (errcode < 0) { + if (errcode == -3) + sh_error("exponent less than 0"); + else if (errcode == -2) + sh_error("divide by zero"); + else if (errcode == -5) + sh_error("expression recursion loop detected"); + else + synerror(s); + } + INTON; + + return result; +} + + +/* + * The let builtin. partial stolen from GNU Bash, the Bourne Again SHell. + * Copyright (C) 1987, 1989, 1991 Free Software Foundation, Inc. + * + * Copyright (C) 2003 Vladimir Oleynik <dzo@simtreas.ru> + */ + +static int +letcmd(int argc, char **argv) +{ + char **ap; + arith_t i = 0; + + ap = argv + 1; + if(!*ap) + sh_error("expression expected"); + for (ap = argv + 1; *ap; ap++) { + i = dash_arith(*ap); + } + + return !i; +} +#endif /* CONFIG_ASH_MATH_SUPPORT */ + +/* miscbltin.c */ + +/* + * Miscellaneous builtins. + */ + +#undef rflag + +#ifdef __GLIBC__ +#if __GLIBC__ == 2 && __GLIBC_MINOR__ < 1 +typedef enum __rlimit_resource rlim_t; +#endif +#endif + + +/* + * The read builtin. The -e option causes backslashes to escape the + * following character. + * + * This uses unbuffered input, which may be avoidable in some cases. + */ + +static int +readcmd(int argc, char **argv) +{ + char **ap; + int backslash; + char c; + int rflag; + char *prompt; + const char *ifs; + char *p; + int startword; + int status; + int i; +#if defined(CONFIG_ASH_READ_NCHARS) + int nch_flag = 0; + int nchars = 0; + int silent = 0; + struct termios tty, old_tty; +#endif +#if defined(CONFIG_ASH_READ_TIMEOUT) + fd_set set; + struct timeval ts; + + ts.tv_sec = ts.tv_usec = 0; +#endif + + rflag = 0; + prompt = NULL; +#if defined(CONFIG_ASH_READ_NCHARS) && defined(CONFIG_ASH_READ_TIMEOUT) + while ((i = nextopt("p:rt:n:s")) != '\0') +#elif defined(CONFIG_ASH_READ_NCHARS) + while ((i = nextopt("p:rn:s")) != '\0') +#elif defined(CONFIG_ASH_READ_TIMEOUT) + while ((i = nextopt("p:rt:")) != '\0') +#else + while ((i = nextopt("p:r")) != '\0') +#endif + { + switch(i) { + case 'p': + prompt = optionarg; + break; +#if defined(CONFIG_ASH_READ_NCHARS) + case 'n': + nchars = strtol(optionarg, &p, 10); + if (*p) + sh_error("invalid count"); + nch_flag = (nchars > 0); + break; + case 's': + silent = 1; + break; +#endif +#if defined(CONFIG_ASH_READ_TIMEOUT) + case 't': + ts.tv_sec = strtol(optionarg, &p, 10); + ts.tv_usec = 0; + if (*p == '.') { + char *p2; + if (*++p) { + int scale; + ts.tv_usec = strtol(p, &p2, 10); + if (*p2) + sh_error("invalid timeout"); + scale = p2 - p; + /* normalize to usec */ + if (scale > 6) + sh_error("invalid timeout"); + while (scale++ < 6) + ts.tv_usec *= 10; + } + } else if (*p) { + sh_error("invalid timeout"); + } + if ( ! ts.tv_sec && ! ts.tv_usec) + sh_error("invalid timeout"); + break; +#endif + case 'r': + rflag = 1; + break; + default: + break; + } + } + if (prompt && isatty(0)) { + out2str(prompt); + } + if (*(ap = argptr) == NULL) + sh_error("arg count"); + if ((ifs = bltinlookup("IFS")) == NULL) + ifs = defifs; +#if defined(CONFIG_ASH_READ_NCHARS) + if (nch_flag || silent) { + tcgetattr(0, &tty); + old_tty = tty; + if (nch_flag) { + tty.c_lflag &= ~ICANON; + tty.c_cc[VMIN] = nchars; + } + if (silent) { + tty.c_lflag &= ~(ECHO|ECHOK|ECHONL); + + } + tcsetattr(0, TCSANOW, &tty); + } +#endif +#if defined(CONFIG_ASH_READ_TIMEOUT) + if (ts.tv_sec || ts.tv_usec) { + FD_ZERO (&set); + FD_SET (0, &set); + + i = select (FD_SETSIZE, &set, NULL, NULL, &ts); + if (!i) { +#if defined(CONFIG_ASH_READ_NCHARS) + if (nch_flag) + tcsetattr(0, TCSANOW, &old_tty); +#endif + return 1; + } + } +#endif + status = 0; + startword = 1; + backslash = 0; + STARTSTACKSTR(p); +#if defined(CONFIG_ASH_READ_NCHARS) + while (!nch_flag || nchars--) +#else + for (;;) +#endif + { + if (read(0, &c, 1) != 1) { + status = 1; + break; + } + if (c == '\0') + continue; + if (backslash) { + backslash = 0; + if (c != '\n') + goto put; + continue; + } + if (!rflag && c == '\\') { + backslash++; + continue; + } + if (c == '\n') + break; + if (startword && *ifs == ' ' && strchr(ifs, c)) { + continue; + } + startword = 0; + if (ap[1] != NULL && strchr(ifs, c) != NULL) { + STACKSTRNUL(p); + setvar(*ap, stackblock(), 0); + ap++; + startword = 1; + STARTSTACKSTR(p); + } else { +put: + STPUTC(c, p); + } + } +#if defined(CONFIG_ASH_READ_NCHARS) + if (nch_flag || silent) + tcsetattr(0, TCSANOW, &old_tty); +#endif + + STACKSTRNUL(p); + /* Remove trailing blanks */ + while ((char *)stackblock() <= --p && strchr(ifs, *p) != NULL) + *p = '\0'; + setvar(*ap, stackblock(), 0); + while (*++ap != NULL) + setvar(*ap, nullstr, 0); + return status; +} + + +static int umaskcmd(int argc, char **argv) +{ + static const char permuser[3] = "ugo"; + static const char permmode[3] = "rwx"; + static const short int permmask[] = { + S_IRUSR, S_IWUSR, S_IXUSR, + S_IRGRP, S_IWGRP, S_IXGRP, + S_IROTH, S_IWOTH, S_IXOTH + }; + + char *ap; + mode_t mask; + int i; + int symbolic_mode = 0; + + while (nextopt("S") != '\0') { + symbolic_mode = 1; + } + + INTOFF; + mask = umask(0); + umask(mask); + INTON; + + if ((ap = *argptr) == NULL) { + if (symbolic_mode) { + char buf[18]; + char *p = buf; + + for (i = 0; i < 3; i++) { + int j; + + *p++ = permuser[i]; + *p++ = '='; + for (j = 0; j < 3; j++) { + if ((mask & permmask[3 * i + j]) == 0) { + *p++ = permmode[j]; + } + } + *p++ = ','; + } + *--p = 0; + puts(buf); + } else { + out1fmt("%.4o\n", mask); + } + } else { + if (is_digit((unsigned char) *ap)) { + mask = 0; + do { + if (*ap >= '8' || *ap < '0') + sh_error(illnum, argv[1]); + mask = (mask << 3) + (*ap - '0'); + } while (*++ap != '\0'); + umask(mask); + } else { + mask = ~mask & 0777; + if (!bb_parse_mode(ap, &mask)) { + sh_error("Illegal mode: %s", ap); + } + umask(~mask & 0777); + } + } + return 0; +} + +/* + * ulimit builtin + * + * This code, originally by Doug Gwyn, Doug Kingston, Eric Gisin, and + * Michael Rendell was ripped from pdksh 5.0.8 and hacked for use with + * ash by J.T. Conklin. + * + * Public domain. + */ + +struct limits { + const char *name; + int cmd; + int factor; /* multiply by to get rlim_{cur,max} values */ + char option; +}; + +static const struct limits limits[] = { +#ifdef RLIMIT_CPU + { "time(seconds)", RLIMIT_CPU, 1, 't' }, +#endif +#ifdef RLIMIT_FSIZE + { "file(blocks)", RLIMIT_FSIZE, 512, 'f' }, +#endif +#ifdef RLIMIT_DATA + { "data(kbytes)", RLIMIT_DATA, 1024, 'd' }, +#endif +#ifdef RLIMIT_STACK + { "stack(kbytes)", RLIMIT_STACK, 1024, 's' }, +#endif +#ifdef RLIMIT_CORE + { "coredump(blocks)", RLIMIT_CORE, 512, 'c' }, +#endif +#ifdef RLIMIT_RSS + { "memory(kbytes)", RLIMIT_RSS, 1024, 'm' }, +#endif +#ifdef RLIMIT_MEMLOCK + { "locked memory(kbytes)", RLIMIT_MEMLOCK, 1024, 'l' }, +#endif +#ifdef RLIMIT_NPROC + { "process", RLIMIT_NPROC, 1, 'p' }, +#endif +#ifdef RLIMIT_NOFILE + { "nofiles", RLIMIT_NOFILE, 1, 'n' }, +#endif +#ifdef RLIMIT_AS + { "vmemory(kbytes)", RLIMIT_AS, 1024, 'v' }, +#endif +#ifdef RLIMIT_LOCKS + { "locks", RLIMIT_LOCKS, 1, 'w' }, +#endif + { (char *) 0, 0, 0, '\0' } +}; + +enum limtype { SOFT = 0x1, HARD = 0x2 }; + +static void printlim(enum limtype how, const struct rlimit *limit, + const struct limits *l) +{ + rlim_t val; + + val = limit->rlim_max; + if (how & SOFT) + val = limit->rlim_cur; + + if (val == RLIM_INFINITY) + out1fmt("unlimited\n"); + else { + val /= l->factor; + out1fmt("%lld\n", (long long) val); + } +} + +int +ulimitcmd(int argc, char **argv) +{ + int c; + rlim_t val = 0; + enum limtype how = SOFT | HARD; + const struct limits *l; + int set, all = 0; + int optc, what; + struct rlimit limit; + + what = 'f'; + while ((optc = nextopt("HSa" +#ifdef RLIMIT_CPU + "t" +#endif +#ifdef RLIMIT_FSIZE + "f" +#endif +#ifdef RLIMIT_DATA + "d" +#endif +#ifdef RLIMIT_STACK + "s" +#endif +#ifdef RLIMIT_CORE + "c" +#endif +#ifdef RLIMIT_RSS + "m" +#endif +#ifdef RLIMIT_MEMLOCK + "l" +#endif +#ifdef RLIMIT_NPROC + "p" +#endif +#ifdef RLIMIT_NOFILE + "n" +#endif +#ifdef RLIMIT_AS + "v" +#endif +#ifdef RLIMIT_LOCKS + "w" +#endif + )) != '\0') + switch (optc) { + case 'H': + how = HARD; + break; + case 'S': + how = SOFT; + break; + case 'a': + all = 1; + break; + default: + what = optc; + } + + for (l = limits; l->option != what; l++) + ; + + set = *argptr ? 1 : 0; + if (set) { + char *p = *argptr; + + if (all || argptr[1]) + sh_error("too many arguments"); + if (strncmp(p, "unlimited\n", 9) == 0) + val = RLIM_INFINITY; + else { + val = (rlim_t) 0; + + while ((c = *p++) >= '0' && c <= '9') + { + val = (val * 10) + (long)(c - '0'); + if (val < (rlim_t) 0) + break; + } + if (c) + sh_error("bad number"); + val *= l->factor; + } + } + if (all) { + for (l = limits; l->name; l++) { + getrlimit(l->cmd, &limit); + out1fmt("%-20s ", l->name); + printlim(how, &limit, l); + } + return 0; + } + + getrlimit(l->cmd, &limit); + if (set) { + if (how & HARD) + limit.rlim_max = val; + if (how & SOFT) + limit.rlim_cur = val; + if (setrlimit(l->cmd, &limit) < 0) + sh_error("error setting limit (%m)"); + } else { + printlim(how, &limit, l); + } + return 0; +} + + +#ifdef CONFIG_ASH_MATH_SUPPORT + +/* Copyright (c) 2001 Aaron Lehmann <aaronl@vitelus.com> + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* This is my infix parser/evaluator. It is optimized for size, intended + * as a replacement for yacc-based parsers. However, it may well be faster + * than a comparable parser written in yacc. The supported operators are + * listed in #defines below. Parens, order of operations, and error handling + * are supported. This code is thread safe. The exact expression format should + * be that which POSIX specifies for shells. */ + +/* The code uses a simple two-stack algorithm. See + * http://www.onthenet.com.au/~grahamis/int2008/week02/lect02.html + * for a detailed explanation of the infix-to-postfix algorithm on which + * this is based (this code differs in that it applies operators immediately + * to the stack instead of adding them to a queue to end up with an + * expression). */ + +/* To use the routine, call it with an expression string and error return + * pointer */ + +/* + * Aug 24, 2001 Manuel Novoa III + * + * Reduced the generated code size by about 30% (i386) and fixed several bugs. + * + * 1) In arith_apply(): + * a) Cached values of *numptr and &(numptr[-1]). + * b) Removed redundant test for zero denominator. + * + * 2) In arith(): + * a) Eliminated redundant code for processing operator tokens by moving + * to a table-based implementation. Also folded handling of parens + * into the table. + * b) Combined all 3 loops which called arith_apply to reduce generated + * code size at the cost of speed. + * + * 3) The following expressions were treated as valid by the original code: + * 1() , 0! , 1 ( *3 ) . + * These bugs have been fixed by internally enclosing the expression in + * parens and then checking that all binary ops and right parens are + * preceded by a valid expression (NUM_TOKEN). + * + * Note: It may be desirable to replace Aaron's test for whitespace with + * ctype's isspace() if it is used by another busybox applet or if additional + * whitespace chars should be considered. Look below the "#include"s for a + * precompiler test. + */ + +/* + * Aug 26, 2001 Manuel Novoa III + * + * Return 0 for null expressions. Pointed out by Vladimir Oleynik. + * + * Merge in Aaron's comments previously posted to the busybox list, + * modified slightly to take account of my changes to the code. + * + */ + +/* + * (C) 2003 Vladimir Oleynik <dzo@simtreas.ru> + * + * - allow access to variable, + * used recursive find value indirection (c=2*2; a="c"; $((a+=2)) produce 6) + * - realize assign syntax (VAR=expr, +=, *= etc) + * - realize exponentiation (** operator) + * - realize comma separated - expr, expr + * - realise ++expr --expr expr++ expr-- + * - realise expr ? expr : expr (but, second expr calculate always) + * - allow hexadecimal and octal numbers + * - was restored loses XOR operator + * - remove one goto label, added three ;-) + * - protect $((num num)) as true zero expr (Manuel`s error) + * - always use special isspace(), see comment from bash ;-) + */ + + +#define arith_isspace(arithval) \ + (arithval == ' ' || arithval == '\n' || arithval == '\t') + + +typedef unsigned char operator; + +/* An operator's token id is a bit of a bitfield. The lower 5 bits are the + * precedence, and 3 high bits are an ID unique across operators of that + * precedence. The ID portion is so that multiple operators can have the + * same precedence, ensuring that the leftmost one is evaluated first. + * Consider * and /. */ + +#define tok_decl(prec,id) (((id)<<5)|(prec)) +#define PREC(op) ((op) & 0x1F) + +#define TOK_LPAREN tok_decl(0,0) + +#define TOK_COMMA tok_decl(1,0) + +#define TOK_ASSIGN tok_decl(2,0) +#define TOK_AND_ASSIGN tok_decl(2,1) +#define TOK_OR_ASSIGN tok_decl(2,2) +#define TOK_XOR_ASSIGN tok_decl(2,3) +#define TOK_PLUS_ASSIGN tok_decl(2,4) +#define TOK_MINUS_ASSIGN tok_decl(2,5) +#define TOK_LSHIFT_ASSIGN tok_decl(2,6) +#define TOK_RSHIFT_ASSIGN tok_decl(2,7) + +#define TOK_MUL_ASSIGN tok_decl(3,0) +#define TOK_DIV_ASSIGN tok_decl(3,1) +#define TOK_REM_ASSIGN tok_decl(3,2) + +/* all assign is right associativity and precedence eq, but (7+3)<<5 > 256 */ +#define convert_prec_is_assing(prec) do { if(prec == 3) prec = 2; } while(0) + +/* conditional is right associativity too */ +#define TOK_CONDITIONAL tok_decl(4,0) +#define TOK_CONDITIONAL_SEP tok_decl(4,1) + +#define TOK_OR tok_decl(5,0) + +#define TOK_AND tok_decl(6,0) + +#define TOK_BOR tok_decl(7,0) + +#define TOK_BXOR tok_decl(8,0) + +#define TOK_BAND tok_decl(9,0) + +#define TOK_EQ tok_decl(10,0) +#define TOK_NE tok_decl(10,1) + +#define TOK_LT tok_decl(11,0) +#define TOK_GT tok_decl(11,1) +#define TOK_GE tok_decl(11,2) +#define TOK_LE tok_decl(11,3) + +#define TOK_LSHIFT tok_decl(12,0) +#define TOK_RSHIFT tok_decl(12,1) + +#define TOK_ADD tok_decl(13,0) +#define TOK_SUB tok_decl(13,1) + +#define TOK_MUL tok_decl(14,0) +#define TOK_DIV tok_decl(14,1) +#define TOK_REM tok_decl(14,2) + +/* exponent is right associativity */ +#define TOK_EXPONENT tok_decl(15,1) + +/* For now unary operators. */ +#define UNARYPREC 16 +#define TOK_BNOT tok_decl(UNARYPREC,0) +#define TOK_NOT tok_decl(UNARYPREC,1) + +#define TOK_UMINUS tok_decl(UNARYPREC+1,0) +#define TOK_UPLUS tok_decl(UNARYPREC+1,1) + +#define PREC_PRE (UNARYPREC+2) + +#define TOK_PRE_INC tok_decl(PREC_PRE, 0) +#define TOK_PRE_DEC tok_decl(PREC_PRE, 1) + +#define PREC_POST (UNARYPREC+3) + +#define TOK_POST_INC tok_decl(PREC_POST, 0) +#define TOK_POST_DEC tok_decl(PREC_POST, 1) + +#define SPEC_PREC (UNARYPREC+4) + +#define TOK_NUM tok_decl(SPEC_PREC, 0) +#define TOK_RPAREN tok_decl(SPEC_PREC, 1) + +#define NUMPTR (*numstackptr) + +static int tok_have_assign(operator op) +{ + operator prec = PREC(op); + + convert_prec_is_assing(prec); + return (prec == PREC(TOK_ASSIGN) || + prec == PREC_PRE || prec == PREC_POST); +} + +static int is_right_associativity(operator prec) +{ + return (prec == PREC(TOK_ASSIGN) || prec == PREC(TOK_EXPONENT) || + prec == PREC(TOK_CONDITIONAL)); +} + + +typedef struct ARITCH_VAR_NUM { + arith_t val; + arith_t contidional_second_val; + char contidional_second_val_initialized; + char *var; /* if NULL then is regular number, + else is variable name */ +} v_n_t; + + +typedef struct CHK_VAR_RECURSIVE_LOOPED { + const char *var; + struct CHK_VAR_RECURSIVE_LOOPED *next; +} chk_var_recursive_looped_t; + +static chk_var_recursive_looped_t *prev_chk_var_recursive; + + +static int arith_lookup_val(v_n_t *t) +{ + if(t->var) { + const char * p = lookupvar(t->var); + + if(p) { + int errcode; + + /* recursive try as expression */ + chk_var_recursive_looped_t *cur; + chk_var_recursive_looped_t cur_save; + + for(cur = prev_chk_var_recursive; cur; cur = cur->next) { + if(strcmp(cur->var, t->var) == 0) { + /* expression recursion loop detected */ + return -5; + } + } + /* save current lookuped var name */ + cur = prev_chk_var_recursive; + cur_save.var = t->var; + cur_save.next = cur; + prev_chk_var_recursive = &cur_save; + + t->val = arith (p, &errcode); + /* restore previous ptr after recursiving */ + prev_chk_var_recursive = cur; + return errcode; + } else { + /* allow undefined var as 0 */ + t->val = 0; + } + } + return 0; +} + +/* "applying" a token means performing it on the top elements on the integer + * stack. For a unary operator it will only change the top element, but a + * binary operator will pop two arguments and push a result */ +static int arith_apply(operator op, v_n_t *numstack, v_n_t **numstackptr) +{ + v_n_t *numptr_m1; + arith_t numptr_val, rez; + int ret_arith_lookup_val; + + if (NUMPTR == numstack) goto err; /* There is no operator that can work + without arguments */ + numptr_m1 = NUMPTR - 1; + + /* check operand is var with noninteger value */ + ret_arith_lookup_val = arith_lookup_val(numptr_m1); + if(ret_arith_lookup_val) + return ret_arith_lookup_val; + + rez = numptr_m1->val; + if (op == TOK_UMINUS) + rez *= -1; + else if (op == TOK_NOT) + rez = !rez; + else if (op == TOK_BNOT) + rez = ~rez; + else if (op == TOK_POST_INC || op == TOK_PRE_INC) + rez++; + else if (op == TOK_POST_DEC || op == TOK_PRE_DEC) + rez--; + else if (op != TOK_UPLUS) { + /* Binary operators */ + + /* check and binary operators need two arguments */ + if (numptr_m1 == numstack) goto err; + + /* ... and they pop one */ + --NUMPTR; + numptr_val = rez; + if (op == TOK_CONDITIONAL) { + if(! numptr_m1->contidional_second_val_initialized) { + /* protect $((expr1 ? expr2)) without ": expr" */ + goto err; + } + rez = numptr_m1->contidional_second_val; + } else if(numptr_m1->contidional_second_val_initialized) { + /* protect $((expr1 : expr2)) without "expr ? " */ + goto err; + } + numptr_m1 = NUMPTR - 1; + if(op != TOK_ASSIGN) { + /* check operand is var with noninteger value for not '=' */ + ret_arith_lookup_val = arith_lookup_val(numptr_m1); + if(ret_arith_lookup_val) + return ret_arith_lookup_val; + } + if (op == TOK_CONDITIONAL) { + numptr_m1->contidional_second_val = rez; + } + rez = numptr_m1->val; + if (op == TOK_BOR || op == TOK_OR_ASSIGN) + rez |= numptr_val; + else if (op == TOK_OR) + rez = numptr_val || rez; + else if (op == TOK_BAND || op == TOK_AND_ASSIGN) + rez &= numptr_val; + else if (op == TOK_BXOR || op == TOK_XOR_ASSIGN) + rez ^= numptr_val; + else if (op == TOK_AND) + rez = rez && numptr_val; + else if (op == TOK_EQ) + rez = (rez == numptr_val); + else if (op == TOK_NE) + rez = (rez != numptr_val); + else if (op == TOK_GE) + rez = (rez >= numptr_val); + else if (op == TOK_RSHIFT || op == TOK_RSHIFT_ASSIGN) + rez >>= numptr_val; + else if (op == TOK_LSHIFT || op == TOK_LSHIFT_ASSIGN) + rez <<= numptr_val; + else if (op == TOK_GT) + rez = (rez > numptr_val); + else if (op == TOK_LT) + rez = (rez < numptr_val); + else if (op == TOK_LE) + rez = (rez <= numptr_val); + else if (op == TOK_MUL || op == TOK_MUL_ASSIGN) + rez *= numptr_val; + else if (op == TOK_ADD || op == TOK_PLUS_ASSIGN) + rez += numptr_val; + else if (op == TOK_SUB || op == TOK_MINUS_ASSIGN) + rez -= numptr_val; + else if (op == TOK_ASSIGN || op == TOK_COMMA) + rez = numptr_val; + else if (op == TOK_CONDITIONAL_SEP) { + if (numptr_m1 == numstack) { + /* protect $((expr : expr)) without "expr ? " */ + goto err; + } + numptr_m1->contidional_second_val_initialized = op; + numptr_m1->contidional_second_val = numptr_val; + } + else if (op == TOK_CONDITIONAL) { + rez = rez ? + numptr_val : numptr_m1->contidional_second_val; + } + else if(op == TOK_EXPONENT) { + if(numptr_val < 0) + return -3; /* exponent less than 0 */ + else { + arith_t c = 1; + + if(numptr_val) + while(numptr_val--) + c *= rez; + rez = c; + } + } + else if(numptr_val==0) /* zero divisor check */ + return -2; + else if (op == TOK_DIV || op == TOK_DIV_ASSIGN) + rez /= numptr_val; + else if (op == TOK_REM || op == TOK_REM_ASSIGN) + rez %= numptr_val; + } + if(tok_have_assign(op)) { + char buf[32]; + + if(numptr_m1->var == NULL) { + /* Hmm, 1=2 ? */ + goto err; + } + /* save to shell variable */ +#ifdef CONFIG_ASH_MATH_SUPPORT_64 + snprintf(buf, sizeof(buf), "%lld", arith_t_type rez); +#else + snprintf(buf, sizeof(buf), "%ld", arith_t_type rez); +#endif + setvar(numptr_m1->var, buf, 0); + /* after saving, make previous value for v++ or v-- */ + if(op == TOK_POST_INC) + rez--; + else if(op == TOK_POST_DEC) + rez++; + } + numptr_m1->val = rez; + /* protect geting var value, is number now */ + numptr_m1->var = NULL; + return 0; + err: + return -1; +} + +/* longest must first */ +static const char op_tokens[] = { + '<','<','=',0, TOK_LSHIFT_ASSIGN, + '>','>','=',0, TOK_RSHIFT_ASSIGN, + '<','<', 0, TOK_LSHIFT, + '>','>', 0, TOK_RSHIFT, + '|','|', 0, TOK_OR, + '&','&', 0, TOK_AND, + '!','=', 0, TOK_NE, + '<','=', 0, TOK_LE, + '>','=', 0, TOK_GE, + '=','=', 0, TOK_EQ, + '|','=', 0, TOK_OR_ASSIGN, + '&','=', 0, TOK_AND_ASSIGN, + '*','=', 0, TOK_MUL_ASSIGN, + '/','=', 0, TOK_DIV_ASSIGN, + '%','=', 0, TOK_REM_ASSIGN, + '+','=', 0, TOK_PLUS_ASSIGN, + '-','=', 0, TOK_MINUS_ASSIGN, + '-','-', 0, TOK_POST_DEC, + '^','=', 0, TOK_XOR_ASSIGN, + '+','+', 0, TOK_POST_INC, + '*','*', 0, TOK_EXPONENT, + '!', 0, TOK_NOT, + '<', 0, TOK_LT, + '>', 0, TOK_GT, + '=', 0, TOK_ASSIGN, + '|', 0, TOK_BOR, + '&', 0, TOK_BAND, + '*', 0, TOK_MUL, + '/', 0, TOK_DIV, + '%', 0, TOK_REM, + '+', 0, TOK_ADD, + '-', 0, TOK_SUB, + '^', 0, TOK_BXOR, + /* uniq */ + '~', 0, TOK_BNOT, + ',', 0, TOK_COMMA, + '?', 0, TOK_CONDITIONAL, + ':', 0, TOK_CONDITIONAL_SEP, + ')', 0, TOK_RPAREN, + '(', 0, TOK_LPAREN, + 0 +}; +/* ptr to ")" */ +#define endexpression &op_tokens[sizeof(op_tokens)-7] + + +static arith_t arith (const char *expr, int *perrcode) +{ + char arithval; /* Current character under analysis */ + operator lasttok, op; + operator prec; + + const char *p = endexpression; + int errcode; + + size_t datasizes = strlen(expr) + 2; + + /* Stack of integers */ + /* The proof that there can be no more than strlen(startbuf)/2+1 integers + * in any given correct or incorrect expression is left as an exercise to + * the reader. */ + v_n_t *numstack = alloca(((datasizes)/2)*sizeof(v_n_t)), + *numstackptr = numstack; + /* Stack of operator tokens */ + operator *stack = alloca((datasizes) * sizeof(operator)), + *stackptr = stack; + + *stackptr++ = lasttok = TOK_LPAREN; /* start off with a left paren */ + *perrcode = errcode = 0; + + while(1) { + if ((arithval = *expr) == 0) { + if (p == endexpression) { + /* Null expression. */ + return 0; + } + + /* This is only reached after all tokens have been extracted from the + * input stream. If there are still tokens on the operator stack, they + * are to be applied in order. At the end, there should be a final + * result on the integer stack */ + + if (expr != endexpression + 1) { + /* If we haven't done so already, */ + /* append a closing right paren */ + expr = endexpression; + /* and let the loop process it. */ + continue; + } + /* At this point, we're done with the expression. */ + if (numstackptr != numstack+1) { + /* ... but if there isn't, it's bad */ + err: + return (*perrcode = -1); + } + if(numstack->var) { + /* expression is $((var)) only, lookup now */ + errcode = arith_lookup_val(numstack); + } + ret: + *perrcode = errcode; + return numstack->val; + } else { + /* Continue processing the expression. */ + if (arith_isspace(arithval)) { + /* Skip whitespace */ + goto prologue; + } + if((p = endofname(expr)) != expr) { + size_t var_name_size = (p-expr) + 1; /* trailing zero */ + + numstackptr->var = alloca(var_name_size); + safe_strncpy(numstackptr->var, expr, var_name_size); + expr = p; + num: + numstackptr->contidional_second_val_initialized = 0; + numstackptr++; + lasttok = TOK_NUM; + continue; + } else if (is_digit(arithval)) { + numstackptr->var = NULL; +#ifdef CONFIG_ASH_MATH_SUPPORT_64 + numstackptr->val = strtoll(expr, (char **) &expr, 0); +#else + numstackptr->val = strtol(expr, (char **) &expr, 0); +#endif + goto num; + } + for(p = op_tokens; ; p++) { + const char *o; + + if(*p == 0) { + /* strange operator not found */ + goto err; + } + for(o = expr; *p && *o == *p; p++) + o++; + if(! *p) { + /* found */ + expr = o - 1; + break; + } + /* skip tail uncompared token */ + while(*p) + p++; + /* skip zero delim */ + p++; + } + op = p[1]; + + /* post grammar: a++ reduce to num */ + if(lasttok == TOK_POST_INC || lasttok == TOK_POST_DEC) + lasttok = TOK_NUM; + + /* Plus and minus are binary (not unary) _only_ if the last + * token was as number, or a right paren (which pretends to be + * a number, since it evaluates to one). Think about it. + * It makes sense. */ + if (lasttok != TOK_NUM) { + switch(op) { + case TOK_ADD: + op = TOK_UPLUS; + break; + case TOK_SUB: + op = TOK_UMINUS; + break; + case TOK_POST_INC: + op = TOK_PRE_INC; + break; + case TOK_POST_DEC: + op = TOK_PRE_DEC; + break; + } + } + /* We don't want a unary operator to cause recursive descent on the + * stack, because there can be many in a row and it could cause an + * operator to be evaluated before its argument is pushed onto the + * integer stack. */ + /* But for binary operators, "apply" everything on the operator + * stack until we find an operator with a lesser priority than the + * one we have just extracted. */ + /* Left paren is given the lowest priority so it will never be + * "applied" in this way. + * if associativity is right and priority eq, applied also skip + */ + prec = PREC(op); + if ((prec > 0 && prec < UNARYPREC) || prec == SPEC_PREC) { + /* not left paren or unary */ + if (lasttok != TOK_NUM) { + /* binary op must be preceded by a num */ + goto err; + } + while (stackptr != stack) { + if (op == TOK_RPAREN) { + /* The algorithm employed here is simple: while we don't + * hit an open paren nor the bottom of the stack, pop + * tokens and apply them */ + if (stackptr[-1] == TOK_LPAREN) { + --stackptr; + /* Any operator directly after a */ + lasttok = TOK_NUM; + /* close paren should consider itself binary */ + goto prologue; + } + } else { + operator prev_prec = PREC(stackptr[-1]); + + convert_prec_is_assing(prec); + convert_prec_is_assing(prev_prec); + if (prev_prec < prec) + break; + /* check right assoc */ + if(prev_prec == prec && is_right_associativity(prec)) + break; + } + errcode = arith_apply(*--stackptr, numstack, &numstackptr); + if(errcode) goto ret; + } + if (op == TOK_RPAREN) { + goto err; + } + } + + /* Push this operator to the stack and remember it. */ + *stackptr++ = lasttok = op; + + prologue: + ++expr; + } + } +} +#endif /* CONFIG_ASH_MATH_SUPPORT */ + + +#if DEBUG +const char *applet_name = "debug stuff usage"; +int main(int argc, char **argv) +{ + return ash_main(argc, argv); +} +#endif + +/*- + * Copyright (c) 1989, 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ diff --git a/shell/bbsh.c b/shell/bbsh.c new file mode 100644 index 000000000..99e4f61fb --- /dev/null +++ b/shell/bbsh.c @@ -0,0 +1,222 @@ +/* vi: set ts=4 : + * + * bbsh - busybox shell + * + * Copyright 2006 Rob Landley <rob@landley.net> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +// A section of code that gets repeatedly or conditionally executed is stored +// as a string and parsed each time it's run. + + + +// Wheee, debugging. + +// Terminal control +#define ENABLE_BBSH_TTY 0 + +// &, fg, bg, jobs. (ctrl-z with tty.) +#define ENABLE_BBSH_JOBCTL 0 + +// Flow control (if, while, for, functions { }) +#define ENABLE_BBSH_FLOWCTL 0 + +#define ENABLE_BBSH_ENVVARS 0 // Environment variable support + +// Local and synthetic variables, fancy prompts, set, $?, etc. +#define ENABLE_BBSH_LOCALVARS 0 + +// Pipes and redirects: | > < >> << && || & () ; +#define ENABLE_BBSH_PIPES 0 + +/* Fun: + + echo `echo hello#comment " woot` and more +*/ + +#include "busybox.h" + +// A single executable, its arguments, and other information we know about it. +#define BBSH_FLAG_EXIT 1 +#define BBSH_FLAG_SUSPEND 2 +#define BBSH_FLAG_PIPE 4 +#define BBSH_FLAG_AND 8 +#define BBSH_FLAG_OR 16 +#define BBSH_FLAG_AMP 32 +#define BBSH_FLAG_SEMI 64 +#define BBSH_FLAG_PAREN 128 + +// What we know about a single process. +struct command { + struct command *next; + int flags; // exit, suspend, && || + int pid; // pid (or exit code) + int argc; + char *argv[0]; +}; + +// A collection of processes piped into/waiting on each other. +struct pipeline { + struct pipeline *next; + int job_id; + struct command *cmd; + char *cmdline; + int cmdlinelen; +}; + +static void free_list(void *list, void (*freeit)(void *data)) +{ + while(list) { + void **next = (void **)list; + void *list_next = *next; + freeit(list); + free(list); + list = list_next; + } +} + +// Parse one word from the command line, appending one or more argv[] entries +// to struct command. Handles environment variable substitution and +// substrings. Returns pointer to next used byte, or NULL if it +// hit an ending token. +static char *parse_word(char *start, struct command **cmd) +{ + char *end; + + // Detect end of line (and truncate line at comment) + if (ENABLE_BBSH_PIPES && strchr("><&|(;", *start)) return 0; + + // Grab next word. (Add dequote and envvar logic here) + end = start; + while (*end && !isspace(*end)) end++; + (*cmd)->argv[(*cmd)->argc++] = xstrndup(start, end-start); + + // Allocate more space if there's no room for NULL terminator. + + if (!((*cmd)->argc & 7)) + *cmd = xrealloc(*cmd, + sizeof(struct command) + ((*cmd)->argc+8)*sizeof(char *)); + (*cmd)->argv[(*cmd)->argc] = 0; + return end; +} + +// Parse a line of text into a pipeline. +// Returns a pointer to the next line. + +static char *parse_pipeline(char *cmdline, struct pipeline *line) +{ + struct command **cmd = &(line->cmd); + char *start = line->cmdline = cmdline; + + if (!cmdline) return 0; + + if (ENABLE_BBSH_JOBCTL) line->cmdline = cmdline; + + // Parse command into argv[] + for (;;) { + char *end; + + // Skip leading whitespace and detect end of line. + start = skip_whitespace(start); + if (!*start || *start=='#') { + if (ENABLE_BBSH_JOBCTL) line->cmdlinelen = start-cmdline; + return 0; + } + + // Allocate next command structure if necessary + if (!*cmd) *cmd = xzalloc(sizeof(struct command)+8*sizeof(char *)); + + // Parse next argument and add the results to argv[] + end = parse_word(start, cmd); + + // If we hit the end of this command, how did it end? + if (!end) { + if (ENABLE_BBSH_PIPES && *start) { + if (*start==';') { + start++; + break; + } + // handle | & < > >> << || && + } + break; + } + start = end; + } + + if (ENABLE_BBSH_JOBCTL) line->cmdlinelen = start-cmdline; + + return start; +} + +// Execute the commands in a pipeline +static int run_pipeline(struct pipeline *line) +{ + struct command *cmd = line->cmd; + if (!cmd || !cmd->argc) return 0; + + // Handle local commands. This is totally fake and plastic. + if (cmd->argc==2 && !strcmp(cmd->argv[0],"cd")) + chdir(cmd->argv[1]); + else if(!strcmp(cmd->argv[0],"exit")) + exit(cmd->argc>1 ? atoi(cmd->argv[1]) : 0); + else { + int status; + pid_t pid=fork(); + if(!pid) { + run_applet_by_name(cmd->argv[0],cmd->argc,cmd->argv); + execvp(cmd->argv[0],cmd->argv); + printf("No %s",cmd->argv[0]); + exit(1); + } else waitpid(pid, &status, 0); + } + + return 0; +} + +static void free_cmd(void *data) +{ + struct command *cmd=(struct command *)data; + + while(cmd->argc) free(cmd->argv[--cmd->argc]); +} + + +static void handle(char *command) +{ + struct pipeline line; + char *start = command; + + for (;;) { + memset(&line,0,sizeof(struct pipeline)); + start = parse_pipeline(start, &line); + if (!line.cmd) break; + + run_pipeline(&line); + free_list(line.cmd, free_cmd); + } +} + +int bbsh_main(int argc, char *argv[]) +{ + char *command=NULL; + FILE *f; + + getopt32(argc, argv, "c:", &command); + + f = argv[optind] ? xfopen(argv[optind],"r") : NULL; + if (command) handle(command); + else { + unsigned cmdlen=0; + for (;;) { + if(!f) putchar('$'); + if(1 > getline(&command, &cmdlen,f ? : stdin)) break; + + handle(command); + } + if (ENABLE_FEATURE_CLEAN_UP) free(command); + } + + return 1; +} diff --git a/shell/cmdedit.c b/shell/cmdedit.c new file mode 100644 index 000000000..ceaa2e885 --- /dev/null +++ b/shell/cmdedit.c @@ -0,0 +1,1924 @@ +/* vi: set sw=4 ts=4: */ +/* + * Termios command line History and Editing. + * + * Copyright (c) 1986-2003 may safely be consumed by a BSD or GPL license. + * Written by: Vladimir Oleynik <dzo@simtreas.ru> + * + * Used ideas: + * Adam Rogoyski <rogoyski@cs.utexas.edu> + * Dave Cinege <dcinege@psychosis.com> + * Jakub Jelinek (c) 1995 + * Erik Andersen <andersen@codepoet.org> (Majorly adjusted for busybox) + * + * This code is 'as is' with no warranty. + * + * + */ + +/* + Usage and Known bugs: + Terminal key codes are not extensive, and more will probably + need to be added. This version was created on Debian GNU/Linux 2.x. + Delete, Backspace, Home, End, and the arrow keys were tested + to work in an Xterm and console. Ctrl-A also works as Home. + Ctrl-E also works as End. + + Small bugs (simple effect): + - not true viewing if terminal size (x*y symbols) less + size (prompt + editor`s line + 2 symbols) + - not true viewing if length prompt less terminal width + */ + + +#include "busybox.h" +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <ctype.h> +#include <signal.h> +#include <limits.h> + +#include "cmdedit.h" + + +#ifdef CONFIG_LOCALE_SUPPORT +#define Isprint(c) isprint((c)) +#else +#define Isprint(c) ( (c) >= ' ' && (c) != ((unsigned char)'\233') ) +#endif + +#ifdef TEST + +/* pretect redefined for test */ +#undef CONFIG_FEATURE_COMMAND_EDITING +#undef CONFIG_FEATURE_COMMAND_TAB_COMPLETION +#undef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION +#undef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT +#undef CONFIG_FEATURE_CLEAN_UP + +#define CONFIG_FEATURE_COMMAND_EDITING +#define CONFIG_FEATURE_COMMAND_TAB_COMPLETION +#define CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION +#define CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT +#define CONFIG_FEATURE_CLEAN_UP + +#endif /* TEST */ + +#ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION +#include <dirent.h> +#include <sys/stat.h> +#endif + +#ifdef CONFIG_FEATURE_COMMAND_EDITING + +#if defined(CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION) || defined(CONFIG_FEATURE_SH_FANCY_PROMPT) +#define CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR +#endif + +/* Maximum length of the linked list for the command line history */ +#ifndef CONFIG_FEATURE_COMMAND_HISTORY +#define MAX_HISTORY 15 +#else +#define MAX_HISTORY (CONFIG_FEATURE_COMMAND_HISTORY + 0) +#endif + +#if MAX_HISTORY > 0 +static char *history[MAX_HISTORY+1]; /* history + current */ +/* saved history lines */ +static int n_history; +/* current pointer to history line */ +static int cur_history; +#endif + +#include <termios.h> +#define setTermSettings(fd,argp) tcsetattr(fd,TCSANOW,argp) +#define getTermSettings(fd,argp) tcgetattr(fd, argp); + +/* Current termio and the previous termio before starting sh */ +static struct termios initial_settings, new_settings; + + +static +volatile int cmdedit_termw = 80; /* actual terminal width */ +static +volatile int handlers_sets = 0; /* Set next bites: */ + +enum { + SET_ATEXIT = 1, /* when atexit() has been called + and get euid,uid,gid to fast compare */ + SET_WCHG_HANDLERS = 2, /* winchg signal handler */ + SET_RESET_TERM = 4, /* if the terminal needs to be reset upon exit */ +}; + + +static int cmdedit_x; /* real x terminal position */ +static int cmdedit_y; /* pseudoreal y terminal position */ +static int cmdedit_prmt_len; /* lenght prompt without colores string */ + +static int cursor; /* required global for signal handler */ +static int len; /* --- "" - - "" - -"- --""-- --""--- */ +static char *command_ps; /* --- "" - - "" - -"- --""-- --""--- */ +static +#ifndef CONFIG_FEATURE_SH_FANCY_PROMPT + const +#endif +char *cmdedit_prompt; /* --- "" - - "" - -"- --""-- --""--- */ + +#ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR +static char *user_buf = ""; +static char *home_pwd_buf = ""; +static int my_euid; +#endif + +#ifdef CONFIG_FEATURE_SH_FANCY_PROMPT +static char *hostname_buf; +static int num_ok_lines = 1; +#endif + + +#ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION + +#ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR +static int my_euid; +#endif + +static int my_uid; +static int my_gid; + +#endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */ + +static void cmdedit_setwidth(int w, int redraw_flg); + +static void win_changed(int nsig) +{ + static sighandler_t previous_SIGWINCH_handler; /* for reset */ + + /* emulate || signal call */ + if (nsig == -SIGWINCH || nsig == SIGWINCH) { + int width = 0; + get_terminal_width_height(0, &width, NULL); + cmdedit_setwidth(width, nsig == SIGWINCH); + } + /* Unix not all standart in recall signal */ + + if (nsig == -SIGWINCH) /* save previous handler */ + previous_SIGWINCH_handler = signal(SIGWINCH, win_changed); + else if (nsig == SIGWINCH) /* signaled called handler */ + signal(SIGWINCH, win_changed); /* set for next call */ + else /* nsig == 0 */ + /* set previous handler */ + signal(SIGWINCH, previous_SIGWINCH_handler); /* reset */ +} + +static void cmdedit_reset_term(void) +{ + if ((handlers_sets & SET_RESET_TERM) != 0) { +/* sparc and other have broken termios support: use old termio handling. */ + setTermSettings(STDIN_FILENO, (void *) &initial_settings); + handlers_sets &= ~SET_RESET_TERM; + } + if ((handlers_sets & SET_WCHG_HANDLERS) != 0) { + /* reset SIGWINCH handler to previous (default) */ + win_changed(0); + handlers_sets &= ~SET_WCHG_HANDLERS; + } + fflush(stdout); +} + + +/* special for recount position for scroll and remove terminal margin effect */ +static void cmdedit_set_out_char(int next_char) +{ + + int c = (int)((unsigned char) command_ps[cursor]); + + if (c == 0) + c = ' '; /* destroy end char? */ +#ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT + if (!Isprint(c)) { /* Inverse put non-printable characters */ + if (c >= 128) + c -= 128; + if (c < ' ') + c += '@'; + if (c == 127) + c = '?'; + printf("\033[7m%c\033[0m", c); + } else +#endif + if (initial_settings.c_lflag & ECHO) putchar(c); + if (++cmdedit_x >= cmdedit_termw) { + /* terminal is scrolled down */ + cmdedit_y++; + cmdedit_x = 0; + + if (!next_char) + next_char = ' '; + /* destroy "(auto)margin" */ + putchar(next_char); + putchar('\b'); + } + cursor++; +} + +/* Move to end line. Bonus: rewrite line from cursor */ +static void input_end(void) +{ + while (cursor < len) + cmdedit_set_out_char(0); +} + +/* Go to the next line */ +static void goto_new_line(void) +{ + input_end(); + if (cmdedit_x) + putchar('\n'); +} + + +static void out1str(const char *s) +{ + if ( s ) + fputs(s, stdout); +} + +static void beep(void) +{ + putchar('\007'); +} + +/* Move back one character */ +/* special for slow terminal */ +static void input_backward(int num) +{ + if (num > cursor) + num = cursor; + cursor -= num; /* new cursor (in command, not terminal) */ + + if (cmdedit_x >= num) { /* no to up line */ + cmdedit_x -= num; + if (num < 4) + while (num-- > 0) + putchar('\b'); + else + printf("\033[%dD", num); + } else { + int count_y; + + if (cmdedit_x) { + putchar('\r'); /* back to first terminal pos. */ + num -= cmdedit_x; /* set previous backward */ + } + count_y = 1 + num / cmdedit_termw; + printf("\033[%dA", count_y); + cmdedit_y -= count_y; + /* require forward after uping */ + cmdedit_x = cmdedit_termw * count_y - num; + printf("\033[%dC", cmdedit_x); /* set term cursor */ + } +} + +static void put_prompt(void) +{ + out1str(cmdedit_prompt); + cmdedit_x = cmdedit_prmt_len; /* count real x terminal position */ + cursor = 0; + cmdedit_y = 0; /* new quasireal y */ +} + +#ifndef CONFIG_FEATURE_SH_FANCY_PROMPT +static void parse_prompt(const char *prmt_ptr) +{ + cmdedit_prompt = prmt_ptr; + cmdedit_prmt_len = strlen(prmt_ptr); + put_prompt(); +} +#else +static void parse_prompt(const char *prmt_ptr) +{ + int prmt_len = 0; + size_t cur_prmt_len = 0; + char flg_not_length = '['; + char *prmt_mem_ptr = xzalloc(1); + char *pwd_buf = xgetcwd(0); + char buf2[PATH_MAX + 1]; + char buf[2]; + char c; + char *pbuf; + + if (!pwd_buf) { + pwd_buf=(char *)bb_msg_unknown; + } + + while (*prmt_ptr) { + pbuf = buf; + pbuf[1] = 0; + c = *prmt_ptr++; + if (c == '\\') { + const char *cp = prmt_ptr; + int l; + + c = bb_process_escape_sequence(&prmt_ptr); + if(prmt_ptr==cp) { + if (*cp == 0) + break; + c = *prmt_ptr++; + switch (c) { +#ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR + case 'u': + pbuf = user_buf; + break; +#endif + case 'h': + pbuf = hostname_buf; + if (pbuf == 0) { + pbuf = xzalloc(256); + if (gethostname(pbuf, 255) < 0) { + strcpy(pbuf, "?"); + } else { + char *s = strchr(pbuf, '.'); + + if (s) + *s = 0; + } + hostname_buf = pbuf; + } + break; + case '$': + c = my_euid == 0 ? '#' : '$'; + break; +#ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR + case 'w': + pbuf = pwd_buf; + l = strlen(home_pwd_buf); + if (home_pwd_buf[0] != 0 && + strncmp(home_pwd_buf, pbuf, l) == 0 && + (pbuf[l]=='/' || pbuf[l]=='\0') && + strlen(pwd_buf+l)<PATH_MAX) { + pbuf = buf2; + *pbuf = '~'; + strcpy(pbuf+1, pwd_buf+l); + } + break; +#endif + case 'W': + pbuf = pwd_buf; + cp = strrchr(pbuf,'/'); + if ( (cp != NULL) && (cp != pbuf) ) + pbuf += (cp-pbuf)+1; + break; + case '!': + snprintf(pbuf = buf2, sizeof(buf2), "%d", num_ok_lines); + break; + case 'e': case 'E': /* \e \E = \033 */ + c = '\033'; + break; + case 'x': case 'X': + for (l = 0; l < 3;) { + int h; + buf2[l++] = *prmt_ptr; + buf2[l] = 0; + h = strtol(buf2, &pbuf, 16); + if (h > UCHAR_MAX || (pbuf - buf2) < l) { + l--; + break; + } + prmt_ptr++; + } + buf2[l] = 0; + c = (char)strtol(buf2, 0, 16); + if(c==0) + c = '?'; + pbuf = buf; + break; + case '[': case ']': + if (c == flg_not_length) { + flg_not_length = flg_not_length == '[' ? ']' : '['; + continue; + } + break; + } + } + } + if(pbuf == buf) + *pbuf = c; + cur_prmt_len = strlen(pbuf); + prmt_len += cur_prmt_len; + if (flg_not_length != ']') + cmdedit_prmt_len += cur_prmt_len; + prmt_mem_ptr = strcat(xrealloc(prmt_mem_ptr, prmt_len+1), pbuf); + } + if(pwd_buf!=(char *)bb_msg_unknown) + free(pwd_buf); + cmdedit_prompt = prmt_mem_ptr; + put_prompt(); +} +#endif + + +/* draw prompt, editor line, and clear tail */ +static void redraw(int y, int back_cursor) +{ + if (y > 0) /* up to start y */ + printf("\033[%dA", y); + putchar('\r'); + put_prompt(); + input_end(); /* rewrite */ + printf("\033[J"); /* destroy tail after cursor */ + input_backward(back_cursor); +} + +#ifdef CONFIG_FEATURE_COMMAND_EDITING_VI +#define DELBUFSIZ 128 +static char *delbuf; /* a (malloced) place to store deleted characters */ +static char *delp; +static char newdelflag; /* whether delbuf should be reused yet */ +#endif + +/* Delete the char in front of the cursor, optionally saving it + * for later putback */ +static void input_delete(int save) +{ + int j = cursor; + + if (j == len) + return; + +#ifdef CONFIG_FEATURE_COMMAND_EDITING_VI + if (save) { + if (newdelflag) { + if (!delbuf) + delbuf = malloc(DELBUFSIZ); + /* safe if malloc fails */ + delp = delbuf; + newdelflag = 0; + } + if (delbuf && (delp - delbuf < DELBUFSIZ)) + *delp++ = command_ps[j]; + } +#endif + + strcpy(command_ps + j, command_ps + j + 1); + len--; + input_end(); /* rewrite new line */ + cmdedit_set_out_char(0); /* destroy end char */ + input_backward(cursor - j); /* back to old pos cursor */ +} + +#ifdef CONFIG_FEATURE_COMMAND_EDITING_VI +static void put(void) +{ + int ocursor, j = delp - delbuf; + if (j == 0) + return; + ocursor = cursor; + /* open hole and then fill it */ + memmove(command_ps + cursor + j, command_ps + cursor, len - cursor + 1); + strncpy(command_ps + cursor, delbuf, j); + len += j; + input_end(); /* rewrite new line */ + input_backward(cursor-ocursor-j+1); /* at end of new text */ +} +#endif + +/* Delete the char in back of the cursor */ +static void input_backspace(void) +{ + if (cursor > 0) { + input_backward(1); + input_delete(0); + } +} + + +/* Move forward one character */ +static void input_forward(void) +{ + if (cursor < len) + cmdedit_set_out_char(command_ps[cursor + 1]); +} + +static void cmdedit_setwidth(int w, int redraw_flg) +{ + cmdedit_termw = cmdedit_prmt_len + 2; + if (w <= cmdedit_termw) { + cmdedit_termw = cmdedit_termw % w; + } + if (w > cmdedit_termw) { + cmdedit_termw = w; + + if (redraw_flg) { + /* new y for current cursor */ + int new_y = (cursor + cmdedit_prmt_len) / w; + + /* redraw */ + redraw((new_y >= cmdedit_y ? new_y : cmdedit_y), len - cursor); + fflush(stdout); + } + } +} + +static void cmdedit_init(void) +{ + cmdedit_prmt_len = 0; + if ((handlers_sets & SET_WCHG_HANDLERS) == 0) { + /* emulate usage handler to set handler and call yours work */ + win_changed(-SIGWINCH); + handlers_sets |= SET_WCHG_HANDLERS; + } + + if ((handlers_sets & SET_ATEXIT) == 0) { +#ifdef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR + struct passwd *entry; + + my_euid = geteuid(); + entry = getpwuid(my_euid); + if (entry) { + user_buf = xstrdup(entry->pw_name); + home_pwd_buf = xstrdup(entry->pw_dir); + } +#endif + +#ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION + +#ifndef CONFIG_FEATURE_GETUSERNAME_AND_HOMEDIR + my_euid = geteuid(); +#endif + my_uid = getuid(); + my_gid = getgid(); +#endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */ + handlers_sets |= SET_ATEXIT; + atexit(cmdedit_reset_term); /* be sure to do this only once */ + } +} + +#ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION + +static char **matches; +static int num_matches; +static char *add_char_to_match; + +static void add_match(char *matched, int add_char) +{ + int nm = num_matches; + int nm1 = nm + 1; + + matches = xrealloc(matches, nm1 * sizeof(char *)); + add_char_to_match = xrealloc(add_char_to_match, nm1); + matches[nm] = matched; + add_char_to_match[nm] = (char)add_char; + num_matches++; +} + +static int is_execute(const struct stat *st) +{ + if ((!my_euid && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))) || + (my_uid == st->st_uid && (st->st_mode & S_IXUSR)) || + (my_gid == st->st_gid && (st->st_mode & S_IXGRP)) || + (st->st_mode & S_IXOTH)) return TRUE; + return FALSE; +} + +#ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION + +static void username_tab_completion(char *ud, char *with_shash_flg) +{ + struct passwd *entry; + int userlen; + + ud++; /* ~user/... to user/... */ + userlen = strlen(ud); + + if (with_shash_flg) { /* "~/..." or "~user/..." */ + char *sav_ud = ud - 1; + char *home = 0; + char *temp; + + if (*ud == '/') { /* "~/..." */ + home = home_pwd_buf; + } else { + /* "~user/..." */ + temp = strchr(ud, '/'); + *temp = 0; /* ~user\0 */ + entry = getpwnam(ud); + *temp = '/'; /* restore ~user/... */ + ud = temp; + if (entry) + home = entry->pw_dir; + } + if (home) { + if ((userlen + strlen(home) + 1) < BUFSIZ) { + char temp2[BUFSIZ]; /* argument size */ + + /* /home/user/... */ + sprintf(temp2, "%s%s", home, ud); + strcpy(sav_ud, temp2); + } + } + } else { + /* "~[^/]*" */ + setpwent(); + + while ((entry = getpwent()) != NULL) { + /* Null usernames should result in all users as possible completions. */ + if ( /*!userlen || */ !strncmp(ud, entry->pw_name, userlen)) { + add_match(xasprintf("~%s", entry->pw_name), '/'); + } + } + + endpwent(); + } +} +#endif /* CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION */ + +enum { + FIND_EXE_ONLY = 0, + FIND_DIR_ONLY = 1, + FIND_FILE_ONLY = 2, +}; + +#ifdef CONFIG_ASH +const char *cmdedit_path_lookup; +#else +#define cmdedit_path_lookup getenv("PATH") +#endif + +static int path_parse(char ***p, int flags) +{ + int npth; + const char *tmp; + const char *pth; + + /* if not setenv PATH variable, to search cur dir "." */ + if (flags != FIND_EXE_ONLY || (pth = cmdedit_path_lookup) == 0 || + /* PATH=<empty> or PATH=:<empty> */ + *pth == 0 || (*pth == ':' && *(pth + 1) == 0)) { + return 1; + } + + tmp = pth; + npth = 0; + + for (;;) { + npth++; /* count words is + 1 count ':' */ + tmp = strchr(tmp, ':'); + if (tmp) { + if (*++tmp == 0) + break; /* :<empty> */ + } else + break; + } + + *p = xmalloc(npth * sizeof(char *)); + + tmp = pth; + (*p)[0] = xstrdup(tmp); + npth = 1; /* count words is + 1 count ':' */ + + for (;;) { + tmp = strchr(tmp, ':'); + if (tmp) { + (*p)[0][(tmp - pth)] = 0; /* ':' -> '\0' */ + if (*++tmp == 0) + break; /* :<empty> */ + } else + break; + (*p)[npth++] = &(*p)[0][(tmp - pth)]; /* p[next]=p[0][&'\0'+1] */ + } + + return npth; +} + +static char *add_quote_for_spec_chars(char *found, int add) +{ + int l = 0; + char *s = xmalloc((strlen(found) + 1) * 2); + + while (*found) { + if (strchr(" `\"#$%^&*()=+{}[]:;\'|\\<>", *found)) + s[l++] = '\\'; + s[l++] = *found++; + } + if(add) + s[l++] = (char)add; + s[l] = 0; + return s; +} + +static void exe_n_cwd_tab_completion(char *command, int type) +{ + DIR *dir; + struct dirent *next; + char dirbuf[BUFSIZ]; + struct stat st; + char *path1[1]; + char **paths = path1; + int npaths; + int i; + char *found; + char *pfind = strrchr(command, '/'); + + path1[0] = "."; + + if (pfind == NULL) { + /* no dir, if flags==EXE_ONLY - get paths, else "." */ + npaths = path_parse(&paths, type); + pfind = command; + } else { + /* with dir */ + /* save for change */ + strcpy(dirbuf, command); + /* set dir only */ + dirbuf[(pfind - command) + 1] = 0; +#ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION + if (dirbuf[0] == '~') /* ~/... or ~user/... */ + username_tab_completion(dirbuf, dirbuf); +#endif + /* "strip" dirname in command */ + pfind++; + + paths[0] = dirbuf; + npaths = 1; /* only 1 dir */ + } + + for (i = 0; i < npaths; i++) { + + dir = opendir(paths[i]); + if (!dir) /* Don't print an error */ + continue; + + while ((next = readdir(dir)) != NULL) { + char *str_found = next->d_name; + int add_chr = 0; + + /* matched ? */ + if (strncmp(str_found, pfind, strlen(pfind))) + continue; + /* not see .name without .match */ + if (*str_found == '.' && *pfind == 0) { + if (*paths[i] == '/' && paths[i][1] == 0 + && str_found[1] == 0) str_found = ""; /* only "/" */ + else + continue; + } + found = concat_path_file(paths[i], str_found); + /* hmm, remover in progress? */ + if (stat(found, &st) < 0) + goto cont; + /* find with dirs ? */ + if (paths[i] != dirbuf) + strcpy(found, next->d_name); /* only name */ + if (S_ISDIR(st.st_mode)) { + /* name is directory */ + char *e = found + strlen(found) - 1; + + add_chr = '/'; + if(*e == '/') + *e = '\0'; + } else { + /* not put found file if search only dirs for cd */ + if (type == FIND_DIR_ONLY) + goto cont; + if (type == FIND_FILE_ONLY || + (type == FIND_EXE_ONLY && is_execute(&st))) + add_chr = ' '; + } + /* Add it to the list */ + add_match(found, add_chr); + continue; +cont: + free(found); + } + closedir(dir); + } + if (paths != path1) { + free(paths[0]); /* allocated memory only in first member */ + free(paths); + } +} + + +#define QUOT (UCHAR_MAX+1) + +#define collapse_pos(is, in) { \ + memmove(int_buf+(is), int_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); \ + memmove(pos_buf+(is), pos_buf+(in), (BUFSIZ+1-(is)-(in))*sizeof(int)); } + +static int find_match(char *matchBuf, int *len_with_quotes) +{ + int i, j; + int command_mode; + int c, c2; + int int_buf[BUFSIZ + 1]; + int pos_buf[BUFSIZ + 1]; + + /* set to integer dimension characters and own positions */ + for (i = 0;; i++) { + int_buf[i] = (int) ((unsigned char) matchBuf[i]); + if (int_buf[i] == 0) { + pos_buf[i] = -1; /* indicator end line */ + break; + } else + pos_buf[i] = i; + } + + /* mask \+symbol and convert '\t' to ' ' */ + for (i = j = 0; matchBuf[i]; i++, j++) + if (matchBuf[i] == '\\') { + collapse_pos(j, j + 1); + int_buf[j] |= QUOT; + i++; +#ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT + if (matchBuf[i] == '\t') /* algorithm equivalent */ + int_buf[j] = ' ' | QUOT; +#endif + } +#ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT + else if (matchBuf[i] == '\t') + int_buf[j] = ' '; +#endif + + /* mask "symbols" or 'symbols' */ + c2 = 0; + for (i = 0; int_buf[i]; i++) { + c = int_buf[i]; + if (c == '\'' || c == '"') { + if (c2 == 0) + c2 = c; + else { + if (c == c2) + c2 = 0; + else + int_buf[i] |= QUOT; + } + } else if (c2 != 0 && c != '$') + int_buf[i] |= QUOT; + } + + /* skip commands with arguments if line have commands delimiters */ + /* ';' ';;' '&' '|' '&&' '||' but `>&' `<&' `>|' */ + for (i = 0; int_buf[i]; i++) { + c = int_buf[i]; + c2 = int_buf[i + 1]; + j = i ? int_buf[i - 1] : -1; + command_mode = 0; + if (c == ';' || c == '&' || c == '|') { + command_mode = 1 + (c == c2); + if (c == '&') { + if (j == '>' || j == '<') + command_mode = 0; + } else if (c == '|' && j == '>') + command_mode = 0; + } + if (command_mode) { + collapse_pos(0, i + command_mode); + i = -1; /* hack incremet */ + } + } + /* collapse `command...` */ + for (i = 0; int_buf[i]; i++) + if (int_buf[i] == '`') { + for (j = i + 1; int_buf[j]; j++) + if (int_buf[j] == '`') { + collapse_pos(i, j + 1); + j = 0; + break; + } + if (j) { + /* not found close ` - command mode, collapse all previous */ + collapse_pos(0, i + 1); + break; + } else + i--; /* hack incremet */ + } + + /* collapse (command...(command...)...) or {command...{command...}...} */ + c = 0; /* "recursive" level */ + c2 = 0; + for (i = 0; int_buf[i]; i++) + if (int_buf[i] == '(' || int_buf[i] == '{') { + if (int_buf[i] == '(') + c++; + else + c2++; + collapse_pos(0, i + 1); + i = -1; /* hack incremet */ + } + for (i = 0; pos_buf[i] >= 0 && (c > 0 || c2 > 0); i++) + if ((int_buf[i] == ')' && c > 0) || (int_buf[i] == '}' && c2 > 0)) { + if (int_buf[i] == ')') + c--; + else + c2--; + collapse_pos(0, i + 1); + i = -1; /* hack incremet */ + } + + /* skip first not quote space */ + for (i = 0; int_buf[i]; i++) + if (int_buf[i] != ' ') + break; + if (i) + collapse_pos(0, i); + + /* set find mode for completion */ + command_mode = FIND_EXE_ONLY; + for (i = 0; int_buf[i]; i++) + if (int_buf[i] == ' ' || int_buf[i] == '<' || int_buf[i] == '>') { + if (int_buf[i] == ' ' && command_mode == FIND_EXE_ONLY + && matchBuf[pos_buf[0]]=='c' + && matchBuf[pos_buf[1]]=='d' ) + command_mode = FIND_DIR_ONLY; + else { + command_mode = FIND_FILE_ONLY; + break; + } + } + /* "strlen" */ + for (i = 0; int_buf[i]; i++); + /* find last word */ + for (--i; i >= 0; i--) { + c = int_buf[i]; + if (c == ' ' || c == '<' || c == '>' || c == '|' || c == '&') { + collapse_pos(0, i + 1); + break; + } + } + /* skip first not quoted '\'' or '"' */ + for (i = 0; int_buf[i] == '\'' || int_buf[i] == '"'; i++); + /* collapse quote or unquote // or /~ */ + while ((int_buf[i] & ~QUOT) == '/' && + ((int_buf[i + 1] & ~QUOT) == '/' + || (int_buf[i + 1] & ~QUOT) == '~')) { + i++; + } + + /* set only match and destroy quotes */ + j = 0; + for (c = 0; pos_buf[i] >= 0; i++) { + matchBuf[c++] = matchBuf[pos_buf[i]]; + j = pos_buf[i] + 1; + } + matchBuf[c] = 0; + /* old lenght matchBuf with quotes symbols */ + *len_with_quotes = j ? j - pos_buf[0] : 0; + + return command_mode; +} + +/* + display by column original ideas from ls applet, + very optimize by my :) +*/ +static void showfiles(void) +{ + int ncols, row; + int column_width = 0; + int nfiles = num_matches; + int nrows = nfiles; + char str_add_chr[2]; + int l; + + /* find the longest file name- use that as the column width */ + for (row = 0; row < nrows; row++) { + l = strlen(matches[row]); + if(add_char_to_match[row]) + l++; + if (column_width < l) + column_width = l; + } + column_width += 2; /* min space for columns */ + ncols = cmdedit_termw / column_width; + + if (ncols > 1) { + nrows /= ncols; + if(nfiles % ncols) + nrows++; /* round up fractionals */ + } else { + ncols = 1; + } + str_add_chr[1] = 0; + for (row = 0; row < nrows; row++) { + int n = row; + int nc; + int acol; + + for(nc = 1; nc < ncols && n+nrows < nfiles; n += nrows, nc++) { + str_add_chr[0] = add_char_to_match[n]; + acol = str_add_chr[0] ? column_width - 1 : column_width; + printf("%s%s", matches[n], str_add_chr); + l = strlen(matches[n]); + while(l < acol) { + putchar(' '); + l++; + } + } + str_add_chr[0] = add_char_to_match[n]; + printf("%s%s\n", matches[n], str_add_chr); + } +} + + +static void input_tab(int *lastWasTab) +{ + /* Do TAB completion */ + if (lastWasTab == 0) { /* free all memory */ + if (matches) { + while (num_matches > 0) + free(matches[--num_matches]); + free(matches); + matches = (char **) NULL; + free(add_char_to_match); + add_char_to_match = NULL; + } + return; + } + if (! *lastWasTab) { + + char *tmp, *tmp1; + int len_found; + char matchBuf[BUFSIZ]; + int find_type; + int recalc_pos; + + *lastWasTab = TRUE; /* flop trigger */ + + /* Make a local copy of the string -- up + * to the position of the cursor */ + tmp = strncpy(matchBuf, command_ps, cursor); + tmp[cursor] = 0; + + find_type = find_match(matchBuf, &recalc_pos); + + /* Free up any memory already allocated */ + input_tab(0); + +#ifdef CONFIG_FEATURE_COMMAND_USERNAME_COMPLETION + /* If the word starts with `~' and there is no slash in the word, + * then try completing this word as a username. */ + + if (matchBuf[0] == '~' && strchr(matchBuf, '/') == 0) + username_tab_completion(matchBuf, NULL); + if (!matches) +#endif + /* Try to match any executable in our path and everything + * in the current working directory that matches. */ + exe_n_cwd_tab_completion(matchBuf, find_type); + /* Remove duplicate found and sort */ + if(matches) { + int i, j, n, srt; + /* bubble */ + n = num_matches; + for(i=0; i<(n-1); i++) { + for(j=i+1; j<n; j++) { + if(matches[i]!=NULL && matches[j]!=NULL) { + srt = strcmp(matches[i], matches[j]); + if(srt == 0) { + free(matches[j]); + matches[j]=0; + } else if(srt > 0) { + tmp1 = matches[i]; + matches[i] = matches[j]; + matches[j] = tmp1; + srt = add_char_to_match[i]; + add_char_to_match[i] = add_char_to_match[j]; + add_char_to_match[j] = srt; + } + } + } + } + j = n; + n = 0; + for(i=0; i<j; i++) + if(matches[i]) { + matches[n]=matches[i]; + add_char_to_match[n]=add_char_to_match[i]; + n++; + } + num_matches = n; + } + /* Did we find exactly one match? */ + if (!matches || num_matches > 1) { + + beep(); + if (!matches) + return; /* not found */ + /* find minimal match */ + tmp1 = xstrdup(matches[0]); + for (tmp = tmp1; *tmp; tmp++) + for (len_found = 1; len_found < num_matches; len_found++) + if (matches[len_found][(tmp - tmp1)] != *tmp) { + *tmp = 0; + break; + } + if (*tmp1 == 0) { /* have unique */ + free(tmp1); + return; + } + tmp = add_quote_for_spec_chars(tmp1, 0); + free(tmp1); + } else { /* one match */ + tmp = add_quote_for_spec_chars(matches[0], add_char_to_match[0]); + /* for next completion current found */ + *lastWasTab = FALSE; + } + len_found = strlen(tmp); + /* have space to placed match? */ + if ((len_found - strlen(matchBuf) + len) < BUFSIZ) { + + /* before word for match */ + command_ps[cursor - recalc_pos] = 0; + /* save tail line */ + strcpy(matchBuf, command_ps + cursor); + /* add match */ + strcat(command_ps, tmp); + /* add tail */ + strcat(command_ps, matchBuf); + /* back to begin word for match */ + input_backward(recalc_pos); + /* new pos */ + recalc_pos = cursor + len_found; + /* new len */ + len = strlen(command_ps); + /* write out the matched command */ + redraw(cmdedit_y, len - recalc_pos); + } + free(tmp); + } else { + /* Ok -- the last char was a TAB. Since they + * just hit TAB again, print a list of all the + * available choices... */ + if (matches && num_matches > 0) { + int sav_cursor = cursor; /* change goto_new_line() */ + + /* Go to the next line */ + goto_new_line(); + showfiles(); + redraw(0, len - sav_cursor); + } + } +} +#endif /* CONFIG_FEATURE_COMMAND_TAB_COMPLETION */ + +#if MAX_HISTORY > 0 +static void get_previous_history(void) +{ + if(command_ps[0] != 0 || history[cur_history] == 0) { + free(history[cur_history]); + history[cur_history] = xstrdup(command_ps); + } + cur_history--; +} + +static int get_next_history(void) +{ + int ch = cur_history; + + if (ch < n_history) { + get_previous_history(); /* save the current history line */ + cur_history = ch + 1; + return cur_history; + } else { + beep(); + return 0; + } +} + +#ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY +void load_history ( const char *fromfile ) +{ + FILE *fp; + int hi; + + /* cleanup old */ + + for(hi = n_history; hi > 0; ) { + hi--; + free ( history [hi] ); + } + + if (( fp = fopen ( fromfile, "r" ))) { + + for ( hi = 0; hi < MAX_HISTORY; ) { + char * hl = xmalloc_getline(fp); + int l; + + if(!hl) + break; + l = strlen(hl); + if(l >= BUFSIZ) + hl[BUFSIZ-1] = 0; + if(l == 0 || hl[0] == ' ') { + free(hl); + continue; + } + history [hi++] = hl; + } + fclose ( fp ); + } + cur_history = n_history = hi; +} + +void save_history ( const char *tofile ) +{ + FILE *fp = fopen ( tofile, "w" ); + + if ( fp ) { + int i; + + for ( i = 0; i < n_history; i++ ) { + fprintf(fp, "%s\n", history [i]); + } + fclose ( fp ); + } +} +#endif + +#endif + +enum { + ESC = 27, + DEL = 127, +}; + + +/* + * This function is used to grab a character buffer + * from the input file descriptor and allows you to + * a string with full command editing (sort of like + * a mini readline). + * + * The following standard commands are not implemented: + * ESC-b -- Move back one word + * ESC-f -- Move forward one word + * ESC-d -- Delete back one word + * ESC-h -- Delete forward one word + * CTL-t -- Transpose two characters + * + * Minimalist vi-style command line editing available if configured. + * vi mode implemented 2005 by Paul Fox <pgf@foxharp.boston.ma.us> + * + */ + +#ifdef CONFIG_FEATURE_COMMAND_EDITING_VI +static int vi_mode; + +void setvimode ( int viflag ) +{ + vi_mode = viflag; +} + +static void +vi_Word_motion(char *command, int eat) +{ + while (cursor < len && !isspace(command[cursor])) + input_forward(); + if (eat) while (cursor < len && isspace(command[cursor])) + input_forward(); +} + +static void +vi_word_motion(char *command, int eat) +{ + if (isalnum(command[cursor]) || command[cursor] == '_') { + while (cursor < len && + (isalnum(command[cursor+1]) || + command[cursor+1] == '_')) + input_forward(); + } else if (ispunct(command[cursor])) { + while (cursor < len && + (ispunct(command[cursor+1]))) + input_forward(); + } + + if (cursor < len) + input_forward(); + + if (eat && cursor < len && isspace(command[cursor])) + while (cursor < len && isspace(command[cursor])) + input_forward(); +} + +static void +vi_End_motion(char *command) +{ + input_forward(); + while (cursor < len && isspace(command[cursor])) + input_forward(); + while (cursor < len-1 && !isspace(command[cursor+1])) + input_forward(); +} + +static void +vi_end_motion(char *command) +{ + if (cursor >= len-1) + return; + input_forward(); + while (cursor < len-1 && isspace(command[cursor])) + input_forward(); + if (cursor >= len-1) + return; + if (isalnum(command[cursor]) || command[cursor] == '_') { + while (cursor < len-1 && + (isalnum(command[cursor+1]) || + command[cursor+1] == '_')) + input_forward(); + } else if (ispunct(command[cursor])) { + while (cursor < len-1 && + (ispunct(command[cursor+1]))) + input_forward(); + } +} + +static void +vi_Back_motion(char *command) +{ + while (cursor > 0 && isspace(command[cursor-1])) + input_backward(1); + while (cursor > 0 && !isspace(command[cursor-1])) + input_backward(1); +} + +static void +vi_back_motion(char *command) +{ + if (cursor <= 0) + return; + input_backward(1); + while (cursor > 0 && isspace(command[cursor])) + input_backward(1); + if (cursor <= 0) + return; + if (isalnum(command[cursor]) || command[cursor] == '_') { + while (cursor > 0 && + (isalnum(command[cursor-1]) || + command[cursor-1] == '_')) + input_backward(1); + } else if (ispunct(command[cursor])) { + while (cursor > 0 && + (ispunct(command[cursor-1]))) + input_backward(1); + } +} +#endif + +/* + * the emacs and vi modes share much of the code in the big + * command loop. commands entered when in vi's command mode (aka + * "escape mode") get an extra bit added to distinguish them -- + * this keeps them from being self-inserted. this clutters the + * big switch a bit, but keeps all the code in one place. + */ + +#define vbit 0x100 + +/* leave out the "vi-mode"-only case labels if vi editing isn't + * configured. */ +#define vi_case(caselabel) USE_FEATURE_COMMAND_EDITING(caselabel) + +/* convert uppercase ascii to equivalent control char, for readability */ +#define CNTRL(uc_char) ((uc_char) - 0x40) + + +int cmdedit_read_input(char *prompt, char command[BUFSIZ]) +{ + + int break_out = 0; + int lastWasTab = FALSE; + unsigned char c; + unsigned int ic; +#ifdef CONFIG_FEATURE_COMMAND_EDITING_VI + unsigned int prevc; + int vi_cmdmode = 0; +#endif + /* prepare before init handlers */ + cmdedit_y = 0; /* quasireal y, not true work if line > xt*yt */ + len = 0; + command_ps = command; + + getTermSettings(0, (void *) &initial_settings); + memcpy(&new_settings, &initial_settings, sizeof(struct termios)); + new_settings.c_lflag &= ~ICANON; /* unbuffered input */ + /* Turn off echoing and CTRL-C, so we can trap it */ + new_settings.c_lflag &= ~(ECHO | ECHONL | ISIG); + /* Hmm, in linux c_cc[] not parsed if set ~ICANON */ + new_settings.c_cc[VMIN] = 1; + new_settings.c_cc[VTIME] = 0; + /* Turn off CTRL-C, so we can trap it */ +# ifndef _POSIX_VDISABLE +# define _POSIX_VDISABLE '\0' +# endif + new_settings.c_cc[VINTR] = _POSIX_VDISABLE; + command[0] = 0; + + setTermSettings(0, (void *) &new_settings); + handlers_sets |= SET_RESET_TERM; + + /* Now initialize things */ + cmdedit_init(); + /* Print out the command prompt */ + parse_prompt(prompt); + + while (1) { + + fflush(stdout); /* buffered out to fast */ + + if (safe_read(0, &c, 1) < 1) + /* if we can't read input then exit */ + goto prepare_to_die; + + ic = c; + +#ifdef CONFIG_FEATURE_COMMAND_EDITING_VI + newdelflag = 1; + if (vi_cmdmode) + ic |= vbit; +#endif + switch (ic) + { + case '\n': + case '\r': + vi_case( case '\n'|vbit: ) + vi_case( case '\r'|vbit: ) + /* Enter */ + goto_new_line(); + break_out = 1; + break; + case CNTRL('A'): + vi_case( case '0'|vbit: ) + /* Control-a -- Beginning of line */ + input_backward(cursor); + break; + case CNTRL('B'): + vi_case( case 'h'|vbit: ) + vi_case( case '\b'|vbit: ) + vi_case( case DEL|vbit: ) + /* Control-b -- Move back one character */ + input_backward(1); + break; + case CNTRL('C'): + vi_case( case CNTRL('C')|vbit: ) + /* Control-c -- stop gathering input */ + goto_new_line(); +#ifndef CONFIG_ASH + command[0] = 0; + len = 0; + lastWasTab = FALSE; + put_prompt(); +#else + len = 0; + break_out = -1; /* to control traps */ +#endif + break; + case CNTRL('D'): + /* Control-d -- Delete one character, or exit + * if the len=0 and no chars to delete */ + if (len == 0) { + errno = 0; +prepare_to_die: +#if !defined(CONFIG_ASH) + printf("exit"); + goto_new_line(); + /* cmdedit_reset_term() called in atexit */ + exit(EXIT_SUCCESS); +#else + /* to control stopped jobs */ + len = break_out = -1; + break; +#endif + } else { + input_delete(0); + } + break; + case CNTRL('E'): + vi_case( case '$'|vbit: ) + /* Control-e -- End of line */ + input_end(); + break; + case CNTRL('F'): + vi_case( case 'l'|vbit: ) + vi_case( case ' '|vbit: ) + /* Control-f -- Move forward one character */ + input_forward(); + break; + case '\b': + case DEL: + /* Control-h and DEL */ + input_backspace(); + break; + case '\t': +#ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION + input_tab(&lastWasTab); +#endif + break; + case CNTRL('K'): + /* Control-k -- clear to end of line */ + *(command + cursor) = 0; + len = cursor; + printf("\033[J"); + break; + case CNTRL('L'): + vi_case( case CNTRL('L')|vbit: ) + /* Control-l -- clear screen */ + printf("\033[H"); + redraw(0, len-cursor); + break; +#if MAX_HISTORY > 0 + case CNTRL('N'): + vi_case( case CNTRL('N')|vbit: ) + vi_case( case 'j'|vbit: ) + /* Control-n -- Get next command in history */ + if (get_next_history()) + goto rewrite_line; + break; + case CNTRL('P'): + vi_case( case CNTRL('P')|vbit: ) + vi_case( case 'k'|vbit: ) + /* Control-p -- Get previous command from history */ + if (cur_history > 0) { + get_previous_history(); + goto rewrite_line; + } else { + beep(); + } + break; +#endif + case CNTRL('U'): + vi_case( case CNTRL('U')|vbit: ) + /* Control-U -- Clear line before cursor */ + if (cursor) { + strcpy(command, command + cursor); + redraw(cmdedit_y, len -= cursor); + } + break; + case CNTRL('W'): + vi_case( case CNTRL('W')|vbit: ) + /* Control-W -- Remove the last word */ + while (cursor > 0 && isspace(command[cursor-1])) + input_backspace(); + while (cursor > 0 &&!isspace(command[cursor-1])) + input_backspace(); + break; +#if ENABLE_FEATURE_COMMAND_EDITING_VI + case 'i'|vbit: + vi_cmdmode = 0; + break; + case 'I'|vbit: + input_backward(cursor); + vi_cmdmode = 0; + break; + case 'a'|vbit: + input_forward(); + vi_cmdmode = 0; + break; + case 'A'|vbit: + input_end(); + vi_cmdmode = 0; + break; + case 'x'|vbit: + input_delete(1); + break; + case 'X'|vbit: + if (cursor > 0) { + input_backward(1); + input_delete(1); + } + break; + case 'W'|vbit: + vi_Word_motion(command, 1); + break; + case 'w'|vbit: + vi_word_motion(command, 1); + break; + case 'E'|vbit: + vi_End_motion(command); + break; + case 'e'|vbit: + vi_end_motion(command); + break; + case 'B'|vbit: + vi_Back_motion(command); + break; + case 'b'|vbit: + vi_back_motion(command); + break; + case 'C'|vbit: + vi_cmdmode = 0; + /* fall through */ + case 'D'|vbit: + goto clear_to_eol; + + case 'c'|vbit: + vi_cmdmode = 0; + /* fall through */ + case 'd'|vbit: + { + int nc, sc; + sc = cursor; + prevc = ic; + if (safe_read(0, &c, 1) < 1) + goto prepare_to_die; + if (c == (prevc & 0xff)) { + /* "cc", "dd" */ + input_backward(cursor); + goto clear_to_eol; + break; + } + switch(c) { + case 'w': + case 'W': + case 'e': + case 'E': + switch (c) { + case 'w': /* "dw", "cw" */ + vi_word_motion(command, vi_cmdmode); + break; + case 'W': /* 'dW', 'cW' */ + vi_Word_motion(command, vi_cmdmode); + break; + case 'e': /* 'de', 'ce' */ + vi_end_motion(command); + input_forward(); + break; + case 'E': /* 'dE', 'cE' */ + vi_End_motion(command); + input_forward(); + break; + } + nc = cursor; + input_backward(cursor - sc); + while (nc-- > cursor) + input_delete(1); + break; + case 'b': /* "db", "cb" */ + case 'B': /* implemented as B */ + if (c == 'b') + vi_back_motion(command); + else + vi_Back_motion(command); + while (sc-- > cursor) + input_delete(1); + break; + case ' ': /* "d ", "c " */ + input_delete(1); + break; + case '$': /* "d$", "c$" */ + clear_to_eol: + while (cursor < len) + input_delete(1); + break; + } + } + break; + case 'p'|vbit: + input_forward(); + /* fallthrough */ + case 'P'|vbit: + put(); + break; + case 'r'|vbit: + if (safe_read(0, &c, 1) < 1) + goto prepare_to_die; + if (c == 0) + beep(); + else { + *(command + cursor) = c; + putchar(c); + putchar('\b'); + } + break; +#endif /* CONFIG_FEATURE_COMMAND_EDITING_VI */ + + case ESC: + +#if ENABLE_FEATURE_COMMAND_EDITING_VI + if (vi_mode) { + /* ESC: insert mode --> command mode */ + vi_cmdmode = 1; + input_backward(1); + break; + } +#endif + /* escape sequence follows */ + if (safe_read(0, &c, 1) < 1) + goto prepare_to_die; + /* different vt100 emulations */ + if (c == '[' || c == 'O') { + vi_case( case '['|vbit: ) + vi_case( case 'O'|vbit: ) + if (safe_read(0, &c, 1) < 1) + goto prepare_to_die; + } + if (c >= '1' && c <= '9') { + unsigned char dummy; + + if (safe_read(0, &dummy, 1) < 1) + goto prepare_to_die; + if(dummy != '~') + c = 0; + } + switch (c) { +#ifdef CONFIG_FEATURE_COMMAND_TAB_COMPLETION + case '\t': /* Alt-Tab */ + + input_tab(&lastWasTab); + break; +#endif +#if MAX_HISTORY > 0 + case 'A': + /* Up Arrow -- Get previous command from history */ + if (cur_history > 0) { + get_previous_history(); + goto rewrite_line; + } else { + beep(); + } + break; + case 'B': + /* Down Arrow -- Get next command in history */ + if (!get_next_history()) + break; + /* Rewrite the line with the selected history item */ +rewrite_line: + /* change command */ + len = strlen(strcpy(command, history[cur_history])); + /* redraw and go to eol (bol, in vi */ +#if ENABLE_FEATURE_COMMAND_EDITING_VI + redraw(cmdedit_y, vi_mode ? 9999:0); +#else + redraw(cmdedit_y, 0); +#endif + break; +#endif + case 'C': + /* Right Arrow -- Move forward one character */ + input_forward(); + break; + case 'D': + /* Left Arrow -- Move back one character */ + input_backward(1); + break; + case '3': + /* Delete */ + input_delete(0); + break; + case '1': + case 'H': + /* <Home> */ + input_backward(cursor); + break; + case '4': + case 'F': + /* <End> */ + input_end(); + break; + default: + c = 0; + beep(); + } + break; + + default: /* If it's regular input, do the normal thing */ +#ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT + /* Control-V -- Add non-printable symbol */ + if (c == CNTRL('V')) { + if (safe_read(0, &c, 1) < 1) + goto prepare_to_die; + if (c == 0) { + beep(); + break; + } + } else +#endif + { +#if ENABLE_FEATURE_COMMAND_EDITING_VI + if (vi_cmdmode) /* don't self-insert */ + break; +#endif + if (!Isprint(c)) /* Skip non-printable characters */ + break; + } + + if (len >= (BUFSIZ - 2)) /* Need to leave space for enter */ + break; + + len++; + + if (cursor == (len - 1)) { /* Append if at the end of the line */ + *(command + cursor) = c; + *(command + cursor + 1) = 0; + cmdedit_set_out_char(0); + } else { /* Insert otherwise */ + int sc = cursor; + + memmove(command + sc + 1, command + sc, len - sc); + *(command + sc) = c; + sc++; + /* rewrite from cursor */ + input_end(); + /* to prev x pos + 1 */ + input_backward(cursor - sc); + } + + break; + } + if (break_out) /* Enter is the command terminator, no more input. */ + break; + + if (c != '\t') + lastWasTab = FALSE; + } + + setTermSettings(0, (void *) &initial_settings); + handlers_sets &= ~SET_RESET_TERM; + +#if MAX_HISTORY > 0 + /* Handle command history log */ + /* cleanup may be saved current command line */ + if (len> 0) { /* no put empty line */ + int i = n_history; + + free(history[MAX_HISTORY]); + history[MAX_HISTORY] = 0; + /* After max history, remove the oldest command */ + if (i >= MAX_HISTORY) { + free(history[0]); + for(i = 0; i < (MAX_HISTORY-1); i++) + history[i] = history[i+1]; + } + history[i++] = xstrdup(command); + cur_history = i; + n_history = i; +#if defined(CONFIG_FEATURE_SH_FANCY_PROMPT) + num_ok_lines++; +#endif + } +#else /* MAX_HISTORY == 0 */ +#if defined(CONFIG_FEATURE_SH_FANCY_PROMPT) + if (len > 0) { /* no put empty line */ + num_ok_lines++; + } +#endif +#endif /* MAX_HISTORY > 0 */ + if (break_out > 0) { + command[len++] = '\n'; /* set '\n' */ + command[len] = 0; + } +#if defined(CONFIG_FEATURE_CLEAN_UP) && defined(CONFIG_FEATURE_COMMAND_TAB_COMPLETION) + input_tab(0); /* strong free */ +#endif +#if defined(CONFIG_FEATURE_SH_FANCY_PROMPT) + free(cmdedit_prompt); +#endif + cmdedit_reset_term(); + return len; +} + + + +#endif /* CONFIG_FEATURE_COMMAND_EDITING */ + + +#ifdef TEST + +const char *applet_name = "debug stuff usage"; + +#ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT +#include <locale.h> +#endif + +int main(int argc, char **argv) +{ + char buff[BUFSIZ]; + char *prompt = +#if defined(CONFIG_FEATURE_SH_FANCY_PROMPT) + "\\[\\033[32;1m\\]\\u@\\[\\x1b[33;1m\\]\\h:\ +\\[\\033[34;1m\\]\\w\\[\\033[35;1m\\] \ +\\!\\[\\e[36;1m\\]\\$ \\[\\E[0m\\]"; +#else + "% "; +#endif + +#ifdef CONFIG_FEATURE_NONPRINTABLE_INVERSE_PUT + setlocale(LC_ALL, ""); +#endif + while(1) { + int l; + l = cmdedit_read_input(prompt, buff); + if(l > 0 && buff[l-1] == '\n') { + buff[l-1] = 0; + printf("*** cmdedit_read_input() returned line =%s=\n", buff); + } else { + break; + } + } + printf("*** cmdedit_read_input() detect ^D\n"); + return 0; +} + +#endif /* TEST */ diff --git a/shell/cmdedit.h b/shell/cmdedit.h new file mode 100644 index 000000000..f5863921a --- /dev/null +++ b/shell/cmdedit.h @@ -0,0 +1,20 @@ +/* vi: set sw=4 ts=4: */ +#ifndef CMDEDIT_H +#define CMDEDIT_H + +int cmdedit_read_input(char* promptStr, char* command); + +#ifdef CONFIG_ASH +extern const char *cmdedit_path_lookup; +#endif + +#ifdef CONFIG_FEATURE_COMMAND_SAVEHISTORY +void load_history(const char *fromfile); +void save_history(const char *tofile); +#endif + +#if ENABLE_FEATURE_COMMAND_EDITING_VI +void setvimode(int viflag); +#endif + +#endif /* CMDEDIT_H */ diff --git a/shell/hush.c b/shell/hush.c new file mode 100644 index 000000000..a6e029e4e --- /dev/null +++ b/shell/hush.c @@ -0,0 +1,2875 @@ +/* vi: set sw=4 ts=4: */ +/* + * sh.c -- a prototype Bourne shell grammar parser + * Intended to follow the original Thompson and Ritchie + * "small and simple is beautiful" philosophy, which + * incidentally is a good match to today's BusyBox. + * + * Copyright (C) 2000,2001 Larry Doolittle <larry@doolittle.boa.org> + * + * Credits: + * The parser routines proper are all original material, first + * written Dec 2000 and Jan 2001 by Larry Doolittle. The + * execution engine, the builtins, and much of the underlying + * support has been adapted from busybox-0.49pre's lash, which is + * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> + * written by Erik Andersen <andersen@codepoet.org>. That, in turn, + * is based in part on ladsh.c, by Michael K. Johnson and Erik W. + * Troan, which they placed in the public domain. I don't know + * how much of the Johnson/Troan code has survived the repeated + * rewrites. + * + * Other credits: + * simple_itoa() was lifted from boa-0.93.15 + * b_addchr() derived from similar w_addchar function in glibc-2.2 + * setup_redirect(), redirect_opt_num(), and big chunks of main() + * and many builtins derived from contributions by Erik Andersen + * miscellaneous bugfixes from Matt Kraai + * + * There are two big (and related) architecture differences between + * this parser and the lash parser. One is that this version is + * actually designed from the ground up to understand nearly all + * of the Bourne grammar. The second, consequential change is that + * the parser and input reader have been turned inside out. Now, + * the parser is in control, and asks for input as needed. The old + * way had the input reader in control, and it asked for parsing to + * take place as needed. The new way makes it much easier to properly + * handle the recursion implicit in the various substitutions, especially + * across continuation lines. + * + * Bash grammar not implemented: (how many of these were in original sh?) + * $@ (those sure look like weird quoting rules) + * $_ + * ! negation operator for pipes + * &> and >& redirection of stdout+stderr + * Brace Expansion + * Tilde Expansion + * fancy forms of Parameter Expansion + * aliases + * Arithmetic Expansion + * <(list) and >(list) Process Substitution + * reserved words: case, esac, select, function + * Here Documents ( << word ) + * Functions + * Major bugs: + * job handling woefully incomplete and buggy + * reserved word execution woefully incomplete and buggy + * to-do: + * port selected bugfixes from post-0.49 busybox lash - done? + * finish implementing reserved words: for, while, until, do, done + * change { and } from special chars to reserved words + * builtins: break, continue, eval, return, set, trap, ulimit + * test magic exec + * handle children going into background + * clean up recognition of null pipes + * check setting of global_argc and global_argv + * control-C handling, probably with longjmp + * follow IFS rules more precisely, including update semantics + * figure out what to do with backslash-newline + * explain why we use signal instead of sigaction + * propagate syntax errors, die on resource errors? + * continuation lines, both explicit and implicit - done? + * memory leak finding and plugging - done? + * more testing, especially quoting rules and redirection + * document how quoting rules not precisely followed for variable assignments + * maybe change map[] to use 2-bit entries + * (eventually) remove all the printf's + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include <ctype.h> /* isalpha, isdigit */ +#include <unistd.h> /* getpid */ +#include <stdlib.h> /* getenv, atoi */ +#include <string.h> /* strchr */ +#include <stdio.h> /* popen etc. */ +#include <glob.h> /* glob, of course */ +#include <stdarg.h> /* va_list */ +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> /* should be pretty obvious */ + +#include <sys/stat.h> /* ulimit */ +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> + +/* #include <dmalloc.h> */ +/* #define DEBUG_SHELL */ + +#include "cmdedit.h" + +#define SPECIAL_VAR_SYMBOL 03 +#define FLAG_EXIT_FROM_LOOP 1 +#define FLAG_PARSE_SEMICOLON (1 << 1) /* symbol ';' is special for parser */ +#define FLAG_REPARSING (1 << 2) /* >=2nd pass */ + +typedef enum { + REDIRECT_INPUT = 1, + REDIRECT_OVERWRITE = 2, + REDIRECT_APPEND = 3, + REDIRECT_HEREIS = 4, + REDIRECT_IO = 5 +} redir_type; + +/* The descrip member of this structure is only used to make debugging + * output pretty */ +static const struct {int mode; int default_fd; const char *descrip;} redir_table[] = { + { 0, 0, "()" }, + { O_RDONLY, 0, "<" }, + { O_CREAT|O_TRUNC|O_WRONLY, 1, ">" }, + { O_CREAT|O_APPEND|O_WRONLY, 1, ">>" }, + { O_RDONLY, -1, "<<" }, + { O_RDWR, 1, "<>" } +}; + +typedef enum { + PIPE_SEQ = 1, + PIPE_AND = 2, + PIPE_OR = 3, + PIPE_BG = 4, +} pipe_style; + +/* might eventually control execution */ +typedef enum { + RES_NONE = 0, + RES_IF = 1, + RES_THEN = 2, + RES_ELIF = 3, + RES_ELSE = 4, + RES_FI = 5, + RES_FOR = 6, + RES_WHILE = 7, + RES_UNTIL = 8, + RES_DO = 9, + RES_DONE = 10, + RES_XXXX = 11, + RES_IN = 12, + RES_SNTX = 13 +} reserved_style; +#define FLAG_END (1<<RES_NONE) +#define FLAG_IF (1<<RES_IF) +#define FLAG_THEN (1<<RES_THEN) +#define FLAG_ELIF (1<<RES_ELIF) +#define FLAG_ELSE (1<<RES_ELSE) +#define FLAG_FI (1<<RES_FI) +#define FLAG_FOR (1<<RES_FOR) +#define FLAG_WHILE (1<<RES_WHILE) +#define FLAG_UNTIL (1<<RES_UNTIL) +#define FLAG_DO (1<<RES_DO) +#define FLAG_DONE (1<<RES_DONE) +#define FLAG_IN (1<<RES_IN) +#define FLAG_START (1<<RES_XXXX) + +/* This holds pointers to the various results of parsing */ +struct p_context { + struct child_prog *child; + struct pipe *list_head; + struct pipe *pipe; + struct redir_struct *pending_redirect; + reserved_style w; + int old_flag; /* for figuring out valid reserved words */ + struct p_context *stack; + int type; /* define type of parser : ";$" common or special symbol */ + /* How about quoting status? */ +}; + +struct redir_struct { + redir_type type; /* type of redirection */ + int fd; /* file descriptor being redirected */ + int dup; /* -1, or file descriptor being duplicated */ + struct redir_struct *next; /* pointer to the next redirect in the list */ + glob_t word; /* *word.gl_pathv is the filename */ +}; + +struct child_prog { + pid_t pid; /* 0 if exited */ + char **argv; /* program name and arguments */ + struct pipe *group; /* if non-NULL, first in group or subshell */ + int subshell; /* flag, non-zero if group must be forked */ + struct redir_struct *redirects; /* I/O redirections */ + glob_t glob_result; /* result of parameter globbing */ + int is_stopped; /* is the program currently running? */ + struct pipe *family; /* pointer back to the child's parent pipe */ + int sp; /* number of SPECIAL_VAR_SYMBOL */ + int type; +}; + +struct pipe { + int jobid; /* job number */ + int num_progs; /* total number of programs in job */ + int running_progs; /* number of programs running */ + char *text; /* name of job */ + char *cmdbuf; /* buffer various argv's point into */ + pid_t pgrp; /* process group ID for the job */ + struct child_prog *progs; /* array of commands in pipe */ + struct pipe *next; /* to track background commands */ + int stopped_progs; /* number of programs alive, but stopped */ + int job_context; /* bitmask defining current context */ + pipe_style followup; /* PIPE_BG, PIPE_SEQ, PIPE_OR, PIPE_AND */ + reserved_style r_mode; /* supports if, for, while, until */ +}; + +struct close_me { + int fd; + struct close_me *next; +}; + +struct variables { + char *name; + char *value; + int flg_export; + int flg_read_only; + struct variables *next; +}; + +/* globals, connect us to the outside world + * the first three support $?, $#, and $1 */ +static char **global_argv; +static int global_argc; +static int last_return_code; +extern char **environ; /* This is in <unistd.h>, but protected with __USE_GNU */ + +/* "globals" within this file */ +static char *ifs; +static unsigned char map[256]; +static int fake_mode; +static int interactive; +static struct close_me *close_me_head; +static const char *cwd; +static struct pipe *job_list; +static unsigned int last_bg_pid; +static int last_jobid; +static unsigned int shell_terminal; +static char *PS1; +static char *PS2; +static struct variables shell_ver = { "HUSH_VERSION", "0.01", 1, 1, 0 }; +static struct variables *top_vars = &shell_ver; + + +#define B_CHUNK (100) +#define B_NOSPAC 1 + +typedef struct { + char *data; + int length; + int maxlen; + int quote; + int nonnull; +} o_string; +#define NULL_O_STRING {NULL,0,0,0,0} +/* used for initialization: + o_string foo = NULL_O_STRING; */ + +/* I can almost use ordinary FILE *. Is open_memstream() universally + * available? Where is it documented? */ +struct in_str { + const char *p; + char peek_buf[2]; + int __promptme; + int promptmode; + FILE *file; + int (*get) (struct in_str *); + int (*peek) (struct in_str *); +}; +#define b_getch(input) ((input)->get(input)) +#define b_peek(input) ((input)->peek(input)) + +#define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n" + +struct built_in_command { + const char *cmd; /* name */ + const char *descr; /* description */ + int (*function) (struct child_prog *); /* function ptr */ +}; + +/* belongs in busybox.h */ +static int max(int a, int b) { + return (a>b)?a:b; +} + +/* This should be in utility.c */ +#ifdef DEBUG_SHELL +static void debug_printf(const char *format, ...) +{ + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); +} +/* broken, of course, but OK for testing */ +static char *indenter(int i) +{ + static char blanks[]=" "; + return &blanks[sizeof(blanks)-i-1]; +} +#else +#define debug_printf(...) do {;} while(0); +#endif +#define final_printf debug_printf + +static void __syntax(char *file, int line) { + bb_error_msg("syntax error %s:%d", file, line); +} +// NB: was __FILE__, but that produces full path sometimess, so... +#define syntax() __syntax("hush.c", __LINE__) + +/* Index of subroutines: */ +/* function prototypes for builtins */ +static int builtin_cd(struct child_prog *child); +static int builtin_env(struct child_prog *child); +static int builtin_eval(struct child_prog *child); +static int builtin_exec(struct child_prog *child); +static int builtin_exit(struct child_prog *child); +static int builtin_export(struct child_prog *child); +static int builtin_fg_bg(struct child_prog *child); +static int builtin_help(struct child_prog *child); +static int builtin_jobs(struct child_prog *child); +static int builtin_pwd(struct child_prog *child); +static int builtin_read(struct child_prog *child); +static int builtin_set(struct child_prog *child); +static int builtin_shift(struct child_prog *child); +static int builtin_source(struct child_prog *child); +static int builtin_umask(struct child_prog *child); +static int builtin_unset(struct child_prog *child); +static int builtin_not_written(struct child_prog *child); +/* o_string manipulation: */ +static int b_check_space(o_string *o, int len); +static int b_addchr(o_string *o, int ch); +static void b_reset(o_string *o); +static int b_addqchr(o_string *o, int ch, int quote); +static int b_adduint(o_string *o, unsigned int i); +/* in_str manipulations: */ +static int static_get(struct in_str *i); +static int static_peek(struct in_str *i); +static int file_get(struct in_str *i); +static int file_peek(struct in_str *i); +static void setup_file_in_str(struct in_str *i, FILE *f); +static void setup_string_in_str(struct in_str *i, const char *s); +/* close_me manipulations: */ +static void mark_open(int fd); +static void mark_closed(int fd); +static void close_all(void); +/* "run" the final data structures: */ +static int free_pipe_list(struct pipe *head, int indent); +static int free_pipe(struct pipe *pi, int indent); +/* really run the final data structures: */ +static int setup_redirects(struct child_prog *prog, int squirrel[]); +static int run_list_real(struct pipe *pi); +static void pseudo_exec(struct child_prog *child) ATTRIBUTE_NORETURN; +static int run_pipe_real(struct pipe *pi); +/* extended glob support: */ +static int globhack(const char *src, int flags, glob_t *pglob); +static int glob_needed(const char *s); +static int xglob(o_string *dest, int flags, glob_t *pglob); +/* variable assignment: */ +static int is_assignment(const char *s); +/* data structure manipulation: */ +static int setup_redirect(struct p_context *ctx, int fd, redir_type style, struct in_str *input); +static void initialize_context(struct p_context *ctx); +static int done_word(o_string *dest, struct p_context *ctx); +static int done_command(struct p_context *ctx); +static int done_pipe(struct p_context *ctx, pipe_style type); +/* primary string parsing: */ +static int redirect_dup_num(struct in_str *input); +static int redirect_opt_num(o_string *o); +static int process_command_subs(o_string *dest, struct p_context *ctx, struct in_str *input, int subst_end); +static int parse_group(o_string *dest, struct p_context *ctx, struct in_str *input, int ch); +static char *lookup_param(char *src); +static char *make_string(char **inp); +static int handle_dollar(o_string *dest, struct p_context *ctx, struct in_str *input); +static int parse_string(o_string *dest, struct p_context *ctx, const char *src); +static int parse_stream(o_string *dest, struct p_context *ctx, struct in_str *input0, int end_trigger); +/* setup: */ +static int parse_stream_outer(struct in_str *inp, int flag); +static int parse_string_outer(const char *s, int flag); +static int parse_file_outer(FILE *f); +/* job management: */ +static int checkjobs(struct pipe* fg_pipe); +static void insert_bg_job(struct pipe *pi); +static void remove_bg_job(struct pipe *pi); +/* local variable support */ +static char **make_list_in(char **inp, char *name); +static char *insert_var_value(char *inp); +static char *get_local_var(const char *var); +static void unset_local_var(const char *name); +static int set_local_var(const char *s, int flg_export); + +/* Table of built-in functions. They can be forked or not, depending on + * context: within pipes, they fork. As simple commands, they do not. + * When used in non-forking context, they can change global variables + * in the parent shell process. If forked, of course they cannot. + * For example, 'unset foo | whatever' will parse and run, but foo will + * still be set at the end. */ +static const struct built_in_command bltins[] = { + {"bg", "Resume a job in the background", builtin_fg_bg}, + {"break", "Exit for, while or until loop", builtin_not_written}, + {"cd", "Change working directory", builtin_cd}, + {"continue", "Continue for, while or until loop", builtin_not_written}, + {"env", "Print all environment variables", builtin_env}, + {"eval", "Construct and run shell command", builtin_eval}, + {"exec", "Exec command, replacing this shell with the exec'd process", + builtin_exec}, + {"exit", "Exit from shell()", builtin_exit}, + {"export", "Set environment variable", builtin_export}, + {"fg", "Bring job into the foreground", builtin_fg_bg}, + {"jobs", "Lists the active jobs", builtin_jobs}, + {"pwd", "Print current directory", builtin_pwd}, + {"read", "Input environment variable", builtin_read}, + {"return", "Return from a function", builtin_not_written}, + {"set", "Set/unset shell local variables", builtin_set}, + {"shift", "Shift positional parameters", builtin_shift}, + {"trap", "Trap signals", builtin_not_written}, + {"ulimit","Controls resource limits", builtin_not_written}, + {"umask","Sets file creation mask", builtin_umask}, + {"unset", "Unset environment variable", builtin_unset}, + {".", "Source-in and run commands in a file", builtin_source}, + {"help", "List shell built-in commands", builtin_help}, + {NULL, NULL, NULL} +}; + +static const char *set_cwd(void) +{ + if(cwd==bb_msg_unknown) + cwd = NULL; /* xgetcwd(arg) called free(arg) */ + cwd = xgetcwd((char *)cwd); + if (!cwd) + cwd = bb_msg_unknown; + return cwd; +} + +/* built-in 'eval' handler */ +static int builtin_eval(struct child_prog *child) +{ + char *str = NULL; + int rcode = EXIT_SUCCESS; + + if (child->argv[1]) { + str = make_string(child->argv + 1); + parse_string_outer(str, FLAG_EXIT_FROM_LOOP | + FLAG_PARSE_SEMICOLON); + free(str); + rcode = last_return_code; + } + return rcode; +} + +/* built-in 'cd <path>' handler */ +static int builtin_cd(struct child_prog *child) +{ + char *newdir; + if (child->argv[1] == NULL) + newdir = getenv("HOME"); + else + newdir = child->argv[1]; + if (chdir(newdir)) { + printf("cd: %s: %s\n", newdir, strerror(errno)); + return EXIT_FAILURE; + } + set_cwd(); + return EXIT_SUCCESS; +} + +/* built-in 'env' handler */ +static int builtin_env(struct child_prog *dummy ATTRIBUTE_UNUSED) +{ + char **e = environ; + if (e == NULL) return EXIT_FAILURE; + for (; *e; e++) { + puts(*e); + } + return EXIT_SUCCESS; +} + +/* built-in 'exec' handler */ +static int builtin_exec(struct child_prog *child) +{ + if (child->argv[1] == NULL) + return EXIT_SUCCESS; /* Really? */ + child->argv++; + pseudo_exec(child); + /* never returns */ +} + +/* built-in 'exit' handler */ +static int builtin_exit(struct child_prog *child) +{ + if (child->argv[1] == NULL) + exit(last_return_code); + exit (atoi(child->argv[1])); +} + +/* built-in 'export VAR=value' handler */ +static int builtin_export(struct child_prog *child) +{ + int res = 0; + char *name = child->argv[1]; + + if (name == NULL) { + return builtin_env(child); + } + + name = strdup(name); + + if(name) { + char *value = strchr(name, '='); + + if (!value) { + char *tmp; + /* They are exporting something without an =VALUE */ + + value = get_local_var(name); + if (value) { + size_t ln = strlen(name); + + tmp = realloc(name, ln+strlen(value)+2); + if(tmp==NULL) + res = -1; + else { + sprintf(tmp+ln, "=%s", value); + name = tmp; + } + } else { + /* bash does not return an error when trying to export + * an undefined variable. Do likewise. */ + res = 1; + } + } + } + if (res<0) + bb_perror_msg("export"); + else if(res==0) + res = set_local_var(name, 1); + else + res = 0; + free(name); + return res; +} + +/* built-in 'fg' and 'bg' handler */ +static int builtin_fg_bg(struct child_prog *child) +{ + int i, jobnum; + struct pipe *pi=NULL; + + if (!interactive) + return EXIT_FAILURE; + /* If they gave us no args, assume they want the last backgrounded task */ + if (!child->argv[1]) { + for (pi = job_list; pi; pi = pi->next) { + if (pi->jobid == last_jobid) { + break; + } + } + if (!pi) { + bb_error_msg("%s: no current job", child->argv[0]); + return EXIT_FAILURE; + } + } else { + if (sscanf(child->argv[1], "%%%d", &jobnum) != 1) { + bb_error_msg("%s: bad argument '%s'", child->argv[0], child->argv[1]); + return EXIT_FAILURE; + } + for (pi = job_list; pi; pi = pi->next) { + if (pi->jobid == jobnum) { + break; + } + } + if (!pi) { + bb_error_msg("%s: %d: no such job", child->argv[0], jobnum); + return EXIT_FAILURE; + } + } + + if (*child->argv[0] == 'f') { + /* Put the job into the foreground. */ + tcsetpgrp(shell_terminal, pi->pgrp); + } + + /* Restart the processes in the job */ + for (i = 0; i < pi->num_progs; i++) + pi->progs[i].is_stopped = 0; + + if ( (i=kill(- pi->pgrp, SIGCONT)) < 0) { + if (i == ESRCH) { + remove_bg_job(pi); + } else { + bb_perror_msg("kill (SIGCONT)"); + } + } + + pi->stopped_progs = 0; + return EXIT_SUCCESS; +} + +/* built-in 'help' handler */ +static int builtin_help(struct child_prog *dummy ATTRIBUTE_UNUSED) +{ + const struct built_in_command *x; + + printf("\nBuilt-in commands:\n"); + printf("-------------------\n"); + for (x = bltins; x->cmd; x++) { + if (x->descr==NULL) + continue; + printf("%s\t%s\n", x->cmd, x->descr); + } + printf("\n\n"); + return EXIT_SUCCESS; +} + +/* built-in 'jobs' handler */ +static int builtin_jobs(struct child_prog *child ATTRIBUTE_UNUSED) +{ + struct pipe *job; + char *status_string; + + for (job = job_list; job; job = job->next) { + if (job->running_progs == job->stopped_progs) + status_string = "Stopped"; + else + status_string = "Running"; + + printf(JOB_STATUS_FORMAT, job->jobid, status_string, job->text); + } + return EXIT_SUCCESS; +} + + +/* built-in 'pwd' handler */ +static int builtin_pwd(struct child_prog *dummy ATTRIBUTE_UNUSED) +{ + puts(set_cwd()); + return EXIT_SUCCESS; +} + +/* built-in 'read VAR' handler */ +static int builtin_read(struct child_prog *child) +{ + int res; + + if (child->argv[1]) { + char string[BUFSIZ]; + char *var = 0; + + string[0] = 0; /* In case stdin has only EOF */ + /* read string */ + fgets(string, sizeof(string), stdin); + chomp(string); + var = malloc(strlen(child->argv[1])+strlen(string)+2); + if(var) { + sprintf(var, "%s=%s", child->argv[1], string); + res = set_local_var(var, 0); + } else + res = -1; + if (res) + bb_perror_msg("read"); + free(var); /* So not move up to avoid breaking errno */ + return res; + } else { + do res=getchar(); while(res!='\n' && res!=EOF); + return 0; + } +} + +/* built-in 'set VAR=value' handler */ +static int builtin_set(struct child_prog *child) +{ + char *temp = child->argv[1]; + struct variables *e; + + if (temp == NULL) + for(e = top_vars; e; e=e->next) + printf("%s=%s\n", e->name, e->value); + else + set_local_var(temp, 0); + + return EXIT_SUCCESS; +} + + +/* Built-in 'shift' handler */ +static int builtin_shift(struct child_prog *child) +{ + int n=1; + if (child->argv[1]) { + n=atoi(child->argv[1]); + } + if (n>=0 && n<global_argc) { + /* XXX This probably breaks $0 */ + global_argc -= n; + global_argv += n; + return EXIT_SUCCESS; + } else { + return EXIT_FAILURE; + } +} + +/* Built-in '.' handler (read-in and execute commands from file) */ +static int builtin_source(struct child_prog *child) +{ + FILE *input; + int status; + + if (child->argv[1] == NULL) + return EXIT_FAILURE; + + /* XXX search through $PATH is missing */ + input = fopen(child->argv[1], "r"); + if (!input) { + bb_error_msg("cannot open '%s'", child->argv[1]); + return EXIT_FAILURE; + } + + /* Now run the file */ + /* XXX argv and argc are broken; need to save old global_argv + * (pointer only is OK!) on this stack frame, + * set global_argv=child->argv+1, recurse, and restore. */ + mark_open(fileno(input)); + status = parse_file_outer(input); + mark_closed(fileno(input)); + fclose(input); + return status; +} + +static int builtin_umask(struct child_prog *child) +{ + mode_t new_umask; + const char *arg = child->argv[1]; + char *end; + if (arg) { + new_umask=strtoul(arg, &end, 8); + if (*end!='\0' || end == arg) { + return EXIT_FAILURE; + } + } else { + printf("%.3o\n", (unsigned int) (new_umask=umask(0))); + } + umask(new_umask); + return EXIT_SUCCESS; +} + +/* built-in 'unset VAR' handler */ +static int builtin_unset(struct child_prog *child) +{ + /* bash returned already true */ + unset_local_var(child->argv[1]); + return EXIT_SUCCESS; +} + +static int builtin_not_written(struct child_prog *child) +{ + printf("builtin_%s not written\n",child->argv[0]); + return EXIT_FAILURE; +} + +static int b_check_space(o_string *o, int len) +{ + /* It would be easy to drop a more restrictive policy + * in here, such as setting a maximum string length */ + if (o->length + len > o->maxlen) { + char *old_data = o->data; + /* assert (data == NULL || o->maxlen != 0); */ + o->maxlen += max(2*len, B_CHUNK); + o->data = realloc(o->data, 1 + o->maxlen); + if (o->data == NULL) { + free(old_data); + } + } + return o->data == NULL; +} + +static int b_addchr(o_string *o, int ch) +{ + debug_printf("b_addchr: %c %d %p\n", ch, o->length, o); + if (b_check_space(o, 1)) return B_NOSPAC; + o->data[o->length] = ch; + o->length++; + o->data[o->length] = '\0'; + return 0; +} + +static void b_reset(o_string *o) +{ + o->length = 0; + o->nonnull = 0; + if (o->data != NULL) *o->data = '\0'; +} + +static void b_free(o_string *o) +{ + b_reset(o); + free(o->data); + o->data = NULL; + o->maxlen = 0; +} + +/* My analysis of quoting semantics tells me that state information + * is associated with a destination, not a source. + */ +static int b_addqchr(o_string *o, int ch, int quote) +{ + if (quote && strchr("*?[\\",ch)) { + int rc; + rc = b_addchr(o, '\\'); + if (rc) return rc; + } + return b_addchr(o, ch); +} + +/* belongs in utility.c */ +static char *simple_itoa(unsigned int i) +{ + /* 21 digits plus null terminator, good for 64-bit or smaller ints */ + static char local[22]; + char *p = &local[21]; + *p-- = '\0'; + do { + *p-- = '0' + i % 10; + i /= 10; + } while (i > 0); + return p + 1; +} + +static int b_adduint(o_string *o, unsigned int i) +{ + int r; + char *p = simple_itoa(i); + /* no escape checking necessary */ + do r=b_addchr(o, *p++); while (r==0 && *p); + return r; +} + +static int static_get(struct in_str *i) +{ + int ch=*i->p++; + if (ch=='\0') return EOF; + return ch; +} + +static int static_peek(struct in_str *i) +{ + return *i->p; +} + +static void cmdedit_set_initial_prompt(void) +{ +#ifndef CONFIG_FEATURE_SH_FANCY_PROMPT + PS1 = NULL; +#else + PS1 = getenv("PS1"); + if(PS1==0) + PS1 = "\\w \\$ "; +#endif +} + +static void setup_prompt_string(int promptmode, char **prompt_str) +{ + debug_printf("setup_prompt_string %d ",promptmode); +#ifndef CONFIG_FEATURE_SH_FANCY_PROMPT + /* Set up the prompt */ + if (promptmode == 1) { + free(PS1); + PS1=xmalloc(strlen(cwd)+4); + sprintf(PS1, "%s %s", cwd, ( geteuid() != 0 ) ? "$ ":"# "); + *prompt_str = PS1; + } else { + *prompt_str = PS2; + } +#else + *prompt_str = (promptmode==1)? PS1 : PS2; +#endif + debug_printf("result %s\n",*prompt_str); +} + +static void get_user_input(struct in_str *i) +{ + char *prompt_str; + static char the_command[BUFSIZ]; + + setup_prompt_string(i->promptmode, &prompt_str); +#ifdef CONFIG_FEATURE_COMMAND_EDITING + /* + ** enable command line editing only while a command line + ** is actually being read; otherwise, we'll end up bequeathing + ** atexit() handlers and other unwanted stuff to our + ** child processes (rob@sysgo.de) + */ + cmdedit_read_input(prompt_str, the_command); +#else + fputs(prompt_str, stdout); + fflush(stdout); + the_command[0]=fgetc(i->file); + the_command[1]='\0'; +#endif + fflush(stdout); + i->p = the_command; +} + +/* This is the magic location that prints prompts + * and gets data back from the user */ +static int file_get(struct in_str *i) +{ + int ch; + + ch = 0; + /* If there is data waiting, eat it up */ + if (i->p && *i->p) { + ch=*i->p++; + } else { + /* need to double check i->file because we might be doing something + * more complicated by now, like sourcing or substituting. */ + if (i->__promptme && interactive && i->file == stdin) { + while(! i->p || (interactive && strlen(i->p)==0) ) { + get_user_input(i); + } + i->promptmode=2; + i->__promptme = 0; + if (i->p && *i->p) { + ch=*i->p++; + } + } else { + ch = fgetc(i->file); + } + + debug_printf("b_getch: got a %d\n", ch); + } + if (ch == '\n') i->__promptme=1; + return ch; +} + +/* All the callers guarantee this routine will never be + * used right after a newline, so prompting is not needed. + */ +static int file_peek(struct in_str *i) +{ + if (i->p && *i->p) { + return *i->p; + } else { + i->peek_buf[0] = fgetc(i->file); + i->peek_buf[1] = '\0'; + i->p = i->peek_buf; + debug_printf("b_peek: got a %d\n", *i->p); + return *i->p; + } +} + +static void setup_file_in_str(struct in_str *i, FILE *f) +{ + i->peek = file_peek; + i->get = file_get; + i->__promptme=1; + i->promptmode=1; + i->file = f; + i->p = NULL; +} + +static void setup_string_in_str(struct in_str *i, const char *s) +{ + i->peek = static_peek; + i->get = static_get; + i->__promptme=1; + i->promptmode=1; + i->p = s; +} + +static void mark_open(int fd) +{ + struct close_me *new = xmalloc(sizeof(struct close_me)); + new->fd = fd; + new->next = close_me_head; + close_me_head = new; +} + +static void mark_closed(int fd) +{ + struct close_me *tmp; + if (close_me_head == NULL || close_me_head->fd != fd) + bb_error_msg_and_die("corrupt close_me"); + tmp = close_me_head; + close_me_head = close_me_head->next; + free(tmp); +} + +static void close_all(void) +{ + struct close_me *c; + for (c=close_me_head; c; c=c->next) { + close(c->fd); + } + close_me_head = NULL; +} + +/* squirrel != NULL means we squirrel away copies of stdin, stdout, + * and stderr if they are redirected. */ +static int setup_redirects(struct child_prog *prog, int squirrel[]) +{ + int openfd, mode; + struct redir_struct *redir; + + for (redir=prog->redirects; redir; redir=redir->next) { + if (redir->dup == -1 && redir->word.gl_pathv == NULL) { + /* something went wrong in the parse. Pretend it didn't happen */ + continue; + } + if (redir->dup == -1) { + mode=redir_table[redir->type].mode; + openfd = open(redir->word.gl_pathv[0], mode, 0666); + if (openfd < 0) { + /* this could get lost if stderr has been redirected, but + bash and ash both lose it as well (though zsh doesn't!) */ + bb_perror_msg("error opening %s", redir->word.gl_pathv[0]); + return 1; + } + } else { + openfd = redir->dup; + } + + if (openfd != redir->fd) { + if (squirrel && redir->fd < 3) { + squirrel[redir->fd] = dup(redir->fd); + } + if (openfd == -3) { + close(openfd); + } else { + dup2(openfd, redir->fd); + if (redir->dup == -1) + close (openfd); + } + } + } + return 0; +} + +static void restore_redirects(int squirrel[]) +{ + int i, fd; + for (i=0; i<3; i++) { + fd = squirrel[i]; + if (fd != -1) { + /* No error checking. I sure wouldn't know what + * to do with an error if I found one! */ + dup2(fd, i); + close(fd); + } + } +} + +/* never returns */ +/* XXX no exit() here. If you don't exec, use _exit instead. + * The at_exit handlers apparently confuse the calling process, + * in particular stdin handling. Not sure why? */ +static void pseudo_exec(struct child_prog *child) +{ + int i, rcode; + char *p; + const struct built_in_command *x; + if (child->argv) { + for (i=0; is_assignment(child->argv[i]); i++) { + debug_printf("pid %d environment modification: %s\n",getpid(),child->argv[i]); + p = insert_var_value(child->argv[i]); + putenv(strdup(p)); + if (p != child->argv[i]) free(p); + } + child->argv+=i; /* XXX this hack isn't so horrible, since we are about + to exit, and therefore don't need to keep data + structures consistent for free() use. */ + /* If a variable is assigned in a forest, and nobody listens, + * was it ever really set? + */ + if (child->argv[0] == NULL) { + _exit(EXIT_SUCCESS); + } + + /* + * Check if the command matches any of the builtins. + * Depending on context, this might be redundant. But it's + * easier to waste a few CPU cycles than it is to figure out + * if this is one of those cases. + */ + for (x = bltins; x->cmd; x++) { + if (strcmp(child->argv[0], x->cmd) == 0 ) { + debug_printf("builtin exec %s\n", child->argv[0]); + rcode = x->function(child); + fflush(stdout); + _exit(rcode); + } + } + + /* Check if the command matches any busybox internal commands + * ("applets") here. + * FIXME: This feature is not 100% safe, since + * BusyBox is not fully reentrant, so we have no guarantee the things + * from the .bss are still zeroed, or that things from .data are still + * at their defaults. We could exec ourself from /proc/self/exe, but I + * really dislike relying on /proc for things. We could exec ourself + * from global_argv[0], but if we are in a chroot, we may not be able + * to find ourself... */ +#ifdef CONFIG_FEATURE_SH_STANDALONE_SHELL + { + int argc_l; + char** argv_l=child->argv; + char *name = child->argv[0]; + + /* Count argc for use in a second... */ + for(argc_l=0;*argv_l!=NULL; argv_l++, argc_l++); + optind = 1; + debug_printf("running applet %s\n", name); + run_applet_by_name(name, argc_l, child->argv); + } +#endif + debug_printf("exec of %s\n",child->argv[0]); + execvp(child->argv[0],child->argv); + bb_perror_msg("cannot exec: %s",child->argv[0]); + _exit(1); + } else if (child->group) { + debug_printf("runtime nesting to group\n"); + interactive=0; /* crucial!!!! */ + rcode = run_list_real(child->group); + /* OK to leak memory by not calling free_pipe_list, + * since this process is about to exit */ + _exit(rcode); + } else { + /* Can happen. See what bash does with ">foo" by itself. */ + debug_printf("trying to pseudo_exec null command\n"); + _exit(EXIT_SUCCESS); + } +} + +static void insert_bg_job(struct pipe *pi) +{ + struct pipe *thejob; + + /* Linear search for the ID of the job to use */ + pi->jobid = 1; + for (thejob = job_list; thejob; thejob = thejob->next) + if (thejob->jobid >= pi->jobid) + pi->jobid = thejob->jobid + 1; + + /* add thejob to the list of running jobs */ + if (!job_list) { + thejob = job_list = xmalloc(sizeof(*thejob)); + } else { + for (thejob = job_list; thejob->next; thejob = thejob->next) /* nothing */; + thejob->next = xmalloc(sizeof(*thejob)); + thejob = thejob->next; + } + + /* physically copy the struct job */ + memcpy(thejob, pi, sizeof(struct pipe)); + thejob->next = NULL; + thejob->running_progs = thejob->num_progs; + thejob->stopped_progs = 0; + thejob->text = xmalloc(BUFSIZ); /* cmdedit buffer size */ + + //if (pi->progs[0] && pi->progs[0].argv && pi->progs[0].argv[0]) + { + char *bar=thejob->text; + char **foo=pi->progs[0].argv; + while(foo && *foo) { + bar += sprintf(bar, "%s ", *foo++); + } + } + + /* we don't wait for background thejobs to return -- append it + to the list of backgrounded thejobs and leave it alone */ + printf("[%d] %d\n", thejob->jobid, thejob->progs[0].pid); + last_bg_pid = thejob->progs[0].pid; + last_jobid = thejob->jobid; +} + +/* remove a backgrounded job */ +static void remove_bg_job(struct pipe *pi) +{ + struct pipe *prev_pipe; + + if (pi == job_list) { + job_list = pi->next; + } else { + prev_pipe = job_list; + while (prev_pipe->next != pi) + prev_pipe = prev_pipe->next; + prev_pipe->next = pi->next; + } + if (job_list) + last_jobid = job_list->jobid; + else + last_jobid = 0; + + pi->stopped_progs = 0; + free_pipe(pi, 0); + free(pi); +} + +/* Checks to see if any processes have exited -- if they + have, figure out why and see if a job has completed */ +static int checkjobs(struct pipe* fg_pipe) +{ + int attributes; + int status; + int prognum = 0; + struct pipe *pi; + pid_t childpid; + + attributes = WUNTRACED; + if (fg_pipe==NULL) { + attributes |= WNOHANG; + } + + while ((childpid = waitpid(-1, &status, attributes)) > 0) { + if (fg_pipe) { + int i, rcode = 0; + for (i=0; i < fg_pipe->num_progs; i++) { + if (fg_pipe->progs[i].pid == childpid) { + if (i==fg_pipe->num_progs-1) + rcode=WEXITSTATUS(status); + (fg_pipe->num_progs)--; + return rcode; + } + } + } + + for (pi = job_list; pi; pi = pi->next) { + prognum = 0; + while (prognum < pi->num_progs && pi->progs[prognum].pid != childpid) { + prognum++; + } + if (prognum < pi->num_progs) + break; + } + + if(pi==NULL) { + debug_printf("checkjobs: pid %d was not in our list!\n", childpid); + continue; + } + + if (WIFEXITED(status) || WIFSIGNALED(status)) { + /* child exited */ + pi->running_progs--; + pi->progs[prognum].pid = 0; + + if (!pi->running_progs) { + printf(JOB_STATUS_FORMAT, pi->jobid, "Done", pi->text); + remove_bg_job(pi); + } + } else { + /* child stopped */ + pi->stopped_progs++; + pi->progs[prognum].is_stopped = 1; + } + } + + if (childpid == -1 && errno != ECHILD) + bb_perror_msg("waitpid"); + + /* move the shell to the foreground */ + //if (interactive && tcsetpgrp(shell_terminal, getpgid(0))) + // bb_perror_msg("tcsetpgrp-2"); + return -1; +} + +/* run_pipe_real() starts all the jobs, but doesn't wait for anything + * to finish. See checkjobs(). + * + * return code is normally -1, when the caller has to wait for children + * to finish to determine the exit status of the pipe. If the pipe + * is a simple builtin command, however, the action is done by the + * time run_pipe_real returns, and the exit code is provided as the + * return value. + * + * The input of the pipe is always stdin, the output is always + * stdout. The outpipe[] mechanism in BusyBox-0.48 lash is bogus, + * because it tries to avoid running the command substitution in + * subshell, when that is in fact necessary. The subshell process + * now has its stdout directed to the input of the appropriate pipe, + * so this routine is noticeably simpler. + */ +static int run_pipe_real(struct pipe *pi) +{ + int i; + int nextin, nextout; + int pipefds[2]; /* pipefds[0] is for reading */ + struct child_prog *child; + const struct built_in_command *x; + char *p; + + nextin = 0; + pi->pgrp = -1; + + /* Check if this is a simple builtin (not part of a pipe). + * Builtins within pipes have to fork anyway, and are handled in + * pseudo_exec. "echo foo | read bar" doesn't work on bash, either. + */ + child = & (pi->progs[0]); + if (pi->num_progs == 1 && child->group && child->subshell == 0) { + int squirrel[] = {-1, -1, -1}; + int rcode; + debug_printf("non-subshell grouping\n"); + setup_redirects(child, squirrel); + /* XXX could we merge code with following builtin case, + * by creating a pseudo builtin that calls run_list_real? */ + rcode = run_list_real(child->group); + restore_redirects(squirrel); + return rcode; + } else if (pi->num_progs == 1 && pi->progs[0].argv != NULL) { + for (i=0; is_assignment(child->argv[i]); i++) { /* nothing */ } + if (i!=0 && child->argv[i]==NULL) { + /* assignments, but no command: set the local environment */ + for (i=0; child->argv[i]!=NULL; i++) { + + /* Ok, this case is tricky. We have to decide if this is a + * local variable, or an already exported variable. If it is + * already exported, we have to export the new value. If it is + * not exported, we need only set this as a local variable. + * This junk is all to decide whether or not to export this + * variable. */ + int export_me=0; + char *name, *value; + name = xstrdup(child->argv[i]); + debug_printf("Local environment set: %s\n", name); + value = strchr(name, '='); + if (value) + *value=0; + if ( get_local_var(name)) { + export_me=1; + } + free(name); + p = insert_var_value(child->argv[i]); + set_local_var(p, export_me); + if (p != child->argv[i]) free(p); + } + return EXIT_SUCCESS; /* don't worry about errors in set_local_var() yet */ + } + for (i = 0; is_assignment(child->argv[i]); i++) { + p = insert_var_value(child->argv[i]); + putenv(strdup(p)); + if (p != child->argv[i]) { + child->sp--; + free(p); + } + } + if (child->sp) { + char * str = NULL; + + str = make_string((child->argv + i)); + parse_string_outer(str, FLAG_EXIT_FROM_LOOP | FLAG_REPARSING); + free(str); + return last_return_code; + } + for (x = bltins; x->cmd; x++) { + if (strcmp(child->argv[i], x->cmd) == 0 ) { + int squirrel[] = {-1, -1, -1}; + int rcode; + if (x->function == builtin_exec && child->argv[i+1]==NULL) { + debug_printf("magic exec\n"); + setup_redirects(child,NULL); + return EXIT_SUCCESS; + } + debug_printf("builtin inline %s\n", child->argv[0]); + /* XXX setup_redirects acts on file descriptors, not FILEs. + * This is perfect for work that comes after exec(). + * Is it really safe for inline use? Experimentally, + * things seem to work with glibc. */ + setup_redirects(child, squirrel); + child->argv+=i; /* XXX horrible hack */ + rcode = x->function(child); + child->argv-=i; /* XXX restore hack so free() can work right */ + restore_redirects(squirrel); + return rcode; + } + } + } + + for (i = 0; i < pi->num_progs; i++) { + child = & (pi->progs[i]); + + /* pipes are inserted between pairs of commands */ + if ((i + 1) < pi->num_progs) { + if (pipe(pipefds)<0) bb_perror_msg_and_die("pipe"); + nextout = pipefds[1]; + } else { + nextout=1; + pipefds[0] = -1; + } + + /* XXX test for failed fork()? */ +#if !defined(__UCLIBC__) || defined(__ARCH_HAS_MMU__) + if (!(child->pid = fork())) +#else + if (!(child->pid = vfork())) +#endif + { + /* Set the handling for job control signals back to the default. */ + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + signal(SIGTSTP, SIG_DFL); + signal(SIGTTIN, SIG_DFL); + signal(SIGTTOU, SIG_DFL); + signal(SIGCHLD, SIG_DFL); + + close_all(); + + if (nextin != 0) { + dup2(nextin, 0); + close(nextin); + } + if (nextout != 1) { + dup2(nextout, 1); + close(nextout); + } + if (pipefds[0]!=-1) { + close(pipefds[0]); /* opposite end of our output pipe */ + } + + /* Like bash, explicit redirects override pipes, + * and the pipe fd is available for dup'ing. */ + setup_redirects(child,NULL); + + if (interactive && pi->followup!=PIPE_BG) { + /* If we (the child) win the race, put ourselves in the process + * group whose leader is the first process in this pipe. */ + if (pi->pgrp < 0) { + pi->pgrp = getpid(); + } + if (setpgid(0, pi->pgrp) == 0) { + tcsetpgrp(2, pi->pgrp); + } + } + + pseudo_exec(child); + } + + + /* put our child in the process group whose leader is the + first process in this pipe */ + if (pi->pgrp < 0) { + pi->pgrp = child->pid; + } + /* Don't check for errors. The child may be dead already, + * in which case setpgid returns error code EACCES. */ + setpgid(child->pid, pi->pgrp); + + if (nextin != 0) + close(nextin); + if (nextout != 1) + close(nextout); + + /* If there isn't another process, nextin is garbage + but it doesn't matter */ + nextin = pipefds[0]; + } + return -1; +} + +static int run_list_real(struct pipe *pi) +{ + char *save_name = NULL; + char **list = NULL; + char **save_list = NULL; + struct pipe *rpipe; + int flag_rep = 0; + int save_num_progs; + int rcode=0, flag_skip=1; + int flag_restore = 0; + int if_code=0, next_if_code=0; /* need double-buffer to handle elif */ + reserved_style rmode, skip_more_in_this_rmode=RES_XXXX; + /* check syntax for "for" */ + for (rpipe = pi; rpipe; rpipe = rpipe->next) { + if ((rpipe->r_mode == RES_IN || + rpipe->r_mode == RES_FOR) && + (rpipe->next == NULL)) { + syntax(); + return 1; + } + if ((rpipe->r_mode == RES_IN && + (rpipe->next->r_mode == RES_IN && + rpipe->next->progs->argv != NULL))|| + (rpipe->r_mode == RES_FOR && + rpipe->next->r_mode != RES_IN)) { + syntax(); + return 1; + } + } + for (; pi; pi = (flag_restore != 0) ? rpipe : pi->next) { + if (pi->r_mode == RES_WHILE || pi->r_mode == RES_UNTIL || + pi->r_mode == RES_FOR) { + flag_restore = 0; + if (!rpipe) { + flag_rep = 0; + rpipe = pi; + } + } + rmode = pi->r_mode; + debug_printf("rmode=%d if_code=%d next_if_code=%d skip_more=%d\n", rmode, if_code, next_if_code, skip_more_in_this_rmode); + if (rmode == skip_more_in_this_rmode && flag_skip) { + if (pi->followup == PIPE_SEQ) flag_skip=0; + continue; + } + flag_skip = 1; + skip_more_in_this_rmode = RES_XXXX; + if (rmode == RES_THEN || rmode == RES_ELSE) if_code = next_if_code; + if (rmode == RES_THEN && if_code) continue; + if (rmode == RES_ELSE && !if_code) continue; + if (rmode == RES_ELIF && !if_code) break; + if (rmode == RES_FOR && pi->num_progs) { + if (!list) { + /* if no variable values after "in" we skip "for" */ + if (!pi->next->progs->argv) continue; + /* create list of variable values */ + list = make_list_in(pi->next->progs->argv, + pi->progs->argv[0]); + save_list = list; + save_name = pi->progs->argv[0]; + pi->progs->argv[0] = NULL; + flag_rep = 1; + } + if (!(*list)) { + free(pi->progs->argv[0]); + free(save_list); + list = NULL; + flag_rep = 0; + pi->progs->argv[0] = save_name; + pi->progs->glob_result.gl_pathv[0] = + pi->progs->argv[0]; + continue; + } else { + /* insert new value from list for variable */ + if (pi->progs->argv[0]) + free(pi->progs->argv[0]); + pi->progs->argv[0] = *list++; + pi->progs->glob_result.gl_pathv[0] = + pi->progs->argv[0]; + } + } + if (rmode == RES_IN) continue; + if (rmode == RES_DO) { + if (!flag_rep) continue; + } + if ((rmode == RES_DONE)) { + if (flag_rep) { + flag_restore = 1; + } else { + rpipe = NULL; + } + } + if (pi->num_progs == 0) continue; + save_num_progs = pi->num_progs; /* save number of programs */ + rcode = run_pipe_real(pi); + debug_printf("run_pipe_real returned %d\n",rcode); + if (rcode!=-1) { + /* We only ran a builtin: rcode was set by the return value + * of run_pipe_real(), and we don't need to wait for anything. */ + } else if (pi->followup==PIPE_BG) { + /* XXX check bash's behavior with nontrivial pipes */ + /* XXX compute jobid */ + /* XXX what does bash do with attempts to background builtins? */ + insert_bg_job(pi); + rcode = EXIT_SUCCESS; + } else { + if (interactive) { + /* move the new process group into the foreground */ + if (tcsetpgrp(shell_terminal, pi->pgrp) && errno != ENOTTY) + bb_perror_msg("tcsetpgrp-3"); + rcode = checkjobs(pi); + /* move the shell to the foreground */ + if (tcsetpgrp(shell_terminal, getpgid(0)) && errno != ENOTTY) + bb_perror_msg("tcsetpgrp-4"); + } else { + rcode = checkjobs(pi); + } + debug_printf("checkjobs returned %d\n",rcode); + } + last_return_code=rcode; + pi->num_progs = save_num_progs; /* restore number of programs */ + if ( rmode == RES_IF || rmode == RES_ELIF ) + next_if_code=rcode; /* can be overwritten a number of times */ + if (rmode == RES_WHILE) + flag_rep = !last_return_code; + if (rmode == RES_UNTIL) + flag_rep = last_return_code; + if ( (rcode==EXIT_SUCCESS && pi->followup==PIPE_OR) || + (rcode!=EXIT_SUCCESS && pi->followup==PIPE_AND) ) + skip_more_in_this_rmode=rmode; + checkjobs(NULL); + } + return rcode; +} + +/* return code is the exit status of the pipe */ +static int free_pipe(struct pipe *pi, int indent) +{ + char **p; + struct child_prog *child; + struct redir_struct *r, *rnext; + int a, i, ret_code=0; + + if (pi->stopped_progs > 0) + return ret_code; + final_printf("%s run pipe: (pid %d)\n",indenter(indent),getpid()); + for (i=0; i<pi->num_progs; i++) { + child = &pi->progs[i]; + final_printf("%s command %d:\n",indenter(indent),i); + if (child->argv) { + for (a=0,p=child->argv; *p; a++,p++) { + final_printf("%s argv[%d] = %s\n",indenter(indent),a,*p); + } + globfree(&child->glob_result); + child->argv=NULL; + } else if (child->group) { + final_printf("%s begin group (subshell:%d)\n",indenter(indent), child->subshell); + ret_code = free_pipe_list(child->group,indent+3); + final_printf("%s end group\n",indenter(indent)); + } else { + final_printf("%s (nil)\n",indenter(indent)); + } + for (r=child->redirects; r; r=rnext) { + final_printf("%s redirect %d%s", indenter(indent), r->fd, redir_table[r->type].descrip); + if (r->dup == -1) { + /* guard against the case >$FOO, where foo is unset or blank */ + if (r->word.gl_pathv) { + final_printf(" %s\n", *r->word.gl_pathv); + globfree(&r->word); + } + } else { + final_printf("&%d\n", r->dup); + } + rnext=r->next; + free(r); + } + child->redirects=NULL; + } + free(pi->progs); /* children are an array, they get freed all at once */ + pi->progs=NULL; + return ret_code; +} + +static int free_pipe_list(struct pipe *head, int indent) +{ + int rcode=0; /* if list has no members */ + struct pipe *pi, *next; + for (pi=head; pi; pi=next) { + final_printf("%s pipe reserved mode %d\n", indenter(indent), pi->r_mode); + rcode = free_pipe(pi, indent); + final_printf("%s pipe followup code %d\n", indenter(indent), pi->followup); + next=pi->next; + pi->next=NULL; + free(pi); + } + return rcode; +} + +/* Select which version we will use */ +static int run_list(struct pipe *pi) +{ + int rcode=0; + if (fake_mode==0) { + rcode = run_list_real(pi); + } + /* free_pipe_list has the side effect of clearing memory + * In the long run that function can be merged with run_list_real, + * but doing that now would hobble the debugging effort. */ + free_pipe_list(pi,0); + return rcode; +} + +/* The API for glob is arguably broken. This routine pushes a non-matching + * string into the output structure, removing non-backslashed backslashes. + * If someone can prove me wrong, by performing this function within the + * original glob(3) api, feel free to rewrite this routine into oblivion. + * Return code (0 vs. GLOB_NOSPACE) matches glob(3). + * XXX broken if the last character is '\\', check that before calling. + */ +static int globhack(const char *src, int flags, glob_t *pglob) +{ + int cnt=0, pathc; + const char *s; + char *dest; + for (cnt=1, s=src; s && *s; s++) { + if (*s == '\\') s++; + cnt++; + } + dest = malloc(cnt); + if (!dest) return GLOB_NOSPACE; + if (!(flags & GLOB_APPEND)) { + pglob->gl_pathv=NULL; + pglob->gl_pathc=0; + pglob->gl_offs=0; + pglob->gl_offs=0; + } + pathc = ++pglob->gl_pathc; + pglob->gl_pathv = realloc(pglob->gl_pathv, (pathc+1)*sizeof(*pglob->gl_pathv)); + if (pglob->gl_pathv == NULL) return GLOB_NOSPACE; + pglob->gl_pathv[pathc-1]=dest; + pglob->gl_pathv[pathc]=NULL; + for (s=src; s && *s; s++, dest++) { + if (*s == '\\') s++; + *dest = *s; + } + *dest='\0'; + return 0; +} + +/* XXX broken if the last character is '\\', check that before calling */ +static int glob_needed(const char *s) +{ + for (; *s; s++) { + if (*s == '\\') s++; + if (strchr("*[?",*s)) return 1; + } + return 0; +} + +static int xglob(o_string *dest, int flags, glob_t *pglob) +{ + int gr; + + /* short-circuit for null word */ + /* we can code this better when the debug_printf's are gone */ + if (dest->length == 0) { + if (dest->nonnull) { + /* bash man page calls this an "explicit" null */ + gr = globhack(dest->data, flags, pglob); + debug_printf("globhack returned %d\n",gr); + } else { + return 0; + } + } else if (glob_needed(dest->data)) { + gr = glob(dest->data, flags, NULL, pglob); + debug_printf("glob returned %d\n",gr); + if (gr == GLOB_NOMATCH) { + /* quote removal, or more accurately, backslash removal */ + gr = globhack(dest->data, flags, pglob); + debug_printf("globhack returned %d\n",gr); + } + } else { + gr = globhack(dest->data, flags, pglob); + debug_printf("globhack returned %d\n",gr); + } + if (gr == GLOB_NOSPACE) + bb_error_msg_and_die("out of memory during glob"); + if (gr != 0) { /* GLOB_ABORTED ? */ + bb_error_msg("glob(3) error %d",gr); + } + /* globprint(glob_target); */ + return gr; +} + +/* This is used to get/check local shell variables */ +static char *get_local_var(const char *s) +{ + struct variables *cur; + + if (!s) + return NULL; + for (cur = top_vars; cur; cur=cur->next) + if(strcmp(cur->name, s)==0) + return cur->value; + return NULL; +} + +/* This is used to set local shell variables + flg_export==0 if only local (not exporting) variable + flg_export==1 if "new" exporting environ + flg_export>1 if current startup environ (not call putenv()) */ +static int set_local_var(const char *s, int flg_export) +{ + char *name, *value; + int result=0; + struct variables *cur; + + name=strdup(s); + + /* Assume when we enter this function that we are already in + * NAME=VALUE format. So the first order of business is to + * split 's' on the '=' into 'name' and 'value' */ + value = strchr(name, '='); + if (value==0 && ++value==0) { + free(name); + return -1; + } + *value++ = 0; + + for(cur = top_vars; cur; cur = cur->next) { + if(strcmp(cur->name, name)==0) + break; + } + + if(cur) { + if(strcmp(cur->value, value)==0) { + if(flg_export>0 && cur->flg_export==0) + cur->flg_export=flg_export; + else + result++; + } else { + if(cur->flg_read_only) { + bb_error_msg("%s: readonly variable", name); + result = -1; + } else { + if(flg_export>0 || cur->flg_export>1) + cur->flg_export=1; + free(cur->value); + + cur->value = strdup(value); + } + } + } else { + cur = malloc(sizeof(struct variables)); + if(!cur) { + result = -1; + } else { + cur->name = strdup(name); + if(cur->name == 0) { + free(cur); + result = -1; + } else { + struct variables *bottom = top_vars; + cur->value = strdup(value); + cur->next = 0; + cur->flg_export = flg_export; + cur->flg_read_only = 0; + while(bottom->next) bottom=bottom->next; + bottom->next = cur; + } + } + } + + if(result==0 && cur->flg_export==1) { + *(value-1) = '='; + result = putenv(name); + } else { + free(name); + if(result>0) /* equivalent to previous set */ + result = 0; + } + return result; +} + +static void unset_local_var(const char *name) +{ + struct variables *cur; + + if (name) { + for (cur = top_vars; cur; cur=cur->next) { + if(strcmp(cur->name, name)==0) + break; + } + if(cur!=0) { + struct variables *next = top_vars; + if(cur->flg_read_only) { + bb_error_msg("%s: readonly variable", name); + return; + } else { + if(cur->flg_export) + unsetenv(cur->name); + free(cur->name); + free(cur->value); + while (next->next != cur) + next = next->next; + next->next = cur->next; + } + free(cur); + } + } +} + +static int is_assignment(const char *s) +{ + if (s==NULL || !isalpha(*s)) return 0; + ++s; + while(isalnum(*s) || *s=='_') ++s; + return *s=='='; +} + +/* the src parameter allows us to peek forward to a possible &n syntax + * for file descriptor duplication, e.g., "2>&1". + * Return code is 0 normally, 1 if a syntax error is detected in src. + * Resource errors (in xmalloc) cause the process to exit */ +static int setup_redirect(struct p_context *ctx, int fd, redir_type style, + struct in_str *input) +{ + struct child_prog *child=ctx->child; + struct redir_struct *redir = child->redirects; + struct redir_struct *last_redir=NULL; + + /* Create a new redir_struct and drop it onto the end of the linked list */ + while(redir) { + last_redir=redir; + redir=redir->next; + } + redir = xmalloc(sizeof(struct redir_struct)); + redir->next=NULL; + redir->word.gl_pathv=NULL; + if (last_redir) { + last_redir->next=redir; + } else { + child->redirects=redir; + } + + redir->type=style; + redir->fd= (fd==-1) ? redir_table[style].default_fd : fd ; + + debug_printf("Redirect type %d%s\n", redir->fd, redir_table[style].descrip); + + /* Check for a '2>&1' type redirect */ + redir->dup = redirect_dup_num(input); + if (redir->dup == -2) return 1; /* syntax error */ + if (redir->dup != -1) { + /* Erik had a check here that the file descriptor in question + * is legit; I postpone that to "run time" + * A "-" representation of "close me" shows up as a -3 here */ + debug_printf("Duplicating redirect '%d>&%d'\n", redir->fd, redir->dup); + } else { + /* We do _not_ try to open the file that src points to, + * since we need to return and let src be expanded first. + * Set ctx->pending_redirect, so we know what to do at the + * end of the next parsed word. + */ + ctx->pending_redirect = redir; + } + return 0; +} + +static struct pipe *new_pipe(void) { + struct pipe *pi; + pi = xmalloc(sizeof(struct pipe)); + pi->num_progs = 0; + pi->progs = NULL; + pi->next = NULL; + pi->followup = 0; /* invalid */ + pi->r_mode = RES_NONE; + return pi; +} + +static void initialize_context(struct p_context *ctx) +{ + ctx->pipe=NULL; + ctx->pending_redirect=NULL; + ctx->child=NULL; + ctx->list_head=new_pipe(); + ctx->pipe=ctx->list_head; + ctx->w=RES_NONE; + ctx->stack=NULL; + ctx->old_flag=0; + done_command(ctx); /* creates the memory for working child */ +} + +/* normal return is 0 + * if a reserved word is found, and processed, return 1 + * should handle if, then, elif, else, fi, for, while, until, do, done. + * case, function, and select are obnoxious, save those for later. + */ +static int reserved_word(o_string *dest, struct p_context *ctx) +{ + struct reserved_combo { + char *literal; + int code; + long flag; + }; + /* Mostly a list of accepted follow-up reserved words. + * FLAG_END means we are done with the sequence, and are ready + * to turn the compound list into a command. + * FLAG_START means the word must start a new compound list. + */ + static struct reserved_combo reserved_list[] = { + { "if", RES_IF, FLAG_THEN | FLAG_START }, + { "then", RES_THEN, FLAG_ELIF | FLAG_ELSE | FLAG_FI }, + { "elif", RES_ELIF, FLAG_THEN }, + { "else", RES_ELSE, FLAG_FI }, + { "fi", RES_FI, FLAG_END }, + { "for", RES_FOR, FLAG_IN | FLAG_START }, + { "while", RES_WHILE, FLAG_DO | FLAG_START }, + { "until", RES_UNTIL, FLAG_DO | FLAG_START }, + { "in", RES_IN, FLAG_DO }, + { "do", RES_DO, FLAG_DONE }, + { "done", RES_DONE, FLAG_END } + }; + struct reserved_combo *r; + for (r=reserved_list; +#define NRES sizeof(reserved_list)/sizeof(struct reserved_combo) + r<reserved_list+NRES; r++) { + if (strcmp(dest->data, r->literal) == 0) { + debug_printf("found reserved word %s, code %d\n",r->literal,r->code); + if (r->flag & FLAG_START) { + struct p_context *new = xmalloc(sizeof(struct p_context)); + debug_printf("push stack\n"); + if (ctx->w == RES_IN || ctx->w == RES_FOR) { + syntax(); + free(new); + ctx->w = RES_SNTX; + b_reset(dest); + return 1; + } + *new = *ctx; /* physical copy */ + initialize_context(ctx); + ctx->stack=new; + } else if ( ctx->w == RES_NONE || ! (ctx->old_flag & (1<<r->code))) { + syntax(); + ctx->w = RES_SNTX; + b_reset(dest); + return 1; + } + ctx->w=r->code; + ctx->old_flag = r->flag; + if (ctx->old_flag & FLAG_END) { + struct p_context *old; + debug_printf("pop stack\n"); + done_pipe(ctx,PIPE_SEQ); + old = ctx->stack; + old->child->group = ctx->list_head; + old->child->subshell = 0; + *ctx = *old; /* physical copy */ + free(old); + } + b_reset (dest); + return 1; + } + } + return 0; +} + +/* normal return is 0. + * Syntax or xglob errors return 1. */ +static int done_word(o_string *dest, struct p_context *ctx) +{ + struct child_prog *child=ctx->child; + glob_t *glob_target; + int gr, flags = 0; + + debug_printf("done_word: %s %p\n", dest->data, child); + if (dest->length == 0 && !dest->nonnull) { + debug_printf(" true null, ignored\n"); + return 0; + } + if (ctx->pending_redirect) { + glob_target = &ctx->pending_redirect->word; + } else { + if (child->group) { + syntax(); + return 1; /* syntax error, groups and arglists don't mix */ + } + if (!child->argv && (ctx->type & FLAG_PARSE_SEMICOLON)) { + debug_printf("checking %s for reserved-ness\n",dest->data); + if (reserved_word(dest,ctx)) return ctx->w==RES_SNTX; + } + glob_target = &child->glob_result; + if (child->argv) flags |= GLOB_APPEND; + } + gr = xglob(dest, flags, glob_target); + if (gr != 0) return 1; + + b_reset(dest); + if (ctx->pending_redirect) { + ctx->pending_redirect=NULL; + if (glob_target->gl_pathc != 1) { + bb_error_msg("ambiguous redirect"); + return 1; + } + } else { + child->argv = glob_target->gl_pathv; + } + if (ctx->w == RES_FOR) { + done_word(dest,ctx); + done_pipe(ctx,PIPE_SEQ); + } + return 0; +} + +/* The only possible error here is out of memory, in which case + * xmalloc exits. */ +static int done_command(struct p_context *ctx) +{ + /* The child is really already in the pipe structure, so + * advance the pipe counter and make a new, null child. + * Only real trickiness here is that the uncommitted + * child structure, to which ctx->child points, is not + * counted in pi->num_progs. */ + struct pipe *pi=ctx->pipe; + struct child_prog *prog=ctx->child; + + if (prog && prog->group == NULL + && prog->argv == NULL + && prog->redirects == NULL) { + debug_printf("done_command: skipping null command\n"); + return 0; + } else if (prog) { + pi->num_progs++; + debug_printf("done_command: num_progs incremented to %d\n",pi->num_progs); + } else { + debug_printf("done_command: initializing\n"); + } + pi->progs = xrealloc(pi->progs, sizeof(*pi->progs) * (pi->num_progs+1)); + + prog = pi->progs + pi->num_progs; + prog->redirects = NULL; + prog->argv = NULL; + prog->is_stopped = 0; + prog->group = NULL; + prog->glob_result.gl_pathv = NULL; + prog->family = pi; + prog->sp = 0; + ctx->child = prog; + prog->type = ctx->type; + + /* but ctx->pipe and ctx->list_head remain unchanged */ + return 0; +} + +static int done_pipe(struct p_context *ctx, pipe_style type) +{ + struct pipe *new_p; + done_command(ctx); /* implicit closure of previous command */ + debug_printf("done_pipe, type %d\n", type); + ctx->pipe->followup = type; + ctx->pipe->r_mode = ctx->w; + new_p=new_pipe(); + ctx->pipe->next = new_p; + ctx->pipe = new_p; + ctx->child = NULL; + done_command(ctx); /* set up new pipe to accept commands */ + return 0; +} + +/* peek ahead in the in_str to find out if we have a "&n" construct, + * as in "2>&1", that represents duplicating a file descriptor. + * returns either -2 (syntax error), -1 (no &), or the number found. + */ +static int redirect_dup_num(struct in_str *input) +{ + int ch, d=0, ok=0; + ch = b_peek(input); + if (ch != '&') return -1; + + b_getch(input); /* get the & */ + ch=b_peek(input); + if (ch == '-') { + b_getch(input); + return -3; /* "-" represents "close me" */ + } + while (isdigit(ch)) { + d = d*10+(ch-'0'); + ok=1; + b_getch(input); + ch = b_peek(input); + } + if (ok) return d; + + bb_error_msg("ambiguous redirect"); + return -2; +} + +/* If a redirect is immediately preceded by a number, that number is + * supposed to tell which file descriptor to redirect. This routine + * looks for such preceding numbers. In an ideal world this routine + * needs to handle all the following classes of redirects... + * echo 2>foo # redirects fd 2 to file "foo", nothing passed to echo + * echo 49>foo # redirects fd 49 to file "foo", nothing passed to echo + * echo -2>foo # redirects fd 1 to file "foo", "-2" passed to echo + * echo 49x>foo # redirects fd 1 to file "foo", "49x" passed to echo + * A -1 output from this program means no valid number was found, so the + * caller should use the appropriate default for this redirection. + */ +static int redirect_opt_num(o_string *o) +{ + int num; + + if (o->length==0) return -1; + for(num=0; num<o->length; num++) { + if (!isdigit(*(o->data+num))) { + return -1; + } + } + /* reuse num (and save an int) */ + num=atoi(o->data); + b_reset(o); + return num; +} + +static FILE *generate_stream_from_list(struct pipe *head) +{ + FILE *pf; + int pid, channel[2]; + if (pipe(channel)<0) bb_perror_msg_and_die("pipe"); +#if !defined(__UCLIBC__) || defined(__ARCH_HAS_MMU__) + pid=fork(); +#else + pid=vfork(); +#endif + if (pid<0) { + bb_perror_msg_and_die("fork"); + } else if (pid==0) { + close(channel[0]); + if (channel[1] != 1) { + dup2(channel[1],1); + close(channel[1]); + } + _exit(run_list_real(head)); /* leaks memory */ + } + debug_printf("forked child %d\n",pid); + close(channel[1]); + pf = fdopen(channel[0],"r"); + debug_printf("pipe on FILE *%p\n",pf); + return pf; +} + +/* this version hacked for testing purposes */ +/* return code is exit status of the process that is run. */ +static int process_command_subs(o_string *dest, struct p_context *ctx, struct in_str *input, int subst_end) +{ + int retcode; + o_string result=NULL_O_STRING; + struct p_context inner; + FILE *p; + struct in_str pipe_str; + initialize_context(&inner); + + /* recursion to generate command */ + retcode = parse_stream(&result, &inner, input, subst_end); + if (retcode != 0) return retcode; /* syntax error or EOF */ + done_word(&result, &inner); + done_pipe(&inner, PIPE_SEQ); + b_free(&result); + + p=generate_stream_from_list(inner.list_head); + if (p==NULL) return 1; + mark_open(fileno(p)); + setup_file_in_str(&pipe_str, p); + + /* now send results of command back into original context */ + retcode = parse_stream(dest, ctx, &pipe_str, '\0'); + /* XXX In case of a syntax error, should we try to kill the child? + * That would be tough to do right, so just read until EOF. */ + if (retcode == 1) { + while (b_getch(&pipe_str)!=EOF) { /* discard */ }; + } + + debug_printf("done reading from pipe, pclose()ing\n"); + /* This is the step that wait()s for the child. Should be pretty + * safe, since we just read an EOF from its stdout. We could try + * to better, by using wait(), and keeping track of background jobs + * at the same time. That would be a lot of work, and contrary + * to the KISS philosophy of this program. */ + mark_closed(fileno(p)); + retcode=pclose(p); + free_pipe_list(inner.list_head,0); + debug_printf("pclosed, retcode=%d\n",retcode); + /* XXX this process fails to trim a single trailing newline */ + return retcode; +} + +static int parse_group(o_string *dest, struct p_context *ctx, + struct in_str *input, int ch) +{ + int rcode, endch=0; + struct p_context sub; + struct child_prog *child = ctx->child; + if (child->argv) { + syntax(); + return 1; /* syntax error, groups and arglists don't mix */ + } + initialize_context(&sub); + switch(ch) { + case '(': endch=')'; child->subshell=1; break; + case '{': endch='}'; break; + default: syntax(); /* really logic error */ + } + rcode=parse_stream(dest,&sub,input,endch); + done_word(dest,&sub); /* finish off the final word in the subcontext */ + done_pipe(&sub, PIPE_SEQ); /* and the final command there, too */ + child->group = sub.list_head; + return rcode; + /* child remains "open", available for possible redirects */ +} + +/* basically useful version until someone wants to get fancier, + * see the bash man page under "Parameter Expansion" */ +static char *lookup_param(char *src) +{ + char *p=NULL; + if (src) { + p = getenv(src); + if (!p) + p = get_local_var(src); + } + return p; +} + +/* return code: 0 for OK, 1 for syntax error */ +static int handle_dollar(o_string *dest, struct p_context *ctx, struct in_str *input) +{ + int i, advance=0; + char sep[]=" "; + int ch = input->peek(input); /* first character after the $ */ + debug_printf("handle_dollar: ch=%c\n",ch); + if (isalpha(ch)) { + b_addchr(dest, SPECIAL_VAR_SYMBOL); + ctx->child->sp++; + while(ch=b_peek(input),isalnum(ch) || ch=='_') { + b_getch(input); + b_addchr(dest,ch); + } + b_addchr(dest, SPECIAL_VAR_SYMBOL); + } else if (isdigit(ch)) { + i = ch-'0'; /* XXX is $0 special? */ + if (i<global_argc) { + parse_string(dest, ctx, global_argv[i]); /* recursion */ + } + advance = 1; + } else switch (ch) { + case '$': + b_adduint(dest,getpid()); + advance = 1; + break; + case '!': + if (last_bg_pid > 0) b_adduint(dest, last_bg_pid); + advance = 1; + break; + case '?': + b_adduint(dest,last_return_code); + advance = 1; + break; + case '#': + b_adduint(dest,global_argc ? global_argc-1 : 0); + advance = 1; + break; + case '{': + b_addchr(dest, SPECIAL_VAR_SYMBOL); + ctx->child->sp++; + b_getch(input); + /* XXX maybe someone will try to escape the '}' */ + while(ch=b_getch(input),ch!=EOF && ch!='}') { + b_addchr(dest,ch); + } + if (ch != '}') { + syntax(); + return 1; + } + b_addchr(dest, SPECIAL_VAR_SYMBOL); + break; + case '(': + b_getch(input); + process_command_subs(dest, ctx, input, ')'); + break; + case '*': + sep[0]=ifs[0]; + for (i=1; i<global_argc; i++) { + parse_string(dest, ctx, global_argv[i]); + if (i+1 < global_argc) parse_string(dest, ctx, sep); + } + break; + case '@': + case '-': + case '_': + /* still unhandled, but should be eventually */ + bb_error_msg("unhandled syntax: $%c",ch); + return 1; + break; + default: + b_addqchr(dest,'$',dest->quote); + } + /* Eat the character if the flag was set. If the compiler + * is smart enough, we could substitute "b_getch(input);" + * for all the "advance = 1;" above, and also end up with + * a nice size-optimized program. Hah! That'll be the day. + */ + if (advance) b_getch(input); + return 0; +} + +int parse_string(o_string *dest, struct p_context *ctx, const char *src) +{ + struct in_str foo; + setup_string_in_str(&foo, src); + return parse_stream(dest, ctx, &foo, '\0'); +} + +/* return code is 0 for normal exit, 1 for syntax error */ +int parse_stream(o_string *dest, struct p_context *ctx, + struct in_str *input, int end_trigger) +{ + int ch, m; + int redir_fd; + redir_type redir_style; + int next; + + /* Only double-quote state is handled in the state variable dest->quote. + * A single-quote triggers a bypass of the main loop until its mate is + * found. When recursing, quote state is passed in via dest->quote. */ + + debug_printf("parse_stream, end_trigger=%d\n",end_trigger); + while ((ch=b_getch(input))!=EOF) { + m = map[ch]; + next = (ch == '\n') ? 0 : b_peek(input); + debug_printf("parse_stream: ch=%c (%d) m=%d quote=%d\n", + ch,ch,m,dest->quote); + if (m==0 || ((m==1 || m==2) && dest->quote)) { + b_addqchr(dest, ch, dest->quote); + } else { + if (m==2) { /* unquoted IFS */ + if (done_word(dest, ctx)) { + return 1; + } + /* If we aren't performing a substitution, treat a newline as a + * command separator. */ + if (end_trigger != '\0' && ch=='\n') + done_pipe(ctx,PIPE_SEQ); + } + if (ch == end_trigger && !dest->quote && ctx->w==RES_NONE) { + debug_printf("leaving parse_stream (triggered)\n"); + return 0; + } + if (m!=2) switch (ch) { + case '#': + if (dest->length == 0 && !dest->quote) { + while(ch=b_peek(input),ch!=EOF && ch!='\n') { b_getch(input); } + } else { + b_addqchr(dest, ch, dest->quote); + } + break; + case '\\': + if (next == EOF) { + syntax(); + return 1; + } + b_addqchr(dest, '\\', dest->quote); + b_addqchr(dest, b_getch(input), dest->quote); + break; + case '$': + if (handle_dollar(dest, ctx, input)!=0) return 1; + break; + case '\'': + dest->nonnull = 1; + while(ch=b_getch(input),ch!=EOF && ch!='\'') { + b_addchr(dest,ch); + } + if (ch==EOF) { + syntax(); + return 1; + } + break; + case '"': + dest->nonnull = 1; + dest->quote = !dest->quote; + break; + case '`': + process_command_subs(dest, ctx, input, '`'); + break; + case '>': + redir_fd = redirect_opt_num(dest); + done_word(dest, ctx); + redir_style=REDIRECT_OVERWRITE; + if (next == '>') { + redir_style=REDIRECT_APPEND; + b_getch(input); + } else if (next == '(') { + syntax(); /* until we support >(list) Process Substitution */ + return 1; + } + setup_redirect(ctx, redir_fd, redir_style, input); + break; + case '<': + redir_fd = redirect_opt_num(dest); + done_word(dest, ctx); + redir_style=REDIRECT_INPUT; + if (next == '<') { + redir_style=REDIRECT_HEREIS; + b_getch(input); + } else if (next == '>') { + redir_style=REDIRECT_IO; + b_getch(input); + } else if (next == '(') { + syntax(); /* until we support <(list) Process Substitution */ + return 1; + } + setup_redirect(ctx, redir_fd, redir_style, input); + break; + case ';': + done_word(dest, ctx); + done_pipe(ctx,PIPE_SEQ); + break; + case '&': + done_word(dest, ctx); + if (next=='&') { + b_getch(input); + done_pipe(ctx,PIPE_AND); + } else { + done_pipe(ctx,PIPE_BG); + } + break; + case '|': + done_word(dest, ctx); + if (next=='|') { + b_getch(input); + done_pipe(ctx,PIPE_OR); + } else { + /* we could pick up a file descriptor choice here + * with redirect_opt_num(), but bash doesn't do it. + * "echo foo 2| cat" yields "foo 2". */ + done_command(ctx); + } + break; + case '(': + case '{': + if (parse_group(dest, ctx, input, ch)!=0) return 1; + break; + case ')': + case '}': + syntax(); /* Proper use of this character caught by end_trigger */ + return 1; + break; + default: + syntax(); /* this is really an internal logic error */ + return 1; + } + } + } + /* complain if quote? No, maybe we just finished a command substitution + * that was quoted. Example: + * $ echo "`cat foo` plus more" + * and we just got the EOF generated by the subshell that ran "cat foo" + * The only real complaint is if we got an EOF when end_trigger != '\0', + * that is, we were really supposed to get end_trigger, and never got + * one before the EOF. Can't use the standard "syntax error" return code, + * so that parse_stream_outer can distinguish the EOF and exit smoothly. */ + debug_printf("leaving parse_stream (EOF)\n"); + if (end_trigger != '\0') return -1; + return 0; +} + +static void mapset(const char *set, int code) +{ + const unsigned char *s; + for (s = (const unsigned char *)set; *s; s++) map[(int)*s] = code; +} + +static void update_ifs_map(void) +{ + /* char *ifs and char map[256] are both globals. */ + ifs = getenv("IFS"); + if (ifs == NULL) ifs=" \t\n"; + /* Precompute a list of 'flow through' behavior so it can be treated + * quickly up front. Computation is necessary because of IFS. + * Special case handling of IFS == " \t\n" is not implemented. + * The map[] array only really needs two bits each, and on most machines + * that would be faster because of the reduced L1 cache footprint. + */ + memset(map,0,sizeof(map)); /* most characters flow through always */ + mapset("\\$'\"`", 3); /* never flow through */ + mapset("<>;&|(){}#", 1); /* flow through if quoted */ + mapset(ifs, 2); /* also flow through if quoted */ +} + +/* most recursion does not come through here, the exception is + * from builtin_source() */ +int parse_stream_outer(struct in_str *inp, int flag) +{ + + struct p_context ctx; + o_string temp=NULL_O_STRING; + int rcode; + do { + ctx.type = flag; + initialize_context(&ctx); + update_ifs_map(); + if (!(flag & FLAG_PARSE_SEMICOLON) || (flag & FLAG_REPARSING)) mapset(";$&|", 0); + inp->promptmode=1; + rcode = parse_stream(&temp, &ctx, inp, '\n'); + if (rcode != 1 && ctx.old_flag != 0) { + syntax(); + } + if (rcode != 1 && ctx.old_flag == 0) { + done_word(&temp, &ctx); + done_pipe(&ctx,PIPE_SEQ); + run_list(ctx.list_head); + } else { + if (ctx.old_flag != 0) { + free(ctx.stack); + b_reset(&temp); + } + temp.nonnull = 0; + temp.quote = 0; + inp->p = NULL; + free_pipe_list(ctx.list_head,0); + } + b_free(&temp); + } while (rcode != -1 && !(flag & FLAG_EXIT_FROM_LOOP)); /* loop on syntax errors, return on EOF */ + return 0; +} + +static int parse_string_outer(const char *s, int flag) +{ + struct in_str input; + setup_string_in_str(&input, s); + return parse_stream_outer(&input, flag); +} + +static int parse_file_outer(FILE *f) +{ + int rcode; + struct in_str input; + setup_file_in_str(&input, f); + rcode = parse_stream_outer(&input, FLAG_PARSE_SEMICOLON); + return rcode; +} + +/* Make sure we have a controlling tty. If we get started under a job + * aware app (like bash for example), make sure we are now in charge so + * we don't fight over who gets the foreground */ +static void setup_job_control(void) +{ + static pid_t shell_pgrp; + /* Loop until we are in the foreground. */ + while (tcgetpgrp (shell_terminal) != (shell_pgrp = getpgrp ())) + kill (- shell_pgrp, SIGTTIN); + + /* Ignore interactive and job-control signals. */ + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + signal(SIGTERM, SIG_IGN); + signal(SIGTSTP, SIG_IGN); + signal(SIGTTIN, SIG_IGN); + signal(SIGTTOU, SIG_IGN); + signal(SIGCHLD, SIG_IGN); + + /* Put ourselves in our own process group. */ + setsid(); + shell_pgrp = getpid (); + setpgid (shell_pgrp, shell_pgrp); + + /* Grab control of the terminal. */ + tcsetpgrp(shell_terminal, shell_pgrp); +} + +int hush_main(int argc, char **argv) +{ + int opt; + FILE *input; + char **e = environ; + + /* XXX what should these be while sourcing /etc/profile? */ + global_argc = argc; + global_argv = argv; + + /* (re?) initialize globals. Sometimes hush_main() ends up calling + * hush_main(), therefore we cannot rely on the BSS to zero out this + * stuff. Reset these to 0 every time. */ + ifs = NULL; + /* map[] is taken care of with call to update_ifs_map() */ + fake_mode = 0; + interactive = 0; + close_me_head = NULL; + last_bg_pid = 0; + job_list = NULL; + last_jobid = 0; + + /* Initialize some more globals to non-zero values */ + set_cwd(); + if (ENABLE_FEATURE_COMMAND_EDITING) cmdedit_set_initial_prompt(); + else PS1 = NULL; + PS2 = "> "; + + /* initialize our shell local variables with the values + * currently living in the environment */ + if (e) { + for (; *e; e++) + set_local_var(*e, 2); /* without call putenv() */ + } + + last_return_code=EXIT_SUCCESS; + + + if (argv[0] && argv[0][0] == '-') { + debug_printf("\nsourcing /etc/profile\n"); + if ((input = fopen("/etc/profile", "r")) != NULL) { + mark_open(fileno(input)); + parse_file_outer(input); + mark_closed(fileno(input)); + fclose(input); + } + } + input=stdin; + + while ((opt = getopt(argc, argv, "c:xif")) > 0) { + switch (opt) { + case 'c': + { + global_argv = argv+optind; + global_argc = argc-optind; + opt = parse_string_outer(optarg, FLAG_PARSE_SEMICOLON); + goto final_return; + } + break; + case 'i': + interactive++; + break; + case 'f': + fake_mode++; + break; + default: +#ifndef BB_VER + fprintf(stderr, "Usage: sh [FILE]...\n" + " or: sh -c command [args]...\n\n"); + exit(EXIT_FAILURE); +#else + bb_show_usage(); +#endif + } + } + /* A shell is interactive if the `-i' flag was given, or if all of + * the following conditions are met: + * no -c command + * no arguments remaining or the -s flag given + * standard input is a terminal + * standard output is a terminal + * Refer to Posix.2, the description of the `sh' utility. */ + if (argv[optind]==NULL && input==stdin && + isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) { + interactive++; + } + + debug_printf("\ninteractive=%d\n", interactive); + if (interactive) { + /* Looks like they want an interactive shell */ +#ifndef CONFIG_FEATURE_SH_EXTRA_QUIET + printf( "\n\n%s hush - the humble shell v0.01 (testing)\n", + BB_BANNER); + printf( "Enter 'help' for a list of built-in commands.\n\n"); +#endif + setup_job_control(); + } + + if (argv[optind]==NULL) { + opt=parse_file_outer(stdin); + goto final_return; + } + + debug_printf("\nrunning script '%s'\n", argv[optind]); + global_argv = argv+optind; + global_argc = argc-optind; + input = xfopen(argv[optind], "r"); + opt = parse_file_outer(input); + +#ifdef CONFIG_FEATURE_CLEAN_UP + fclose(input); + if (cwd && cwd != bb_msg_unknown) + free((char*)cwd); + { + struct variables *cur, *tmp; + for(cur = top_vars; cur; cur = tmp) { + tmp = cur->next; + if (!cur->flg_read_only) { + free(cur->name); + free(cur->value); + free(cur); + } + } + } +#endif + +final_return: + return opt ? opt : last_return_code; +} + +static char *insert_var_value(char *inp) +{ + int res_str_len = 0; + int len; + int done = 0; + char *p, *p1, *res_str = NULL; + + while ((p = strchr(inp, SPECIAL_VAR_SYMBOL))) { + if (p != inp) { + len = p - inp; + res_str = xrealloc(res_str, (res_str_len + len)); + strncpy((res_str + res_str_len), inp, len); + res_str_len += len; + } + inp = ++p; + p = strchr(inp, SPECIAL_VAR_SYMBOL); + *p = '\0'; + if ((p1 = lookup_param(inp))) { + len = res_str_len + strlen(p1); + res_str = xrealloc(res_str, (1 + len)); + strcpy((res_str + res_str_len), p1); + res_str_len = len; + } + *p = SPECIAL_VAR_SYMBOL; + inp = ++p; + done = 1; + } + if (done) { + res_str = xrealloc(res_str, (1 + res_str_len + strlen(inp))); + strcpy((res_str + res_str_len), inp); + while ((p = strchr(res_str, '\n'))) { + *p = ' '; + } + } + return (res_str == NULL) ? inp : res_str; +} + +static char **make_list_in(char **inp, char *name) +{ + int len, i; + int name_len = strlen(name); + int n = 0; + char **list; + char *p1, *p2, *p3; + + /* create list of variable values */ + list = xmalloc(sizeof(*list)); + for (i = 0; inp[i]; i++) { + p3 = insert_var_value(inp[i]); + p1 = p3; + while (*p1) { + if ((*p1 == ' ')) { + p1++; + continue; + } + if ((p2 = strchr(p1, ' '))) { + len = p2 - p1; + } else { + len = strlen(p1); + p2 = p1 + len; + } + /* we use n + 2 in realloc for list,because we add + * new element and then we will add NULL element */ + list = xrealloc(list, sizeof(*list) * (n + 2)); + list[n] = xmalloc(2 + name_len + len); + strcpy(list[n], name); + strcat(list[n], "="); + strncat(list[n], p1, len); + list[n++][name_len + len + 1] = '\0'; + p1 = p2; + } + if (p3 != inp[i]) free(p3); + } + list[n] = NULL; + return list; +} + +/* Make new string for parser */ +static char * make_string(char ** inp) +{ + char *p; + char *str = NULL; + int n; + int len = 2; + + for (n = 0; inp[n]; n++) { + p = insert_var_value(inp[n]); + str = xrealloc(str, (len + strlen(p))); + if (n) { + strcat(str, " "); + } else { + *str = '\0'; + } + strcat(str, p); + len = strlen(str) + 3; + if (p != inp[n]) free(p); + } + len = strlen(str); + *(str + len) = '\n'; + *(str + len + 1) = '\0'; + return str; +} diff --git a/shell/lash.c b/shell/lash.c new file mode 100644 index 000000000..3b51e98a9 --- /dev/null +++ b/shell/lash.c @@ -0,0 +1,1573 @@ +/* vi: set sw=4 ts=4: */ +/* + * lash -- the BusyBox Lame-Ass SHell + * + * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> + * + * Based in part on ladsh.c by Michael K. Johnson and Erik W. Troan, which is + * under the following liberal license: "We have placed this source code in the + * public domain. Use it in any project, free or commercial." + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +/* This shell's parsing engine is officially at a dead-end. Future + * work shell work should be done using hush, msh, or ash. This is + * still a very useful, small shell -- it just don't need any more + * features beyond what it already has... + */ + +//For debugging/development on the shell only... +//#define DEBUG_SHELL + + +#include "busybox.h" +#include <getopt.h> +#include "cmdedit.h" + +#include <glob.h> +#define expand_t glob_t + +/* Always enable for the moment... */ +#define CONFIG_LASH_PIPE_N_REDIRECTS +#define CONFIG_LASH_JOB_CONTROL + +static const int MAX_READ = 128; /* size of input buffer for `read' builtin */ +#define JOB_STATUS_FORMAT "[%d] %-22s %.40s\n" + + +#ifdef CONFIG_LASH_PIPE_N_REDIRECTS +enum redir_type { REDIRECT_INPUT, REDIRECT_OVERWRITE, + REDIRECT_APPEND +}; +#endif + +enum { + DEFAULT_CONTEXT = 0x1, + IF_TRUE_CONTEXT = 0x2, + IF_FALSE_CONTEXT = 0x4, + THEN_EXP_CONTEXT = 0x8, + ELSE_EXP_CONTEXT = 0x10 +}; + +#define LASH_OPT_DONE (1) +#define LASH_OPT_SAW_QUOTE (2) + +#ifdef CONFIG_LASH_PIPE_N_REDIRECTS +struct redir_struct { + enum redir_type type; /* type of redirection */ + int fd; /* file descriptor being redirected */ + char *filename; /* file to redirect fd to */ +}; +#endif + +struct child_prog { + pid_t pid; /* 0 if exited */ + char **argv; /* program name and arguments */ + int num_redirects; /* elements in redirection array */ + int is_stopped; /* is the program currently running? */ + struct job *family; /* pointer back to the child's parent job */ +#ifdef CONFIG_LASH_PIPE_N_REDIRECTS + struct redir_struct *redirects; /* I/O redirects */ +#endif +}; + +struct jobset { + struct job *head; /* head of list of running jobs */ + struct job *fg; /* current foreground job */ +}; + +struct job { + int jobid; /* job number */ + int num_progs; /* total number of programs in job */ + int running_progs; /* number of programs running */ + char *text; /* name of job */ + char *cmdbuf; /* buffer various argv's point into */ + pid_t pgrp; /* process group ID for the job */ + struct child_prog *progs; /* array of programs in job */ + struct job *next; /* to track background commands */ + int stopped_progs; /* number of programs alive, but stopped */ + unsigned int job_context; /* bitmask defining current context */ + struct jobset *job_list; +}; + +struct built_in_command { + char *cmd; /* name */ + char *descr; /* description */ + int (*function) (struct child_prog *); /* function ptr */ +}; + +/* function prototypes for builtins */ +static int builtin_cd(struct child_prog *cmd); +static int builtin_exec(struct child_prog *cmd); +static int builtin_exit(struct child_prog *cmd); +static int builtin_fg_bg(struct child_prog *cmd); +static int builtin_help(struct child_prog *cmd); +static int builtin_jobs(struct child_prog *dummy); +static int builtin_pwd(struct child_prog *dummy); +static int builtin_export(struct child_prog *cmd); +static int builtin_source(struct child_prog *cmd); +static int builtin_unset(struct child_prog *cmd); +static int builtin_read(struct child_prog *cmd); + + +/* function prototypes for shell stuff */ +static void checkjobs(struct jobset *job_list); +static void remove_job(struct jobset *j_list, struct job *job); +static int get_command(FILE * source, char *command); +static int parse_command(char **command_ptr, struct job *job, int *inbg); +static int run_command(struct job *newjob, int inbg, int outpipe[2]); +static int pseudo_exec(struct child_prog *cmd) ATTRIBUTE_NORETURN; +static int busy_loop(FILE * input); + + +/* Table of built-in functions (these are non-forking builtins, meaning they + * can change global variables in the parent shell process but they will not + * work with pipes and redirects; 'unset foo | whatever' will not work) */ +static struct built_in_command bltins[] = { + {"bg", "Resume a job in the background", builtin_fg_bg}, + {"cd", "Change working directory", builtin_cd}, + {"exec", "Exec command, replacing this shell with the exec'd process", builtin_exec}, + {"exit", "Exit from shell()", builtin_exit}, + {"fg", "Bring job into the foreground", builtin_fg_bg}, + {"jobs", "Lists the active jobs", builtin_jobs}, + {"export", "Set environment variable", builtin_export}, + {"unset", "Unset environment variable", builtin_unset}, + {"read", "Input environment variable", builtin_read}, + {".", "Source-in and run commands in a file", builtin_source}, + /* to do: add ulimit */ + {NULL, NULL, NULL} +}; + +/* Table of forking built-in functions (things that fork cannot change global + * variables in the parent process, such as the current working directory) */ +static struct built_in_command bltins_forking[] = { + {"pwd", "Print current directory", builtin_pwd}, + {"help", "List shell built-in commands", builtin_help}, + {NULL, NULL, NULL} +}; + + +static int shell_context; /* Type prompt trigger (PS1 or PS2) */ + + +/* Globals that are static to this file */ +static const char *cwd; +static char *local_pending_command; +static struct jobset job_list = { NULL, NULL }; +static int argc; +static char **argv; +static llist_t *close_me_list; +static int last_return_code; +static int last_bg_pid; +static unsigned int last_jobid; +static int shell_terminal; +static char *PS1; +static char *PS2 = "> "; + + +#ifdef DEBUG_SHELL +static inline void debug_printf(const char *format, ...) +{ + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); +} +#else +static inline void debug_printf(const char ATTRIBUTE_UNUSED *format, ...) { } +#endif + +/* + Most builtins need access to the struct child_prog that has + their arguments, previously coded as cmd->progs[0]. That coding + can exhibit a bug, if the builtin is not the first command in + a pipeline: "echo foo | exec sort" will attempt to exec foo. + +builtin previous use notes +------ ----------------- --------- +cd cmd->progs[0] +exec cmd->progs[0] squashed bug: didn't look for applets or forking builtins +exit cmd->progs[0] +fg_bg cmd->progs[0], job_list->head, job_list->fg +help 0 +jobs job_list->head +pwd 0 +export cmd->progs[0] +source cmd->progs[0] +unset cmd->progs[0] +read cmd->progs[0] + +I added "struct job *family;" to struct child_prog, +and switched API to builtin_foo(struct child_prog *child); +So cmd->text becomes child->family->text + cmd->job_context becomes child->family->job_context + cmd->progs[0] becomes *child + job_list becomes child->family->job_list + */ + +/* built-in 'cd <path>' handler */ +static int builtin_cd(struct child_prog *child) +{ + char *newdir; + + if (child->argv[1] == NULL) + newdir = getenv("HOME"); + else + newdir = child->argv[1]; + if (chdir(newdir)) { + bb_perror_msg("cd: %s", newdir); + return EXIT_FAILURE; + } + cwd = xgetcwd((char *)cwd); + if (!cwd) + cwd = bb_msg_unknown; + return EXIT_SUCCESS; +} + +/* built-in 'exec' handler */ +static int builtin_exec(struct child_prog *child) +{ + if (child->argv[1] == NULL) + return EXIT_SUCCESS; /* Really? */ + child->argv++; + while(close_me_list) close((long)llist_pop(&close_me_list)); + pseudo_exec(child); + /* never returns */ +} + +/* built-in 'exit' handler */ +static int builtin_exit(struct child_prog *child) +{ + if (child->argv[1] == NULL) + exit(EXIT_SUCCESS); + + exit (atoi(child->argv[1])); +} + +/* built-in 'fg' and 'bg' handler */ +static int builtin_fg_bg(struct child_prog *child) +{ + int i, jobnum; + struct job *job=NULL; + + /* If they gave us no args, assume they want the last backgrounded task */ + if (!child->argv[1]) { + for (job = child->family->job_list->head; job; job = job->next) { + if (job->jobid == last_jobid) { + break; + } + } + if (!job) { + bb_error_msg("%s: no current job", child->argv[0]); + return EXIT_FAILURE; + } + } else { + if (sscanf(child->argv[1], "%%%d", &jobnum) != 1) { + bb_error_msg(bb_msg_invalid_arg, child->argv[1], child->argv[0]); + return EXIT_FAILURE; + } + for (job = child->family->job_list->head; job; job = job->next) { + if (job->jobid == jobnum) { + break; + } + } + if (!job) { + bb_error_msg("%s: %d: no such job", child->argv[0], jobnum); + return EXIT_FAILURE; + } + } + + if (*child->argv[0] == 'f') { + /* Put the job into the foreground. */ + tcsetpgrp(shell_terminal, job->pgrp); + + child->family->job_list->fg = job; + } + + /* Restart the processes in the job */ + for (i = 0; i < job->num_progs; i++) + job->progs[i].is_stopped = 0; + + job->stopped_progs = 0; + + if ( (i=kill(- job->pgrp, SIGCONT)) < 0) { + if (i == ESRCH) { + remove_job(&job_list, job); + } else { + bb_perror_msg("kill (SIGCONT)"); + } + } + + return EXIT_SUCCESS; +} + +/* built-in 'help' handler */ +static int builtin_help(struct child_prog ATTRIBUTE_UNUSED *dummy) +{ + struct built_in_command *x; + + printf("\nBuilt-in commands:\n" + "-------------------\n"); + for (x = bltins; x->cmd; x++) { + if (x->descr==NULL) + continue; + printf("%s\t%s\n", x->cmd, x->descr); + } + for (x = bltins_forking; x->cmd; x++) { + if (x->descr==NULL) + continue; + printf("%s\t%s\n", x->cmd, x->descr); + } + putchar('\n'); + return EXIT_SUCCESS; +} + +/* built-in 'jobs' handler */ +static int builtin_jobs(struct child_prog *child) +{ + struct job *job; + char *status_string; + + for (job = child->family->job_list->head; job; job = job->next) { + if (job->running_progs == job->stopped_progs) + status_string = "Stopped"; + else + status_string = "Running"; + + printf(JOB_STATUS_FORMAT, job->jobid, status_string, job->text); + } + return EXIT_SUCCESS; +} + + +/* built-in 'pwd' handler */ +static int builtin_pwd(struct child_prog ATTRIBUTE_UNUSED *dummy) +{ + cwd = xgetcwd((char *)cwd); + if (!cwd) + cwd = bb_msg_unknown; + puts(cwd); + return EXIT_SUCCESS; +} + +/* built-in 'export VAR=value' handler */ +static int builtin_export(struct child_prog *child) +{ + int res; + char *v = child->argv[1]; + + if (v == NULL) { + char **e; + for (e = environ; *e; e++) { + puts(*e); + } + return 0; + } + res = putenv(v); + if (res) + bb_perror_msg("export"); +#ifdef CONFIG_FEATURE_SH_FANCY_PROMPT + if (strncmp(v, "PS1=", 4)==0) + PS1 = getenv("PS1"); +#endif + +#ifdef CONFIG_LOCALE_SUPPORT + // TODO: why getenv? "" would be just as good... + if(strncmp(v, "LC_ALL=", 7)==0) + setlocale(LC_ALL, getenv("LC_ALL")); + if(strncmp(v, "LC_CTYPE=", 9)==0) + setlocale(LC_CTYPE, getenv("LC_CTYPE")); +#endif + + return res; +} + +/* built-in 'read VAR' handler */ +static int builtin_read(struct child_prog *child) +{ + int res = 0, len; + char *s; + char string[MAX_READ]; + + if (child->argv[1]) { + /* argument (VAR) given: put "VAR=" into buffer */ + safe_strncpy(string, child->argv[1], MAX_READ-1); + len = strlen(string); + string[len++] = '='; + string[len] = '\0'; + fgets(&string[len], sizeof(string) - len, stdin); /* read string */ + res = strlen(string); + if (res > len) + string[--res] = '\0'; /* chomp trailing newline */ + /* + ** string should now contain "VAR=<value>" + ** copy it (putenv() won't do that, so we must make sure + ** the string resides in a static buffer!) + */ + res = -1; + if ((s = strdup(string))) + res = putenv(s); + if (res) + bb_perror_msg("read"); + } + else + fgets(string, sizeof(string), stdin); + + return res; +} + +/* Built-in '.' handler (read-in and execute commands from file) */ +static int builtin_source(struct child_prog *child) +{ + FILE *input; + int status; + + input = fopen_or_warn(child->argv[1], "r"); + if (!input) { + return EXIT_FAILURE; + } + + llist_add_to(&close_me_list, (void *)(long)fileno(input)); + /* Now run the file */ + status = busy_loop(input); + fclose(input); + llist_pop(&close_me_list); + return status; +} + +/* built-in 'unset VAR' handler */ +static int builtin_unset(struct child_prog *child) +{ + if (child->argv[1] == NULL) { + printf(bb_msg_requires_arg, "unset"); + return EXIT_FAILURE; + } + unsetenv(child->argv[1]); + return EXIT_SUCCESS; +} + +#ifdef CONFIG_LASH_JOB_CONTROL +/* free up all memory from a job */ +static void free_job(struct job *cmd) +{ + int i; + struct jobset *keep; + + for (i = 0; i < cmd->num_progs; i++) { + free(cmd->progs[i].argv); +#ifdef CONFIG_LASH_PIPE_N_REDIRECTS + if (cmd->progs[i].redirects) + free(cmd->progs[i].redirects); +#endif + } + free(cmd->progs); + free(cmd->text); + free(cmd->cmdbuf); + keep = cmd->job_list; + memset(cmd, 0, sizeof(struct job)); + cmd->job_list = keep; +} + +/* remove a job from a jobset */ +static void remove_job(struct jobset *j_list, struct job *job) +{ + struct job *prevjob; + + free_job(job); + if (job == j_list->head) { + j_list->head = job->next; + } else { + prevjob = j_list->head; + while (prevjob->next != job) + prevjob = prevjob->next; + prevjob->next = job->next; + } + + if (j_list->head) + last_jobid = j_list->head->jobid; + else + last_jobid = 0; + + free(job); +} + +/* Checks to see if any background processes have exited -- if they + have, figure out why and see if a job has completed */ +static void checkjobs(struct jobset *j_list) +{ + struct job *job; + pid_t childpid; + int status; + int prognum = 0; + + while ((childpid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0) { + for (job = j_list->head; job; job = job->next) { + prognum = 0; + while (prognum < job->num_progs && + job->progs[prognum].pid != childpid) prognum++; + if (prognum < job->num_progs) + break; + } + + /* This happens on backticked commands */ + if(job==NULL) + return; + + if (WIFEXITED(status) || WIFSIGNALED(status)) { + /* child exited */ + job->running_progs--; + job->progs[prognum].pid = 0; + + if (!job->running_progs) { + printf(JOB_STATUS_FORMAT, job->jobid, "Done", job->text); + last_jobid=0; + remove_job(j_list, job); + } + } else { + /* child stopped */ + job->stopped_progs++; + job->progs[prognum].is_stopped = 1; + } + } + + if (childpid == -1 && errno != ECHILD) + bb_perror_msg("waitpid"); +} +#else +static void checkjobs(struct jobset *j_list) +{ +} +static void free_job(struct job *cmd) +{ +} +static void remove_job(struct jobset *j_list, struct job *job) +{ +} +#endif + +#ifdef CONFIG_LASH_PIPE_N_REDIRECTS +/* squirrel != NULL means we squirrel away copies of stdin, stdout, + * and stderr if they are redirected. */ +static int setup_redirects(struct child_prog *prog, int squirrel[]) +{ + int i; + int openfd; + int mode = O_RDONLY; + struct redir_struct *redir = prog->redirects; + + for (i = 0; i < prog->num_redirects; i++, redir++) { + switch (redir->type) { + case REDIRECT_INPUT: + mode = O_RDONLY; + break; + case REDIRECT_OVERWRITE: + mode = O_WRONLY | O_CREAT | O_TRUNC; + break; + case REDIRECT_APPEND: + mode = O_WRONLY | O_CREAT | O_APPEND; + break; + } + + openfd = open(redir->filename, mode, 0666); + if (openfd < 0) { + /* this could get lost if stderr has been redirected, but + bash and ash both lose it as well (though zsh doesn't!) */ + bb_perror_msg("error opening %s", redir->filename); + return 1; + } + + if (openfd != redir->fd) { + if (squirrel && redir->fd < 3) { + squirrel[redir->fd] = dup(redir->fd); + fcntl (squirrel[redir->fd], F_SETFD, FD_CLOEXEC); + } + dup2(openfd, redir->fd); + close(openfd); + } + } + + return 0; +} + +static void restore_redirects(int squirrel[]) +{ + int i, fd; + for (i=0; i<3; i++) { + fd = squirrel[i]; + if (fd != -1) { + /* No error checking. I sure wouldn't know what + * to do with an error if I found one! */ + dup2(fd, i); + close(fd); + } + } +} +#else +static inline int setup_redirects(struct child_prog *prog, int squirrel[]) +{ + return 0; +} +static inline void restore_redirects(int squirrel[]) +{ +} +#endif + +static inline void cmdedit_set_initial_prompt(void) +{ +#ifndef CONFIG_FEATURE_SH_FANCY_PROMPT + PS1 = NULL; +#else + PS1 = getenv("PS1"); + if(PS1==0) + PS1 = "\\w \\$ "; +#endif +} + +static inline void setup_prompt_string(char **prompt_str) +{ +#ifndef CONFIG_FEATURE_SH_FANCY_PROMPT + /* Set up the prompt */ + if (shell_context == 0) { + free(PS1); + PS1=xmalloc(strlen(cwd)+4); + sprintf(PS1, "%s %c ", cwd, ( geteuid() != 0 ) ? '$': '#'); + *prompt_str = PS1; + } else { + *prompt_str = PS2; + } +#else + *prompt_str = (shell_context==0)? PS1 : PS2; +#endif +} + +static int get_command(FILE * source, char *command) +{ + char *prompt_str; + + if (source == NULL) { + if (local_pending_command) { + /* a command specified (-c option): return it & mark it done */ + strcpy(command, local_pending_command); + free(local_pending_command); + local_pending_command = NULL; + return 0; + } + return 1; + } + + if (source == stdin) { + setup_prompt_string(&prompt_str); + +#ifdef CONFIG_FEATURE_COMMAND_EDITING + /* + ** enable command line editing only while a command line + ** is actually being read; otherwise, we'll end up bequeathing + ** atexit() handlers and other unwanted stuff to our + ** child processes (rob@sysgo.de) + */ + cmdedit_read_input(prompt_str, command); + return 0; +#else + fputs(prompt_str, stdout); +#endif + } + + if (!fgets(command, BUFSIZ - 2, source)) { + if (source == stdin) + puts(""); + return 1; + } + + return 0; +} + +static char * strsep_space( char *string, int * ix) +{ + /* Short circuit the trivial case */ + if ( !string || ! string[*ix]) + return NULL; + + /* Find the end of the token. */ + while (string[*ix] && !isspace(string[*ix]) ) { + (*ix)++; + } + + /* Find the end of any whitespace trailing behind + * the token and let that be part of the token */ + while (string[*ix] && (isspace)(string[*ix]) ) { + (*ix)++; + } + + if (!*ix) { + /* Nothing useful was found */ + return NULL; + } + + return xstrndup(string, *ix); +} + +static int expand_arguments(char *command) +{ + int total_length=0, length, i, retval, ix = 0; + expand_t expand_result; + char *tmpcmd, *cmd, *cmd_copy; + char *src, *dst, *var; + const char * const out_of_space = "out of space during expansion"; + int flags = GLOB_NOCHECK +#ifdef GLOB_BRACE + | GLOB_BRACE +#endif +#ifdef GLOB_TILDE + | GLOB_TILDE +#endif + ; + + /* get rid of the terminating \n */ + chomp(command); + + /* Fix up escape sequences to be the Real Thing(tm) */ + while( command && command[ix]) { + if (command[ix] == '\\') { + const char *tmp = command+ix+1; + command[ix] = bb_process_escape_sequence( &tmp ); + memmove(command+ix + 1, tmp, strlen(tmp)+1); + } + ix++; + } + /* Use glob and then fixup environment variables and such */ + + /* It turns out that glob is very stupid. We have to feed it one word at a + * time since it can't cope with a full string. Here we convert command + * (char*) into cmd (char**, one word per string) */ + + /* We need a clean copy, so strsep can mess up the copy while + * we write stuff into the original (in a minute) */ + cmd = cmd_copy = xstrdup(command); + *command = '\0'; + for (ix = 0, tmpcmd = cmd; + (tmpcmd = strsep_space(cmd, &ix)) != NULL; cmd += ix, ix=0) { + if (*tmpcmd == '\0') + break; + /* we need to trim() the result for glob! */ + trim(tmpcmd); + retval = glob(tmpcmd, flags, NULL, &expand_result); + free(tmpcmd); /* Free mem allocated by strsep_space */ + if (retval == GLOB_NOSPACE) { + /* Mem may have been allocated... */ + globfree (&expand_result); + bb_error_msg(out_of_space); + return FALSE; + } else if (retval != 0) { + /* Some other error. GLOB_NOMATCH shouldn't + * happen because of the GLOB_NOCHECK flag in + * the glob call. */ + bb_error_msg("syntax error"); + return FALSE; + } else { + /* Convert from char** (one word per string) to a simple char*, + * but don't overflow command which is BUFSIZ in length */ + for (i=0; i < expand_result.gl_pathc; i++) { + length=strlen(expand_result.gl_pathv[i]); + if (total_length+length+1 >= BUFSIZ) { + bb_error_msg(out_of_space); + return FALSE; + } + strcat(command+total_length, " "); + total_length+=1; + strcat(command+total_length, expand_result.gl_pathv[i]); + total_length+=length; + } + globfree (&expand_result); + } + } + free(cmd_copy); + trim(command); + + /* Now do the shell variable substitutions which + * wordexp can't do for us, namely $? and $! */ + src = command; + while((dst = strchr(src,'$')) != NULL){ + var = NULL; + switch(*(dst+1)) { + case '?': + var = itoa(last_return_code); + break; + case '!': + if (last_bg_pid==-1) + *(var)='\0'; + else + var = itoa(last_bg_pid); + break; + /* Everything else like $$, $#, $[0-9], etc. should all be + * expanded by wordexp(), so we can in theory skip that stuff + * here, but just to be on the safe side (i.e., since uClibc + * wordexp doesn't do this stuff yet), lets leave it in for + * now. */ + case '$': + var = itoa(getpid()); + break; + case '#': + var = itoa(argc-1); + break; + case '0':case '1':case '2':case '3':case '4': + case '5':case '6':case '7':case '8':case '9': + { + int ixx=*(dst+1)-48+1; + if (ixx >= argc) { + var='\0'; + } else { + var = argv[ixx]; + } + } + break; + + } + if (var) { + /* a single character construction was found, and + * already handled in the case statement */ + src=dst+2; + } else { + /* Looks like an environment variable */ + char delim_hold; + int num_skip_chars=0; + int dstlen = strlen(dst); + /* Is this a ${foo} type variable? */ + if (dstlen >=2 && *(dst+1) == '{') { + src=strchr(dst+1, '}'); + num_skip_chars=1; + } else { + src=dst+1; + while((isalnum)(*src) || *src=='_') src++; + } + if (src == NULL) { + src = dst+dstlen; + } + delim_hold=*src; + *src='\0'; /* temporary */ + var = getenv(dst + 1 + num_skip_chars); + *src=delim_hold; + src += num_skip_chars; + } + if (var == NULL) { + /* Seems we got an un-expandable variable. So delete it. */ + var = ""; + } + { + int subst_len = strlen(var); + int trail_len = strlen(src); + if (dst+subst_len+trail_len >= command+BUFSIZ) { + bb_error_msg(out_of_space); + return FALSE; + } + /* Move stuff to the end of the string to accommodate + * filling the created gap with the new stuff */ + memmove(dst+subst_len, src, trail_len+1); + /* Now copy in the new stuff */ + memcpy(dst, var, subst_len); + src = dst+subst_len; + } + } + + return TRUE; +} + +/* Return cmd->num_progs as 0 if no command is present (e.g. an empty + line). If a valid command is found, command_ptr is set to point to + the beginning of the next command (if the original command had more + then one job associated with it) or NULL if no more commands are + present. */ +static int parse_command(char **command_ptr, struct job *job, int *inbg) +{ + char *command; + char *return_command = NULL; + char *src, *buf; + int argc_l; + int flag; + int argv_alloced; + char quote = '\0'; + struct child_prog *prog; +#ifdef CONFIG_LASH_PIPE_N_REDIRECTS + int i; + char *chptr; +#endif + + /* skip leading white space */ + *command_ptr = skip_whitespace(*command_ptr); + + /* this handles empty lines or leading '#' characters */ + if (!**command_ptr || (**command_ptr == '#')) { + job->num_progs=0; + return 0; + } + + *inbg = 0; + job->num_progs = 1; + job->progs = xmalloc(sizeof(*job->progs)); + + /* We set the argv elements to point inside of this string. The + memory is freed by free_job(). Allocate twice the original + length in case we need to quote every single character. + + Getting clean memory relieves us of the task of NULL + terminating things and makes the rest of this look a bit + cleaner (though it is, admittedly, a tad less efficient) */ + job->cmdbuf = command = xzalloc(2*strlen(*command_ptr) + 1); + job->text = NULL; + + prog = job->progs; + prog->num_redirects = 0; + prog->is_stopped = 0; + prog->family = job; +#ifdef CONFIG_LASH_PIPE_N_REDIRECTS + prog->redirects = NULL; +#endif + + argv_alloced = 5; + prog->argv = xmalloc(sizeof(*prog->argv) * argv_alloced); + prog->argv[0] = job->cmdbuf; + + flag = argc_l = 0; + buf = command; + src = *command_ptr; + while (*src && !(flag & LASH_OPT_DONE)) { + if (quote == *src) { + quote = '\0'; + } else if (quote) { + if (*src == '\\') { + src++; + if (!*src) { + bb_error_msg("character expected after \\"); + free_job(job); + return 1; + } + + /* in shell, "\'" should yield \' */ + if (*src != quote) { + *buf++ = '\\'; + *buf++ = '\\'; + } + } else if (*src == '*' || *src == '?' || *src == '[' || + *src == ']') *buf++ = '\\'; + *buf++ = *src; + } else if (isspace(*src)) { + if (*prog->argv[argc_l] || flag & LASH_OPT_SAW_QUOTE) { + buf++, argc_l++; + /* +1 here leaves room for the NULL which ends argv */ + if ((argc_l + 1) == argv_alloced) { + argv_alloced += 5; + prog->argv = xrealloc(prog->argv, + sizeof(*prog->argv) * + argv_alloced); + } + prog->argv[argc_l] = buf; + flag ^= LASH_OPT_SAW_QUOTE; + } + } else + switch (*src) { + case '"': + case '\'': + quote = *src; + flag |= LASH_OPT_SAW_QUOTE; + break; + + case '#': /* comment */ + if (*(src-1)== '$') + *buf++ = *src; + else + flag |= LASH_OPT_DONE; + break; + +#ifdef CONFIG_LASH_PIPE_N_REDIRECTS + case '>': /* redirects */ + case '<': + i = prog->num_redirects++; + prog->redirects = xrealloc(prog->redirects, + sizeof(*prog->redirects) * + (i + 1)); + + prog->redirects[i].fd = -1; + if (buf != prog->argv[argc_l]) { + /* the stuff before this character may be the file number + being redirected */ + prog->redirects[i].fd = + strtol(prog->argv[argc_l], &chptr, 10); + + if (*chptr && *prog->argv[argc_l]) { + buf++, argc_l++; + prog->argv[argc_l] = buf; + } + } + + if (prog->redirects[i].fd == -1) { + if (*src == '>') + prog->redirects[i].fd = 1; + else + prog->redirects[i].fd = 0; + } + + if (*src++ == '>') { + if (*src == '>') + prog->redirects[i].type = + REDIRECT_APPEND, src++; + else + prog->redirects[i].type = REDIRECT_OVERWRITE; + } else { + prog->redirects[i].type = REDIRECT_INPUT; + } + + /* This isn't POSIX sh compliant. Oh well. */ + chptr = src; + chptr = skip_whitespace(chptr); + + if (!*chptr) { + bb_error_msg("file name expected after %c", *(src-1)); + free_job(job); + job->num_progs=0; + return 1; + } + + prog->redirects[i].filename = buf; + while (*chptr && !isspace(*chptr)) + *buf++ = *chptr++; + + src = chptr - 1; /* we src++ later */ + prog->argv[argc_l] = ++buf; + break; + + case '|': /* pipe */ + /* finish this command */ + if (*prog->argv[argc_l] || flag & LASH_OPT_SAW_QUOTE) + argc_l++; + if (!argc_l) { + goto empty_command_in_pipe; + } + prog->argv[argc_l] = NULL; + + /* and start the next */ + job->num_progs++; + job->progs = xrealloc(job->progs, + sizeof(*job->progs) * job->num_progs); + prog = job->progs + (job->num_progs - 1); + prog->num_redirects = 0; + prog->redirects = NULL; + prog->is_stopped = 0; + prog->family = job; + argc_l = 0; + + argv_alloced = 5; + prog->argv = xmalloc(sizeof(*prog->argv) * argv_alloced); + prog->argv[0] = ++buf; + + src++; + src = skip_whitespace(src); + + if (!*src) { +empty_command_in_pipe: + bb_error_msg("empty command in pipe"); + free_job(job); + job->num_progs=0; + return 1; + } + src--; /* we'll ++ it at the end of the loop */ + + break; +#endif + +#ifdef CONFIG_LASH_JOB_CONTROL + case '&': /* background */ + *inbg = 1; + /* fallthrough */ +#endif + case ';': /* multiple commands */ + flag |= LASH_OPT_DONE; + return_command = *command_ptr + (src - *command_ptr) + 1; + break; + + case '\\': + src++; + if (!*src) { + bb_error_msg("character expected after \\"); + free_job(job); + return 1; + } + if (*src == '*' || *src == '[' || *src == ']' + || *src == '?') *buf++ = '\\'; + /* fallthrough */ + default: + *buf++ = *src; + } + + src++; + } + + if (*prog->argv[argc_l] || flag & LASH_OPT_SAW_QUOTE) { + argc_l++; + } + if (!argc_l) { + free_job(job); + return 0; + } + prog->argv[argc_l] = NULL; + + if (!return_command) { + job->text = xstrdup(*command_ptr); + } else { + /* This leaves any trailing spaces, which is a bit sloppy */ + job->text = xstrndup(*command_ptr, return_command - *command_ptr); + } + + *command_ptr = return_command; + + return 0; +} + +/* Run the child_prog, no matter what kind of command it uses. + */ +static int pseudo_exec(struct child_prog *child) +{ + struct built_in_command *x; + + /* Check if the command matches any of the non-forking builtins. + * Depending on context, this might be redundant. But it's + * easier to waste a few CPU cycles than it is to figure out + * if this is one of those cases. + */ + for (x = bltins; x->cmd; x++) { + if (strcmp(child->argv[0], x->cmd) == 0 ) { + _exit(x->function(child)); + } + } + + /* Check if the command matches any of the forking builtins. */ + for (x = bltins_forking; x->cmd; x++) { + if (strcmp(child->argv[0], x->cmd) == 0) { + applet_name=x->cmd; + _exit (x->function(child)); + } + } + + /* Check if the command matches any busybox internal + * commands ("applets") here. Following discussions from + * November 2000 on busybox@busybox.net, don't use + * bb_get_last_path_component(). This way explicit (with + * slashes) filenames will never be interpreted as an + * applet, just like with builtins. This way the user can + * override an applet with an explicit filename reference. + * The only downside to this change is that an explicit + * /bin/foo invocation will fork and exec /bin/foo, even if + * /bin/foo is a symlink to busybox. + */ + + if (ENABLE_FEATURE_SH_STANDALONE_SHELL) { + char **argv_l = child->argv; + int argc_l; + + for(argc_l=0; *argv_l; argv_l++, argc_l++); + optind = 1; + run_applet_by_name(child->argv[0], argc_l, child->argv); + } + + execvp(child->argv[0], child->argv); + + /* Do not use bb_perror_msg_and_die() here, since we must not + * call exit() but should call _exit() instead */ + bb_perror_msg("%s", child->argv[0]); + _exit(EXIT_FAILURE); +} + +static void insert_job(struct job *newjob, int inbg) +{ + struct job *thejob; + struct jobset *j_list=newjob->job_list; + + /* find the ID for thejob to use */ + newjob->jobid = 1; + for (thejob = j_list->head; thejob; thejob = thejob->next) + if (thejob->jobid >= newjob->jobid) + newjob->jobid = thejob->jobid + 1; + + /* add thejob to the list of running jobs */ + if (!j_list->head) { + thejob = j_list->head = xmalloc(sizeof(*thejob)); + } else { + for (thejob = j_list->head; thejob->next; thejob = thejob->next) /* nothing */; + thejob->next = xmalloc(sizeof(*thejob)); + thejob = thejob->next; + } + + *thejob = *newjob; /* physically copy the struct job */ + thejob->next = NULL; + thejob->running_progs = thejob->num_progs; + thejob->stopped_progs = 0; + +#ifdef CONFIG_LASH_JOB_CONTROL + if (inbg) { + /* we don't wait for background thejobs to return -- append it + to the list of backgrounded thejobs and leave it alone */ + printf("[%d] %d\n", thejob->jobid, + newjob->progs[newjob->num_progs - 1].pid); + last_jobid = newjob->jobid; + last_bg_pid=newjob->progs[newjob->num_progs - 1].pid; + } else { + newjob->job_list->fg = thejob; + + /* move the new process group into the foreground */ + /* Ignore errors since child could have already exited */ + tcsetpgrp(shell_terminal, newjob->pgrp); + } +#endif +} + +static int run_command(struct job *newjob, int inbg, int outpipe[2]) +{ + /* struct job *thejob; */ + int i; + int nextin, nextout; + int pipefds[2]; /* pipefd[0] is for reading */ + struct built_in_command *x; + struct child_prog *child; + + nextin = 0, nextout = 1; + for (i = 0; i < newjob->num_progs; i++) { + child = & (newjob->progs[i]); + + if ((i + 1) < newjob->num_progs) { + if (pipe(pipefds)<0) bb_perror_msg_and_die("pipe"); + nextout = pipefds[1]; + } else { + if (outpipe[1]!=-1) { + nextout = outpipe[1]; + } else { + nextout = 1; + } + } + + + /* Check if the command matches any non-forking builtins, + * but only if this is a simple command. + * Non-forking builtins within pipes have to fork anyway, + * and are handled in pseudo_exec. "echo foo | read bar" + * is doomed to failure, and doesn't work on bash, either. + */ + if (newjob->num_progs == 1) { + /* Check if the command sets an environment variable. */ + if (strchr(child->argv[0], '=') != NULL) { + child->argv[1] = child->argv[0]; + return builtin_export(child); + } + + for (x = bltins; x->cmd; x++) { + if (strcmp(child->argv[0], x->cmd) == 0 ) { + int rcode; + int squirrel[] = {-1, -1, -1}; + setup_redirects(child, squirrel); + rcode = x->function(child); + restore_redirects(squirrel); + return rcode; + } + } + } + +#if !defined(__UCLIBC__) || defined(__ARCH_HAS_MMU__) + if (!(child->pid = fork())) +#else + if (!(child->pid = vfork())) +#endif + { + /* Set the handling for job control signals back to the default. */ + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGTSTP, SIG_DFL); + signal(SIGTTIN, SIG_DFL); + signal(SIGTTOU, SIG_DFL); + signal(SIGCHLD, SIG_DFL); + + /* Close all open filehandles. */ + while(close_me_list) close((long)llist_pop(&close_me_list)); + + if (outpipe[1]!=-1) { + close(outpipe[0]); + } + if (nextin != 0) { + dup2(nextin, 0); + close(nextin); + } + + if (nextout != 1) { + dup2(nextout, 1); + dup2(nextout, 2); /* Really? */ + close(nextout); + close(pipefds[0]); + } + + /* explicit redirects override pipes */ + setup_redirects(child,NULL); + + pseudo_exec(child); + } + if (outpipe[1]!=-1) { + close(outpipe[1]); + } + + /* put our child in the process group whose leader is the + first process in this pipe */ + setpgid(child->pid, newjob->progs[0].pid); + if (nextin != 0) + close(nextin); + if (nextout != 1) + close(nextout); + + /* If there isn't another process, nextin is garbage + but it doesn't matter */ + nextin = pipefds[0]; + } + + newjob->pgrp = newjob->progs[0].pid; + + insert_job(newjob, inbg); + + return 0; +} + +static int busy_loop(FILE * input) +{ + char *command; + char *next_command = NULL; + struct job newjob; + int i; + int inbg = 0; + int status; +#ifdef CONFIG_LASH_JOB_CONTROL + pid_t parent_pgrp; + /* save current owner of TTY so we can restore it on exit */ + parent_pgrp = tcgetpgrp(shell_terminal); +#endif + newjob.job_list = &job_list; + newjob.job_context = DEFAULT_CONTEXT; + + command = xzalloc(BUFSIZ); + + while (1) { + if (!job_list.fg) { + /* no job is in the foreground */ + + /* see if any background processes have exited */ + checkjobs(&job_list); + + if (!next_command) { + if (get_command(input, command)) + break; + next_command = command; + } + + if (! expand_arguments(next_command)) { + free(command); + command = xzalloc(BUFSIZ); + next_command = NULL; + continue; + } + + if (!parse_command(&next_command, &newjob, &inbg) && + newjob.num_progs) { + int pipefds[2] = {-1,-1}; + debug_printf( "job=%p fed to run_command by busy_loop()'\n", + &newjob); + run_command(&newjob, inbg, pipefds); + } + else { + free(command); + command = (char *) xzalloc(BUFSIZ); + next_command = NULL; + } + } else { + /* a job is running in the foreground; wait for it */ + i = 0; + while (!job_list.fg->progs[i].pid || + job_list.fg->progs[i].is_stopped == 1) i++; + + if (waitpid(job_list.fg->progs[i].pid, &status, WUNTRACED)<0) { + if (errno != ECHILD) { + bb_perror_msg_and_die("waitpid(%d)",job_list.fg->progs[i].pid); + } + } + + if (WIFEXITED(status) || WIFSIGNALED(status)) { + /* the child exited */ + job_list.fg->running_progs--; + job_list.fg->progs[i].pid = 0; + + last_return_code=WEXITSTATUS(status); + + if (!job_list.fg->running_progs) { + /* child exited */ + remove_job(&job_list, job_list.fg); + job_list.fg = NULL; + } + } +#ifdef CONFIG_LASH_JOB_CONTROL + else { + /* the child was stopped */ + job_list.fg->stopped_progs++; + job_list.fg->progs[i].is_stopped = 1; + + if (job_list.fg->stopped_progs == job_list.fg->running_progs) { + printf("\n" JOB_STATUS_FORMAT, job_list.fg->jobid, + "Stopped", job_list.fg->text); + job_list.fg = NULL; + } + } + + if (!job_list.fg) { + /* move the shell to the foreground */ + /* suppress messages when run from /linuxrc mag@sysgo.de */ + if (tcsetpgrp(shell_terminal, getpgrp()) && errno != ENOTTY) + bb_perror_msg("tcsetpgrp"); + } +#endif + } + } + free(command); + +#ifdef CONFIG_LASH_JOB_CONTROL + /* return controlling TTY back to parent process group before exiting */ + if (tcsetpgrp(shell_terminal, parent_pgrp) && errno != ENOTTY) + bb_perror_msg("tcsetpgrp"); +#endif + + /* return exit status if called with "-c" */ + if (input == NULL && WIFEXITED(status)) + return WEXITSTATUS(status); + + return 0; +} + +#ifdef CONFIG_FEATURE_CLEAN_UP +static void free_memory(void) +{ + if (cwd && cwd!=bb_msg_unknown) { + free((char*)cwd); + } + if (local_pending_command) + free(local_pending_command); + + if (job_list.fg && !job_list.fg->running_progs) { + remove_job(&job_list, job_list.fg); + } +} +#else +void free_memory(void); +#endif + +#ifdef CONFIG_LASH_JOB_CONTROL +/* Make sure we have a controlling tty. If we get started under a job + * aware app (like bash for example), make sure we are now in charge so + * we don't fight over who gets the foreground */ +static void setup_job_control(void) +{ + int status; + pid_t shell_pgrp; + + /* Loop until we are in the foreground. */ + while ((status = tcgetpgrp (shell_terminal)) >= 0) { + if (status == (shell_pgrp = getpgrp ())) { + break; + } + kill (- shell_pgrp, SIGTTIN); + } + + /* Ignore interactive and job-control signals. */ + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + signal(SIGTSTP, SIG_IGN); + signal(SIGTTIN, SIG_IGN); + signal(SIGTTOU, SIG_IGN); + signal(SIGCHLD, SIG_IGN); + + /* Put ourselves in our own process group. */ + setsid(); + shell_pgrp = getpid (); + setpgid(shell_pgrp, shell_pgrp); + + /* Grab control of the terminal. */ + tcsetpgrp(shell_terminal, shell_pgrp); +} +#else +static inline void setup_job_control(void) +{ +} +#endif + +int lash_main(int argc_l, char **argv_l) +{ + unsigned opt; + FILE *input = stdin; + argc = argc_l; + argv = argv_l; + + /* These variables need re-initializing when recursing */ + last_jobid = 0; + close_me_list = NULL; + job_list.head = NULL; + job_list.fg = NULL; + last_return_code=1; + + if (argv[0] && argv[0][0] == '-') { + FILE *prof_input; + prof_input = fopen("/etc/profile", "r"); + if (prof_input) { + llist_add_to(&close_me_list, (void *)(long)fileno(prof_input)); + /* Now run the file */ + busy_loop(prof_input); + fclose_if_not_stdin(prof_input); + llist_pop(&close_me_list); + } + } + + opt = getopt32(argc_l, argv_l, "+ic:", &local_pending_command); +#define LASH_OPT_i (1<<0) +#define LASH_OPT_c (1<<2) + if (opt & LASH_OPT_c) { + input = NULL; + optind++; + argv += optind; + } + /* A shell is interactive if the `-i' flag was given, or if all of + * the following conditions are met: + * no -c command + * no arguments remaining or the -s flag given + * standard input is a terminal + * standard output is a terminal + * Refer to Posix.2, the description of the `sh' utility. */ + if (argv[optind]==NULL && input==stdin && + isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) + { + opt |= LASH_OPT_i; + } + setup_job_control(); + if (opt & LASH_OPT_i) { + /* Looks like they want an interactive shell */ + if (!ENABLE_FEATURE_SH_EXTRA_QUIET) { + printf("\n\n%s Built-in shell (lash)\n" + "Enter 'help' for a list of built-in commands.\n\n", + BB_BANNER); + } + } else if (!local_pending_command && argv[optind]) { + //printf( "optind=%d argv[optind]='%s'\n", optind, argv[optind]); + input = xfopen(argv[optind], "r"); + /* be lazy, never mark this closed */ + llist_add_to(&close_me_list, (void *)(long)fileno(input)); + } + + /* initialize the cwd -- this is never freed...*/ + cwd = xgetcwd(0); + if (!cwd) + cwd = bb_msg_unknown; + + if (ENABLE_FEATURE_CLEAN_UP) atexit(free_memory); + + if (ENABLE_FEATURE_COMMAND_EDITING) cmdedit_set_initial_prompt(); + else PS1 = NULL; + + return (busy_loop(input)); +} diff --git a/shell/msh.c b/shell/msh.c new file mode 100644 index 000000000..492d3cf43 --- /dev/null +++ b/shell/msh.c @@ -0,0 +1,5250 @@ +/* vi: set sw=4 ts=4: */ +/* + * Minix shell port for busybox + * + * This version of the Minix shell was adapted for use in busybox + * by Erik Andersen <andersen@codepoet.org> + * + * - backtick expansion did not work properly + * Jonas Holmberg <jonas.holmberg@axis.com> + * Robert Schwebel <r.schwebel@pengutronix.de> + * Erik Andersen <andersen@codepoet.org> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include <setjmp.h> +#include <sys/times.h> + +#include "cmdedit.h" + +/*#define MSHDEBUG 1*/ + +#ifdef MSHDEBUG +int mshdbg = MSHDEBUG; + +#define DBGPRINTF(x) if(mshdbg>0)printf x +#define DBGPRINTF0(x) if(mshdbg>0)printf x +#define DBGPRINTF1(x) if(mshdbg>1)printf x +#define DBGPRINTF2(x) if(mshdbg>2)printf x +#define DBGPRINTF3(x) if(mshdbg>3)printf x +#define DBGPRINTF4(x) if(mshdbg>4)printf x +#define DBGPRINTF5(x) if(mshdbg>5)printf x +#define DBGPRINTF6(x) if(mshdbg>6)printf x +#define DBGPRINTF7(x) if(mshdbg>7)printf x +#define DBGPRINTF8(x) if(mshdbg>8)printf x +#define DBGPRINTF9(x) if(mshdbg>9)printf x + +int mshdbg_rc = 0; + +#define RCPRINTF(x) if(mshdbg_rc)printf x + +#else + +#define DBGPRINTF(x) +#define DBGPRINTF0(x) +#define DBGPRINTF1(x) +#define DBGPRINTF2(x) +#define DBGPRINTF3(x) +#define DBGPRINTF4(x) +#define DBGPRINTF5(x) +#define DBGPRINTF6(x) +#define DBGPRINTF7(x) +#define DBGPRINTF8(x) +#define DBGPRINTF9(x) + +#define RCPRINTF(x) + +#endif /* MSHDEBUG */ + + +#ifdef CONFIG_FEATURE_SH_FANCY_PROMPT +# define DEFAULT_ROOT_PROMPT "\\u:\\w> " +# define DEFAULT_USER_PROMPT "\\u:\\w$ " +#else +# define DEFAULT_ROOT_PROMPT "# " +# define DEFAULT_USER_PROMPT "$ " +#endif + + +/* -------- sh.h -------- */ +/* + * shell + */ + +#define LINELIM 2100 +#define NPUSH 8 /* limit to input nesting */ + +#undef NOFILE +#define NOFILE 20 /* Number of open files */ +#define NUFILE 10 /* Number of user-accessible files */ +#define FDBASE 10 /* First file usable by Shell */ + +/* + * values returned by wait + */ +#define WAITSIG(s) ((s)&0177) +#define WAITVAL(s) (((s)>>8)&0377) +#define WAITCORE(s) (((s)&0200)!=0) + +/* + * library and system definitions + */ +typedef void xint; /* base type of jmp_buf, for not broken compilers */ + +/* + * shell components + */ + +#define QUOTE 0200 + +#define NOBLOCK ((struct op *)NULL) +#define NOWORD ((char *)NULL) +#define NOWORDS ((char **)NULL) +#define NOPIPE ((int *)NULL) + +/* + * Description of a command or an operation on commands. + * Might eventually use a union. + */ +struct op { + int type; /* operation type, see below */ + char **words; /* arguments to a command */ + struct ioword **ioact; /* IO actions (eg, < > >>) */ + struct op *left; + struct op *right; + char *str; /* identifier for case and for */ +}; + +#define TCOM 1 /* command */ +#define TPAREN 2 /* (c-list) */ +#define TPIPE 3 /* a | b */ +#define TLIST 4 /* a [&;] b */ +#define TOR 5 /* || */ +#define TAND 6 /* && */ +#define TFOR 7 +#define TDO 8 +#define TCASE 9 +#define TIF 10 +#define TWHILE 11 +#define TUNTIL 12 +#define TELIF 13 +#define TPAT 14 /* pattern in case */ +#define TBRACE 15 /* {c-list} */ +#define TASYNC 16 /* c & */ +/* Added to support "." file expansion */ +#define TDOT 17 + +/* Strings for names to make debug easier */ +#ifdef MSHDEBUG +static char *T_CMD_NAMES[] = { + "PLACEHOLDER", + "TCOM", + "TPAREN", + "TPIPE", + "TLIST", + "TOR", + "TAND", + "TFOR", + "TDO", + "TCASE", + "TIF", + "TWHILE", + "TUNTIL", + "TELIF", + "TPAT", + "TBRACE", + "TASYNC", + "TDOT", +}; +#endif + +/* + * actions determining the environment of a process + */ +#define BIT(i) (1<<(i)) +#define FEXEC BIT(0) /* execute without forking */ + +#define AREASIZE (90000) + +/* + * flags to control evaluation of words + */ +#define DOSUB 1 /* interpret $, `, and quotes */ +#define DOBLANK 2 /* perform blank interpretation */ +#define DOGLOB 4 /* interpret [?* */ +#define DOKEY 8 /* move words with `=' to 2nd arg. list */ +#define DOTRIM 16 /* trim resulting string */ + +#define DOALL (DOSUB|DOBLANK|DOGLOB|DOKEY|DOTRIM) + + +/* PROTOTYPES */ +static int newfile(char *s); +static char *findeq(char *cp); +static char *cclass(char *p, int sub); +static void initarea(void); +extern int msh_main(int argc, char **argv); + + +struct brkcon { + jmp_buf brkpt; + struct brkcon *nextlev; +}; + + +/* + * redirection + */ +struct ioword { + short io_unit; /* unit affected */ + short io_flag; /* action (below) */ + char *io_name; /* file name */ +}; + +#define IOREAD 1 /* < */ +#define IOHERE 2 /* << (here file) */ +#define IOWRITE 4 /* > */ +#define IOCAT 8 /* >> */ +#define IOXHERE 16 /* ${}, ` in << */ +#define IODUP 32 /* >&digit */ +#define IOCLOSE 64 /* >&- */ + +#define IODEFAULT (-1) /* token for default IO unit */ + + + +/* + * parsing & execution environment + */ +static struct env { + char *linep; + struct io *iobase; + struct io *iop; + xint *errpt; /* void * */ + int iofd; + struct env *oenv; +} e; + +/* + * flags: + * -e: quit on error + * -k: look for name=value everywhere on command line + * -n: no execution + * -t: exit after reading and executing one command + * -v: echo as read + * -x: trace + * -u: unset variables net diagnostic + */ +static char flags['z' - 'a' + 1]; +/* this looks weird, but is OK ... we index flag with 'a'...'z' */ +static char *flag = flags - 'a'; + +static char *null; /* null value for variable */ +static int intr; /* interrupt pending */ + +static char *trap[_NSIG + 1]; +static char ourtrap[_NSIG + 1]; +static int trapset; /* trap pending */ + +static int heedint; /* heed interrupt signals */ + +static int yynerrs; /* yacc */ + +static char line[LINELIM]; +static char *elinep; + + +/* + * other functions + */ +static int (*inbuilt(char *s)) (struct op *); + +static char *rexecve(char *c, char **v, char **envp); +static char *space(int n); +static char *strsave(char *s, int a); +static char *evalstr(char *cp, int f); +static char *putn(int n); +static char *unquote(char *as); +static struct var *lookup(char *n); +static int rlookup(char *n); +static struct wdblock *glob(char *cp, struct wdblock *wb); +static int my_getc(int ec); +static int subgetc(char ec, int quoted); +static char **makenv(int all, struct wdblock *wb); +static char **eval(char **ap, int f); +static int setstatus(int s); +static int waitfor(int lastpid, int canintr); + +static void onintr(int s); /* SIGINT handler */ + +static int newenv(int f); +static void quitenv(void); +static void err(char *s); +static int anys(char *s1, char *s2); +static int any(int c, char *s); +static void next(int f); +static void setdash(void); +static void onecommand(void); +static void runtrap(int i); +static int gmatch(char *s, char *p); + + +/* + * error handling + */ +static void leave(void); /* abort shell (or fail in subshell) */ +static void fail(void); /* fail but return to process next command */ +static void warn(char *s); +static void sig(int i); /* default signal handler */ + + + +/* -------- area stuff -------- */ + +#define REGSIZE sizeof(struct region) +#define GROWBY (256) +/* #define SHRINKBY (64) */ +#undef SHRINKBY +#define FREE (32767) +#define BUSY (0) +#define ALIGN (sizeof(int)-1) + + +struct region { + struct region *next; + int area; +}; + + + +/* -------- grammar stuff -------- */ +typedef union { + char *cp; + char **wp; + int i; + struct op *o; +} YYSTYPE; + +#define WORD 256 +#define LOGAND 257 +#define LOGOR 258 +#define BREAK 259 +#define IF 260 +#define THEN 261 +#define ELSE 262 +#define ELIF 263 +#define FI 264 +#define CASE 265 +#define ESAC 266 +#define FOR 267 +#define WHILE 268 +#define UNTIL 269 +#define DO 270 +#define DONE 271 +#define IN 272 +/* Added for "." file expansion */ +#define DOT 273 + +#define YYERRCODE 300 + +/* flags to yylex */ +#define CONTIN 01 /* skip new lines to complete command */ + +#define SYNTAXERR zzerr() + +static struct op *pipeline(int cf); +static struct op *andor(void); +static struct op *c_list(void); +static int synio(int cf); +static void musthave(int c, int cf); +static struct op *simple(void); +static struct op *nested(int type, int mark); +static struct op *command(int cf); +static struct op *dogroup(int onlydone); +static struct op *thenpart(void); +static struct op *elsepart(void); +static struct op *caselist(void); +static struct op *casepart(void); +static char **pattern(void); +static char **wordlist(void); +static struct op *list(struct op *t1, struct op *t2); +static struct op *block(int type, struct op *t1, struct op *t2, char **wp); +static struct op *newtp(void); +static struct op *namelist(struct op *t); +static char **copyw(void); +static void word(char *cp); +static struct ioword **copyio(void); +static struct ioword *io(int u, int f, char *cp); +static void zzerr(void); +static void yyerror(char *s); +static int yylex(int cf); +static int collect(int c, int c1); +static int dual(int c); +static void diag(int ec); +static char *tree(unsigned size); + +/* -------- var.h -------- */ + +struct var { + char *value; + char *name; + struct var *next; + char status; +}; + +#define COPYV 1 /* flag to setval, suggesting copy */ +#define RONLY 01 /* variable is read-only */ +#define EXPORT 02 /* variable is to be exported */ +#define GETCELL 04 /* name & value space was got with getcell */ + +static int yyparse(void); +static struct var *lookup(char *n); +static void setval(struct var *vp, char *val); +static void nameval(struct var *vp, char *val, char *name); +static void export(struct var *vp); +static void ronly(struct var *vp); +static int isassign(char *s); +static int checkname(char *cp); +static int assign(char *s, int cf); +static void putvlist(int f, int out); +static int eqname(char *n1, char *n2); + +static int execute(struct op *t, int *pin, int *pout, int act); + + +/* -------- io.h -------- */ +/* io buffer */ +struct iobuf { + unsigned id; /* buffer id */ + char buf[512]; /* buffer */ + char *bufp; /* pointer into buffer */ + char *ebufp; /* pointer to end of buffer */ +}; + +/* possible arguments to an IO function */ +struct ioarg { + char *aword; + char **awordlist; + int afile; /* file descriptor */ + unsigned afid; /* buffer id */ + long afpos; /* file position */ + struct iobuf *afbuf; /* buffer for this file */ +}; + +//static struct ioarg ioargstack[NPUSH]; +#define AFID_NOBUF (~0) +#define AFID_ID 0 + +/* an input generator's state */ +struct io { + int (*iofn) (struct ioarg *, struct io *); + struct ioarg *argp; + int peekc; + char prev; /* previous character read by readc() */ + char nlcount; /* for `'s */ + char xchar; /* for `'s */ + char task; /* reason for pushed IO */ +}; + +//static struct io iostack[NPUSH]; +#define XOTHER 0 /* none of the below */ +#define XDOLL 1 /* expanding ${} */ +#define XGRAVE 2 /* expanding `'s */ +#define XIO 3 /* file IO */ + +/* in substitution */ +#define INSUB() (e.iop->task == XGRAVE || e.iop->task == XDOLL) + + +/* + * input generators for IO structure + */ +static int nlchar(struct ioarg *ap); +static int strchar(struct ioarg *ap); +static int qstrchar(struct ioarg *ap); +static int filechar(struct ioarg *ap); +static int herechar(struct ioarg *ap); +static int linechar(struct ioarg *ap); +static int gravechar(struct ioarg *ap, struct io *iop); +static int qgravechar(struct ioarg *ap, struct io *iop); +static int dolchar(struct ioarg *ap); +static int wdchar(struct ioarg *ap); +static void scraphere(void); +static void freehere(int area); +static void gethere(void); +static void markhere(char *s, struct ioword *iop); +static int herein(char *hname, int xdoll); +static int run(struct ioarg *argp, int (*f) (struct ioarg *)); + + +/* + * IO functions + */ +static int eofc(void); +static int readc(void); +static void unget(int c); +static void ioecho(char c); +static void prs(const char *s); +static void prn(unsigned u); +static void closef(int i); +static void closeall(void); + + +/* + * IO control + */ +static void pushio(struct ioarg *argp, int (*f) (struct ioarg *)); +static int remap(int fd); +static int openpipe(int *pv); +static void closepipe(int *pv); +static struct io *setbase(struct io *ip); + +#define PUSHIO(what,arg,gen) ((temparg.what = (arg)),pushio(&temparg,(gen))) +#define RUN(what,arg,gen) ((temparg.what = (arg)), run(&temparg,(gen))) + +/* -------- word.h -------- */ + +#define NSTART 16 /* default number of words to allow for initially */ + +struct wdblock { + short w_bsize; + short w_nword; + /* bounds are arbitrary */ + char *w_words[1]; +}; + +static struct wdblock *addword(char *wd, struct wdblock *wb); +static struct wdblock *newword(int nw); +static char **getwords(struct wdblock *wb); + +/* -------- area.h -------- */ + +/* + * storage allocation + */ +static char *getcell(unsigned nbytes); +static void garbage(void); +static void setarea(char *cp, int a); +static int getarea(char *cp); +static void freearea(int a); +static void freecell(char *cp); +static int areanum; /* current allocation area */ + +#define NEW(type) (type *)getcell(sizeof(type)) +#define DELETE(obj) freecell((char *)obj) + + +/* -------- misc stuff -------- */ + +static int forkexec(struct op *t, int *pin, int *pout, int act, char **wp); +static int iosetup(struct ioword *iop, int pipein, int pipeout); +static void echo(char **wp); +static struct op **find1case(struct op *t, char *w); +static struct op *findcase(struct op *t, char *w); +static void brkset(struct brkcon *bc); +static int dolabel(struct op *t); +static int dohelp(struct op *t); +static int dochdir(struct op *t); +static int doshift(struct op *t); +static int dologin(struct op *t); +static int doumask(struct op *t); +static int doexec(struct op *t); +static int dodot(struct op *t); +static int dowait(struct op *t); +static int doread(struct op *t); +static int doeval(struct op *t); +static int dotrap(struct op *t); +static int getsig(char *s); +static void setsig(int n, sighandler_t f); +static int getn(char *as); +static int dobreak(struct op *t); +static int docontinue(struct op *t); +static int brkcontin(char *cp, int val); +static int doexit(struct op *t); +static int doexport(struct op *t); +static int doreadonly(struct op *t); +static void rdexp(char **wp, void (*f) (struct var *), int key); +static void badid(char *s); +static int doset(struct op *t); +static void varput(char *s, int out); +static int dotimes(struct op *t); +static int expand(char *cp, struct wdblock **wbp, int f); +static char *blank(int f); +static int dollar(int quoted); +static int grave(int quoted); +static void globname(char *we, char *pp); +static char *generate(char *start1, char *end1, char *middle, char *end); +static int anyspcl(struct wdblock *wb); +static int xstrcmp(char *p1, char *p2); +static void glob0(char *a0, unsigned int a1, int a2, + int (*a3) (char *, char *)); +static void glob1(char *base, char *lim); +static void glob2(char *i, char *j); +static void glob3(char *i, char *j, char *k); +static void readhere(char **name, char *s, int ec); +static void pushio(struct ioarg *argp, int (*f) (struct ioarg *)); +static int xxchar(struct ioarg *ap); + +struct here { + char *h_tag; + int h_dosub; + struct ioword *h_iop; + struct here *h_next; +}; + +static const char * const signame[] = { + "Signal 0", + "Hangup", + (char *) NULL, /* interrupt */ + "Quit", + "Illegal instruction", + "Trace/BPT trap", + "Abort", + "Bus error", + "Floating Point Exception", + "Killed", + "SIGUSR1", + "SIGSEGV", + "SIGUSR2", + (char *) NULL, /* broken pipe */ + "Alarm clock", + "Terminated", +}; + +#define NSIGNAL (sizeof(signame)/sizeof(signame[0])) + +struct res { + const char *r_name; + int r_val; +}; +static const struct res restab[] = { + {"for", FOR}, + {"case", CASE}, + {"esac", ESAC}, + {"while", WHILE}, + {"do", DO}, + {"done", DONE}, + {"if", IF}, + {"in", IN}, + {"then", THEN}, + {"else", ELSE}, + {"elif", ELIF}, + {"until", UNTIL}, + {"fi", FI}, + {";;", BREAK}, + {"||", LOGOR}, + {"&&", LOGAND}, + {"{", '{'}, + {"}", '}'}, + {".", DOT}, + {0, 0}, +}; + + +struct builtincmd { + const char *name; + int (*builtinfunc) (struct op * t); +}; +static const struct builtincmd builtincmds[] = { + {".", dodot}, + {":", dolabel}, + {"break", dobreak}, + {"cd", dochdir}, + {"continue", docontinue}, + {"eval", doeval}, + {"exec", doexec}, + {"exit", doexit}, + {"export", doexport}, + {"help", dohelp}, + {"login", dologin}, + {"newgrp", dologin}, + {"read", doread}, + {"readonly", doreadonly}, + {"set", doset}, + {"shift", doshift}, + {"times", dotimes}, + {"trap", dotrap}, + {"umask", doumask}, + {"wait", dowait}, + {0, 0} +}; + +static struct op *scantree(struct op *); +static struct op *dowholefile(int, int); + +/* Globals */ +extern char **environ; /* environment pointer */ + +static char **dolv; +static int dolc; +static int exstat; +static char gflg; +static int interactive; /* Is this an interactive shell */ +static int execflg; +static int multiline; /* \n changed to ; */ +static struct op *outtree; /* result from parser */ +static xint *failpt; +static xint *errpt; +static struct brkcon *brklist; +static int isbreak; +static struct wdblock *wdlist; +static struct wdblock *iolist; +static char *trap[_NSIG + 1]; +static char ourtrap[_NSIG + 1]; +static int trapset; /* trap pending */ +static int yynerrs; /* yacc */ +static char line[LINELIM]; + +#ifdef MSHDEBUG +static struct var *mshdbg_var; +#endif +static struct var *vlist; /* dictionary */ +static struct var *homedir; /* home directory */ +static struct var *prompt; /* main prompt */ +static struct var *cprompt; /* continuation prompt */ +static struct var *path; /* search path for commands */ +static struct var *shell; /* shell to interpret command files */ +static struct var *ifs; /* field separators */ + +static int areanum; /* current allocation area */ +static int intr; +static int inparse; +static char *null = ""; +static int heedint = 1; +static void (*qflag) (int) = SIG_IGN; +static int startl; +static int peeksym; +static int nlseen; +static int iounit = IODEFAULT; +static YYSTYPE yylval; +static char *elinep = line + sizeof(line) - 5; + +static struct ioarg temparg = { 0, 0, 0, AFID_NOBUF, 0 }; /* temporary for PUSHIO */ +static struct ioarg ioargstack[NPUSH]; +static struct io iostack[NPUSH]; +static struct iobuf sharedbuf = { AFID_NOBUF }; +static struct iobuf mainbuf = { AFID_NOBUF }; +static unsigned bufid = AFID_ID; /* buffer id counter */ + +static struct here *inhere; /* list of hear docs while parsing */ +static struct here *acthere; /* list of active here documents */ +static struct region *areabot; /* bottom of area */ +static struct region *areatop; /* top of area */ +static struct region *areanxt; /* starting point of scan */ +static void *brktop; +static void *brkaddr; + +static struct env e = { + line, /* linep: char ptr */ + iostack, /* iobase: struct io ptr */ + iostack - 1, /* iop: struct io ptr */ + (xint *) NULL, /* errpt: void ptr for errors? */ + FDBASE, /* iofd: file desc */ + (struct env *) NULL /* oenv: struct env ptr */ +}; + +#ifdef MSHDEBUG +void print_t(struct op *t); +void print_t(struct op *t) +{ + DBGPRINTF(("T: t=%p, type %s, words=%p, IOword=%p\n", t, + T_CMD_NAMES[t->type], t->words, t->ioact)); + + if (t->words) { + DBGPRINTF(("T: W1: %s", t->words[0])); + } + + return; +} + +void print_tree(struct op *head); +void print_tree(struct op *head) +{ + if (head == NULL) { + DBGPRINTF(("PRINT_TREE: no tree\n")); + return; + } + + DBGPRINTF(("NODE: %p, left %p, right %p\n", head, head->left, + head->right)); + + if (head->left) + print_tree(head->left); + + if (head->right) + print_tree(head->right); + + return; +} +#endif /* MSHDEBUG */ + + +#ifdef CONFIG_FEATURE_COMMAND_EDITING +static char *current_prompt; +#endif + +/* -------- sh.c -------- */ +/* + * shell + */ + + +int msh_main(int argc, char **argv) +{ + int f; + char *s; + int cflag; + char *name, **ap; + int (*iof) (struct ioarg *); + + DBGPRINTF(("MSH_MAIN: argc %d, environ %p\n", argc, environ)); + + initarea(); + if ((ap = environ) != NULL) { + while (*ap) + assign(*ap++, !COPYV); + for (ap = environ; *ap;) + export(lookup(*ap++)); + } + closeall(); + areanum = 1; + + shell = lookup("SHELL"); + if (shell->value == null) + setval(shell, (char *)DEFAULT_SHELL); + export(shell); + + homedir = lookup("HOME"); + if (homedir->value == null) + setval(homedir, "/"); + export(homedir); + + setval(lookup("$"), putn(getpid())); + + path = lookup("PATH"); + if (path->value == null) { + if (geteuid() == 0) + setval(path, "/sbin:/bin:/usr/sbin:/usr/bin"); + else + setval(path, "/bin:/usr/bin"); + } + export(path); + + ifs = lookup("IFS"); + if (ifs->value == null) + setval(ifs, " \t\n"); + +#ifdef MSHDEBUG + mshdbg_var = lookup("MSHDEBUG"); + if (mshdbg_var->value == null) + setval(mshdbg_var, "0"); +#endif + + prompt = lookup("PS1"); +#ifdef CONFIG_FEATURE_SH_FANCY_PROMPT + if (prompt->value == null) +#endif + setval(prompt, DEFAULT_USER_PROMPT); + if (geteuid() == 0) { + setval(prompt, DEFAULT_ROOT_PROMPT); + prompt->status &= ~EXPORT; + } + cprompt = lookup("PS2"); +#ifdef CONFIG_FEATURE_SH_FANCY_PROMPT + if (cprompt->value == null) +#endif + setval(cprompt, "> "); + + iof = filechar; + cflag = 0; + name = *argv++; + if (--argc >= 1) { + if (argv[0][0] == '-' && argv[0][1] != '\0') { + for (s = argv[0] + 1; *s; s++) + switch (*s) { + case 'c': + prompt->status &= ~EXPORT; + cprompt->status &= ~EXPORT; + setval(prompt, ""); + setval(cprompt, ""); + cflag = 1; + if (--argc > 0) + PUSHIO(aword, *++argv, iof = nlchar); + break; + + case 'q': + qflag = SIG_DFL; + break; + + case 's': + /* standard input */ + break; + + case 't': + prompt->status &= ~EXPORT; + setval(prompt, ""); + iof = linechar; + break; + + case 'i': + interactive++; + default: + if (*s >= 'a' && *s <= 'z') + flag[(int) *s]++; + } + } else { + argv--; + argc++; + } + + if (iof == filechar && --argc > 0) { + setval(prompt, ""); + setval(cprompt, ""); + prompt->status &= ~EXPORT; + cprompt->status &= ~EXPORT; + +/* Shell is non-interactive, activate printf-based debug */ +#ifdef MSHDEBUG + mshdbg = (int) (((char) (mshdbg_var->value[0])) - '0'); + if (mshdbg < 0) + mshdbg = 0; +#endif + DBGPRINTF(("MSH_MAIN: calling newfile()\n")); + + if (newfile(name = *++argv)) + exit(1); /* Exit on error */ + } + } + + setdash(); + + /* This won't be true if PUSHIO has been called, say from newfile() above */ + if (e.iop < iostack) { + PUSHIO(afile, 0, iof); + if (isatty(0) && isatty(1) && !cflag) { + interactive++; +#ifndef CONFIG_FEATURE_SH_EXTRA_QUIET +#ifdef MSHDEBUG + printf("\n\n%s Built-in shell (msh with debug)\n", BB_BANNER); +#else + printf("\n\n%s Built-in shell (msh)\n", BB_BANNER); +#endif + printf("Enter 'help' for a list of built-in commands.\n\n"); +#endif + } + } + + signal(SIGQUIT, qflag); + if (name && name[0] == '-') { + interactive++; + if ((f = open(".profile", 0)) >= 0) + next(remap(f)); + if ((f = open("/etc/profile", 0)) >= 0) + next(remap(f)); + } + if (interactive) + signal(SIGTERM, sig); + + if (signal(SIGINT, SIG_IGN) != SIG_IGN) + signal(SIGINT, onintr); + dolv = argv; + dolc = argc; + dolv[0] = name; + if (dolc > 1) { + for (ap = ++argv; --argc > 0;) { + if (assign(*ap = *argv++, !COPYV)) { + dolc--; /* keyword */ + } else { + ap++; + } + } + } + setval(lookup("#"), putn((--dolc < 0) ? (dolc = 0) : dolc)); + + DBGPRINTF(("MSH_MAIN: begin FOR loop, interactive %d, e.iop %p, iostack %p\n", interactive, e.iop, iostack)); + + for (;;) { + if (interactive && e.iop <= iostack) { +#ifdef CONFIG_FEATURE_COMMAND_EDITING + current_prompt = prompt->value; +#else + prs(prompt->value); +#endif + } + onecommand(); + /* Ensure that getenv("PATH") stays current */ + setenv("PATH", path->value, 1); + } + + DBGPRINTF(("MSH_MAIN: returning.\n")); +} + +static void setdash(void) +{ + char *cp; + int c; + char m['z' - 'a' + 1]; + + cp = m; + for (c = 'a'; c <= 'z'; c++) + if (flag[(int) c]) + *cp++ = c; + *cp = 0; + setval(lookup("-"), m); +} + +static int newfile(char *s) +{ + int f; + + DBGPRINTF7(("NEWFILE: opening %s\n", s)); + + if (strcmp(s, "-") != 0) { + DBGPRINTF(("NEWFILE: s is %s\n", s)); + f = open(s, 0); + if (f < 0) { + prs(s); + err(": cannot open"); + return 1; + } + } else + f = 0; + + next(remap(f)); + return 0; +} + + +struct op *scantree(struct op *head) +{ + struct op *dotnode; + + if (head == NULL) + return NULL; + + if (head->left != NULL) { + dotnode = scantree(head->left); + if (dotnode) + return dotnode; + } + + if (head->right != NULL) { + dotnode = scantree(head->right); + if (dotnode) + return dotnode; + } + + if (head->words == NULL) + return NULL; + + DBGPRINTF5(("SCANTREE: checking node %p\n", head)); + + if ((head->type != TDOT) && (strcmp(".", head->words[0]) == 0)) { + DBGPRINTF5(("SCANTREE: dot found in node %p\n", head)); + return head; + } + + return NULL; +} + + +static void onecommand(void) +{ + int i; + jmp_buf m1; + + DBGPRINTF(("ONECOMMAND: enter, outtree=%p\n", outtree)); + + while (e.oenv) + quitenv(); + + areanum = 1; + freehere(areanum); + freearea(areanum); + garbage(); + wdlist = 0; + iolist = 0; + e.errpt = 0; + e.linep = line; + yynerrs = 0; + multiline = 0; + inparse = 1; + intr = 0; + execflg = 0; + + setjmp(failpt = m1); /* Bruce Evans' fix */ + if (setjmp(failpt = m1) || yyparse() || intr) { + + DBGPRINTF(("ONECOMMAND: this is not good.\n")); + + while (e.oenv) + quitenv(); + scraphere(); + if (!interactive && intr) + leave(); + inparse = 0; + intr = 0; + return; + } + + inparse = 0; + brklist = 0; + intr = 0; + execflg = 0; + + if (!flag['n']) { + DBGPRINTF(("ONECOMMAND: calling execute, t=outtree=%p\n", + outtree)); + execute(outtree, NOPIPE, NOPIPE, 0); + } + + if (!interactive && intr) { + execflg = 0; + leave(); + } + + if ((i = trapset) != 0) { + trapset = 0; + runtrap(i); + } +} + +static void fail(void) +{ + longjmp(failpt, 1); + /* NOTREACHED */ +} + +static void leave(void) +{ + DBGPRINTF(("LEAVE: leave called!\n")); + + if (execflg) + fail(); + scraphere(); + freehere(1); + runtrap(0); + _exit(exstat); + /* NOTREACHED */ +} + +static void warn(char *s) +{ + if (*s) { + prs(s); + exstat = -1; + } + prs("\n"); + if (flag['e']) + leave(); +} + +static void err(char *s) +{ + warn(s); + if (flag['n']) + return; + if (!interactive) + leave(); + if (e.errpt) + longjmp(e.errpt, 1); + closeall(); + e.iop = e.iobase = iostack; +} + +static int newenv(int f) +{ + struct env *ep; + + DBGPRINTF(("NEWENV: f=%d (indicates quitenv and return)\n", f)); + + if (f) { + quitenv(); + return 1; + } + + ep = (struct env *) space(sizeof(*ep)); + if (ep == NULL) { + while (e.oenv) + quitenv(); + fail(); + } + *ep = e; + e.oenv = ep; + e.errpt = errpt; + + return 0; +} + +static void quitenv(void) +{ + struct env *ep; + int fd; + + DBGPRINTF(("QUITENV: e.oenv=%p\n", e.oenv)); + + if ((ep = e.oenv) != NULL) { + fd = e.iofd; + e = *ep; + /* should close `'d files */ + DELETE(ep); + while (--fd >= e.iofd) + close(fd); + } +} + +/* + * Is any character from s1 in s2? + */ +static int anys(char *s1, char *s2) +{ + while (*s1) + if (any(*s1++, s2)) + return 1; + return 0; +} + +/* + * Is character c in s? + */ +static int any(int c, char *s) +{ + while (*s) + if (*s++ == c) + return 1; + return 0; +} + +static char *putn(int n) +{ + return itoa(n); +} + +static void next(int f) +{ + PUSHIO(afile, f, filechar); +} + +static void onintr(int s) /* ANSI C requires a parameter */ +{ + signal(SIGINT, onintr); + intr = 1; + if (interactive) { + if (inparse) { + prs("\n"); + fail(); + } + } else if (heedint) { + execflg = 0; + leave(); + } +} + +static char *space(int n) +{ + char *cp; + + if ((cp = getcell(n)) == 0) + err("out of string space"); + return cp; +} + +static char *strsave(char *s, int a) +{ + char *cp, *xp; + + if ((cp = space(strlen(s) + 1)) != NULL) { + setarea((char *) cp, a); + for (xp = cp; (*xp++ = *s++) != '\0';); + return cp; + } + return ""; +} + +/* + * trap handling + */ +static void sig(int i) +{ + trapset = i; + signal(i, sig); +} + +static void runtrap(int i) +{ + char *trapstr; + + if ((trapstr = trap[i]) == NULL) + return; + + if (i == 0) + trap[i] = 0; + + RUN(aword, trapstr, nlchar); +} + +/* -------- var.c -------- */ + +/* + * Find the given name in the dictionary + * and return its value. If the name was + * not previously there, enter it now and + * return a null value. + */ +static struct var *lookup(char *n) +{ + struct var *vp; + char *cp; + int c; + static struct var dummy; + + if (isdigit(*n)) { + dummy.name = n; + for (c = 0; isdigit(*n) && c < 1000; n++) + c = c * 10 + *n - '0'; + dummy.status = RONLY; + dummy.value = c <= dolc ? dolv[c] : null; + return &dummy; + } + for (vp = vlist; vp; vp = vp->next) + if (eqname(vp->name, n)) + return vp; + cp = findeq(n); + vp = (struct var *) space(sizeof(*vp)); + if (vp == 0 || (vp->name = space((int) (cp - n) + 2)) == 0) { + dummy.name = dummy.value = ""; + return &dummy; + } + for (cp = vp->name; (*cp = *n++) && *cp != '='; cp++); + if (*cp == 0) + *cp = '='; + *++cp = 0; + setarea((char *) vp, 0); + setarea((char *) vp->name, 0); + vp->value = null; + vp->next = vlist; + vp->status = GETCELL; + vlist = vp; + return vp; +} + +/* + * give variable at `vp' the value `val'. + */ +static void setval(struct var *vp, char *val) +{ + nameval(vp, val, (char *) NULL); +} + +/* + * if name is not NULL, it must be + * a prefix of the space `val', + * and end with `='. + * this is all so that exporting + * values is reasonably painless. + */ +static void nameval(struct var *vp, char *val, char *name) +{ + char *cp, *xp; + char *nv; + int fl; + + if (vp->status & RONLY) { + for (xp = vp->name; *xp && *xp != '=';) + putc(*xp++, stderr); + err(" is read-only"); + return; + } + fl = 0; + if (name == NULL) { + xp = space(strlen(vp->name) + strlen(val) + 2); + if (xp == 0) + return; + /* make string: name=value */ + setarea((char *) xp, 0); + name = xp; + for (cp = vp->name; (*xp = *cp++) && *xp != '='; xp++); + if (*xp++ == 0) + xp[-1] = '='; + nv = xp; + for (cp = val; (*xp++ = *cp++) != '\0';); + val = nv; + fl = GETCELL; + } + if (vp->status & GETCELL) + freecell(vp->name); /* form new string `name=value' */ + vp->name = name; + vp->value = val; + vp->status |= fl; +} + +static void export(struct var *vp) +{ + vp->status |= EXPORT; +} + +static void ronly(struct var *vp) +{ + if (isalpha(vp->name[0]) || vp->name[0] == '_') /* not an internal symbol */ + vp->status |= RONLY; +} + +static int isassign(char *s) +{ + DBGPRINTF7(("ISASSIGN: enter, s=%s\n", s)); + + if (!isalpha((int) *s) && *s != '_') + return 0; + for (; *s != '='; s++) + if (*s == 0 || (!isalnum(*s) && *s != '_')) + return 0; + + return 1; +} + +static int assign(char *s, int cf) +{ + char *cp; + struct var *vp; + + DBGPRINTF7(("ASSIGN: enter, s=%s, cf=%d\n", s, cf)); + + if (!isalpha(*s) && *s != '_') + return 0; + for (cp = s; *cp != '='; cp++) + if (*cp == 0 || (!isalnum(*cp) && *cp != '_')) + return 0; + vp = lookup(s); + nameval(vp, ++cp, cf == COPYV ? (char *) NULL : s); + if (cf != COPYV) + vp->status &= ~GETCELL; + return 1; +} + +static int checkname(char *cp) +{ + DBGPRINTF7(("CHECKNAME: enter, cp=%s\n", cp)); + + if (!isalpha(*cp++) && *(cp - 1) != '_') + return 0; + while (*cp) + if (!isalnum(*cp++) && *(cp - 1) != '_') + return 0; + return 1; +} + +static void putvlist(int f, int out) +{ + struct var *vp; + + for (vp = vlist; vp; vp = vp->next) + if (vp->status & f && (isalpha(*vp->name) || *vp->name == '_')) { + if (vp->status & EXPORT) + write(out, "export ", 7); + if (vp->status & RONLY) + write(out, "readonly ", 9); + write(out, vp->name, (int) (findeq(vp->name) - vp->name)); + write(out, "\n", 1); + } +} + +static int eqname(char *n1, char *n2) +{ + for (; *n1 != '=' && *n1 != 0; n1++) + if (*n2++ != *n1) + return 0; + return *n2 == 0 || *n2 == '='; +} + +static char *findeq(char *cp) +{ + while (*cp != '\0' && *cp != '=') + cp++; + return cp; +} + +/* -------- gmatch.c -------- */ +/* + * int gmatch(string, pattern) + * char *string, *pattern; + * + * Match a pattern as in sh(1). + */ + +#define CMASK 0377 +#define QUOTE 0200 +#define QMASK (CMASK&~QUOTE) +#define NOT '!' /* might use ^ */ + +static int gmatch(char *s, char *p) +{ + int sc, pc; + + if (s == NULL || p == NULL) + return 0; + while ((pc = *p++ & CMASK) != '\0') { + sc = *s++ & QMASK; + switch (pc) { + case '[': + if ((p = cclass(p, sc)) == NULL) + return 0; + break; + + case '?': + if (sc == 0) + return 0; + break; + + case '*': + s--; + do { + if (*p == '\0' || gmatch(s, p)) + return 1; + } while (*s++ != '\0'); + return 0; + + default: + if (sc != (pc & ~QUOTE)) + return 0; + } + } + return *s == 0; +} + +static char *cclass(char *p, int sub) +{ + int c, d, not, found; + + if ((not = *p == NOT) != 0) + p++; + found = not; + do { + if (*p == '\0') + return NULL; + c = *p & CMASK; + if (p[1] == '-' && p[2] != ']') { + d = p[2] & CMASK; + p++; + } else + d = c; + if (c == sub || (c <= sub && sub <= d)) + found = !not; + } while (*++p != ']'); + return found ? p + 1 : NULL; +} + + +/* -------- area.c -------- */ + +/* + * All memory between (char *)areabot and (char *)(areatop+1) is + * exclusively administered by the area management routines. + * It is assumed that sbrk() and brk() manipulate the high end. + */ + +#define sbrk(X) ({ void * __q = (void *)-1; if (brkaddr + (int)(X) < brktop) { __q = brkaddr; brkaddr+=(int)(X); } __q;}) + +static void initarea(void) +{ + brkaddr = xmalloc(AREASIZE); + brktop = brkaddr + AREASIZE; + + while ((long) sbrk(0) & ALIGN) + sbrk(1); + areabot = (struct region *) sbrk(REGSIZE); + + areabot->next = areabot; + areabot->area = BUSY; + areatop = areabot; + areanxt = areabot; +} + +char *getcell(unsigned nbytes) +{ + int nregio; + struct region *p, *q; + int i; + + if (nbytes == 0) { + puts("getcell(0)"); + abort(); + } + /* silly and defeats the algorithm */ + /* + * round upwards and add administration area + */ + nregio = (nbytes + (REGSIZE - 1)) / REGSIZE + 1; + for (p = areanxt;;) { + if (p->area > areanum) { + /* + * merge free cells + */ + while ((q = p->next)->area > areanum && q != areanxt) + p->next = q->next; + /* + * exit loop if cell big enough + */ + if (q >= p + nregio) + goto found; + } + p = p->next; + if (p == areanxt) + break; + } + i = nregio >= GROWBY ? nregio : GROWBY; + p = (struct region *) sbrk(i * REGSIZE); + if (p == (struct region *) -1) + return NULL; + p--; + if (p != areatop) { + puts("not contig"); + abort(); /* allocated areas are contiguous */ + } + q = p + i; + p->next = q; + p->area = FREE; + q->next = areabot; + q->area = BUSY; + areatop = q; + found: + /* + * we found a FREE area big enough, pointed to by 'p', and up to 'q' + */ + areanxt = p + nregio; + if (areanxt < q) { + /* + * split into requested area and rest + */ + if (areanxt + 1 > q) { + puts("OOM"); + abort(); /* insufficient space left for admin */ + } + areanxt->next = q; + areanxt->area = FREE; + p->next = areanxt; + } + p->area = areanum; + return (char *) (p + 1); +} + +static void freecell(char *cp) +{ + struct region *p; + + if ((p = (struct region *) cp) != NULL) { + p--; + if (p < areanxt) + areanxt = p; + p->area = FREE; + } +} + +static void freearea(int a) +{ + struct region *p, *top; + + top = areatop; + for (p = areabot; p != top; p = p->next) + if (p->area >= a) + p->area = FREE; +} + +static void setarea(char *cp, int a) +{ + struct region *p; + + if ((p = (struct region *) cp) != NULL) + (p - 1)->area = a; +} + +int getarea(char *cp) +{ + return ((struct region *) cp - 1)->area; +} + +static void garbage(void) +{ + struct region *p, *q, *top; + + top = areatop; + for (p = areabot; p != top; p = p->next) { + if (p->area > areanum) { + while ((q = p->next)->area > areanum) + p->next = q->next; + areanxt = p; + } + } +#ifdef SHRINKBY + if (areatop >= q + SHRINKBY && q->area > areanum) { + brk((char *) (q + 1)); + q->next = areabot; + q->area = BUSY; + areatop = q; + } +#endif +} + +/* -------- csyn.c -------- */ +/* + * shell: syntax (C version) + */ + +int yyparse(void) +{ + DBGPRINTF7(("YYPARSE: enter...\n")); + + startl = 1; + peeksym = 0; + yynerrs = 0; + outtree = c_list(); + musthave('\n', 0); + return (yynerrs != 0); +} + +static struct op *pipeline(int cf) +{ + struct op *t, *p; + int c; + + DBGPRINTF7(("PIPELINE: enter, cf=%d\n", cf)); + + t = command(cf); + + DBGPRINTF9(("PIPELINE: t=%p\n", t)); + + if (t != NULL) { + while ((c = yylex(0)) == '|') { + if ((p = command(CONTIN)) == NULL) { + DBGPRINTF8(("PIPELINE: error!\n")); + SYNTAXERR; + } + + if (t->type != TPAREN && t->type != TCOM) { + /* shell statement */ + t = block(TPAREN, t, NOBLOCK, NOWORDS); + } + + t = block(TPIPE, t, p, NOWORDS); + } + peeksym = c; + } + + DBGPRINTF7(("PIPELINE: returning t=%p\n", t)); + return t; +} + +static struct op *andor(void) +{ + struct op *t, *p; + int c; + + DBGPRINTF7(("ANDOR: enter...\n")); + + t = pipeline(0); + + DBGPRINTF9(("ANDOR: t=%p\n", t)); + + if (t != NULL) { + while ((c = yylex(0)) == LOGAND || c == LOGOR) { + if ((p = pipeline(CONTIN)) == NULL) { + DBGPRINTF8(("ANDOR: error!\n")); + SYNTAXERR; + } + + t = block(c == LOGAND ? TAND : TOR, t, p, NOWORDS); + } /* WHILE */ + + peeksym = c; + } + + DBGPRINTF7(("ANDOR: returning t=%p\n", t)); + return t; +} + +static struct op *c_list(void) +{ + struct op *t, *p; + int c; + + DBGPRINTF7(("C_LIST: enter...\n")); + + t = andor(); + + if (t != NULL) { + if ((peeksym = yylex(0)) == '&') + t = block(TASYNC, t, NOBLOCK, NOWORDS); + + while ((c = yylex(0)) == ';' || c == '&' + || (multiline && c == '\n')) { + + if ((p = andor()) == NULL) + return t; + + if ((peeksym = yylex(0)) == '&') + p = block(TASYNC, p, NOBLOCK, NOWORDS); + + t = list(t, p); + } /* WHILE */ + + peeksym = c; + } + /* IF */ + DBGPRINTF7(("C_LIST: returning t=%p\n", t)); + return t; +} + +static int synio(int cf) +{ + struct ioword *iop; + int i; + int c; + + DBGPRINTF7(("SYNIO: enter, cf=%d\n", cf)); + + if ((c = yylex(cf)) != '<' && c != '>') { + peeksym = c; + return 0; + } + + i = yylval.i; + musthave(WORD, 0); + iop = io(iounit, i, yylval.cp); + iounit = IODEFAULT; + + if (i & IOHERE) + markhere(yylval.cp, iop); + + DBGPRINTF7(("SYNIO: returning 1\n")); + return 1; +} + +static void musthave(int c, int cf) +{ + if ((peeksym = yylex(cf)) != c) { + DBGPRINTF7(("MUSTHAVE: error!\n")); + SYNTAXERR; + } + + peeksym = 0; +} + +static struct op *simple(void) +{ + struct op *t; + + t = NULL; + for (;;) { + switch (peeksym = yylex(0)) { + case '<': + case '>': + (void) synio(0); + break; + + case WORD: + if (t == NULL) { + t = newtp(); + t->type = TCOM; + } + peeksym = 0; + word(yylval.cp); + break; + + default: + return t; + } + } +} + +static struct op *nested(int type, int mark) +{ + struct op *t; + + DBGPRINTF3(("NESTED: enter, type=%d, mark=%d\n", type, mark)); + + multiline++; + t = c_list(); + musthave(mark, 0); + multiline--; + return block(type, t, NOBLOCK, NOWORDS); +} + +static struct op *command(int cf) +{ + struct op *t; + struct wdblock *iosave; + int c; + + DBGPRINTF(("COMMAND: enter, cf=%d\n", cf)); + + iosave = iolist; + iolist = NULL; + + if (multiline) + cf |= CONTIN; + + while (synio(cf)) + cf = 0; + + c = yylex(cf); + + switch (c) { + default: + peeksym = c; + if ((t = simple()) == NULL) { + if (iolist == NULL) + return NULL; + t = newtp(); + t->type = TCOM; + } + break; + + case '(': + t = nested(TPAREN, ')'); + break; + + case '{': + t = nested(TBRACE, '}'); + break; + + case FOR: + t = newtp(); + t->type = TFOR; + musthave(WORD, 0); + startl = 1; + t->str = yylval.cp; + multiline++; + t->words = wordlist(); + if ((c = yylex(0)) != '\n' && c != ';') + peeksym = c; + t->left = dogroup(0); + multiline--; + break; + + case WHILE: + case UNTIL: + multiline++; + t = newtp(); + t->type = c == WHILE ? TWHILE : TUNTIL; + t->left = c_list(); + t->right = dogroup(1); + t->words = NULL; + multiline--; + break; + + case CASE: + t = newtp(); + t->type = TCASE; + musthave(WORD, 0); + t->str = yylval.cp; + startl++; + multiline++; + musthave(IN, CONTIN); + startl++; + + t->left = caselist(); + + musthave(ESAC, 0); + multiline--; + break; + + case IF: + multiline++; + t = newtp(); + t->type = TIF; + t->left = c_list(); + t->right = thenpart(); + musthave(FI, 0); + multiline--; + break; + + case DOT: + t = newtp(); + t->type = TDOT; + + musthave(WORD, 0); /* gets name of file */ + DBGPRINTF7(("COMMAND: DOT clause, yylval.cp is %s\n", yylval.cp)); + + word(yylval.cp); /* add word to wdlist */ + word(NOWORD); /* terminate wdlist */ + t->words = copyw(); /* dup wdlist */ + break; + + } + + while (synio(0)); + + t = namelist(t); + iolist = iosave; + + DBGPRINTF(("COMMAND: returning %p\n", t)); + + return t; +} + +static struct op *dowholefile(int type, int mark) +{ + struct op *t; + + DBGPRINTF(("DOWHOLEFILE: enter, type=%d, mark=%d\n", type, mark)); + + multiline++; + t = c_list(); + multiline--; + t = block(type, t, NOBLOCK, NOWORDS); + DBGPRINTF(("DOWHOLEFILE: return t=%p\n", t)); + return t; +} + +static struct op *dogroup(int onlydone) +{ + int c; + struct op *mylist; + + c = yylex(CONTIN); + if (c == DONE && onlydone) + return NULL; + if (c != DO) + SYNTAXERR; + mylist = c_list(); + musthave(DONE, 0); + return mylist; +} + +static struct op *thenpart(void) +{ + int c; + struct op *t; + + if ((c = yylex(0)) != THEN) { + peeksym = c; + return NULL; + } + t = newtp(); + t->type = 0; + t->left = c_list(); + if (t->left == NULL) + SYNTAXERR; + t->right = elsepart(); + return t; +} + +static struct op *elsepart(void) +{ + int c; + struct op *t; + + switch (c = yylex(0)) { + case ELSE: + if ((t = c_list()) == NULL) + SYNTAXERR; + return t; + + case ELIF: + t = newtp(); + t->type = TELIF; + t->left = c_list(); + t->right = thenpart(); + return t; + + default: + peeksym = c; + return NULL; + } +} + +static struct op *caselist(void) +{ + struct op *t; + + t = NULL; + while ((peeksym = yylex(CONTIN)) != ESAC) { + DBGPRINTF(("CASELIST, doing yylex, peeksym=%d\n", peeksym)); + t = list(t, casepart()); + } + + DBGPRINTF(("CASELIST, returning t=%p\n", t)); + return t; +} + +static struct op *casepart(void) +{ + struct op *t; + + DBGPRINTF7(("CASEPART: enter...\n")); + + t = newtp(); + t->type = TPAT; + t->words = pattern(); + musthave(')', 0); + t->left = c_list(); + if ((peeksym = yylex(CONTIN)) != ESAC) + musthave(BREAK, CONTIN); + + DBGPRINTF7(("CASEPART: made newtp(TPAT, t=%p)\n", t)); + + return t; +} + +static char **pattern(void) +{ + int c, cf; + + cf = CONTIN; + do { + musthave(WORD, cf); + word(yylval.cp); + cf = 0; + } while ((c = yylex(0)) == '|'); + peeksym = c; + word(NOWORD); + + return copyw(); +} + +static char **wordlist(void) +{ + int c; + + if ((c = yylex(0)) != IN) { + peeksym = c; + return NULL; + } + startl = 0; + while ((c = yylex(0)) == WORD) + word(yylval.cp); + word(NOWORD); + peeksym = c; + return copyw(); +} + +/* + * supporting functions + */ +static struct op *list(struct op *t1, struct op *t2) +{ + DBGPRINTF7(("LIST: enter, t1=%p, t2=%p\n", t1, t2)); + + if (t1 == NULL) + return t2; + if (t2 == NULL) + return t1; + + return block(TLIST, t1, t2, NOWORDS); +} + +static struct op *block(int type, struct op *t1, struct op *t2, char **wp) +{ + struct op *t; + + DBGPRINTF7(("BLOCK: enter, type=%d (%s)\n", type, T_CMD_NAMES[type])); + + t = newtp(); + t->type = type; + t->left = t1; + t->right = t2; + t->words = wp; + + DBGPRINTF7(("BLOCK: inserted %p between %p and %p\n", t, t1, + t2)); + + return t; +} + +/* See if given string is a shell multiline (FOR, IF, etc) */ +static int rlookup(char *n) +{ + const struct res *rp; + + DBGPRINTF7(("RLOOKUP: enter, n is %s\n", n)); + + for (rp = restab; rp->r_name; rp++) + if (strcmp(rp->r_name, n) == 0) { + DBGPRINTF7(("RLOOKUP: match, returning %d\n", rp->r_val)); + return rp->r_val; /* Return numeric code for shell multiline */ + } + + DBGPRINTF7(("RLOOKUP: NO match, returning 0\n")); + return 0; /* Not a shell multiline */ +} + +static struct op *newtp(void) +{ + struct op *t; + + t = (struct op *) tree(sizeof(*t)); + t->type = 0; + t->words = NULL; + t->ioact = NULL; + t->left = NULL; + t->right = NULL; + t->str = NULL; + + DBGPRINTF3(("NEWTP: allocated %p\n", t)); + + return t; +} + +static struct op *namelist(struct op *t) +{ + + DBGPRINTF7(("NAMELIST: enter, t=%p, type %s, iolist=%p\n", t, + T_CMD_NAMES[t->type], iolist)); + + if (iolist) { + iolist = addword((char *) NULL, iolist); + t->ioact = copyio(); + } else + t->ioact = NULL; + + if (t->type != TCOM) { + if (t->type != TPAREN && t->ioact != NULL) { + t = block(TPAREN, t, NOBLOCK, NOWORDS); + t->ioact = t->left->ioact; + t->left->ioact = NULL; + } + return t; + } + + word(NOWORD); + t->words = copyw(); + + + return t; +} + +static char **copyw(void) +{ + char **wd; + + wd = getwords(wdlist); + wdlist = 0; + return wd; +} + +static void word(char *cp) +{ + wdlist = addword(cp, wdlist); +} + +static struct ioword **copyio(void) +{ + struct ioword **iop; + + iop = (struct ioword **) getwords(iolist); + iolist = 0; + return iop; +} + +static struct ioword *io(int u, int f, char *cp) +{ + struct ioword *iop; + + iop = (struct ioword *) tree(sizeof(*iop)); + iop->io_unit = u; + iop->io_flag = f; + iop->io_name = cp; + iolist = addword((char *) iop, iolist); + return iop; +} + +static void zzerr(void) +{ + yyerror("syntax error"); +} + +static void yyerror(char *s) +{ + yynerrs++; + if (interactive && e.iop <= iostack) { + multiline = 0; + while (eofc() == 0 && yylex(0) != '\n'); + } + err(s); + fail(); +} + +static int yylex(int cf) +{ + int c, c1; + int atstart; + + if ((c = peeksym) > 0) { + peeksym = 0; + if (c == '\n') + startl = 1; + return c; + } + + + nlseen = 0; + atstart = startl; + startl = 0; + yylval.i = 0; + e.linep = line; + +/* MALAMO */ + line[LINELIM - 1] = '\0'; + + loop: + while ((c = my_getc(0)) == ' ' || c == '\t') /* Skip whitespace */ + ; + + switch (c) { + default: + if (any(c, "0123456789")) { + unget(c1 = my_getc(0)); + if (c1 == '<' || c1 == '>') { + iounit = c - '0'; + goto loop; + } + *e.linep++ = c; + c = c1; + } + break; + + case '#': /* Comment, skip to next newline or End-of-string */ + while ((c = my_getc(0)) != 0 && c != '\n'); + unget(c); + goto loop; + + case 0: + DBGPRINTF5(("YYLEX: return 0, c=%d\n", c)); + return c; + + case '$': + DBGPRINTF9(("YYLEX: found $\n")); + *e.linep++ = c; + if ((c = my_getc(0)) == '{') { + if ((c = collect(c, '}')) != '\0') + return c; + goto pack; + } + break; + + case '`': + case '\'': + case '"': + if ((c = collect(c, c)) != '\0') + return c; + goto pack; + + case '|': + case '&': + case ';': + startl = 1; + /* If more chars process them, else return NULL char */ + if ((c1 = dual(c)) != '\0') + return c1; + else + return c; + + case '^': + startl = 1; + return '|'; + case '>': + case '<': + diag(c); + return c; + + case '\n': + nlseen++; + gethere(); + startl = 1; + if (multiline || cf & CONTIN) { + if (interactive && e.iop <= iostack) { +#ifdef CONFIG_FEATURE_COMMAND_EDITING + current_prompt = cprompt->value; +#else + prs(cprompt->value); +#endif + } + if (cf & CONTIN) + goto loop; + } + return c; + + case '(': + case ')': + startl = 1; + return c; + } + + unget(c); + + pack: + while ((c = my_getc(0)) != 0 && !any(c, "`$ '\"\t;&<>()|^\n")) { + if (e.linep >= elinep) + err("word too long"); + else + *e.linep++ = c; + }; + + unget(c); + + if (any(c, "\"'`$")) + goto loop; + + *e.linep++ = '\0'; + + if (atstart && (c = rlookup(line)) != 0) { + startl = 1; + return c; + } + + yylval.cp = strsave(line, areanum); + return WORD; +} + + +static int collect(int c, int c1) +{ + char s[2]; + + DBGPRINTF8(("COLLECT: enter, c=%d, c1=%d\n", c, c1)); + + *e.linep++ = c; + while ((c = my_getc(c1)) != c1) { + if (c == 0) { + unget(c); + s[0] = c1; + s[1] = 0; + prs("no closing "); + yyerror(s); + return YYERRCODE; + } + if (interactive && c == '\n' && e.iop <= iostack) { +#ifdef CONFIG_FEATURE_COMMAND_EDITING + current_prompt = cprompt->value; +#else + prs(cprompt->value); +#endif + } + *e.linep++ = c; + } + + *e.linep++ = c; + + DBGPRINTF8(("COLLECT: return 0, line is %s\n", line)); + + return 0; +} + +/* "multiline commands" helper func */ +/* see if next 2 chars form a shell multiline */ +static int dual(int c) +{ + char s[3]; + char *cp = s; + + DBGPRINTF8(("DUAL: enter, c=%d\n", c)); + + *cp++ = c; /* c is the given "peek" char */ + *cp++ = my_getc(0); /* get next char of input */ + *cp = 0; /* add EOS marker */ + + c = rlookup(s); /* see if 2 chars form a shell multiline */ + if (c == 0) + unget(*--cp); /* String is not a shell multiline, put peek char back */ + + return c; /* String is multiline, return numeric multiline (restab) code */ +} + +static void diag(int ec) +{ + int c; + + DBGPRINTF8(("DIAG: enter, ec=%d\n", ec)); + + c = my_getc(0); + if (c == '>' || c == '<') { + if (c != ec) + zzerr(); + yylval.i = ec == '>' ? IOWRITE | IOCAT : IOHERE; + c = my_getc(0); + } else + yylval.i = ec == '>' ? IOWRITE : IOREAD; + if (c != '&' || yylval.i == IOHERE) + unget(c); + else + yylval.i |= IODUP; +} + +static char *tree(unsigned size) +{ + char *t; + + if ((t = getcell(size)) == NULL) { + DBGPRINTF2(("TREE: getcell(%d) failed!\n", size)); + prs("command line too complicated\n"); + fail(); + /* NOTREACHED */ + } + return t; +} + +/* VARARGS1 */ +/* ARGSUSED */ + +/* -------- exec.c -------- */ + +/* + * execute tree + */ + + +static int execute(struct op *t, int *pin, int *pout, int act) +{ + struct op *t1; + volatile int i, rv, a; + char *cp, **wp, **wp2; + struct var *vp; + struct op *outtree_save; + struct brkcon bc; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) ℘ +#endif + + if (t == NULL) { + DBGPRINTF4(("EXECUTE: enter, t==null, returning.\n")); + return 0; + } + + DBGPRINTF(("EXECUTE: t=%p, t->type=%d (%s), t->words is %s\n", t, + t->type, T_CMD_NAMES[t->type], + ((t->words == NULL) ? "NULL" : t->words[0]))); + + rv = 0; + a = areanum++; + wp = (wp2 = t->words) != NULL + ? eval(wp2, t->type == TCOM ? DOALL : DOALL & ~DOKEY) + : NULL; + + switch (t->type) { + case TDOT: + DBGPRINTF3(("EXECUTE: TDOT\n")); + + outtree_save = outtree; + + newfile(evalstr(t->words[0], DOALL)); + + t->left = dowholefile(TLIST, 0); + t->right = NULL; + + outtree = outtree_save; + + if (t->left) + rv = execute(t->left, pin, pout, 0); + if (t->right) + rv = execute(t->right, pin, pout, 0); + break; + + case TPAREN: + rv = execute(t->left, pin, pout, 0); + break; + + case TCOM: + { + rv = forkexec(t, pin, pout, act, wp); + } + break; + + case TPIPE: + { + int pv[2]; + + if ((rv = openpipe(pv)) < 0) + break; + pv[0] = remap(pv[0]); + pv[1] = remap(pv[1]); + (void) execute(t->left, pin, pv, 0); + rv = execute(t->right, pv, pout, 0); + } + break; + + case TLIST: + (void) execute(t->left, pin, pout, 0); + rv = execute(t->right, pin, pout, 0); + break; + + case TASYNC: + { + int hinteractive = interactive; + + DBGPRINTF7(("EXECUTE: TASYNC clause, calling vfork()...\n")); + + i = vfork(); + if (i != 0) { + interactive = hinteractive; + if (i != -1) { + setval(lookup("!"), putn(i)); + if (pin != NULL) + closepipe(pin); + if (interactive) { + prs(putn(i)); + prs("\n"); + } + } else + rv = -1; + setstatus(rv); + } else { + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + if (interactive) + signal(SIGTERM, SIG_DFL); + interactive = 0; + if (pin == NULL) { + close(0); + open(bb_dev_null, 0); + } + _exit(execute(t->left, pin, pout, FEXEC)); + } + } + break; + + case TOR: + case TAND: + rv = execute(t->left, pin, pout, 0); + if ((t1 = t->right) != NULL && (rv == 0) == (t->type == TAND)) + rv = execute(t1, pin, pout, 0); + break; + + case TFOR: + if (wp == NULL) { + wp = dolv + 1; + if ((i = dolc) < 0) + i = 0; + } else { + i = -1; + while (*wp++ != NULL); + } + vp = lookup(t->str); + while (setjmp(bc.brkpt)) + if (isbreak) + goto broken; + brkset(&bc); + for (t1 = t->left; i-- && *wp != NULL;) { + setval(vp, *wp++); + rv = execute(t1, pin, pout, 0); + } + brklist = brklist->nextlev; + break; + + case TWHILE: + case TUNTIL: + while (setjmp(bc.brkpt)) + if (isbreak) + goto broken; + brkset(&bc); + t1 = t->left; + while ((execute(t1, pin, pout, 0) == 0) == (t->type == TWHILE)) + rv = execute(t->right, pin, pout, 0); + brklist = brklist->nextlev; + break; + + case TIF: + case TELIF: + if (t->right != NULL) { + rv = !execute(t->left, pin, pout, 0) ? + execute(t->right->left, pin, pout, 0) : + execute(t->right->right, pin, pout, 0); + } + break; + + case TCASE: + if ((cp = evalstr(t->str, DOSUB | DOTRIM)) == 0) + cp = ""; + + DBGPRINTF7(("EXECUTE: TCASE, t->str is %s, cp is %s\n", + ((t->str == NULL) ? "NULL" : t->str), + ((cp == NULL) ? "NULL" : cp))); + + if ((t1 = findcase(t->left, cp)) != NULL) { + DBGPRINTF7(("EXECUTE: TCASE, calling execute(t=%p, t1=%p)...\n", t, t1)); + rv = execute(t1, pin, pout, 0); + DBGPRINTF7(("EXECUTE: TCASE, back from execute(t=%p, t1=%p)...\n", t, t1)); + } + break; + + case TBRACE: +/* + if (iopp = t->ioact) + while (*iopp) + if (iosetup(*iopp++, pin!=NULL, pout!=NULL)) { + rv = -1; + break; + } +*/ + if (rv >= 0 && (t1 = t->left)) + rv = execute(t1, pin, pout, 0); + break; + + }; + + broken: + t->words = wp2; + isbreak = 0; + freehere(areanum); + freearea(areanum); + areanum = a; + if (interactive && intr) { + closeall(); + fail(); + } + + if ((i = trapset) != 0) { + trapset = 0; + runtrap(i); + } + + DBGPRINTF(("EXECUTE: returning from t=%p, rv=%d\n", t, rv)); + return rv; +} + +static int +forkexec(struct op *t, int *pin, int *pout, int act, char **wp) +{ + pid_t newpid; + int i, rv; + int (*shcom) (struct op *) = NULL; + int f; + char *cp = NULL; + struct ioword **iopp; + int resetsig; + char **owp; + int forked = 0; + + int *hpin = pin; + int *hpout = pout; + char *hwp; + int hinteractive; + int hintr; + struct brkcon *hbrklist; + int hexecflg; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) &pin; + (void) &pout; + (void) ℘ + (void) &shcom; + (void) &cp; + (void) &resetsig; + (void) &owp; +#endif + + DBGPRINTF(("FORKEXEC: t=%p, pin %p, pout %p, act %d\n", t, pin, + pout, act)); + DBGPRINTF7(("FORKEXEC: t->words is %s\n", + ((t->words == NULL) ? "NULL" : t->words[0]))); + + owp = wp; + resetsig = 0; + rv = -1; /* system-detected error */ + if (t->type == TCOM) { + while ((cp = *wp++) != NULL); + cp = *wp; + + /* strip all initial assignments */ + /* not correct wrt PATH=yyy command etc */ + if (flag['x']) { + DBGPRINTF9(("FORKEXEC: echo'ing, cp=%p, wp=%p, owp=%p\n", + cp, wp, owp)); + echo(cp ? wp : owp); + } + + if (cp == NULL && t->ioact == NULL) { + while ((cp = *owp++) != NULL && assign(cp, COPYV)); + DBGPRINTF(("FORKEXEC: returning setstatus()\n")); + return setstatus(0); + } else if (cp != NULL) { + shcom = inbuilt(cp); + } + } + + t->words = wp; + f = act; + + DBGPRINTF(("FORKEXEC: shcom %p, f&FEXEC 0x%x, owp %p\n", shcom, + f & FEXEC, owp)); + + if (shcom == NULL && (f & FEXEC) == 0) { + /* Save values in case the child process alters them */ + hpin = pin; + hpout = pout; + hwp = *wp; + hinteractive = interactive; + hintr = intr; + hbrklist = brklist; + hexecflg = execflg; + + DBGPRINTF3(("FORKEXEC: calling vfork()...\n")); + + newpid = vfork(); + + if (newpid == -1) { + DBGPRINTF(("FORKEXEC: ERROR, cannot vfork()!\n")); + return -1; + } + + + if (newpid > 0) { /* Parent */ + + /* Restore values */ + pin = hpin; + pout = hpout; + *wp = hwp; + interactive = hinteractive; + intr = hintr; + brklist = hbrklist; + execflg = hexecflg; + +/* moved up + if (i == -1) + return rv; +*/ + + if (pin != NULL) + closepipe(pin); + + return (pout == NULL ? setstatus(waitfor(newpid, 0)) : 0); + } + + /* Must be the child process, pid should be 0 */ + DBGPRINTF(("FORKEXEC: child process, shcom=%p\n", shcom)); + + if (interactive) { + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + resetsig = 1; + } + interactive = 0; + intr = 0; + forked = 1; + brklist = 0; + execflg = 0; + } + + + if (owp != NULL) + while ((cp = *owp++) != NULL && assign(cp, COPYV)) + if (shcom == NULL) + export(lookup(cp)); + +#ifdef COMPIPE + if ((pin != NULL || pout != NULL) && shcom != NULL && shcom != doexec) { + err("piping to/from shell builtins not yet done"); + if (forked) + _exit(-1); + return -1; + } +#endif + + if (pin != NULL) { + dup2(pin[0], 0); + closepipe(pin); + } + if (pout != NULL) { + dup2(pout[1], 1); + closepipe(pout); + } + + if ((iopp = t->ioact) != NULL) { + if (shcom != NULL && shcom != doexec) { + prs(cp); + err(": cannot redirect shell command"); + if (forked) + _exit(-1); + return -1; + } + while (*iopp) + if (iosetup(*iopp++, pin != NULL, pout != NULL)) { + if (forked) + _exit(rv); + return rv; + } + } + + if (shcom) { + i = setstatus((*shcom) (t)); + if (forked) + _exit(i); + DBGPRINTF(("FORKEXEC: returning i=%d\n", i)); + return i; + } + + /* should use FIOCEXCL */ + for (i = FDBASE; i < NOFILE; i++) + close(i); + if (resetsig) { + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + } + + if (t->type == TPAREN) + _exit(execute(t->left, NOPIPE, NOPIPE, FEXEC)); + if (wp[0] == NULL) + _exit(0); + + cp = rexecve(wp[0], wp, makenv(0, NULL)); + prs(wp[0]); + prs(": "); + err(cp); + if (!execflg) + trap[0] = NULL; + + DBGPRINTF(("FORKEXEC: calling leave(), pid=%d\n", newpid)); + + leave(); + /* NOTREACHED */ + _exit(1); +} + +/* + * 0< 1> are ignored as required + * within pipelines. + */ +static int iosetup(struct ioword *iop, int pipein, int pipeout) +{ + int u = -1; + char *cp = NULL, *msg; + + DBGPRINTF(("IOSETUP: iop %p, pipein %i, pipeout %i\n", iop, + pipein, pipeout)); + + if (iop->io_unit == IODEFAULT) /* take default */ + iop->io_unit = iop->io_flag & (IOREAD | IOHERE) ? 0 : 1; + + if (pipein && iop->io_unit == 0) + return 0; + + if (pipeout && iop->io_unit == 1) + return 0; + + msg = iop->io_flag & (IOREAD | IOHERE) ? "open" : "create"; + if ((iop->io_flag & IOHERE) == 0) { + cp = iop->io_name; + if ((cp = evalstr(cp, DOSUB | DOTRIM)) == NULL) + return 1; + } + + if (iop->io_flag & IODUP) { + if (cp[1] || (!isdigit(*cp) && *cp != '-')) { + prs(cp); + err(": illegal >& argument"); + return 1; + } + if (*cp == '-') + iop->io_flag = IOCLOSE; + iop->io_flag &= ~(IOREAD | IOWRITE); + } + switch (iop->io_flag) { + case IOREAD: + u = open(cp, 0); + break; + + case IOHERE: + case IOHERE | IOXHERE: + u = herein(iop->io_name, iop->io_flag & IOXHERE); + cp = "here file"; + break; + + case IOWRITE | IOCAT: + if ((u = open(cp, 1)) >= 0) { + lseek(u, (long) 0, SEEK_END); + break; + } + case IOWRITE: + u = creat(cp, 0666); + break; + + case IODUP: + u = dup2(*cp - '0', iop->io_unit); + break; + + case IOCLOSE: + close(iop->io_unit); + return 0; + } + if (u < 0) { + prs(cp); + prs(": cannot "); + warn(msg); + return 1; + } else { + if (u != iop->io_unit) { + dup2(u, iop->io_unit); + close(u); + } + } + return 0; +} + +static void echo(char **wp) +{ + int i; + + prs("+"); + for (i = 0; wp[i]; i++) { + if (i) + prs(" "); + prs(wp[i]); + } + prs("\n"); +} + +static struct op **find1case(struct op *t, char *w) +{ + struct op *t1; + struct op **tp; + char **wp, *cp; + + + if (t == NULL) { + DBGPRINTF3(("FIND1CASE: enter, t==NULL, returning.\n")); + return NULL; + } + + DBGPRINTF3(("FIND1CASE: enter, t->type=%d (%s)\n", t->type, + T_CMD_NAMES[t->type])); + + if (t->type == TLIST) { + if ((tp = find1case(t->left, w)) != NULL) { + DBGPRINTF3(("FIND1CASE: found one to the left, returning tp=%p\n", tp)); + return tp; + } + t1 = t->right; /* TPAT */ + } else + t1 = t; + + for (wp = t1->words; *wp;) + if ((cp = evalstr(*wp++, DOSUB)) && gmatch(w, cp)) { + DBGPRINTF3(("FIND1CASE: returning &t1->left= %p.\n", + &t1->left)); + return &t1->left; + } + + DBGPRINTF(("FIND1CASE: returning NULL\n")); + return NULL; +} + +static struct op *findcase(struct op *t, char *w) +{ + struct op **tp; + + tp = find1case(t, w); + return tp != NULL ? *tp : NULL; +} + +/* + * Enter a new loop level (marked for break/continue). + */ +static void brkset(struct brkcon *bc) +{ + bc->nextlev = brklist; + brklist = bc; +} + +/* + * Wait for the last process created. + * Print a message for each process found + * that was killed by a signal. + * Ignore interrupt signals while waiting + * unless `canintr' is true. + */ +static int waitfor(int lastpid, int canintr) +{ + int pid, rv; + int s; + int oheedint = heedint; + + heedint = 0; + rv = 0; + do { + pid = wait(&s); + if (pid == -1) { + if (errno != EINTR || canintr) + break; + } else { + if ((rv = WAITSIG(s)) != 0) { + if (rv < NSIGNAL) { + if (signame[rv] != NULL) { + if (pid != lastpid) { + prn(pid); + prs(": "); + } + prs(signame[rv]); + } + } else { + if (pid != lastpid) { + prn(pid); + prs(": "); + } + prs("Signal "); + prn(rv); + prs(" "); + } + if (WAITCORE(s)) + prs(" - core dumped"); + if (rv >= NSIGNAL || signame[rv]) + prs("\n"); + rv = -1; + } else + rv = WAITVAL(s); + } + } while (pid != lastpid); + heedint = oheedint; + if (intr) { + if (interactive) { + if (canintr) + intr = 0; + } else { + if (exstat == 0) + exstat = rv; + onintr(0); + } + } + return rv; +} + +static int setstatus(int s) +{ + exstat = s; + setval(lookup("?"), putn(s)); + return s; +} + +/* + * PATH-searching interface to execve. + * If getenv("PATH") were kept up-to-date, + * execvp might be used. + */ +static char *rexecve(char *c, char **v, char **envp) +{ + int i; + char *sp, *tp; + int eacces = 0, asis = 0; + char *name = c; + + if (ENABLE_FEATURE_SH_STANDALONE_SHELL) { + optind = 1; + if (find_applet_by_name(name)) { + /* We have to exec here since we vforked. Running + * run_applet_by_name() won't work and bad things + * will happen. */ + execve(CONFIG_BUSYBOX_EXEC_PATH, v, envp); + } + } + + DBGPRINTF(("REXECVE: c=%p, v=%p, envp=%p\n", c, v, envp)); + + sp = any('/', c) ? "" : path->value; + asis = *sp == '\0'; + while (asis || *sp != '\0') { + asis = 0; + tp = e.linep; + for (; *sp != '\0'; tp++) + if ((*tp = *sp++) == ':') { + asis = *sp == '\0'; + break; + } + if (tp != e.linep) + *tp++ = '/'; + for (i = 0; (*tp++ = c[i++]) != '\0';); + + DBGPRINTF3(("REXECVE: e.linep is %s\n", e.linep)); + + execve(e.linep, v, envp); + + switch (errno) { + case ENOEXEC: + *v = e.linep; + tp = *--v; + *v = e.linep; + execve(DEFAULT_SHELL, v, envp); + *v = tp; + return "no Shell"; + + case ENOMEM: + return (char *) bb_msg_memory_exhausted; + + case E2BIG: + return "argument list too long"; + + case EACCES: + eacces++; + break; + } + } + return errno == ENOENT ? "not found" : "cannot execute"; +} + +/* + * Run the command produced by generator `f' + * applied to stream `arg'. + */ +static int run(struct ioarg *argp, int (*f) (struct ioarg *)) +{ + struct op *otree; + struct wdblock *swdlist; + struct wdblock *siolist; + jmp_buf ev, rt; + xint *ofail; + int rv; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) &rv; +#endif + + DBGPRINTF(("RUN: enter, areanum %d, outtree %p, failpt %p\n", + areanum, outtree, failpt)); + + areanum++; + swdlist = wdlist; + siolist = iolist; + otree = outtree; + ofail = failpt; + rv = -1; + + if (newenv(setjmp(errpt = ev)) == 0) { + wdlist = 0; + iolist = 0; + pushio(argp, f); + e.iobase = e.iop; + yynerrs = 0; + if (setjmp(failpt = rt) == 0 && yyparse() == 0) + rv = execute(outtree, NOPIPE, NOPIPE, 0); + quitenv(); + } else { + DBGPRINTF(("RUN: error from newenv()!\n")); + } + + wdlist = swdlist; + iolist = siolist; + failpt = ofail; + outtree = otree; + freearea(areanum--); + + return rv; +} + +/* -------- do.c -------- */ + +/* + * built-in commands: doX + */ + +static int dohelp(struct op *t) +{ + int col; + const struct builtincmd *x; + + printf("\nBuilt-in commands:\n"); + printf("-------------------\n"); + + for (col = 0, x = builtincmds; x->builtinfunc != NULL; x++) { + if (!x->name) + continue; + col += printf("%s%s", ((col == 0) ? "\t" : " "), x->name); + if (col > 60) { + puts(""); + col = 0; + } + } +#ifdef CONFIG_FEATURE_SH_STANDALONE_SHELL + { + int i; + const struct BB_applet *applet; + extern const struct BB_applet applets[]; + extern const size_t NUM_APPLETS; + + for (i = 0, applet = applets; i < NUM_APPLETS; applet++, i++) { + if (!applet->name) + continue; + + col += printf("%s%s", ((col == 0) ? "\t" : " "), applet->name); + if (col > 60) { + puts(""); + col = 0; + } + } + } +#endif + printf("\n\n"); + return EXIT_SUCCESS; +} + + + +static int dolabel(struct op *t) +{ + return 0; +} + +static int dochdir(struct op *t) +{ + char *cp, *er; + + if ((cp = t->words[1]) == NULL && (cp = homedir->value) == NULL) + er = ": no home directory"; + else if (chdir(cp) < 0) + er = ": bad directory"; + else + return 0; + prs(cp != NULL ? cp : "cd"); + err(er); + return 1; +} + +static int doshift(struct op *t) +{ + int n; + + n = t->words[1] ? getn(t->words[1]) : 1; + if (dolc < n) { + err("nothing to shift"); + return 1; + } + dolv[n] = dolv[0]; + dolv += n; + dolc -= n; + setval(lookup("#"), putn(dolc)); + return 0; +} + +/* + * execute login and newgrp directly + */ +static int dologin(struct op *t) +{ + char *cp; + + if (interactive) { + signal(SIGINT, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + } + cp = rexecve(t->words[0], t->words, makenv(0, NULL)); + prs(t->words[0]); + prs(": "); + err(cp); + return 1; +} + +static int doumask(struct op *t) +{ + int i, n; + char *cp; + + if ((cp = t->words[1]) == NULL) { + i = umask(0); + umask(i); + for (n = 3 * 4; (n -= 3) >= 0;) + putc('0' + ((i >> n) & 07), stderr); + putc('\n', stderr); + } else { + for (n = 0; *cp >= '0' && *cp <= '9'; cp++) + n = n * 8 + (*cp - '0'); + umask(n); + } + return 0; +} + +static int doexec(struct op *t) +{ + int i; + jmp_buf ex; + xint *ofail; + + t->ioact = NULL; + for (i = 0; (t->words[i] = t->words[i + 1]) != NULL; i++); + if (i == 0) + return 1; + execflg = 1; + ofail = failpt; + if (setjmp(failpt = ex) == 0) + execute(t, NOPIPE, NOPIPE, FEXEC); + failpt = ofail; + execflg = 0; + return 1; +} + +static int dodot(struct op *t) +{ + int i; + char *sp, *tp; + char *cp; + int maltmp; + + DBGPRINTF(("DODOT: enter, t=%p, tleft %p, tright %p, e.linep is %s\n", t, t->left, t->right, ((e.linep == NULL) ? "NULL" : e.linep))); + + if ((cp = t->words[1]) == NULL) { + DBGPRINTF(("DODOT: bad args, ret 0\n")); + return 0; + } else { + DBGPRINTF(("DODOT: cp is %s\n", cp)); + } + + sp = any('/', cp) ? ":" : path->value; + + DBGPRINTF(("DODOT: sp is %s, e.linep is %s\n", + ((sp == NULL) ? "NULL" : sp), + ((e.linep == NULL) ? "NULL" : e.linep))); + + while (*sp) { + tp = e.linep; + while (*sp && (*tp = *sp++) != ':') + tp++; + if (tp != e.linep) + *tp++ = '/'; + + for (i = 0; (*tp++ = cp[i++]) != '\0';); + + /* Original code */ + if ((i = open(e.linep, 0)) >= 0) { + exstat = 0; + maltmp = remap(i); + DBGPRINTF(("DODOT: remap=%d, exstat=%d, e.iofd %d, i %d, e.linep is %s\n", maltmp, exstat, e.iofd, i, e.linep)); + + next(maltmp); /* Basically a PUSHIO */ + + DBGPRINTF(("DODOT: returning exstat=%d\n", exstat)); + + return exstat; + } + + } /* While */ + + prs(cp); + err(": not found"); + + return -1; +} + +static int dowait(struct op *t) +{ + int i; + char *cp; + + if ((cp = t->words[1]) != NULL) { + i = getn(cp); + if (i == 0) + return 0; + } else + i = -1; + setstatus(waitfor(i, 1)); + return 0; +} + +static int doread(struct op *t) +{ + char *cp, **wp; + int nb = 0; + int nl = 0; + + if (t->words[1] == NULL) { + err("Usage: read name ..."); + return 1; + } + for (wp = t->words + 1; *wp; wp++) { + for (cp = e.linep; !nl && cp < elinep - 1; cp++) + if ((nb = read(0, cp, sizeof(*cp))) != sizeof(*cp) || + (nl = (*cp == '\n')) || (wp[1] && any(*cp, ifs->value))) + break; + *cp = 0; + if (nb <= 0) + break; + setval(lookup(*wp), e.linep); + } + return nb <= 0; +} + +static int doeval(struct op *t) +{ + return RUN(awordlist, t->words + 1, wdchar); +} + +static int dotrap(struct op *t) +{ + int n, i; + int resetsig; + + if (t->words[1] == NULL) { + for (i = 0; i <= _NSIG; i++) + if (trap[i]) { + prn(i); + prs(": "); + prs(trap[i]); + prs("\n"); + } + return 0; + } + resetsig = isdigit(*t->words[1]); + for (i = resetsig ? 1 : 2; t->words[i] != NULL; ++i) { + n = getsig(t->words[i]); + freecell(trap[n]); + trap[n] = 0; + if (!resetsig) { + if (*t->words[1] != '\0') { + trap[n] = strsave(t->words[1], 0); + setsig(n, sig); + } else + setsig(n, SIG_IGN); + } else { + if (interactive) + if (n == SIGINT) + setsig(n, onintr); + else + setsig(n, n == SIGQUIT ? SIG_IGN : SIG_DFL); + else + setsig(n, SIG_DFL); + } + } + return 0; +} + +static int getsig(char *s) +{ + int n; + + if ((n = getn(s)) < 0 || n > _NSIG) { + err("trap: bad signal number"); + n = 0; + } + return n; +} + +static void setsig(int n, sighandler_t f) +{ + if (n == 0) + return; + if (signal(n, SIG_IGN) != SIG_IGN || ourtrap[n]) { + ourtrap[n] = 1; + signal(n, f); + } +} + +static int getn(char *as) +{ + char *s; + int n, m; + + s = as; + m = 1; + if (*s == '-') { + m = -1; + s++; + } + for (n = 0; isdigit(*s); s++) + n = (n * 10) + (*s - '0'); + if (*s) { + prs(as); + err(": bad number"); + } + return n * m; +} + +static int dobreak(struct op *t) +{ + return brkcontin(t->words[1], 1); +} + +static int docontinue(struct op *t) +{ + return brkcontin(t->words[1], 0); +} + +static int brkcontin(char *cp, int val) +{ + struct brkcon *bc; + int nl; + + nl = cp == NULL ? 1 : getn(cp); + if (nl <= 0) + nl = 999; + do { + if ((bc = brklist) == NULL) + break; + brklist = bc->nextlev; + } while (--nl); + if (nl) { + err("bad break/continue level"); + return 1; + } + isbreak = val; + longjmp(bc->brkpt, 1); + /* NOTREACHED */ +} + +static int doexit(struct op *t) +{ + char *cp; + + execflg = 0; + if ((cp = t->words[1]) != NULL) + setstatus(getn(cp)); + + DBGPRINTF(("DOEXIT: calling leave(), t=%p\n", t)); + + leave(); + /* NOTREACHED */ + return 0; +} + +static int doexport(struct op *t) +{ + rdexp(t->words + 1, export, EXPORT); + return 0; +} + +static int doreadonly(struct op *t) +{ + rdexp(t->words + 1, ronly, RONLY); + return 0; +} + +static void rdexp(char **wp, void (*f) (struct var *), int key) +{ + DBGPRINTF6(("RDEXP: enter, wp=%p, func=%p, key=%d\n", wp, f, key)); + DBGPRINTF6(("RDEXP: *wp=%s\n", *wp)); + + if (*wp != NULL) { + for (; *wp != NULL; wp++) { + if (isassign(*wp)) { + char *cp; + + assign(*wp, COPYV); + for (cp = *wp; *cp != '='; cp++); + *cp = '\0'; + } + if (checkname(*wp)) + (*f) (lookup(*wp)); + else + badid(*wp); + } + } else + putvlist(key, 1); +} + +static void badid(char *s) +{ + prs(s); + err(": bad identifier"); +} + +static int doset(struct op *t) +{ + struct var *vp; + char *cp; + int n; + + if ((cp = t->words[1]) == NULL) { + for (vp = vlist; vp; vp = vp->next) + varput(vp->name, 1); + return 0; + } + if (*cp == '-') { + /* bad: t->words++; */ + for (n = 0; (t->words[n] = t->words[n + 1]) != NULL; n++); + if (*++cp == 0) + flag['x'] = flag['v'] = 0; + else + for (; *cp; cp++) + switch (*cp) { + case 'e': + if (!interactive) + flag['e']++; + break; + + default: + if (*cp >= 'a' && *cp <= 'z') + flag[(int) *cp]++; + break; + } + setdash(); + } + if (t->words[1]) { + t->words[0] = dolv[0]; + for (n = 1; t->words[n]; n++) + setarea((char *) t->words[n], 0); + dolc = n - 1; + dolv = t->words; + setval(lookup("#"), putn(dolc)); + setarea((char *) (dolv - 1), 0); + } + return 0; +} + +static void varput(char *s, int out) +{ + if (isalnum(*s) || *s == '_') { + write(out, s, strlen(s)); + write(out, "\n", 1); + } +} + + +/* + * Copyright (c) 1999 Herbert Xu <herbert@debian.org> + * This file contains code for the times builtin. + */ +static int dotimes(struct op *t) +{ + struct tms buf; + long int clk_tck = sysconf(_SC_CLK_TCK); + + times(&buf); + printf("%dm%fs %dm%fs\n%dm%fs %dm%fs\n", + (int) (buf.tms_utime / clk_tck / 60), + ((double) buf.tms_utime) / clk_tck, + (int) (buf.tms_stime / clk_tck / 60), + ((double) buf.tms_stime) / clk_tck, + (int) (buf.tms_cutime / clk_tck / 60), + ((double) buf.tms_cutime) / clk_tck, + (int) (buf.tms_cstime / clk_tck / 60), + ((double) buf.tms_cstime) / clk_tck); + return 0; +} + + +static int (*inbuilt(char *s)) (struct op *) { + const struct builtincmd *bp; + + for (bp = builtincmds; bp->name != NULL; bp++) + if (strcmp(bp->name, s) == 0) + return bp->builtinfunc; + + return NULL; +} + +/* -------- eval.c -------- */ + +/* + * ${} + * `command` + * blank interpretation + * quoting + * glob + */ + +static char **eval(char **ap, int f) +{ + struct wdblock *wb; + char **wp; + char **wf; + jmp_buf ev; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) ℘ + (void) ≈ +#endif + + DBGPRINTF4(("EVAL: enter, f=%d\n", f)); + + wp = NULL; + wb = NULL; + wf = NULL; + if (newenv(setjmp(errpt = ev)) == 0) { + while (*ap && isassign(*ap)) + expand(*ap++, &wb, f & ~DOGLOB); + if (flag['k']) { + for (wf = ap; *wf; wf++) { + if (isassign(*wf)) + expand(*wf, &wb, f & ~DOGLOB); + } + } + for (wb = addword((char *) 0, wb); *ap; ap++) { + if (!flag['k'] || !isassign(*ap)) + expand(*ap, &wb, f & ~DOKEY); + } + wb = addword((char *) 0, wb); + wp = getwords(wb); + quitenv(); + } else + gflg = 1; + + return gflg ? (char **) NULL : wp; +} + +/* + * Make the exported environment from the exported + * names in the dictionary. Keyword assignments + * will already have been done. + */ +static char **makenv(int all, struct wdblock *wb) +{ + struct var *vp; + + DBGPRINTF5(("MAKENV: enter, all=%d\n", all)); + + for (vp = vlist; vp; vp = vp->next) + if (all || vp->status & EXPORT) + wb = addword(vp->name, wb); + wb = addword((char *) 0, wb); + return getwords(wb); +} + +static char *evalstr(char *cp, int f) +{ + struct wdblock *wb; + + DBGPRINTF6(("EVALSTR: enter, cp=%p, f=%d\n", cp, f)); + + wb = NULL; + if (expand(cp, &wb, f)) { + if (wb == NULL || wb->w_nword == 0 + || (cp = wb->w_words[0]) == NULL) + cp = ""; + DELETE(wb); + } else + cp = NULL; + return cp; +} + +static int expand(char *cp, struct wdblock **wbp, int f) +{ + jmp_buf ev; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) &cp; +#endif + + DBGPRINTF3(("EXPAND: enter, f=%d\n", f)); + + gflg = 0; + + if (cp == NULL) + return 0; + + if (!anys("$`'\"", cp) && + !anys(ifs->value, cp) && ((f & DOGLOB) == 0 || !anys("[*?", cp))) { + cp = strsave(cp, areanum); + if (f & DOTRIM) + unquote(cp); + *wbp = addword(cp, *wbp); + return 1; + } + if (newenv(setjmp(errpt = ev)) == 0) { + PUSHIO(aword, cp, strchar); + e.iobase = e.iop; + while ((cp = blank(f)) && gflg == 0) { + e.linep = cp; + cp = strsave(cp, areanum); + if ((f & DOGLOB) == 0) { + if (f & DOTRIM) + unquote(cp); + *wbp = addword(cp, *wbp); + } else + *wbp = glob(cp, *wbp); + } + quitenv(); + } else + gflg = 1; + return gflg == 0; +} + +/* + * Blank interpretation and quoting + */ +static char *blank(int f) +{ + int c, c1; + char *sp; + int scanequals, foundequals; + + DBGPRINTF3(("BLANK: enter, f=%d\n", f)); + + sp = e.linep; + scanequals = f & DOKEY; + foundequals = 0; + + loop: + switch (c = subgetc('"', foundequals)) { + case 0: + if (sp == e.linep) + return 0; + *e.linep++ = 0; + return sp; + + default: + if (f & DOBLANK && any(c, ifs->value)) + goto loop; + break; + + case '"': + case '\'': + scanequals = 0; + if (INSUB()) + break; + for (c1 = c; (c = subgetc(c1, 1)) != c1;) { + if (c == 0) + break; + if (c == '\'' || !any(c, "$`\"")) + c |= QUOTE; + *e.linep++ = c; + } + c = 0; + } + unget(c); + if (!isalpha(c) && c != '_') + scanequals = 0; + for (;;) { + c = subgetc('"', foundequals); + if (c == 0 || + f & (DOBLANK && any(c, ifs->value)) || + (!INSUB() && any(c, "\"'"))) { + scanequals = 0; + unget(c); + if (any(c, "\"'")) + goto loop; + break; + } + if (scanequals) { + if (c == '=') { + foundequals = 1; + scanequals = 0; + } else if (!isalnum(c) && c != '_') + scanequals = 0; + } + *e.linep++ = c; + } + *e.linep++ = 0; + return sp; +} + +/* + * Get characters, substituting for ` and $ + */ +static int subgetc(char ec, int quoted) +{ + char c; + + DBGPRINTF3(("SUBGETC: enter, quoted=%d\n", quoted)); + + again: + c = my_getc(ec); + if (!INSUB() && ec != '\'') { + if (c == '`') { + if (grave(quoted) == 0) + return 0; + e.iop->task = XGRAVE; + goto again; + } + if (c == '$' && (c = dollar(quoted)) == 0) { + e.iop->task = XDOLL; + goto again; + } + } + return c; +} + +/* + * Prepare to generate the string returned by ${} substitution. + */ +static int dollar(int quoted) +{ + int otask; + struct io *oiop; + char *dolp; + char *s, c, *cp = NULL; + struct var *vp; + + DBGPRINTF3(("DOLLAR: enter, quoted=%d\n", quoted)); + + c = readc(); + s = e.linep; + if (c != '{') { + *e.linep++ = c; + if (isalpha(c) || c == '_') { + while ((c = readc()) != 0 && (isalnum(c) || c == '_')) + if (e.linep < elinep) + *e.linep++ = c; + unget(c); + } + c = 0; + } else { + oiop = e.iop; + otask = e.iop->task; + + e.iop->task = XOTHER; + while ((c = subgetc('"', 0)) != 0 && c != '}' && c != '\n') + if (e.linep < elinep) + *e.linep++ = c; + if (oiop == e.iop) + e.iop->task = otask; + if (c != '}') { + err("unclosed ${"); + gflg++; + return c; + } + } + if (e.linep >= elinep) { + err("string in ${} too long"); + gflg++; + e.linep -= 10; + } + *e.linep = 0; + if (*s) + for (cp = s + 1; *cp; cp++) + if (any(*cp, "=-+?")) { + c = *cp; + *cp++ = 0; + break; + } + if (s[1] == 0 && (*s == '*' || *s == '@')) { + if (dolc > 1) { + /* currently this does not distinguish $* and $@ */ + /* should check dollar */ + e.linep = s; + PUSHIO(awordlist, dolv + 1, dolchar); + return 0; + } else { /* trap the nasty ${=} */ + s[0] = '1'; + s[1] = 0; + } + } + vp = lookup(s); + if ((dolp = vp->value) == null) { + switch (c) { + case '=': + if (isdigit(*s)) { + err("cannot use ${...=...} with $n"); + gflg++; + break; + } + setval(vp, cp); + dolp = vp->value; + break; + + case '-': + dolp = strsave(cp, areanum); + break; + + case '?': + if (*cp == 0) { + prs("missing value for "); + err(s); + } else + err(cp); + gflg++; + break; + } + } else if (c == '+') + dolp = strsave(cp, areanum); + if (flag['u'] && dolp == null) { + prs("unset variable: "); + err(s); + gflg++; + } + e.linep = s; + PUSHIO(aword, dolp, quoted ? qstrchar : strchar); + return 0; +} + +/* + * Run the command in `...` and read its output. + */ + +static int grave(int quoted) +{ + char *cp; + int i; + int j; + int pf[2]; + static char child_cmd[LINELIM]; + char *src; + char *dest; + int count; + int ignore; + int ignore_once; + char *argument_list[4]; + struct wdblock *wb = NULL; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) &cp; +#endif + + for (cp = e.iop->argp->aword; *cp != '`'; cp++) + if (*cp == 0) { + err("no closing `"); + return 0; + } + + /* string copy with dollar expansion */ + src = e.iop->argp->aword; + dest = child_cmd; + count = 0; + ignore = 0; + ignore_once = 0; + while ((*src != '`') && (count < LINELIM)) { + if (*src == '\'') + ignore = !ignore; + if (*src == '\\') + ignore_once = 1; + if (*src == '$' && !ignore && !ignore_once) { + struct var *vp; + char var_name[LINELIM]; + char alt_value[LINELIM]; + int var_index = 0; + int alt_index = 0; + char operator = 0; + int braces = 0; + char *value; + + src++; + if (*src == '{') { + braces = 1; + src++; + } + + var_name[var_index++] = *src++; + while (isalnum(*src) || *src=='_') + var_name[var_index++] = *src++; + var_name[var_index] = 0; + + if (braces) { + switch (*src) { + case '}': + break; + case '-': + case '=': + case '+': + case '?': + operator = * src; + break; + default: + err("unclosed ${\n"); + return 0; + } + if (operator) { + src++; + while (*src && (*src != '}')) { + alt_value[alt_index++] = *src++; + } + alt_value[alt_index] = 0; + if (*src != '}') { + err("unclosed ${\n"); + return 0; + } + } + src++; + } + + if (isalpha(*var_name)) { + /* let subshell handle it instead */ + + char *namep = var_name; + + *dest++ = '$'; + if (braces) + *dest++ = '{'; + while (*namep) + *dest++ = *namep++; + if (operator) { + char *altp = alt_value; + *dest++ = operator; + while (*altp) + *dest++ = *altp++; + } + if (braces) + *dest++ = '}'; + + wb = addword(lookup(var_name)->name, wb); + } else { + /* expand */ + + vp = lookup(var_name); + if (vp->value != null) + value = (operator == '+') ? + alt_value : vp->value; + else if (operator == '?') { + err(alt_value); + return 0; + } else if (alt_index && (operator != '+')) { + value = alt_value; + if (operator == '=') + setval(vp, value); + } else + continue; + + while (*value && (count < LINELIM)) { + *dest++ = *value++; + count++; + } + } + } else { + *dest++ = *src++; + count++; + ignore_once = 0; + } + } + *dest = '\0'; + + if (openpipe(pf) < 0) + return 0; + + while ((i = vfork()) == -1 && errno == EAGAIN); + + DBGPRINTF3(("GRAVE: i is %p\n", io)); + + if (i < 0) { + closepipe(pf); + err((char *) bb_msg_memory_exhausted); + return 0; + } + if (i != 0) { + waitpid(i, NULL, 0); + e.iop->argp->aword = ++cp; + close(pf[1]); + PUSHIO(afile, remap(pf[0]), + (int (*)(struct ioarg *)) ((quoted) ? qgravechar : + gravechar)); + return 1; + } + /* allow trapped signals */ + /* XXX - Maybe this signal stuff should go as well? */ + for (j = 0; j <= _NSIG; j++) + if (ourtrap[j] && signal(j, SIG_IGN) != SIG_IGN) + signal(j, SIG_DFL); + + dup2(pf[1], 1); + closepipe(pf); + + argument_list[0] = (char *) DEFAULT_SHELL; + argument_list[1] = "-c"; + argument_list[2] = child_cmd; + argument_list[3] = 0; + + cp = rexecve(argument_list[0], argument_list, makenv(1, wb)); + prs(argument_list[0]); + prs(": "); + err(cp); + _exit(1); +} + + +static char *unquote(char *as) +{ + char *s; + + if ((s = as) != NULL) + while (*s) + *s++ &= ~QUOTE; + return as; +} + +/* -------- glob.c -------- */ + +/* + * glob + */ + +#define scopy(x) strsave((x), areanum) +#define BLKSIZ 512 +#define NDENT ((BLKSIZ+sizeof(struct dirent)-1)/sizeof(struct dirent)) + +static struct wdblock *cl, *nl; +static char spcl[] = "[?*"; + +static struct wdblock *glob(char *cp, struct wdblock *wb) +{ + int i; + char *pp; + + if (cp == 0) + return wb; + i = 0; + for (pp = cp; *pp; pp++) + if (any(*pp, spcl)) + i++; + else if (!any(*pp & ~QUOTE, spcl)) + *pp &= ~QUOTE; + if (i != 0) { + for (cl = addword(scopy(cp), (struct wdblock *) 0); anyspcl(cl); + cl = nl) { + nl = newword(cl->w_nword * 2); + for (i = 0; i < cl->w_nword; i++) { /* for each argument */ + for (pp = cl->w_words[i]; *pp; pp++) + if (any(*pp, spcl)) { + globname(cl->w_words[i], pp); + break; + } + if (*pp == '\0') + nl = addword(scopy(cl->w_words[i]), nl); + } + for (i = 0; i < cl->w_nword; i++) + DELETE(cl->w_words[i]); + DELETE(cl); + } + for (i = 0; i < cl->w_nword; i++) + unquote(cl->w_words[i]); + glob0((char *) cl->w_words, cl->w_nword, sizeof(char *), xstrcmp); + if (cl->w_nword) { + for (i = 0; i < cl->w_nword; i++) + wb = addword(cl->w_words[i], wb); + DELETE(cl); + return wb; + } + } + wb = addword(unquote(cp), wb); + return wb; +} + +static void globname(char *we, char *pp) +{ + char *np, *cp; + char *name, *gp, *dp; + int k; + DIR *dirp; + struct dirent *de; + char dname[NAME_MAX + 1]; + struct stat dbuf; + + for (np = we; np != pp; pp--) + if (pp[-1] == '/') + break; + for (dp = cp = space((int) (pp - np) + 3); np < pp;) + *cp++ = *np++; + *cp++ = '.'; + *cp = '\0'; + for (gp = cp = space(strlen(pp) + 1); *np && *np != '/';) + *cp++ = *np++; + *cp = '\0'; + dirp = opendir(dp); + if (dirp == 0) { + DELETE(dp); + DELETE(gp); + return; + } + dname[NAME_MAX] = '\0'; + while ((de = readdir(dirp)) != NULL) { + /* XXX Hmmm... What this could be? (abial) */ + /* + if (ent[j].d_ino == 0) + continue; + */ + strncpy(dname, de->d_name, NAME_MAX); + if (dname[0] == '.') + if (*gp != '.') + continue; + for (k = 0; k < NAME_MAX; k++) + if (any(dname[k], spcl)) + dname[k] |= QUOTE; + if (gmatch(dname, gp)) { + name = generate(we, pp, dname, np); + if (*np && !anys(np, spcl)) { + if (stat(name, &dbuf)) { + DELETE(name); + continue; + } + } + nl = addword(name, nl); + } + } + closedir(dirp); + DELETE(dp); + DELETE(gp); +} + +/* + * generate a pathname as below. + * start..end1 / middle end + * the slashes come for free + */ +static char *generate(char *start1, char *end1, char *middle, char *end) +{ + char *p; + char *op, *xp; + + p = op = + space((int) (end1 - start1) + strlen(middle) + strlen(end) + 2); + for (xp = start1; xp != end1;) + *op++ = *xp++; + for (xp = middle; (*op++ = *xp++) != '\0';); + op--; + for (xp = end; (*op++ = *xp++) != '\0';); + return p; +} + +static int anyspcl(struct wdblock *wb) +{ + int i; + char **wd; + + wd = wb->w_words; + for (i = 0; i < wb->w_nword; i++) + if (anys(spcl, *wd++)) + return 1; + return 0; +} + +static int xstrcmp(char *p1, char *p2) +{ + return strcmp(*(char **) p1, *(char **) p2); +} + +/* -------- word.c -------- */ + +static struct wdblock *newword(int nw) +{ + struct wdblock *wb; + + wb = (struct wdblock *) space(sizeof(*wb) + nw * sizeof(char *)); + wb->w_bsize = nw; + wb->w_nword = 0; + return wb; +} + +static struct wdblock *addword(char *wd, struct wdblock *wb) +{ + struct wdblock *wb2; + int nw; + + if (wb == NULL) + wb = newword(NSTART); + if ((nw = wb->w_nword) >= wb->w_bsize) { + wb2 = newword(nw * 2); + memcpy((char *) wb2->w_words, (char *) wb->w_words, + nw * sizeof(char *)); + wb2->w_nword = nw; + DELETE(wb); + wb = wb2; + } + wb->w_words[wb->w_nword++] = wd; + return wb; +} + +static +char **getwords(struct wdblock *wb) +{ + char **wd; + int nb; + + if (wb == NULL) + return NULL; + if (wb->w_nword == 0) { + DELETE(wb); + return NULL; + } + wd = (char **) space(nb = sizeof(*wd) * wb->w_nword); + memcpy((char *) wd, (char *) wb->w_words, nb); + DELETE(wb); /* perhaps should done by caller */ + return wd; +} + +static int (*func) (char *, char *); +static int globv; + +static void glob0(char *a0, unsigned a1, int a2, int (*a3) (char *, char *)) +{ + func = a3; + globv = a2; + glob1(a0, a0 + a1 * a2); +} + +static void glob1(char *base, char *lim) +{ + char *i, *j; + int v2; + char *lptr, *hptr; + int c; + unsigned n; + + + v2 = globv; + + top: + if ((n = (int) (lim - base)) <= v2) + return; + n = v2 * (n / (2 * v2)); + hptr = lptr = base + n; + i = base; + j = lim - v2; + for (;;) { + if (i < lptr) { + if ((c = (*func) (i, lptr)) == 0) { + glob2(i, lptr -= v2); + continue; + } + if (c < 0) { + i += v2; + continue; + } + } + + begin: + if (j > hptr) { + if ((c = (*func) (hptr, j)) == 0) { + glob2(hptr += v2, j); + goto begin; + } + if (c > 0) { + if (i == lptr) { + glob3(i, hptr += v2, j); + i = lptr += v2; + goto begin; + } + glob2(i, j); + j -= v2; + i += v2; + continue; + } + j -= v2; + goto begin; + } + + + if (i == lptr) { + if (lptr - base >= lim - hptr) { + glob1(hptr + v2, lim); + lim = lptr; + } else { + glob1(base, lptr); + base = hptr + v2; + } + goto top; + } + + + glob3(j, lptr -= v2, i); + j = hptr -= v2; + } +} + +static void glob2(char *i, char *j) +{ + char *index1, *index2, c; + int m; + + m = globv; + index1 = i; + index2 = j; + do { + c = *index1; + *index1++ = *index2; + *index2++ = c; + } while (--m); +} + +static void glob3(char *i, char *j, char *k) +{ + char *index1, *index2, *index3; + int c; + int m; + + m = globv; + index1 = i; + index2 = j; + index3 = k; + do { + c = *index1; + *index1++ = *index3; + *index3++ = *index2; + *index2++ = c; + } while (--m); +} + +/* -------- io.c -------- */ + +/* + * shell IO + */ + +static int my_getc(int ec) +{ + int c; + + if (e.linep > elinep) { + while ((c = readc()) != '\n' && c); + err("input line too long"); + gflg++; + return c; + } + c = readc(); + if ((ec != '\'') && (ec != '`') && (e.iop->task != XGRAVE)) { + if (c == '\\') { + c = readc(); + if (c == '\n' && ec != '\"') + return my_getc(ec); + c |= QUOTE; + } + } + return c; +} + +static void unget(int c) +{ + if (e.iop >= e.iobase) + e.iop->peekc = c; +} + +static int eofc(void) +{ + return e.iop < e.iobase || (e.iop->peekc == 0 && e.iop->prev == 0); +} + +static int readc(void) +{ + int c; + + RCPRINTF(("READC: e.iop %p, e.iobase %p\n", e.iop, e.iobase)); + + for (; e.iop >= e.iobase; e.iop--) { + RCPRINTF(("READC: e.iop %p, peekc 0x%x\n", e.iop, e.iop->peekc)); + if ((c = e.iop->peekc) != '\0') { + e.iop->peekc = 0; + return c; + } else { + if (e.iop->prev != 0) { + if ((c = (*e.iop->iofn) (e.iop->argp, e.iop)) != '\0') { + if (c == -1) { + e.iop++; + continue; + } + if (e.iop == iostack) + ioecho(c); + return (e.iop->prev = c); + } else if (e.iop->task == XIO && e.iop->prev != '\n') { + e.iop->prev = 0; + if (e.iop == iostack) + ioecho('\n'); + return '\n'; + } + } + if (e.iop->task == XIO) { + if (multiline) { + return e.iop->prev = 0; + } + if (interactive && e.iop == iostack + 1) { +#ifdef CONFIG_FEATURE_COMMAND_EDITING + current_prompt = prompt->value; +#else + prs(prompt->value); +#endif + } + } + } + + } /* FOR */ + + if (e.iop >= iostack) { + RCPRINTF(("READC: return 0, e.iop %p\n", e.iop)); + return 0; + } + + DBGPRINTF(("READC: leave()...\n")); + leave(); + + /* NOTREACHED */ + return 0; +} + +static void ioecho(char c) +{ + if (flag['v']) + write(2, &c, sizeof c); +} + + +static void pushio(struct ioarg *argp, int (*fn) (struct ioarg *)) +{ + DBGPRINTF(("PUSHIO: argp %p, argp->afid 0x%x, e.iop %p\n", argp, + argp->afid, e.iop)); + + /* Set env ptr for io source to next array spot and check for array overflow */ + if (++e.iop >= &iostack[NPUSH]) { + e.iop--; + err("Shell input nested too deeply"); + gflg++; + return; + } + + /* We did not overflow the NPUSH array spots so setup data structs */ + + e.iop->iofn = (int (*)(struct ioarg *, struct io *)) fn; /* Store data source func ptr */ + + if (argp->afid != AFID_NOBUF) + e.iop->argp = argp; + else { + + e.iop->argp = ioargstack + (e.iop - iostack); /* MAL - index into stack */ + *e.iop->argp = *argp; /* copy data from temp area into stack spot */ + + /* MAL - mainbuf is for 1st data source (command line?) and all nested use a single shared buffer? */ + + if (e.iop == &iostack[0]) + e.iop->argp->afbuf = &mainbuf; + else + e.iop->argp->afbuf = &sharedbuf; + + /* MAL - if not a termimal AND (commandline OR readable file) then give it a buffer id? */ + /* This line appears to be active when running scripts from command line */ + if ((isatty(e.iop->argp->afile) == 0) + && (e.iop == &iostack[0] + || lseek(e.iop->argp->afile, 0L, SEEK_CUR) != -1)) { + if (++bufid == AFID_NOBUF) /* counter rollover check, AFID_NOBUF = 11111111 */ + bufid = AFID_ID; /* AFID_ID = 0 */ + + e.iop->argp->afid = bufid; /* assign buffer id */ + } + + DBGPRINTF(("PUSHIO: iostack %p, e.iop %p, afbuf %p\n", + iostack, e.iop, e.iop->argp->afbuf)); + DBGPRINTF(("PUSHIO: mbuf %p, sbuf %p, bid %d, e.iop %p\n", + &mainbuf, &sharedbuf, bufid, e.iop)); + + } + + e.iop->prev = ~'\n'; + e.iop->peekc = 0; + e.iop->xchar = 0; + e.iop->nlcount = 0; + + if (fn == filechar || fn == linechar) + e.iop->task = XIO; + else if (fn == (int (*)(struct ioarg *)) gravechar + || fn == (int (*)(struct ioarg *)) qgravechar) + e.iop->task = XGRAVE; + else + e.iop->task = XOTHER; + + return; +} + +static struct io *setbase(struct io *ip) +{ + struct io *xp; + + xp = e.iobase; + e.iobase = ip; + return xp; +} + +/* + * Input generating functions + */ + +/* + * Produce the characters of a string, then a newline, then EOF. + */ +static int nlchar(struct ioarg *ap) +{ + int c; + + if (ap->aword == NULL) + return 0; + if ((c = *ap->aword++) == 0) { + ap->aword = NULL; + return '\n'; + } + return c; +} + +/* + * Given a list of words, produce the characters + * in them, with a space after each word. + */ +static int wdchar(struct ioarg *ap) +{ + char c; + char **wl; + + if ((wl = ap->awordlist) == NULL) + return 0; + if (*wl != NULL) { + if ((c = *(*wl)++) != 0) + return c & 0177; + ap->awordlist++; + return ' '; + } + ap->awordlist = NULL; + return '\n'; +} + +/* + * Return the characters of a list of words, + * producing a space between them. + */ +static int dolchar(struct ioarg *ap) +{ + char *wp; + + if ((wp = *ap->awordlist++) != NULL) { + PUSHIO(aword, wp, *ap->awordlist == NULL ? strchar : xxchar); + return -1; + } + return 0; +} + +static int xxchar(struct ioarg *ap) +{ + int c; + + if (ap->aword == NULL) + return 0; + if ((c = *ap->aword++) == '\0') { + ap->aword = NULL; + return ' '; + } + return c; +} + +/* + * Produce the characters from a single word (string). + */ +static int strchar(struct ioarg *ap) +{ + int c; + + if (ap->aword == NULL || (c = *ap->aword++) == 0) + return 0; + return c; +} + +/* + * Produce quoted characters from a single word (string). + */ +static int qstrchar(struct ioarg *ap) +{ + int c; + + if (ap->aword == NULL || (c = *ap->aword++) == 0) + return 0; + return c | QUOTE; +} + +/* + * Return the characters from a file. + */ +static int filechar(struct ioarg *ap) +{ + int i; + char c; + struct iobuf *bp = ap->afbuf; + + if (ap->afid != AFID_NOBUF) { + if ((i = ap->afid != bp->id) || bp->bufp == bp->ebufp) { + + if (i) + lseek(ap->afile, ap->afpos, SEEK_SET); + + i = safe_read(ap->afile, bp->buf, sizeof(bp->buf)); + + if (i <= 0) { + closef(ap->afile); + return 0; + } + + bp->id = ap->afid; + bp->ebufp = (bp->bufp = bp->buf) + i; + } + + ap->afpos++; + return *bp->bufp++ & 0177; + } +#ifdef CONFIG_FEATURE_COMMAND_EDITING + if (interactive && isatty(ap->afile)) { + static char mycommand[BUFSIZ]; + static int position = 0, size = 0; + + while (size == 0 || position >= size) { + cmdedit_read_input(current_prompt, mycommand); + size = strlen(mycommand); + position = 0; + } + c = mycommand[position]; + position++; + return c; + } else +#endif + + { + i = safe_read(ap->afile, &c, sizeof(c)); + return i == sizeof(c) ? (c & 0x7f) : (closef(ap->afile), 0); + } +} + +/* + * Return the characters from a here temp file. + */ +static int herechar(struct ioarg *ap) +{ + char c; + + + if (read(ap->afile, &c, sizeof(c)) != sizeof(c)) { + close(ap->afile); + c = 0; + } + return c; + +} + +/* + * Return the characters produced by a process (`...`). + * Quote them if required, and remove any trailing newline characters. + */ +static int gravechar(struct ioarg *ap, struct io *iop) +{ + int c; + + if ((c = qgravechar(ap, iop) & ~QUOTE) == '\n') + c = ' '; + return c; +} + +static int qgravechar(struct ioarg *ap, struct io *iop) +{ + int c; + + DBGPRINTF3(("QGRAVECHAR: enter, ap=%p, iop=%p\n", ap, iop)); + + if (iop->xchar) { + if (iop->nlcount) { + iop->nlcount--; + return '\n' | QUOTE; + } + c = iop->xchar; + iop->xchar = 0; + } else if ((c = filechar(ap)) == '\n') { + iop->nlcount = 1; + while ((c = filechar(ap)) == '\n') + iop->nlcount++; + iop->xchar = c; + if (c == 0) + return c; + iop->nlcount--; + c = '\n'; + } + return c != 0 ? c | QUOTE : 0; +} + +/* + * Return a single command (usually the first line) from a file. + */ +static int linechar(struct ioarg *ap) +{ + int c; + + if ((c = filechar(ap)) == '\n') { + if (!multiline) { + closef(ap->afile); + ap->afile = -1; /* illegal value */ + } + } + return c; +} + +static void prs(const char *s) +{ + if (*s) + write(2, s, strlen(s)); +} + +static void prn(unsigned u) +{ + prs(itoa(u)); +} + +static void closef(int i) +{ + if (i > 2) + close(i); +} + +static void closeall(void) +{ + int u; + + for (u = NUFILE; u < NOFILE;) + close(u++); +} + + +/* + * remap fd into Shell's fd space + */ +static int remap(int fd) +{ + int i; + int map[NOFILE]; + int newfd; + + + DBGPRINTF(("REMAP: fd=%d, e.iofd=%d\n", fd, e.iofd)); + + if (fd < e.iofd) { + for (i = 0; i < NOFILE; i++) + map[i] = 0; + + do { + map[fd] = 1; + newfd = dup(fd); + fd = newfd; + } while (fd >= 0 && fd < e.iofd); + + for (i = 0; i < NOFILE; i++) + if (map[i]) + close(i); + + if (fd < 0) + err("too many files open in shell"); + } + + return fd; +} + +static int openpipe(int *pv) +{ + int i; + + if ((i = pipe(pv)) < 0) + err("can't create pipe - try again"); + return i; +} + +static void closepipe(int *pv) +{ + if (pv != NULL) { + close(*pv++); + close(*pv); + } +} + +/* -------- here.c -------- */ + +/* + * here documents + */ + +static void markhere(char *s, struct ioword *iop) +{ + struct here *h, *lh; + + DBGPRINTF7(("MARKHERE: enter, s=%p\n", s)); + + h = (struct here *) space(sizeof(struct here)); + if (h == 0) + return; + + h->h_tag = evalstr(s, DOSUB); + if (h->h_tag == 0) + return; + + h->h_iop = iop; + iop->io_name = 0; + h->h_next = NULL; + if (inhere == 0) + inhere = h; + else + for (lh = inhere; lh != NULL; lh = lh->h_next) + if (lh->h_next == 0) { + lh->h_next = h; + break; + } + iop->io_flag |= IOHERE | IOXHERE; + for (s = h->h_tag; *s; s++) + if (*s & QUOTE) { + iop->io_flag &= ~IOXHERE; + *s &= ~QUOTE; + } + h->h_dosub = iop->io_flag & IOXHERE; +} + +static void gethere(void) +{ + struct here *h, *hp; + + DBGPRINTF7(("GETHERE: enter...\n")); + + /* Scan here files first leaving inhere list in place */ + for (hp = h = inhere; h != NULL; hp = h, h = h->h_next) + readhere(&h->h_iop->io_name, h->h_tag, h->h_dosub ? 0 : '\''); + + /* Make inhere list active - keep list intact for scraphere */ + if (hp != NULL) { + hp->h_next = acthere; + acthere = inhere; + inhere = NULL; + } +} + +static void readhere(char **name, char *s, int ec) +{ + int tf; + char tname[30] = ".msh_XXXXXX"; + int c; + jmp_buf ev; + char myline[LINELIM + 1]; + char *thenext; + + DBGPRINTF7(("READHERE: enter, name=%p, s=%p\n", name, s)); + + tf = mkstemp(tname); + if (tf < 0) + return; + + *name = strsave(tname, areanum); + if (newenv(setjmp(errpt = ev)) != 0) + unlink(tname); + else { + pushio(e.iop->argp, (int (*)(struct ioarg *)) e.iop->iofn); + e.iobase = e.iop; + for (;;) { + if (interactive && e.iop <= iostack) { +#ifdef CONFIG_FEATURE_COMMAND_EDITING + current_prompt = cprompt->value; +#else + prs(cprompt->value); +#endif + } + thenext = myline; + while ((c = my_getc(ec)) != '\n' && c) { + if (ec == '\'') + c &= ~QUOTE; + if (thenext >= &myline[LINELIM]) { + c = 0; + break; + } + *thenext++ = c; + } + *thenext = 0; + if (strcmp(s, myline) == 0 || c == 0) + break; + *thenext++ = '\n'; + write(tf, myline, (int) (thenext - myline)); + } + if (c == 0) { + prs("here document `"); + prs(s); + err("' unclosed"); + } + quitenv(); + } + close(tf); +} + +/* + * open here temp file. + * if unquoted here, expand here temp file into second temp file. + */ +static int herein(char *hname, int xdoll) +{ + int hf; + int tf; + +#if __GNUC__ + /* Avoid longjmp clobbering */ + (void) &tf; +#endif + if (hname == NULL) + return -1; + + DBGPRINTF7(("HEREIN: hname is %s, xdoll=%d\n", hname, xdoll)); + + hf = open(hname, 0); + if (hf < 0) + return -1; + + if (xdoll) { + char c; + char tname[30] = ".msh_XXXXXX"; + jmp_buf ev; + + tf = mkstemp(tname); + if (tf < 0) + return -1; + if (newenv(setjmp(errpt = ev)) == 0) { + PUSHIO(afile, hf, herechar); + setbase(e.iop); + while ((c = subgetc(0, 0)) != 0) { + c &= ~QUOTE; + write(tf, &c, sizeof c); + } + quitenv(); + } else + unlink(tname); + close(tf); + tf = open(tname, 0); + unlink(tname); + return tf; + } else + return hf; +} + +static void scraphere(void) +{ + struct here *h; + + DBGPRINTF7(("SCRAPHERE: enter...\n")); + + for (h = inhere; h != NULL; h = h->h_next) { + if (h->h_iop && h->h_iop->io_name) + unlink(h->h_iop->io_name); + } + inhere = NULL; +} + +/* unlink here temp files before a freearea(area) */ +static void freehere(int area) +{ + struct here *h, *hl; + + DBGPRINTF6(("FREEHERE: enter, area=%d\n", area)); + + hl = NULL; + for (h = acthere; h != NULL; h = h->h_next) + if (getarea((char *) h) >= area) { + if (h->h_iop->io_name != NULL) + unlink(h->h_iop->io_name); + if (hl == NULL) + acthere = h->h_next; + else + hl->h_next = h->h_next; + } else + hl = h; +} + + + +/* + * Copyright (c) 1987,1997, Prentice Hall + * All rights reserved. + * + * Redistribution and use of the MINIX operating system in source and + * binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * Neither the name of Prentice Hall nor the names of the software + * authors or contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS, AUTHORS, AND + * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL PRENTICE HALL OR ANY AUTHORS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ diff --git a/sysklogd/Config.in b/sysklogd/Config.in new file mode 100644 index 000000000..9875b3c12 --- /dev/null +++ b/sysklogd/Config.in @@ -0,0 +1,111 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "System Logging Utilities" + +config SYSLOGD + bool "syslogd" + default n + help + The syslogd utility is used to record logs of all the + significant events that occur on a system. Every + message that is logged records the date and time of the + event, and will generally also record the name of the + application that generated the message. When used in + conjunction with klogd, messages from the Linux kernel + can also be recorded. This is terribly useful, + especially for finding what happened when something goes + wrong. And something almost always will go wrong if + you wait long enough.... + +config FEATURE_ROTATE_LOGFILE + bool "Rotate message files" + default n + depends on SYSLOGD + help + This enables syslogd to rotate the message files + on his own. No need to use an external rotatescript. + +config FEATURE_REMOTE_LOG + bool "Remote Log support" + default n + depends on SYSLOGD + help + When you enable this feature, the syslogd utility can + be used to send system log messages to another system + connected via a network. This allows the remote + machine to log all the system messages, which can be + terribly useful for reducing the number of serial + cables you use. It can also be a very good security + measure to prevent system logs from being tampered with + by an intruder. + +config FEATURE_IPC_SYSLOG + bool "Circular Buffer support" + default n + depends on SYSLOGD + help + When you enable this feature, the syslogd utility will + use a circular buffer to record system log messages. + When the buffer is filled it will continue to overwrite + the oldest messages. This can be very useful for + systems with little or no permanent storage, since + otherwise system logs can eventually fill up your + entire filesystem, which may cause your system to + break badly. + +config FEATURE_IPC_SYSLOG_BUFFER_SIZE + int " Circular buffer size in Kbytes (minimum 4KB)" + default 16 + depends on FEATURE_IPC_SYSLOG + help + This option sets the size of the circular buffer + used to record system log messages. + +config LOGREAD + bool "logread" + default y + depends on FEATURE_IPC_SYSLOG + help + If you enabled Circular Buffer support, you almost + certainly want to enable this feature as well. This + utility will allow you to read the messages that are + stored in the syslogd circular buffer. + +config FEATURE_LOGREAD_REDUCED_LOCKING + bool "logread double buffering" + default n + depends on LOGREAD + help + 'logread' ouput to slow serial terminals can have + side effects on syslog because of the semaphore. + This option make logread to double buffer copy + from circular buffer, minimizing semaphore + contention at some minor memory expense. + +config KLOGD + bool "klogd" + default n + depends on SYSLOGD + select FEATURE_SYSLOG + help + klogd is a utility which intercepts and logs all + messages from the Linux kernel and sends the messages + out to the 'syslogd' utility so they can be logged. If + you wish to record the messages produced by the kernel, + you should enable this option. + +config LOGGER + bool "logger" + default n + select FEATURE_SYSLOG + help + The logger utility allows you to send arbitrary text + messages to the system log (i.e. the 'syslogd' utility) so + they can be logged. This is generally used to help locate + problems that occur within programs and scripts. + +endmenu + diff --git a/sysklogd/Kbuild b/sysklogd/Kbuild new file mode 100644 index 000000000..0d5b2b929 --- /dev/null +++ b/sysklogd/Kbuild @@ -0,0 +1,11 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen <andersen@codepoet.org> +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_KLOGD) += klogd.o +lib-$(CONFIG_LOGGER) += logger.o +lib-$(CONFIG_LOGREAD) += logread.o +lib-$(CONFIG_SYSLOGD) += syslogd.o diff --git a/sysklogd/klogd.c b/sysklogd/klogd.c new file mode 100644 index 000000000..f735d9fc1 --- /dev/null +++ b/sysklogd/klogd.c @@ -0,0 +1,123 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini klogd implementation for busybox + * + * Copyright (C) 2001 by Gennady Feldman <gfeldman@gena01.com>. + * Changes: Made this a standalone busybox module which uses standalone + * syslog() client interface. + * + * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> + * + * Copyright (C) 2000 by Karl M. Hegbloom <karlheg@debian.org> + * + * "circular buffer" Copyright (C) 2000 by Gennady Feldman <gfeldman@gena01.com> + * + * Maintainer: Gennady Feldman <gfeldman@gena01.com> as of Mar 12, 2001 + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include <sys/syslog.h> +#include <sys/klog.h> + +static void klogd_signal(int sig ATTRIBUTE_UNUSED) +{ + klogctl(7, NULL, 0); + klogctl(0, 0, 0); + /* logMessage(0, "Kernel log daemon exiting."); */ + syslog(LOG_NOTICE, "Kernel log daemon exiting."); + exit(EXIT_SUCCESS); +} + +#define OPT_LEVEL 1 +#define OPT_FOREGROUND 2 + +#define KLOGD_LOGBUF_SIZE 4096 + +int klogd_main(int argc, char **argv) +{ + RESERVE_CONFIG_BUFFER(log_buffer, KLOGD_LOGBUF_SIZE); + int console_log_level = -1; + int priority = LOG_INFO; + int i, n, lastc; + char *start; + + { + unsigned opt; + + /* do normal option parsing */ + opt = getopt32(argc, argv, "c:n", &start); + + if (opt & OPT_LEVEL) { + /* Valid levels are between 1 and 8 */ + console_log_level = xatoul_range(start, 1, 8); + } + + if (!(opt & OPT_FOREGROUND)) { +#ifdef BB_NOMMU + vfork_daemon_rexec(0, 1, argc, argv, "-n"); +#else + xdaemon(0, 1); +#endif + } + } + + openlog("kernel", 0, LOG_KERN); + + /* Set up sig handlers */ + signal(SIGINT, klogd_signal); + signal(SIGKILL, klogd_signal); + signal(SIGTERM, klogd_signal); + signal(SIGHUP, SIG_IGN); + + /* "Open the log. Currently a NOP." */ + klogctl(1, NULL, 0); + + /* Set level of kernel console messaging.. */ + if (console_log_level != -1) + klogctl(8, NULL, console_log_level); + + syslog(LOG_NOTICE, "klogd started: %s", BB_BANNER); + + while (1) { + /* Use kernel syscalls */ + memset(log_buffer, '\0', KLOGD_LOGBUF_SIZE); + n = klogctl(2, log_buffer, KLOGD_LOGBUF_SIZE); + if (n < 0) { + if (errno == EINTR) + continue; + syslog(LOG_ERR, "klogd: Error from sys_sycall: %d - %m.\n", + errno); + exit(EXIT_FAILURE); + } + + /* klogctl buffer parsing modelled after code in dmesg.c */ + start = &log_buffer[0]; + lastc = '\0'; + for (i = 0; i < n; i++) { + if (lastc == '\0' && log_buffer[i] == '<') { + priority = 0; + i++; + while (log_buffer[i] >= '0' && log_buffer[i] <= '9') { + priority = priority * 10 + (log_buffer[i] - '0'); + i++; + } + if (log_buffer[i] == '>') + i++; + start = &log_buffer[i]; + } + if (log_buffer[i] == '\n') { + log_buffer[i] = '\0'; /* zero terminate this message */ + syslog(priority, "%s", start); + start = &log_buffer[i + 1]; + priority = LOG_INFO; + } + lastc = log_buffer[i]; + } + } + if (ENABLE_FEATURE_CLEAN_UP) + RELEASE_CONFIG_BUFFER(log_buffer); + + return EXIT_SUCCESS; +} diff --git a/sysklogd/logger.c b/sysklogd/logger.c new file mode 100644 index 000000000..8901bd79f --- /dev/null +++ b/sysklogd/logger.c @@ -0,0 +1,179 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini logger implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include <fcntl.h> +#include <ctype.h> +#include <string.h> +#include <stdlib.h> + +#if !defined CONFIG_SYSLOGD + +#define SYSLOG_NAMES +#include <sys/syslog.h> + +#else +#include <sys/syslog.h> +# ifndef __dietlibc__ + /* We have to do this since the header file defines static + * structures. Argh.... bad libc, bad, bad... + */ + typedef struct _code { + char *c_name; + int c_val; + } CODE; + extern CODE prioritynames[]; + extern CODE facilitynames[]; +# endif +#endif + +/* Decode a symbolic name to a numeric value + * this function is based on code + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Original copyright notice is retained at the end of this file. + */ +static int decode(char *name, CODE * codetab) +{ + CODE *c; + + if (isdigit(*name)) + return atoi(name); + for (c = codetab; c->c_name; c++) { + if (!strcasecmp(name, c->c_name)) { + return c->c_val; + } + } + + return -1; +} + +/* Decode a symbolic name to a numeric value + * this function is based on code + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Original copyright notice is retained at the end of this file. + */ +static int pencode(char *s) +{ + char *save; + int lev, fac = LOG_USER; + + for (save = s; *s && *s != '.'; ++s); + if (*s) { + *s = '\0'; + fac = decode(save, facilitynames); + if (fac < 0) + bb_error_msg_and_die("unknown facility name: %s", save); + *s++ = '.'; + } else { + s = save; + } + lev = decode(s, prioritynames); + if (lev < 0) + bb_error_msg_and_die("unknown priority name: %s", save); + return ((lev & LOG_PRIMASK) | (fac & LOG_FACMASK)); +} + + +int logger_main(int argc, char **argv) +{ + unsigned opt; + char *opt_p, *opt_t; + int pri = LOG_USER | LOG_NOTICE; + int option = 0; + int c, i; + char buf[1024], name[128]; + + /* Fill out the name string early (may be overwritten later) */ + bb_getpwuid(name, geteuid(), sizeof(name)); + + /* Parse any options */ + opt = getopt32(argc, argv, "p:st:", &opt_p, &opt_t); + if (opt & 0x1) pri = pencode(opt_p); // -p + if (opt & 0x2) option |= LOG_PERROR; // -s + if (opt & 0x4) safe_strncpy(name, opt_t, sizeof(name)); // -t + + openlog(name, option, 0); + if (optind == argc) { + do { + /* read from stdin */ + i = 0; + while ((c = getc(stdin)) != EOF && c != '\n' && + i < (sizeof(buf)-1)) { + buf[i++] = c; + } + if (i > 0) { + buf[i++] = '\0'; + syslog(pri, "%s", buf); + } + } while (c != EOF); + } else { + char *message = NULL; + int len = argc - optind; /* for the space between the args + and '\0' */ + opt = len; + argv += optind; + for (i = 0; i < opt; i++) { + len += strlen(*argv); + message = xrealloc(message, len); + if(!i) + message[0] = '\0'; + else + strcat(message, " "); + strcat(message, *argv); + argv++; + } + syslog(pri, "%s", message); + } + + closelog(); + return EXIT_SUCCESS; +} + + +/*- + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * This is the original license statement for the decode and pencode functions. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. <BSD Advertising Clause omitted per the July 22, 1999 licensing change + * ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change> + * + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ diff --git a/sysklogd/logread.c b/sysklogd/logread.c new file mode 100644 index 000000000..9cc6561a4 --- /dev/null +++ b/sysklogd/logread.c @@ -0,0 +1,171 @@ +/* vi: set sw=4 ts=4: */ +/* + * circular buffer syslog implementation for busybox + * + * Copyright (C) 2000 by Gennady Feldman <gfeldman@gena01.com> + * + * Maintainer: Gennady Feldman <gfeldman@gena01.com> as of Mar 12, 2001 + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + + +#include "busybox.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ipc.h> +#include <sys/types.h> +#include <sys/sem.h> +#include <sys/shm.h> +#include <signal.h> +#include <setjmp.h> +#include <unistd.h> + +static const long KEY_ID = 0x414e4547; /*"GENA"*/ + +static struct shbuf_ds { + int size; // size of data written + int head; // start of message list + int tail; // end of message list + char data[1]; // data/messages +} *buf = NULL; // shared memory pointer + + +// Semaphore operation structures +static struct sembuf SMrup[1] = {{0, -1, IPC_NOWAIT | SEM_UNDO}}; // set SMrup +static struct sembuf SMrdn[2] = {{1, 0}, {0, +1, SEM_UNDO}}; // set SMrdn + +static int log_shmid = -1; // ipc shared memory id +static int log_semid = -1; // ipc semaphore id +static jmp_buf jmp_env; + +static void error_exit(const char *str); +static void interrupted(int sig); + +/* + * sem_up - up()'s a semaphore. + */ +static void sem_up(int semid) +{ + if ( semop(semid, SMrup, 1) == -1 ) + error_exit("semop[SMrup]"); +} + +/* + * sem_down - down()'s a semaphore + */ +static void sem_down(int semid) +{ + if ( semop(semid, SMrdn, 2) == -1 ) + error_exit("semop[SMrdn]"); +} + +int logread_main(int argc, char **argv) +{ + int i; + int follow=0; + + if (argc == 2 && argv[1][0]=='-' && argv[1][1]=='f') { + follow = 1; + } else { + /* no options, no getopt */ + if (argc > 1) + bb_show_usage(); + } + + // handle interrupt signal + if (setjmp(jmp_env)) goto output_end; + + // attempt to redefine ^C signal + signal(SIGINT, interrupted); + + if ( (log_shmid = shmget(KEY_ID, 0, 0)) == -1) + error_exit("Can't find circular buffer"); + + // Attach shared memory to our char* + if ( (buf = shmat(log_shmid, NULL, SHM_RDONLY)) == NULL) + error_exit("Can't get access to circular buffer from syslogd"); + + if ( (log_semid = semget(KEY_ID, 0, 0)) == -1) + error_exit("Can't get access to semaphore(s) for circular buffer from syslogd"); + + // Suppose atomic memory move + i = follow ? buf->tail : buf->head; + + do { +#ifdef CONFIG_FEATURE_LOGREAD_REDUCED_LOCKING + char *buf_data; + int log_len,j; +#endif + + sem_down(log_semid); + + //printf("head: %i tail: %i size: %i\n",buf->head,buf->tail,buf->size); + if (buf->head == buf->tail || i==buf->tail) { + if (follow) { + sem_up(log_semid); + sleep(1); /* TODO: replace me with a sleep_on */ + continue; + } else { + printf("<empty syslog>\n"); + } + } + + // Read Memory +#ifdef CONFIG_FEATURE_LOGREAD_REDUCED_LOCKING + log_len = buf->tail - i; + if (log_len < 0) + log_len += buf->size; + buf_data = (char *)xmalloc(log_len); + + if (buf->tail < i) { + memcpy(buf_data, buf->data+i, buf->size-i); + memcpy(buf_data+buf->size-i, buf->data, buf->tail); + } else { + memcpy(buf_data, buf->data+i, buf->tail-i); + } + i = buf->tail; + +#else + while ( i != buf->tail) { + printf("%s", buf->data+i); + i+= strlen(buf->data+i) + 1; + if (i >= buf->size ) + i=0; + } +#endif + // release the lock on the log chain + sem_up(log_semid); + +#ifdef CONFIG_FEATURE_LOGREAD_REDUCED_LOCKING + for (j=0; j < log_len; j+=strlen(buf_data+j)+1) { + printf("%s", buf_data+j); + if (follow) + fflush(stdout); + } + free(buf_data); +#endif + fflush(stdout); + } while (follow); + +output_end: + if (log_shmid != -1) + shmdt(buf); + + return EXIT_SUCCESS; +} + +static void interrupted(int sig){ + signal(SIGINT, SIG_IGN); + longjmp(jmp_env, 1); +} + +static void error_exit(const char *str){ + bb_perror_msg(str); + //release all acquired resources + if (log_shmid != -1) + shmdt(buf); + + exit(1); +} diff --git a/sysklogd/syslogd.c b/sysklogd/syslogd.c new file mode 100644 index 000000000..453cbda35 --- /dev/null +++ b/sysklogd/syslogd.c @@ -0,0 +1,643 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini syslogd implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> + * + * Copyright (C) 2000 by Karl M. Hegbloom <karlheg@debian.org> + * + * "circular buffer" Copyright (C) 2001 by Gennady Feldman <gfeldman@gena01.com> + * + * Maintainer: Gennady Feldman <gfeldman@gena01.com> as of Mar 12, 2001 + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include <paths.h> +#include <stdbool.h> +#include <sys/un.h> + +/* SYSLOG_NAMES defined to pull some extra junk from syslog.h */ +#define SYSLOG_NAMES +#include <sys/syslog.h> +#include <sys/uio.h> + +/* Path to the unix socket */ +static char lfile[MAXPATHLEN]; + +/* Path for the file where all log messages are written */ +static const char *logFilePath = "/var/log/messages"; + +#ifdef CONFIG_FEATURE_ROTATE_LOGFILE +/* max size of message file before being rotated */ +static int logFileSize = 200 * 1024; + +/* number of rotated message files */ +static int logFileRotate = 1; +#endif + +/* interval between marks in seconds */ +static int MarkInterval = 20 * 60; + +/* level of messages to be locally logged */ +static int logLevel = 8; + +/* localhost's name */ +static char LocalHostName[64]; + +#ifdef CONFIG_FEATURE_REMOTE_LOG +#include <netinet/in.h> +/* udp socket for logging to remote host */ +static int remotefd = -1; +static struct sockaddr_in remoteaddr; + +#endif + +/* options */ +/* Correct regardless of combination of CONFIG_xxx */ +enum { + OPTBIT_mark = 0, // -m + OPTBIT_nofork, // -n + OPTBIT_outfile, // -O + OPTBIT_loglevel, // -l + OPTBIT_small, // -S + USE_FEATURE_ROTATE_LOGFILE(OPTBIT_filesize ,) // -s + USE_FEATURE_ROTATE_LOGFILE(OPTBIT_rotatecnt ,) // -b + USE_FEATURE_REMOTE_LOG( OPTBIT_remote ,) // -R + USE_FEATURE_REMOTE_LOG( OPTBIT_localtoo ,) // -L + USE_FEATURE_IPC_SYSLOG( OPTBIT_circularlog,) // -C + + OPT_mark = 1 << OPTBIT_mark , + OPT_nofork = 1 << OPTBIT_nofork , + OPT_outfile = 1 << OPTBIT_outfile , + OPT_loglevel = 1 << OPTBIT_loglevel, + OPT_small = 1 << OPTBIT_small , + OPT_filesize = USE_FEATURE_ROTATE_LOGFILE((1 << OPTBIT_filesize )) + 0, + OPT_rotatecnt = USE_FEATURE_ROTATE_LOGFILE((1 << OPTBIT_rotatecnt )) + 0, + OPT_remotelog = USE_FEATURE_REMOTE_LOG( (1 << OPTBIT_remote )) + 0, + OPT_locallog = USE_FEATURE_REMOTE_LOG( (1 << OPTBIT_localtoo )) + 0, + OPT_circularlog = USE_FEATURE_IPC_SYSLOG( (1 << OPTBIT_circularlog)) + 0, +}; +#define OPTION_STR "m:nO:l:S" \ + USE_FEATURE_ROTATE_LOGFILE("s:" ) \ + USE_FEATURE_ROTATE_LOGFILE("b:" ) \ + USE_FEATURE_REMOTE_LOG( "R:" ) \ + USE_FEATURE_REMOTE_LOG( "L" ) \ + USE_FEATURE_IPC_SYSLOG( "C::") +#define OPTION_DECL *opt_m, *opt_l \ + USE_FEATURE_ROTATE_LOGFILE(,*opt_s) \ + USE_FEATURE_ROTATE_LOGFILE(,*opt_b) \ + USE_FEATURE_REMOTE_LOG( ,*opt_R) \ + USE_FEATURE_IPC_SYSLOG( ,*opt_C = NULL) +#define OPTION_PARAM &opt_m, &logFilePath, &opt_l \ + USE_FEATURE_ROTATE_LOGFILE(,&opt_s) \ + USE_FEATURE_ROTATE_LOGFILE(,&opt_b) \ + USE_FEATURE_REMOTE_LOG( ,&opt_R) \ + USE_FEATURE_IPC_SYSLOG( ,&opt_C) + +#define MAXLINE 1024 /* maximum line length */ + +/* circular buffer variables/structures */ +#ifdef CONFIG_FEATURE_IPC_SYSLOG + +#if CONFIG_FEATURE_IPC_SYSLOG_BUFFER_SIZE < 4 +#error Sorry, you must set the syslogd buffer size to at least 4KB. +#error Please check CONFIG_FEATURE_IPC_SYSLOG_BUFFER_SIZE +#endif + +#include <sys/ipc.h> +#include <sys/sem.h> +#include <sys/shm.h> + +/* our shared key */ +#define KEY_ID ((long)0x414e4547) /* "GENA" */ + +// Semaphore operation structures +static struct shbuf_ds { + int size; // size of data written + int head; // start of message list + int tail; // end of message list + char data[1]; // data/messages +} *shbuf = NULL; // shared memory pointer + +static struct sembuf SMwup[1] = { {1, -1, IPC_NOWAIT} }; // set SMwup +static struct sembuf SMwdn[3] = { {0, 0}, {1, 0}, {1, +1} }; // set SMwdn + +static int shmid = -1; // ipc shared memory id +static int s_semid = -1; // ipc semaphore id +static int shm_size = ((CONFIG_FEATURE_IPC_SYSLOG_BUFFER_SIZE)*1024); // default shm size + +static void ipcsyslog_cleanup(void) +{ + puts("Exiting syslogd!"); + if (shmid != -1) { + shmdt(shbuf); + } + + if (shmid != -1) { + shmctl(shmid, IPC_RMID, NULL); + } + if (s_semid != -1) { + semctl(s_semid, 0, IPC_RMID, 0); + } +} + +static void ipcsyslog_init(void) +{ + if (shbuf == NULL) { + shmid = shmget(KEY_ID, shm_size, IPC_CREAT | 1023); + if (shmid == -1) { + bb_perror_msg_and_die("shmget"); + } + + shbuf = shmat(shmid, NULL, 0); + if (!shbuf) { + bb_perror_msg_and_die("shmat"); + } + + shbuf->size = shm_size - sizeof(*shbuf); + shbuf->head = shbuf->tail = 0; + + // we'll trust the OS to set initial semval to 0 (let's hope) + s_semid = semget(KEY_ID, 2, IPC_CREAT | IPC_EXCL | 1023); + if (s_semid == -1) { + if (errno == EEXIST) { + s_semid = semget(KEY_ID, 2, 0); + if (s_semid == -1) { + bb_perror_msg_and_die("semget"); + } + } else { + bb_perror_msg_and_die("semget"); + } + } + } else { + printf("Buffer already allocated just grab the semaphore?"); + } +} + +/* write message to buffer */ +static void circ_message(const char *msg) +{ + int l = strlen(msg) + 1; /* count the whole message w/ '\0' included */ + const char * const fail_msg = "Can't find the terminator token%s?\n"; + + if (semop(s_semid, SMwdn, 3) == -1) { + bb_perror_msg_and_die("SMwdn"); + } + + /* + * Circular Buffer Algorithm: + * -------------------------- + * + * Start-off w/ empty buffer of specific size SHM_SIZ + * Start filling it up w/ messages. I use '\0' as separator to break up messages. + * This is also very handy since we can do printf on message. + * + * Once the buffer is full we need to get rid of the first message in buffer and + * insert the new message. (Note: if the message being added is >1 message then + * we will need to "remove" >1 old message from the buffer). The way this is done + * is the following: + * When we reach the end of the buffer we set a mark and start from the beginning. + * Now what about the beginning and end of the buffer? Well we have the "head" + * index/pointer which is the starting point for the messages and we have "tail" + * index/pointer which is the ending point for the messages. When we "display" the + * messages we start from the beginning and continue until we reach "tail". If we + * reach end of buffer, then we just start from the beginning (offset 0). "head" and + * "tail" are actually offsets from the beginning of the buffer. + * + * Note: This algorithm uses Linux IPC mechanism w/ shared memory and semaphores to provide + * a threadsafe way of handling shared memory operations. + */ + if ((shbuf->tail + l) < shbuf->size) { + /* before we append the message we need to check the HEAD so that we won't + overwrite any of the message that we still need and adjust HEAD to point + to the next message! */ + if (shbuf->tail < shbuf->head) { + if ((shbuf->tail + l) >= shbuf->head) { + /* we need to move the HEAD to point to the next message + * Theoretically we have enough room to add the whole message to the + * buffer, because of the first outer IF statement, so we don't have + * to worry about overflows here! + */ + /* we need to know how many bytes we are overwriting to make enough room */ + int k = shbuf->tail + l - shbuf->head; + char *c = + memchr(shbuf->data + shbuf->head + k, '\0', + shbuf->size - (shbuf->head + k)); + if (c != NULL) { /* do a sanity check just in case! */ + /* we need to convert pointer to offset + skip the '\0' + since we need to point to the beginning of the next message */ + shbuf->head = c - shbuf->data + 1; + /* Note: HEAD is only used to "retrieve" messages, it's not used + when writing messages into our buffer */ + } else { /* show an error message to know we messed up? */ + printf(fail_msg,""); + shbuf->head = 0; + } + } + } + + /* in other cases no overflows have been done yet, so we don't care! */ + /* we should be ok to append the message now */ + strncpy(shbuf->data + shbuf->tail, msg, l); /* append our message */ + shbuf->tail += l; /* count full message w/ '\0' terminating char */ + } else { + /* we need to break up the message and "circle" it around */ + char *c; + int k = shbuf->tail + l - shbuf->size; /* count # of bytes we don't fit */ + + /* We need to move HEAD! This is always the case since we are going + * to "circle" the message. */ + c = memchr(shbuf->data + k, '\0', shbuf->size - k); + + if (c != NULL) { /* if we don't have '\0'??? weird!!! */ + /* move head pointer */ + shbuf->head = c - shbuf->data + 1; + + /* now write the first part of the message */ + strncpy(shbuf->data + shbuf->tail, msg, l - k - 1); + + /* ALWAYS terminate end of buffer w/ '\0' */ + shbuf->data[shbuf->size - 1] = '\0'; + + /* now write out the rest of the string to the beginning of the buffer */ + strcpy(shbuf->data, &msg[l - k - 1]); + + /* we need to place the TAIL at the end of the message */ + shbuf->tail = k + 1; + } else { + printf(fail_msg, " from the beginning"); + shbuf->head = shbuf->tail = 0; /* reset buffer, since it's probably corrupted */ + } + + } + if (semop(s_semid, SMwup, 1) == -1) { + bb_perror_msg_and_die("SMwup"); + } + +} +#else +void ipcsyslog_cleanup(void); +void ipcsyslog_init(void); +void circ_message(const char *msg); +#endif /* CONFIG_FEATURE_IPC_SYSLOG */ + +/* Note: There is also a function called "message()" in init.c */ +/* Print a message to the log file. */ +static void message(char *fmt, ...) __attribute__ ((format(printf, 1, 2))); +static void message(char *fmt, ...) +{ + int fd = -1; + struct flock fl; + va_list arguments; + + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 1; + +#ifdef CONFIG_FEATURE_IPC_SYSLOG + if ((option_mask32 & OPT_circularlog) && shbuf) { + char b[1024]; + + va_start(arguments, fmt); + vsnprintf(b, sizeof(b) - 1, fmt, arguments); + va_end(arguments); + circ_message(b); + + } else +#endif + fd = device_open(logFilePath, O_WRONLY | O_CREAT + | O_NOCTTY | O_APPEND | O_NONBLOCK); + if (fd >= 0) { + fl.l_type = F_WRLCK; + fcntl(fd, F_SETLKW, &fl); + +#ifdef CONFIG_FEATURE_ROTATE_LOGFILE + if (ENABLE_FEATURE_ROTATE_LOGFILE && logFileSize > 0 ) { + struct stat statf; + int r = fstat(fd, &statf); + if (!r && (statf.st_mode & S_IFREG) + && (lseek(fd,0,SEEK_END) > logFileSize)) { + if (logFileRotate > 0) { + int i = strlen(logFilePath) + 4; + char oldFile[i]; + char newFile[i]; + for (i=logFileRotate-1; i>0; i--) { + sprintf(oldFile, "%s.%d", logFilePath, i-1); + sprintf(newFile, "%s.%d", logFilePath, i); + rename(oldFile, newFile); + } + sprintf(newFile, "%s.%d", logFilePath, 0); + fl.l_type = F_UNLCK; + fcntl(fd, F_SETLKW, &fl); + close(fd); + rename(logFilePath, newFile); + fd = device_open(logFilePath, + O_WRONLY | O_CREAT | O_NOCTTY | O_APPEND | + O_NONBLOCK); + fl.l_type = F_WRLCK; + fcntl(fd, F_SETLKW, &fl); + } else { + ftruncate(fd, 0); + } + } + } +#endif + va_start(arguments, fmt); + vdprintf(fd, fmt, arguments); + va_end(arguments); + fl.l_type = F_UNLCK; + fcntl(fd, F_SETLKW, &fl); + close(fd); + } else { + /* Always send console messages to /dev/console so people will see them. */ + fd = device_open(_PATH_CONSOLE, O_WRONLY | O_NOCTTY | O_NONBLOCK); + if (fd >= 0) { + va_start(arguments, fmt); + vdprintf(fd, fmt, arguments); + va_end(arguments); + close(fd); + } else { + fprintf(stderr, "Bummer, can't print: "); + va_start(arguments, fmt); + vfprintf(stderr, fmt, arguments); + fflush(stderr); + va_end(arguments); + } + } +} + +static void logMessage(int pri, char *msg) +{ + time_t now; + char *timestamp; + char res[20]; + CODE *c_pri, *c_fac; + + if (pri != 0) { + c_fac = facilitynames; + while (c_fac->c_name && !(c_fac->c_val == LOG_FAC(pri) << 3)) + c_fac++; + c_pri = prioritynames; + while (c_pri->c_name && !(c_pri->c_val == LOG_PRI(pri))) + c_pri++; + if (c_fac->c_name == NULL || c_pri->c_name == NULL) { + snprintf(res, sizeof(res), "<%d>", pri); + } else { + snprintf(res, sizeof(res), "%s.%s", c_fac->c_name, c_pri->c_name); + } + } + + if (strlen(msg) < 16 || msg[3] != ' ' || msg[6] != ' ' || + msg[9] != ':' || msg[12] != ':' || msg[15] != ' ') { + time(&now); + timestamp = ctime(&now) + 4; + timestamp[15] = '\0'; + } else { + timestamp = msg; + timestamp[15] = '\0'; + msg += 16; + } + + /* todo: supress duplicates */ + +#ifdef CONFIG_FEATURE_REMOTE_LOG + if (option_mask32 & OPT_remotelog) { + char line[MAXLINE + 1]; + /* trying connect the socket */ + if (-1 == remotefd) { + remotefd = socket(AF_INET, SOCK_DGRAM, 0); + } + /* if we have a valid socket, send the message */ + if (-1 != remotefd) { + snprintf(line, sizeof(line), "<%d>%s", pri, msg); + /* send message to remote logger, ignore possible error */ + sendto(remotefd, line, strlen(line), 0, + (struct sockaddr *) &remoteaddr, sizeof(remoteaddr)); + } + } + + if (option_mask32 & OPT_locallog) +#endif + { + /* now spew out the message to wherever it is supposed to go */ + if (pri == 0 || LOG_PRI(pri) < logLevel) { + if (option_mask32 & OPT_small) + message("%s %s\n", timestamp, msg); + else + message("%s %s %s %s\n", timestamp, LocalHostName, res, msg); + } + } +} + +static void quit_signal(int sig) +{ + logMessage(LOG_SYSLOG | LOG_INFO, "System log daemon exiting."); + unlink(lfile); + if (ENABLE_FEATURE_IPC_SYSLOG) + ipcsyslog_cleanup(); + + exit(1); +} + +static void domark(int sig) +{ + if (MarkInterval > 0) { + logMessage(LOG_SYSLOG | LOG_INFO, "-- MARK --"); + alarm(MarkInterval); + } +} + +/* This must be a #define, since when CONFIG_DEBUG and BUFFERS_GO_IN_BSS are + * enabled, we otherwise get a "storage size isn't constant error. */ +static int serveConnection(char *tmpbuf, int n_read) +{ + char *p = tmpbuf; + + while (p < tmpbuf + n_read) { + + int pri = (LOG_USER | LOG_NOTICE); + int num_lt = 0; + char line[MAXLINE + 1]; + unsigned char c; + char *q = line; + + while ((c = *p) && q < &line[sizeof(line) - 1]) { + if (c == '<' && num_lt == 0) { + /* Parse the magic priority number. */ + num_lt++; + pri = 0; + while (isdigit(*++p)) { + pri = 10 * pri + (*p - '0'); + } + if (pri & ~(LOG_FACMASK | LOG_PRIMASK)) { + pri = (LOG_USER | LOG_NOTICE); + } + } else if (c == '\n') { + *q++ = ' '; + } else if (iscntrl(c) && (c < 0177)) { + *q++ = '^'; + *q++ = c ^ 0100; + } else { + *q++ = c; + } + p++; + } + *q = '\0'; + p++; + /* Now log it */ + logMessage(pri, line); + } + return n_read; +} + +static void doSyslogd(void) ATTRIBUTE_NORETURN; +static void doSyslogd(void) +{ + struct sockaddr_un sunx; + socklen_t addrLength; + + int sock_fd; + fd_set fds; + + /* Set up signal handlers. */ + signal(SIGINT, quit_signal); + signal(SIGTERM, quit_signal); + signal(SIGQUIT, quit_signal); + signal(SIGHUP, SIG_IGN); + signal(SIGCHLD, SIG_IGN); +#ifdef SIGCLD + signal(SIGCLD, SIG_IGN); +#endif + signal(SIGALRM, domark); + alarm(MarkInterval); + + /* Create the syslog file so realpath() can work. */ + if (realpath(_PATH_LOG, lfile) != NULL) { + unlink(lfile); + } + + memset(&sunx, 0, sizeof(sunx)); + sunx.sun_family = AF_UNIX; + strncpy(sunx.sun_path, lfile, sizeof(sunx.sun_path)); + sock_fd = xsocket(AF_UNIX, SOCK_DGRAM, 0); + addrLength = sizeof(sunx.sun_family) + strlen(sunx.sun_path); + if (bind(sock_fd, (struct sockaddr *) &sunx, addrLength) < 0) { + bb_perror_msg_and_die("cannot connect to socket %s", lfile); + } + + if (chmod(lfile, 0666) < 0) { + bb_perror_msg_and_die("cannot set permission on %s", lfile); + } + if (ENABLE_FEATURE_IPC_SYSLOG && (option_mask32 & OPT_circularlog)) { + ipcsyslog_init(); + } + + logMessage(LOG_SYSLOG | LOG_INFO, "syslogd started: " "BusyBox v" BB_VER ); + + for (;;) { + FD_ZERO(&fds); + FD_SET(sock_fd, &fds); + + if (select(sock_fd + 1, &fds, NULL, NULL, NULL) < 0) { + if (errno == EINTR) { + /* alarm may have happened. */ + continue; + } + bb_perror_msg_and_die("select"); + } + + if (FD_ISSET(sock_fd, &fds)) { + int i; +#if MAXLINE > BUFSIZ +# define TMP_BUF_SZ BUFSIZ +#else +# define TMP_BUF_SZ MAXLINE +#endif +#define tmpbuf bb_common_bufsiz1 + + if ((i = recv(sock_fd, tmpbuf, TMP_BUF_SZ, 0)) > 0) { + tmpbuf[i] = '\0'; + serveConnection(tmpbuf, i); + } else { + bb_perror_msg_and_die("UNIX socket error"); + } + } /* FD_ISSET() */ + } /* for main loop */ +} + + +int syslogd_main(int argc, char **argv) +{ + char OPTION_DECL; + char *p; + + /* do normal option parsing */ + getopt32(argc, argv, OPTION_STR, OPTION_PARAM); + if (option_mask32 & OPT_mark) MarkInterval = xatoul_range(opt_m, 0, INT_MAX/60) * 60; // -m + //if (option_mask32 & OPT_nofork) // -n + //if (option_mask32 & OPT_outfile) // -O + if (option_mask32 & OPT_loglevel) { // -l + logLevel = xatoi_u(opt_l); + /* Valid levels are between 1 and 8 */ + if (logLevel < 1 || logLevel > 8) + bb_show_usage(); + } + //if (option_mask32 & OPT_small) // -S +#if ENABLE_FEATURE_ROTATE_LOGFILE + if (option_mask32 & OPT_filesize) logFileSize = xatoul_range(opt_s, 0, INT_MAX/1024) * 1024; // -s + if (option_mask32 & OPT_rotatecnt) { // -b + logFileRotate = xatoi_u(opt_b); + if (logFileRotate > 99) logFileRotate = 99; + } +#endif +#if ENABLE_FEATURE_REMOTE_LOG + if (option_mask32 & OPT_remotelog) { // -R + int port = 514; + char *host = xstrdup(opt_R); + p = strchr(host, ':'); + if (p) { + port = xatou16(p + 1); + *p = '\0'; + } + remoteaddr.sin_family = AF_INET; + /* FIXME: looks ip4-specific. need to do better */ + remoteaddr.sin_addr = *(struct in_addr *) *(xgethostbyname(host)->h_addr_list); + remoteaddr.sin_port = htons(port); + free(host); + } + //if (option_mask32 & OPT_locallog) // -L +#endif +#if ENABLE_FEATURE_IPC_SYSLOG + if (option_mask32 & OPT_circularlog) { // -C + if (opt_C) { + shm_size = xatoul_range(opt_C, 4, INT_MAX/1024) * 1024; + } + } +#endif + + /* If they have not specified remote logging, then log locally */ + if (ENABLE_FEATURE_REMOTE_LOG && !(option_mask32 & OPT_remotelog)) + option_mask32 |= OPT_locallog; + + /* Store away localhost's name before the fork */ + gethostname(LocalHostName, sizeof(LocalHostName)); + p = strchr(LocalHostName, '.'); + if (p) { + *p = '\0'; + } + + umask(0); + + if (!(option_mask32 & OPT_nofork)) { +#ifdef BB_NOMMU + vfork_daemon_rexec(0, 1, argc, argv, "-n"); +#else + xdaemon(0, 1); +#endif + } + doSyslogd(); + + return EXIT_SUCCESS; +} diff --git a/testsuite/README b/testsuite/README new file mode 100644 index 000000000..377f20ef8 --- /dev/null +++ b/testsuite/README @@ -0,0 +1,34 @@ +Update: doesn't work as described. Try "make check" from parent dir... +* * * + +To run the test suite, change to this directory and run "./runtest". It will +run all of the test cases, and list those with unexpected outcomes. Adding the +-v option will cause it to show expected outcomes as well. To only run the test +cases for particular applets, specify them as parameters to runtest. + +The test cases for an applet reside in the subdirectory of the applet name. The +name of the test case should be the assertion that is tested. The test case +should be a shell fragment that returns successfully if the test case passes, +and unsuccessfully otherwise. + +If the test case relies on a certain feature, it should include the string +"FEATURE: " followed by the name of the feature in a comment. If it is always +expected to fail, it should include the string "XFAIL" in a comment. + +For the entire testsuite, the copyright is as follows: + +Copyright (C) 2001, 2002 Matt Kraai + +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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. diff --git a/testsuite/TODO b/testsuite/TODO new file mode 100644 index 000000000..b8957f488 --- /dev/null +++ b/testsuite/TODO @@ -0,0 +1,26 @@ +This testsuite is quite obviously a work in progress. As such, +there are a number of good extensions. If you are looking for +something to do, feel free to tackle one or more of the following: + +Moving to the new format. + The old way was "lots of little tests files in a directory", which + doesn't interact well with source control systems. The new test + format is command.tests files that use testing.sh. + +Every busybox applet needs a corresponding applet.tests. + +Full SUSv3 test suite. + Let's make the Linux Test Project jealous, shall we? Don't just + audit programs for standards compliance, _prove_ it with a regression + test harness. + + http://www.opengroup.org/onlinepubs/009695399/utilities/ + +Some tests need root access. + It's hard to test things like mount or init as a normal user. + Possibly User Mode Linux could be used for this, or perhaps + Erik's buildroot. + +libbb unit testing + Being able to test the functions of libbb individually may + help to prevent regressions. diff --git a/testsuite/all_sourcecode.tests b/testsuite/all_sourcecode.tests new file mode 100755 index 000000000..c115878fe --- /dev/null +++ b/testsuite/all_sourcecode.tests @@ -0,0 +1,78 @@ +#!/bin/sh + +# Tests for the sourcecode base itself. +# Copyright 2006 by Mike Frysinger <vapier@gentoo.org> +# Licensed under GPL v2, see file LICENSE for details. + +[ -n "$srcdir" ] || srcdir=$(pwd) +. testing.sh + + +# +# if we don't have the sourcecode available, let's just bail +# +[ -s "$srcdir/../Makefile" ] || exit 0 +[ -s "$srcdir/../include/applets.h" ] || exit 0 + + +# +# verify the applet order is correct in applets.h, otherwise +# applets won't be called properly. +# +sed -n -e '/^USE_[A-Z]*(APPLET(/{s:.*(::;s:,.*::;s:"::g;p}' \ + $srcdir/../include/applets.h > applet.order.current +LC_ALL=C sort applet.order.current > applet.order.correct +testing "Applet order" "diff -u applet.order.current applet.order.correct" "" "" "" +rm -f applet.order.current applet.order.correct + + +# +# check for misc common typos +# +find $srcdir/../ \ + '(' -type d -a '(' -name .svn -o -name testsuite ')' -prune ')' \ + -o '(' -type f -a -print0 ')' | xargs -0 \ + grep -I \ + -e '\<compatability\>' \ + -e '\<compatable\>' \ + -e '\<fordeground\>' \ + -e '\<depency\>' -e '\<dependancy\>' -e '\<dependancies\>' \ + -e '\<defalt\>' \ + -e '\<remaing\>' \ + -e '\<queueing\>' \ + -e '\<detatch\>' \ + -e '\<sempahore\>' \ + -e '\<reprenstative\>' \ + -e '\<overriden\>' \ + -e '\<readed\>' \ + -e '\<formated\>' \ + -e '\<algorithic\>' \ + -e '\<deamon\>' \ + -e '\<derefernce\>' \ + -e '\<acomadate\>' \ + | sed -e "s:^$srcdir/\.\./::g" > src.typos +testing "Common typos" "cat src.typos" "" "" "" +rm -f src.typos + + +# +# don't allow obsolete functions +# +find $srcdir/.. '(' -name '*.c' -o -name '*.h' ')' -print0 | xargs -0 \ + grep -E -e '\<(bcmp|bcopy|bzero|getwd|index|mktemp|rindex|utimes)\>[[:space:]]*\(' \ + | sed -e "s:^$srcdir/\.\./::g" > src.obsolete.funcs +testing "Obsolete function usage" "cat src.obsolete.funcs" "" "" "" +rm -f src.obsolete.funcs + + +# +# don't allow obsolete headers +# +find $srcdir/.. '(' -name '*.c' -o -name '*.h' ')' -print0 | xargs -0 \ + grep -E -e '\<(malloc|memory|sys/(errno|fcntl|signal|stropts|termios|unistd))\.h\>' \ + | sed -e "s:^$srcdir/\.\./::g" > src.obsolete.headers +testing "Obsolete headers" "cat src.obsolete.headers" "" "" "" +rm -f src.obsolete.headers + + +exit $FAILCOUNT diff --git a/testsuite/basename/basename-does-not-remove-identical-extension b/testsuite/basename/basename-does-not-remove-identical-extension new file mode 100644 index 000000000..4448fdec4 --- /dev/null +++ b/testsuite/basename/basename-does-not-remove-identical-extension @@ -0,0 +1 @@ +test xfoo = x`busybox basename foo foo` diff --git a/testsuite/basename/basename-works b/testsuite/basename/basename-works new file mode 100644 index 000000000..38907d4c1 --- /dev/null +++ b/testsuite/basename/basename-works @@ -0,0 +1,2 @@ +test x$(basename $(pwd)) = x$(busybox basename $(pwd)) + diff --git a/testsuite/bunzip2/bunzip2-reads-from-standard-input b/testsuite/bunzip2/bunzip2-reads-from-standard-input new file mode 100644 index 000000000..e212a1207 --- /dev/null +++ b/testsuite/bunzip2/bunzip2-reads-from-standard-input @@ -0,0 +1,2 @@ +echo foo | bzip2 | busybox bunzip2 > output +echo foo | cmp - output diff --git a/testsuite/bunzip2/bunzip2-removes-compressed-file b/testsuite/bunzip2/bunzip2-removes-compressed-file new file mode 100644 index 000000000..f1d15503e --- /dev/null +++ b/testsuite/bunzip2/bunzip2-removes-compressed-file @@ -0,0 +1,3 @@ +echo foo | bzip2 >foo.bz2 +busybox bunzip2 foo.bz2 +test ! -f foo.bz2 diff --git a/testsuite/bunzip2/bzcat-does-not-remove-compressed-file b/testsuite/bunzip2/bzcat-does-not-remove-compressed-file new file mode 100644 index 000000000..7d4016ec5 --- /dev/null +++ b/testsuite/bunzip2/bzcat-does-not-remove-compressed-file @@ -0,0 +1,3 @@ +echo foo | bzip2 >foo.bz2 +busybox bzcat foo.bz2 +test -f foo.bz2 diff --git a/testsuite/busybox.tests b/testsuite/busybox.tests new file mode 100755 index 000000000..26536c656 --- /dev/null +++ b/testsuite/busybox.tests @@ -0,0 +1,46 @@ +#!/bin/sh + +# Tests for busybox applet itself. +# Copyright 2005 by Rob Landley <rob@landley.net> +# Licensed under GPL v2, see file LICENSE for details. + +. testing.sh + +HELPDUMP=`busybox` + +# We need to test under calling the binary under other names. + + +testing "busybox --help busybox" "busybox --help busybox" "$HELPDUMP\n\n" "" "" + +ln -s `which busybox` busybox-suffix +for i in busybox ./busybox-suffix +do + # The gratuitous "\n"s are due to a shell idiosyncrasy: + # environment variables seem to strip trailing whitespace. + + testing "" "$i" "$HELPDUMP\n\n" "" "" + + testing "$i unknown" "$i unknown 2>&1" \ + "unknown: applet not found\n" "" "" + + testing "$i --help" "$i --help 2>&1" "$HELPDUMP\n\n" "" "" + + optional CAT + testing "" "$i cat" "moo" "" "moo" + testing "$i --help cat" "$i --help cat 2>&1 | grep prints" \ + "Concatenates FILE(s) and prints them to stdout.\n" "" "" + optional "" + + testing "$i --help unknown" "$i --help unknown 2>&1" \ + "unknown: applet not found\n" "" "" +done +rm busybox-suffix + +ln -s `which busybox` unknown + +testing "busybox as unknown name" "./unknown 2>&1" \ + "unknown: applet not found\n" "" "" +rm unknown + +exit $FAILCOUNT diff --git a/testsuite/cat/cat-prints-a-file b/testsuite/cat/cat-prints-a-file new file mode 100644 index 000000000..e3f35a86e --- /dev/null +++ b/testsuite/cat/cat-prints-a-file @@ -0,0 +1,3 @@ +echo I WANT > foo +busybox cat foo >bar +cmp foo bar diff --git a/testsuite/cat/cat-prints-a-file-and-standard-input b/testsuite/cat/cat-prints-a-file-and-standard-input new file mode 100644 index 000000000..bc9231882 --- /dev/null +++ b/testsuite/cat/cat-prints-a-file-and-standard-input @@ -0,0 +1,7 @@ +echo I WANT > foo +echo SOMETHING | busybox cat foo - >bar +cat >baz <<EOF +I WANT +SOMETHING +EOF +cmp bar baz diff --git a/testsuite/cmp/cmp-detects-difference b/testsuite/cmp/cmp-detects-difference new file mode 100644 index 000000000..b9bb628f1 --- /dev/null +++ b/testsuite/cmp/cmp-detects-difference @@ -0,0 +1,9 @@ +echo foo >foo +echo bar >bar +set +e +busybox cmp -s foo bar +if [ $? != 0 ] ; then + exit 0; +fi + +exit 1; diff --git a/testsuite/cp/cp-a-files-to-dir b/testsuite/cp/cp-a-files-to-dir new file mode 100644 index 000000000..39f8f8103 --- /dev/null +++ b/testsuite/cp/cp-a-files-to-dir @@ -0,0 +1,14 @@ +echo file number one > file1 +echo file number two > file2 +ln -s file2 link1 +mkdir dir1 +touch --date='Sat Jan 29 21:24:08 PST 2000' dir1/file3 +mkdir there +busybox cp -a file1 file2 link1 dir1 there +test -f there/file1 +test -f there/file2 +test ! -s there/dir1/file3 +test -L there/link1 +test xfile2 = x`readlink there/link1` +test ! dir1/file3 -ot there/dir1/file3 +test ! dir1/file3 -nt there/dir1/file3 diff --git a/testsuite/cp/cp-a-preserves-links b/testsuite/cp/cp-a-preserves-links new file mode 100644 index 000000000..0c0cd9653 --- /dev/null +++ b/testsuite/cp/cp-a-preserves-links @@ -0,0 +1,5 @@ +touch foo +ln -s foo bar +busybox cp -a bar baz +test -L baz +test xfoo = x`readlink baz` diff --git a/testsuite/cp/cp-copies-empty-file b/testsuite/cp/cp-copies-empty-file new file mode 100644 index 000000000..ad25aa12e --- /dev/null +++ b/testsuite/cp/cp-copies-empty-file @@ -0,0 +1,3 @@ +touch foo +busybox cp foo bar +cmp foo bar diff --git a/testsuite/cp/cp-copies-large-file b/testsuite/cp/cp-copies-large-file new file mode 100644 index 000000000..c2225c6d8 --- /dev/null +++ b/testsuite/cp/cp-copies-large-file @@ -0,0 +1,3 @@ +dd if=/dev/zero of=foo seek=10k count=1 2>/dev/null +busybox cp foo bar +cmp foo bar diff --git a/testsuite/cp/cp-copies-small-file b/testsuite/cp/cp-copies-small-file new file mode 100644 index 000000000..d52a887c0 --- /dev/null +++ b/testsuite/cp/cp-copies-small-file @@ -0,0 +1,3 @@ +echo I WANT > foo +busybox cp foo bar +cmp foo bar diff --git a/testsuite/cp/cp-d-files-to-dir b/testsuite/cp/cp-d-files-to-dir new file mode 100644 index 000000000..9571a567e --- /dev/null +++ b/testsuite/cp/cp-d-files-to-dir @@ -0,0 +1,11 @@ +echo file number one > file1 +echo file number two > file2 +touch file3 +ln -s file2 link1 +mkdir there +busybox cp -d file1 file2 file3 link1 there +test -f there/file1 +test -f there/file2 +test ! -s there/file3 +test -L there/link1 +test xfile2 = x`readlink there/link1` diff --git a/testsuite/cp/cp-dir-create-dir b/testsuite/cp/cp-dir-create-dir new file mode 100644 index 000000000..a8d7b50a5 --- /dev/null +++ b/testsuite/cp/cp-dir-create-dir @@ -0,0 +1,4 @@ +mkdir bar +touch bar/baz +busybox cp -R bar foo +test -f foo/baz diff --git a/testsuite/cp/cp-dir-existing-dir b/testsuite/cp/cp-dir-existing-dir new file mode 100644 index 000000000..4c788ba73 --- /dev/null +++ b/testsuite/cp/cp-dir-existing-dir @@ -0,0 +1,5 @@ +mkdir bar +touch bar/baz +mkdir foo +busybox cp -R bar foo +test -f foo/bar/baz diff --git a/testsuite/cp/cp-does-not-copy-unreadable-file b/testsuite/cp/cp-does-not-copy-unreadable-file new file mode 100644 index 000000000..ce11bfab0 --- /dev/null +++ b/testsuite/cp/cp-does-not-copy-unreadable-file @@ -0,0 +1,6 @@ +touch foo +chmod a-r foo +set +e +busybox cp foo bar +set -e +test ! -f bar diff --git a/testsuite/cp/cp-files-to-dir b/testsuite/cp/cp-files-to-dir new file mode 100644 index 000000000..fdb81916f --- /dev/null +++ b/testsuite/cp/cp-files-to-dir @@ -0,0 +1,11 @@ +echo file number one > file1 +echo file number two > file2 +touch file3 +ln -s file2 link1 +mkdir there +busybox cp file1 file2 file3 link1 there +test -f there/file1 +test -f there/file2 +test ! -s there/file3 +test -f there/link1 +cmp there/file2 there/link1 diff --git a/testsuite/cp/cp-follows-links b/testsuite/cp/cp-follows-links new file mode 100644 index 000000000..2d9f05e9f --- /dev/null +++ b/testsuite/cp/cp-follows-links @@ -0,0 +1,4 @@ +touch foo +ln -s foo bar +busybox cp bar baz +test -f baz diff --git a/testsuite/cp/cp-preserves-hard-links b/testsuite/cp/cp-preserves-hard-links new file mode 100644 index 000000000..4de7b85db --- /dev/null +++ b/testsuite/cp/cp-preserves-hard-links @@ -0,0 +1,6 @@ +# FEATURE: CONFIG_FEATURE_PRESERVE_HARDLINKS +touch foo +ln foo bar +mkdir baz +busybox cp -d foo bar baz +test baz/foo -ef baz/bar diff --git a/testsuite/cp/cp-preserves-links b/testsuite/cp/cp-preserves-links new file mode 100644 index 000000000..301dc5fd8 --- /dev/null +++ b/testsuite/cp/cp-preserves-links @@ -0,0 +1,5 @@ +touch foo +ln -s foo bar +busybox cp -d bar baz +test -L baz +test xfoo = x`readlink baz` diff --git a/testsuite/cp/cp-preserves-source-file b/testsuite/cp/cp-preserves-source-file new file mode 100644 index 000000000..f0f5065f4 --- /dev/null +++ b/testsuite/cp/cp-preserves-source-file @@ -0,0 +1,3 @@ +touch foo +busybox cp foo bar +test -f foo diff --git a/testsuite/cut/cut-cuts-a-character b/testsuite/cut/cut-cuts-a-character new file mode 100644 index 000000000..d6c5efa3a --- /dev/null +++ b/testsuite/cut/cut-cuts-a-character @@ -0,0 +1 @@ +test $(echo abcd | busybox cut -c 3) = c diff --git a/testsuite/cut/cut-cuts-a-closed-range b/testsuite/cut/cut-cuts-a-closed-range new file mode 100644 index 000000000..9680b7650 --- /dev/null +++ b/testsuite/cut/cut-cuts-a-closed-range @@ -0,0 +1 @@ +test $(echo abcd | busybox cut -c 1-2) = ab diff --git a/testsuite/cut/cut-cuts-a-field b/testsuite/cut/cut-cuts-a-field new file mode 100644 index 000000000..4c7f44007 --- /dev/null +++ b/testsuite/cut/cut-cuts-a-field @@ -0,0 +1 @@ +test $(echo -e "f1\tf2\tf3" | busybox cut -f 2) = f2 diff --git a/testsuite/cut/cut-cuts-an-open-range b/testsuite/cut/cut-cuts-an-open-range new file mode 100644 index 000000000..1fbf27742 --- /dev/null +++ b/testsuite/cut/cut-cuts-an-open-range @@ -0,0 +1 @@ +test $(echo abcd | busybox cut -c -3) = abc diff --git a/testsuite/cut/cut-cuts-an-unclosed-range b/testsuite/cut/cut-cuts-an-unclosed-range new file mode 100644 index 000000000..a2b0cdb03 --- /dev/null +++ b/testsuite/cut/cut-cuts-an-unclosed-range @@ -0,0 +1 @@ +test $(echo abcd | busybox cut -c 3-) = cd diff --git a/testsuite/date/date-R-works b/testsuite/date/date-R-works new file mode 100644 index 000000000..ec3a06751 --- /dev/null +++ b/testsuite/date/date-R-works @@ -0,0 +1,2 @@ +test x"`date -R`" = x"`busybox date -R`" + diff --git a/testsuite/date/date-format-works b/testsuite/date/date-format-works new file mode 100644 index 000000000..f28d06cfc --- /dev/null +++ b/testsuite/date/date-format-works @@ -0,0 +1 @@ +test x"`date +%d/%m/%y`" = x"`busybox date +%d/%m/%y`" diff --git a/testsuite/date/date-u-works b/testsuite/date/date-u-works new file mode 100644 index 000000000..7d9902a3f --- /dev/null +++ b/testsuite/date/date-u-works @@ -0,0 +1,2 @@ +test x"`date -u`" = x"`busybox date -u`" + diff --git a/testsuite/date/date-works b/testsuite/date/date-works new file mode 100644 index 000000000..2f6dd1eca --- /dev/null +++ b/testsuite/date/date-works @@ -0,0 +1,2 @@ +test x"`date`" = x"`busybox date`" + diff --git a/testsuite/dd/dd-accepts-if b/testsuite/dd/dd-accepts-if new file mode 100644 index 000000000..03d1af853 --- /dev/null +++ b/testsuite/dd/dd-accepts-if @@ -0,0 +1,2 @@ +echo I WANT >foo +test "$(busybox dd if=foo 2>/dev/null)" = "I WANT" diff --git a/testsuite/dd/dd-accepts-of b/testsuite/dd/dd-accepts-of new file mode 100644 index 000000000..84405e622 --- /dev/null +++ b/testsuite/dd/dd-accepts-of @@ -0,0 +1,2 @@ +echo I WANT | busybox dd of=foo 2>/dev/null +echo I WANT | cmp foo - diff --git a/testsuite/dd/dd-copies-from-standard-input-to-standard-output b/testsuite/dd/dd-copies-from-standard-input-to-standard-output new file mode 100644 index 000000000..d890eb04c --- /dev/null +++ b/testsuite/dd/dd-copies-from-standard-input-to-standard-output @@ -0,0 +1 @@ +test "$(echo I WANT | busybox dd 2>/dev/null)" = "I WANT" diff --git a/testsuite/dd/dd-prints-count-to-standard-error b/testsuite/dd/dd-prints-count-to-standard-error new file mode 100644 index 000000000..2187dc027 --- /dev/null +++ b/testsuite/dd/dd-prints-count-to-standard-error @@ -0,0 +1,2 @@ +echo I WANT | busybox dd of=foo >/dev/null 2>bar +grep -q records bar diff --git a/testsuite/dirname/dirname-handles-absolute-path b/testsuite/dirname/dirname-handles-absolute-path new file mode 100644 index 000000000..ca1a51b3a --- /dev/null +++ b/testsuite/dirname/dirname-handles-absolute-path @@ -0,0 +1 @@ +test $(busybox dirname /foo/bar/baz) = /foo/bar diff --git a/testsuite/dirname/dirname-handles-empty-path b/testsuite/dirname/dirname-handles-empty-path new file mode 100644 index 000000000..04134a58f --- /dev/null +++ b/testsuite/dirname/dirname-handles-empty-path @@ -0,0 +1 @@ +test $(busybox dirname '') = . diff --git a/testsuite/dirname/dirname-handles-multiple-slashes b/testsuite/dirname/dirname-handles-multiple-slashes new file mode 100644 index 000000000..286f25361 --- /dev/null +++ b/testsuite/dirname/dirname-handles-multiple-slashes @@ -0,0 +1 @@ +test $(busybox dirname foo/bar///baz) = foo/bar diff --git a/testsuite/dirname/dirname-handles-relative-path b/testsuite/dirname/dirname-handles-relative-path new file mode 100644 index 000000000..ffe4ab459 --- /dev/null +++ b/testsuite/dirname/dirname-handles-relative-path @@ -0,0 +1 @@ +test $(busybox dirname foo/bar/baz) = foo/bar diff --git a/testsuite/dirname/dirname-handles-root b/testsuite/dirname/dirname-handles-root new file mode 100644 index 000000000..6bd62b8a1 --- /dev/null +++ b/testsuite/dirname/dirname-handles-root @@ -0,0 +1 @@ +test $(busybox dirname /) = / diff --git a/testsuite/dirname/dirname-handles-single-component b/testsuite/dirname/dirname-handles-single-component new file mode 100644 index 000000000..24f9ae163 --- /dev/null +++ b/testsuite/dirname/dirname-handles-single-component @@ -0,0 +1 @@ +test $(busybox dirname foo) = . diff --git a/testsuite/dirname/dirname-works b/testsuite/dirname/dirname-works new file mode 100644 index 000000000..f339c8f73 --- /dev/null +++ b/testsuite/dirname/dirname-works @@ -0,0 +1,2 @@ +test x$(dirname $(pwd)) = x$(busybox dirname $(pwd)) + diff --git a/testsuite/du/du-h-works b/testsuite/du/du-h-works new file mode 100644 index 000000000..82041ab33 --- /dev/null +++ b/testsuite/du/du-h-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +du -h "$d" > logfile.gnu +busybox du -h "$d" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/du/du-k-works b/testsuite/du/du-k-works new file mode 100644 index 000000000..177a1a2cd --- /dev/null +++ b/testsuite/du/du-k-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +du -k "$d" > logfile.gnu +busybox du -k "$d" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/du/du-l-works b/testsuite/du/du-l-works new file mode 100644 index 000000000..61e91400c --- /dev/null +++ b/testsuite/du/du-l-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +du -l "$d" > logfile.gnu +busybox du -l "$d" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/du/du-m-works b/testsuite/du/du-m-works new file mode 100644 index 000000000..bc9707350 --- /dev/null +++ b/testsuite/du/du-m-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +du -m "$d" > logfile.gnu +busybox du -m "$d" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/du/du-s-works b/testsuite/du/du-s-works new file mode 100644 index 000000000..f0b3bf0ae --- /dev/null +++ b/testsuite/du/du-s-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +du -s "$d" > logfile.gnu +busybox du -s "$d" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/du/du-works b/testsuite/du/du-works new file mode 100644 index 000000000..47949c694 --- /dev/null +++ b/testsuite/du/du-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +du "$d" > logfile.gnu +busybox du "$d" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/echo/echo-does-not-print-newline b/testsuite/echo/echo-does-not-print-newline new file mode 100644 index 000000000..2ed03caf5 --- /dev/null +++ b/testsuite/echo/echo-does-not-print-newline @@ -0,0 +1 @@ +test `busybox echo -n word | wc -c` -eq 4 diff --git a/testsuite/echo/echo-prints-argument b/testsuite/echo/echo-prints-argument new file mode 100644 index 000000000..479dac89c --- /dev/null +++ b/testsuite/echo/echo-prints-argument @@ -0,0 +1 @@ +test xfubar = x`busybox echo fubar` diff --git a/testsuite/echo/echo-prints-arguments b/testsuite/echo/echo-prints-arguments new file mode 100644 index 000000000..4e4e3b434 --- /dev/null +++ b/testsuite/echo/echo-prints-arguments @@ -0,0 +1 @@ +test "`busybox echo foo bar`" = "foo bar" diff --git a/testsuite/echo/echo-prints-newline b/testsuite/echo/echo-prints-newline new file mode 100644 index 000000000..838671efe --- /dev/null +++ b/testsuite/echo/echo-prints-newline @@ -0,0 +1 @@ +test `busybox echo word | wc -c` -eq 5 diff --git a/testsuite/expr/expr-works b/testsuite/expr/expr-works new file mode 100644 index 000000000..af49ac4d5 --- /dev/null +++ b/testsuite/expr/expr-works @@ -0,0 +1,59 @@ +# busybox expr +busybox expr 1 \| 1 +busybox expr 1 \| 0 +busybox expr 0 \| 1 +busybox expr 1 \& 1 +busybox expr 0 \< 1 +busybox expr 1 \> 0 +busybox expr 0 \<= 1 +busybox expr 1 \<= 1 +busybox expr 1 \>= 0 +busybox expr 1 \>= 1 +busybox expr 1 + 2 +busybox expr 2 - 1 +busybox expr 2 \* 3 +busybox expr 12 / 2 +busybox expr 12 % 5 + + +set +e +busybox expr 0 \| 0 +if [ $? != 1 ] ; then + exit 1; +fi; + +busybox expr 1 \& 0 +if [ $? != 1 ] ; then + exit 1; +fi; + +busybox expr 0 \& 1 +if [ $? != 1 ] ; then + exit 1; +fi; + +busybox expr 0 \& 0 +if [ $? != 1 ] ; then + exit 1; +fi; + +busybox expr 1 \< 0 +if [ $? != 1 ] ; then + exit 1; +fi; + +busybox expr 0 \> 1 +if [ $? != 1 ] ; then + exit 1; +fi; + +busybox expr 1 \<= 0 +if [ $? != 1 ] ; then + exit 1; +fi; + +busybox expr 0 \>= 1 +if [ $? != 1 ] ; then + exit 1; +fi; + diff --git a/testsuite/false/false-is-silent b/testsuite/false/false-is-silent new file mode 100644 index 000000000..8a9aa0c7f --- /dev/null +++ b/testsuite/false/false-is-silent @@ -0,0 +1 @@ +busybox false 2>&1 | cmp - /dev/null diff --git a/testsuite/false/false-returns-failure b/testsuite/false/false-returns-failure new file mode 100644 index 000000000..1a061f286 --- /dev/null +++ b/testsuite/false/false-returns-failure @@ -0,0 +1 @@ +! busybox false diff --git a/testsuite/find/find-supports-minus-xdev b/testsuite/find/find-supports-minus-xdev new file mode 100644 index 000000000..4c559a1f4 --- /dev/null +++ b/testsuite/find/find-supports-minus-xdev @@ -0,0 +1 @@ +busybox find . -xdev >/dev/null 2>&1 diff --git a/testsuite/grep.tests b/testsuite/grep.tests new file mode 100755 index 000000000..ef0de482b --- /dev/null +++ b/testsuite/grep.tests @@ -0,0 +1,84 @@ +#!/bin/sh + +# grep tests. +# Copyright 2005 by Rob Landley <rob@landley.net> +# Licensed under GPL v2, see file LICENSE for details. + +# AUDIT: + +. testing.sh + +# testing "test name" "options" "expected result" "file input" "stdin" +# file input will be file called "input" +# test can create a file "actual" instead of writing to stdout + +# Test exit status + +testing "grep (exit with error)" "grep nonexistent 2> /dev/null ; echo \$?" \ + "1\n" "" "" +testing "grep (exit success)" "grep grep $0 > /dev/null 2>&1 ; echo \$?" "0\n" \ + "" "" +# Test various data sources and destinations + +testing "grep (default to stdin)" "grep two" "two\n" "" \ + "one\ntwo\nthree\nthree\nthree\n" +testing "grep - (specify stdin)" "grep two -" "two\n" "" \ + "one\ntwo\nthree\nthree\nthree\n" +testing "grep input (specify file)" "grep two input" "two\n" \ + "one\ntwo\nthree\nthree\nthree\n" "" + +testing "grep (no newline at EOL)" "grep bug" "bug" "bug" "" + +# Note that this assumes actual is empty. +testing "grep input actual (two files)" "grep two input actual 2> /dev/null" \ + "input:two\n" "one\ntwo\nthree\nthree\nthree\n" "" + +testing "grep - infile (specify stdin and file)" "grep two - input" \ + "(standard input):two\ninput:two\n" "one\ntwo\nthree\n" \ + "one\ntwo\ntoo\nthree\nthree\n" + +# Check if we see the correct return value if both stdin and non-existing file +# are given. +testing "grep - nofile (specify stdin and nonexisting file)" \ + "grep two - nonexistent 2> /dev/null ; echo \$?" \ + "(standard input):two\n(standard input):two\n2\n" \ + "" "one\ntwo\ntwo\nthree\nthree\nthree\n" +testing "grep -q - nofile (specify stdin and nonexisting file, no match)" \ + "grep -q nomatch - nonexistent 2> /dev/null ; echo \$?" \ + "2\n" "" "one\ntwo\ntwo\nthree\nthree\nthree\n" +# SUSv3: If the -q option is specified, the exit status shall be zero +# if an input line is selected, even if an error was detected. +testing "grep -q - nofile (specify stdin and nonexisting file, match)" \ + "grep -q two - nonexistent ; echo \$?" \ + "0\n" "" "one\ntwo\ntwo\nthree\nthree\nthree\n" + +# Test various command line options +# -s no error messages +testing "grep -s nofile (nonexisting file, no match)" \ + "grep -s nomatch nonexistent ; echo \$?" "2\n" "" "" +testing "grep -s nofile - (stdin and nonexisting file, match)" \ + "grep -s domatch nonexistent - ; echo \$?" \ + "(standard input):domatch\n2\n" "" "nomatch\ndomatch\nend\n" + +# This doesn't match GNU behaviour (Binary file input matches) +# acts like GNU grep -a +testing "grep handles binary files" "grep foo input" "foo\n" "\0foo\n\n" "" +# This doesn't match GNU behaviour (Binary file (standard input) matches) +# acts like GNU grep -a +testing "grep handles binary stdin" "grep foo" "foo\n" "" "\0foo\n\n" + +testing "grep matches NUL" "grep . input > /dev/null 2>&1 ; echo \$?" \ + "0\n" "\0\n" "" + +# -e regex +testing "grep handles multiple regexps" "grep -e one -e two input ; echo \$?" \ + "one\ntwo\n0\n" "one\ntwo\n" "" + +optional FEATURE_GREP_EGREP_ALIAS +testing "grep -E supports extended regexps" "grep -E fo+" "foo\n" "" \ + "b\ar\nfoo\nbaz" +testing "grep is also egrep" "egrep foo" "foo\n" "" "foo\nbar\n" +testing "egrep is not case insensitive" \ + "egrep foo ; [ \$? -ne 0 ] && echo yes" "yes\n" "" "FOO\n" + +exit $FAILCOUNT diff --git a/testsuite/gunzip/gunzip-reads-from-standard-input b/testsuite/gunzip/gunzip-reads-from-standard-input new file mode 100644 index 000000000..7c498c0ce --- /dev/null +++ b/testsuite/gunzip/gunzip-reads-from-standard-input @@ -0,0 +1,2 @@ +echo foo | gzip | busybox gunzip > output +echo foo | cmp - output diff --git a/testsuite/gzip/gzip-accepts-multiple-files b/testsuite/gzip/gzip-accepts-multiple-files new file mode 100644 index 000000000..8f0d9c845 --- /dev/null +++ b/testsuite/gzip/gzip-accepts-multiple-files @@ -0,0 +1,3 @@ +touch foo bar +busybox gzip foo bar +test -f foo.gz -a -f bar.gz diff --git a/testsuite/gzip/gzip-accepts-single-minus b/testsuite/gzip/gzip-accepts-single-minus new file mode 100644 index 000000000..8b51fdfed --- /dev/null +++ b/testsuite/gzip/gzip-accepts-single-minus @@ -0,0 +1 @@ +echo foo | busybox gzip - >/dev/null diff --git a/testsuite/gzip/gzip-removes-original-file b/testsuite/gzip/gzip-removes-original-file new file mode 100644 index 000000000..b9cb995bc --- /dev/null +++ b/testsuite/gzip/gzip-removes-original-file @@ -0,0 +1,3 @@ +touch foo +busybox gzip foo +test ! -f foo diff --git a/testsuite/head/head-n-works b/testsuite/head/head-n-works new file mode 100644 index 000000000..db4325556 --- /dev/null +++ b/testsuite/head/head-n-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +head -n 2 "$d/README" > logfile.gnu +busybox head -n 2 "$d/README" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/head/head-works b/testsuite/head/head-works new file mode 100644 index 000000000..56ad3e36b --- /dev/null +++ b/testsuite/head/head-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +head "$d/README" > logfile.gnu +busybox head "$d/README" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/hostid/hostid-works b/testsuite/hostid/hostid-works new file mode 100644 index 000000000..e85698e66 --- /dev/null +++ b/testsuite/hostid/hostid-works @@ -0,0 +1,2 @@ +test x$(hostid) = x$(busybox hostid) + diff --git a/testsuite/hostname/hostname-d-works b/testsuite/hostname/hostname-d-works new file mode 100644 index 000000000..a9aeb92cb --- /dev/null +++ b/testsuite/hostname/hostname-d-works @@ -0,0 +1,2 @@ +test x$(hostname -d) = x$(busybox hostname -d) + diff --git a/testsuite/hostname/hostname-i-works b/testsuite/hostname/hostname-i-works new file mode 100644 index 000000000..68a3e6789 --- /dev/null +++ b/testsuite/hostname/hostname-i-works @@ -0,0 +1,2 @@ +test x$(hostname -i) = x$(busybox hostname -i) + diff --git a/testsuite/hostname/hostname-s-works b/testsuite/hostname/hostname-s-works new file mode 100644 index 000000000..172b94409 --- /dev/null +++ b/testsuite/hostname/hostname-s-works @@ -0,0 +1 @@ +test x$(hostname -s) = x$(busybox hostname -s) diff --git a/testsuite/hostname/hostname-works b/testsuite/hostname/hostname-works new file mode 100644 index 000000000..f51a406ea --- /dev/null +++ b/testsuite/hostname/hostname-works @@ -0,0 +1 @@ +test x$(hostname) = x$(busybox hostname) diff --git a/testsuite/id/id-g-works b/testsuite/id/id-g-works new file mode 100644 index 000000000..671fc5361 --- /dev/null +++ b/testsuite/id/id-g-works @@ -0,0 +1 @@ +test x$(id -g) = x$(busybox id -g) diff --git a/testsuite/id/id-u-works b/testsuite/id/id-u-works new file mode 100644 index 000000000..2358cb0d7 --- /dev/null +++ b/testsuite/id/id-u-works @@ -0,0 +1 @@ +test x$(id -u) = x$(busybox id -u) diff --git a/testsuite/id/id-un-works b/testsuite/id/id-un-works new file mode 100644 index 000000000..db390e733 --- /dev/null +++ b/testsuite/id/id-un-works @@ -0,0 +1 @@ +test x$(id -un) = x$(busybox id -un) diff --git a/testsuite/id/id-ur-works b/testsuite/id/id-ur-works new file mode 100644 index 000000000..6b0fcb038 --- /dev/null +++ b/testsuite/id/id-ur-works @@ -0,0 +1 @@ +test x$(id -ur) = x$(busybox id -ur) diff --git a/testsuite/ln/ln-creates-hard-links b/testsuite/ln/ln-creates-hard-links new file mode 100644 index 000000000..2f6e23f9a --- /dev/null +++ b/testsuite/ln/ln-creates-hard-links @@ -0,0 +1,4 @@ +echo file number one > file1 +busybox ln file1 link1 +test -f file1 +test -f link1 diff --git a/testsuite/ln/ln-creates-soft-links b/testsuite/ln/ln-creates-soft-links new file mode 100644 index 000000000..e875e4c8a --- /dev/null +++ b/testsuite/ln/ln-creates-soft-links @@ -0,0 +1,4 @@ +echo file number one > file1 +busybox ln -s file1 link1 +test -L link1 +test xfile1 = x`readlink link1` diff --git a/testsuite/ln/ln-force-creates-hard-links b/testsuite/ln/ln-force-creates-hard-links new file mode 100644 index 000000000..c96b7d6cf --- /dev/null +++ b/testsuite/ln/ln-force-creates-hard-links @@ -0,0 +1,5 @@ +echo file number one > file1 +echo file number two > link1 +busybox ln -f file1 link1 +test -f file1 +test -f link1 diff --git a/testsuite/ln/ln-force-creates-soft-links b/testsuite/ln/ln-force-creates-soft-links new file mode 100644 index 000000000..cab8d1db7 --- /dev/null +++ b/testsuite/ln/ln-force-creates-soft-links @@ -0,0 +1,5 @@ +echo file number one > file1 +echo file number two > link1 +busybox ln -f -s file1 link1 +test -L link1 +test xfile1 = x`readlink link1` diff --git a/testsuite/ln/ln-preserves-hard-links b/testsuite/ln/ln-preserves-hard-links new file mode 100644 index 000000000..47fb98961 --- /dev/null +++ b/testsuite/ln/ln-preserves-hard-links @@ -0,0 +1,8 @@ +echo file number one > file1 +echo file number two > link1 +set +e +busybox ln file1 link1 +if [ $? != 0 ] ; then + exit 0; +fi +exit 1; diff --git a/testsuite/ln/ln-preserves-soft-links b/testsuite/ln/ln-preserves-soft-links new file mode 100644 index 000000000..a8123ece3 --- /dev/null +++ b/testsuite/ln/ln-preserves-soft-links @@ -0,0 +1,9 @@ +echo file number one > file1 +echo file number two > link1 +set +e +busybox ln -s file1 link1 +if [ $? != 0 ] ; then + exit 0; +fi +exit 1; + diff --git a/testsuite/ls/ls-1-works b/testsuite/ls/ls-1-works new file mode 100644 index 000000000..8ad484fc3 --- /dev/null +++ b/testsuite/ls/ls-1-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +ls -1 "$d" > logfile.gnu +busybox ls -1 "$d" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/ls/ls-h-works b/testsuite/ls/ls-h-works new file mode 100644 index 000000000..7331262c9 --- /dev/null +++ b/testsuite/ls/ls-h-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +ls -h "$d" > logfile.gnu +busybox ls -h "$d" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/ls/ls-l-works b/testsuite/ls/ls-l-works new file mode 100644 index 000000000..efc2b196e --- /dev/null +++ b/testsuite/ls/ls-l-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +LC_ALL=C ls -l "$d" > logfile.gnu +busybox ls -l "$d" > logfile.bb +diff -w logfile.gnu logfile.bb diff --git a/testsuite/ls/ls-s-works b/testsuite/ls/ls-s-works new file mode 100644 index 000000000..6c8bf3627 --- /dev/null +++ b/testsuite/ls/ls-s-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +LC_ALL=C ls -1s "$d" > logfile.gnu +busybox ls -1s "$d" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/md5sum/md5sum-verifies-non-binary-file b/testsuite/md5sum/md5sum-verifies-non-binary-file new file mode 100644 index 000000000..8566a234d --- /dev/null +++ b/testsuite/md5sum/md5sum-verifies-non-binary-file @@ -0,0 +1,3 @@ +touch foo +md5sum foo > bar +busybox md5sum -c bar diff --git a/testsuite/mkdir/mkdir-makes-a-directory b/testsuite/mkdir/mkdir-makes-a-directory new file mode 100644 index 000000000..6ca5c4d52 --- /dev/null +++ b/testsuite/mkdir/mkdir-makes-a-directory @@ -0,0 +1,2 @@ +busybox mkdir foo +test -d foo diff --git a/testsuite/mkdir/mkdir-makes-parent-directories b/testsuite/mkdir/mkdir-makes-parent-directories new file mode 100644 index 000000000..992facb46 --- /dev/null +++ b/testsuite/mkdir/mkdir-makes-parent-directories @@ -0,0 +1,2 @@ +busybox mkdir -p foo/bar +test -d foo -a -d foo/bar diff --git a/testsuite/mount.testroot b/testsuite/mount.testroot new file mode 100755 index 000000000..e18d0461d --- /dev/null +++ b/testsuite/mount.testroot @@ -0,0 +1,183 @@ +#!/bin/sh + +# SUSv3 compliant mount and umount tests. +# Copyright 2005 by Rob Landley <rob@landley.net> +# Licensed under GPL v2, see file LICENSE for details. + +if [ -z "$TESTDIR" ] +then + echo 'Need $TESTDIR' + exit 1 +fi + +cd "$TESTDIR" + +. testing.sh + +# If we aren't PID 1, barf. + +#if [ $$ -ne 1 ] +#then +# echo "SKIPPED: mount test requires emulation environment" +# exit 0 +#fi + +# Run tests within the chroot environment. +dochroot bash rm ls ln cat ps mknod mkdir dd grep cmp diff tail \ + mkfs.ext2 mkfs.vfat mount umount losetup wc << EOF +#!/bin/bash + +. /testing.sh + +mknod /dev/loop0 b 7 0 +mknod /dev/loop1 b 7 1 + +# We need /proc to do much. Make sure we can mount it explicitly. + +testing "mount no proc [GNUFAIL]" "mount 2> /dev/null || echo yes" "yes\n" "" "" +testing "mount /proc" "mount -t proc /proc /proc && ls -d /proc/1" \ + "/proc/1\n" "" "" + +# Make sure the last thing in the list is /proc + +testing "mount list1" "mount | tail -n 1" "/proc on /proc type proc (rw)\n" \ + "" "" + + +# Create an ext2 image + +mkdir -p images/{ext2.dir,vfat.dir,test1,test2,test3} +dd if=/dev/zero of=images/ext2.img bs=1M count=1 2> /dev/null +mkfs.ext2 -F -b 1024 images/ext2.img > /dev/null 2> /dev/null +dd if=/dev/zero of=images/vfat.img bs=1M count=1 2> /dev/null +mkfs.vfat images/vfat.img > /dev/null + +# Test mount it + +testing "mount vfat image (explicitly autodetect type)" \ + "mount -t auto images/vfat.img images/vfat.dir && mount | tail -n 1 | grep -o 'vfat.dir type vfat'" \ + "vfat.dir type vfat\n" "" "" +testing "mount ext2 image (autodetect type)" \ + "mount images/ext2.img images/ext2.dir 2> /dev/null && mount | tail -n 1" \ + "/dev/loop1 on /images/ext2.dir type ext2 (rw)\n" "" "" +testing "mount remount ext2 image noatime" \ + "mount -o remount,noatime images/ext2.dir && mount | tail -n 1" \ + "/dev/loop1 on /images/ext2.dir type ext2 (rw,noatime)\n" "" "" +testing "mount remount ext2 image ro remembers noatime" \ + "mount -o remount,ro images/ext2.dir && mount | tail -n 1" \ + "/dev/loop1 on /images/ext2.dir type ext2 (ro,noatime)\n" "" "" + +umount -d images/vfat.dir +umount -d images/ext2.dir + +testing "mount umount freed loop device" \ + "mount images/ext2.img images/ext2.dir && mount | tail -n 1" \ + "/dev/loop0 on /images/ext2.dir type ext2 (rw)\n" "" "" + +testing "mount block device" \ + "mount -t ext2 /dev/loop0 images/test1 && mount | tail -n 1" \ + "/dev/loop0 on /images/test1 type ext2 (rw)\n" "" "" + +umount -d images/ext2.dir images/test1 + +testing "mount remount nonexistent directory" \ + "mount -o remount,noatime images/ext2.dir 2> /dev/null || echo yes" \ + "yes\n" "" "" + +# Fun with mount -a + +testing "mount -a no fstab" "mount -a 2>/dev/null || echo yes" "yes\n" "" "" + +umount /proc + +# The first field is space delimited, the rest tabs. + +cat > /etc/fstab << FSTAB +/proc /proc proc defaults 0 0 +# Autodetect loop, and provide flags with commas in them. +/images/ext2.img /images/ext2.dir ext2 noatime,nodev 0 0 +# autodetect filesystem, flags without commas. +/images/vfat.img /images/vfat.dir auto ro 0 0 +# A block device +/dev/loop2 /images/test1 auto defaults 0 0 +# tmpfs, filesystem specific flag. +walrus /images/test2 tmpfs size=42 0 0 +# Autodetect a bind mount. +/images/test2 /images/test3 auto defaults 0 0 +FSTAB + +# Put something on loop2. +mknod /dev/loop2 b 7 2 +cat images/ext2.img > images/ext2-2.img +losetup /dev/loop2 images/ext2-2.img + +testing "mount -a" "mount -a && echo hello > /images/test2/abc && cat /images/test3/abc && (mount | wc -l)" "hello\n8\n" "" "" + +testing "umount -a" "umount -a && ls /proc" "" "" "" + +#/bin/bash < /dev/tty > /dev/tty 2> /dev/tty +mknod /dev/console c 5 1 +/bin/bash < /dev/console > /dev/console 2> /dev/console +EOF + +exit 0 + +# Run some tests + +losetup nonexistent device (should return error 2) +losetup unbound loop device (should return error 1) +losetup bind file to loop device +losetup bound loop device (display) (should return error 0) +losetup filename (error) +losetup nofile (file not found) +losetup -d +losetup bind with offset +losetup -f (print first loop device) +losetup -f filename (associate file with first loop device) +losetup -o (past end of file) -f filename + +mount -a + with multiple entries in fstab + with duplicate entries in fstab + with relative paths in fstab + with user entries in fstab +mount -o async,sync,atime,noatime,dev,nodev,exec,noexec,loop,suid,nosuid,remount,ro,rw,bind,move +mount -r +mount -o rw -r +mount -w -o ro +mount -t auto + +mount with relative path in fstab +mount block device +mount char device +mount file (autoloop) +mount directory (autobind) + + +testing "umount with no /proc" +testing "umount curdir" + +# The basic tests. These should work even with the small busybox. + +testing "sort" "input" "a\nb\nc\n" "c\na\nb\n" "" +testing "sort #2" "input" "010\n1\n3\n" "3\n1\n010\n" "" +testing "sort stdin" "" "a\nb\nc\n" "" "b\na\nc\n" +testing "sort numeric" "-n input" "1\n3\n010\n" "3\n1\n010\n" "" +testing "sort reverse" "-r input" "wook\nwalrus\npoint\npabst\naargh\n" \ + "point\nwook\npabst\naargh\nwalrus\n" "" + +optional FEATURE_MOUNT_LOOP +testing "umount -D" + +optional FEATURE_MTAB_SUPPORT +optional FEATURE_MOUNT_NFS +# No idea what to test here. + +optional UMOUNT +optional FEATURE_UMOUNT_ALL +testing "umount -a" +testing "umount -r" +testing "umount -l" +testing "umount -f" + +exit $FAILCOUNT diff --git a/testsuite/msh/msh-supports-underscores-in-variable-names b/testsuite/msh/msh-supports-underscores-in-variable-names new file mode 100644 index 000000000..9c7834b37 --- /dev/null +++ b/testsuite/msh/msh-supports-underscores-in-variable-names @@ -0,0 +1 @@ +test "`busybox msh -c 'FOO_BAR=foo; echo $FOO_BAR'`" = foo diff --git a/testsuite/mv/mv-files-to-dir b/testsuite/mv/mv-files-to-dir new file mode 100644 index 000000000..c8eaba88e --- /dev/null +++ b/testsuite/mv/mv-files-to-dir @@ -0,0 +1,16 @@ +echo file number one > file1 +echo file number two > file2 +ln -s file2 link1 +mkdir dir1 +touch --date='Sat Jan 29 21:24:08 PST 2000' dir1/file3 +mkdir there +busybox mv file1 file2 link1 dir1 there +test -f there/file1 +test -f there/file2 +test -f there/dir1/file3 +test -L there/link1 +test xfile2 = x`readlink there/link1` +test ! -e file1 +test ! -e file2 +test ! -e link1 +test ! -e dir1/file3 diff --git a/testsuite/mv/mv-follows-links b/testsuite/mv/mv-follows-links new file mode 100644 index 000000000..1fb355b81 --- /dev/null +++ b/testsuite/mv/mv-follows-links @@ -0,0 +1,4 @@ +touch foo +ln -s foo bar +busybox mv bar baz +test -f baz diff --git a/testsuite/mv/mv-moves-empty-file b/testsuite/mv/mv-moves-empty-file new file mode 100644 index 000000000..48afca4d5 --- /dev/null +++ b/testsuite/mv/mv-moves-empty-file @@ -0,0 +1,4 @@ +touch foo +busybox mv foo bar +test ! -e foo +test -f bar diff --git a/testsuite/mv/mv-moves-file b/testsuite/mv/mv-moves-file new file mode 100644 index 000000000..edb4c3751 --- /dev/null +++ b/testsuite/mv/mv-moves-file @@ -0,0 +1,3 @@ +touch foo +busybox mv foo bar +test ! -f foo -a -f bar diff --git a/testsuite/mv/mv-moves-hardlinks b/testsuite/mv/mv-moves-hardlinks new file mode 100644 index 000000000..eaa8215a4 --- /dev/null +++ b/testsuite/mv/mv-moves-hardlinks @@ -0,0 +1,4 @@ +touch foo +ln foo bar +busybox mv bar baz +test ! -f bar -a -f baz diff --git a/testsuite/mv/mv-moves-large-file b/testsuite/mv/mv-moves-large-file new file mode 100644 index 000000000..77d088ff1 --- /dev/null +++ b/testsuite/mv/mv-moves-large-file @@ -0,0 +1,4 @@ +dd if=/dev/zero of=foo seek=10k count=1 2>/dev/null +busybox mv foo bar +test ! -e foo +test -f bar diff --git a/testsuite/mv/mv-moves-small-file b/testsuite/mv/mv-moves-small-file new file mode 100644 index 000000000..065c7f1e9 --- /dev/null +++ b/testsuite/mv/mv-moves-small-file @@ -0,0 +1,4 @@ +echo I WANT > foo +busybox mv foo bar +test ! -e foo +test -f bar diff --git a/testsuite/mv/mv-moves-symlinks b/testsuite/mv/mv-moves-symlinks new file mode 100644 index 000000000..c413af07c --- /dev/null +++ b/testsuite/mv/mv-moves-symlinks @@ -0,0 +1,6 @@ +touch foo +ln -s foo bar +busybox mv bar baz +test -f foo +test ! -e bar +test -L baz diff --git a/testsuite/mv/mv-moves-unreadable-files b/testsuite/mv/mv-moves-unreadable-files new file mode 100644 index 000000000..bc9c3133c --- /dev/null +++ b/testsuite/mv/mv-moves-unreadable-files @@ -0,0 +1,5 @@ +touch foo +chmod a-r foo +busybox mv foo bar +test ! -e foo +test -f bar diff --git a/testsuite/mv/mv-preserves-hard-links b/testsuite/mv/mv-preserves-hard-links new file mode 100644 index 000000000..b3ba3aa29 --- /dev/null +++ b/testsuite/mv/mv-preserves-hard-links @@ -0,0 +1,6 @@ +# FEATURE: CONFIG_FEATURE_PRESERVE_HARDLINKS +touch foo +ln foo bar +mkdir baz +busybox mv foo bar baz +test baz/foo -ef baz/bar diff --git a/testsuite/mv/mv-preserves-links b/testsuite/mv/mv-preserves-links new file mode 100644 index 000000000..ea565d2f1 --- /dev/null +++ b/testsuite/mv/mv-preserves-links @@ -0,0 +1,5 @@ +touch foo +ln -s foo bar +busybox mv bar baz +test -L baz +test xfoo = x`readlink baz` diff --git a/testsuite/mv/mv-refuses-mv-dir-to-subdir b/testsuite/mv/mv-refuses-mv-dir-to-subdir new file mode 100644 index 000000000..7c572c4f8 --- /dev/null +++ b/testsuite/mv/mv-refuses-mv-dir-to-subdir @@ -0,0 +1,23 @@ +echo file number one > file1 +echo file number two > file2 +ln -s file2 link1 +mkdir dir1 +touch --date='Sat Jan 29 21:24:08 PST 2000' dir1/file3 +mkdir there +busybox mv file1 file2 link1 dir1 there +test -f there/file1 +test -f there/file2 +test -f there/dir1/file3 +test -L there/link1 +test xfile2 = x`readlink there/link1` +test ! -e file1 +test ! -e file2 +test ! -e link1 +test ! -e dir1/file3 +set +e +busybox mv there there/dir1 +if [ $? != 0 ] ; then + exit 0; +fi + +exit 1; diff --git a/testsuite/mv/mv-removes-source-file b/testsuite/mv/mv-removes-source-file new file mode 100644 index 000000000..48afca4d5 --- /dev/null +++ b/testsuite/mv/mv-removes-source-file @@ -0,0 +1,4 @@ +touch foo +busybox mv foo bar +test ! -e foo +test -f bar diff --git a/testsuite/pidof.tests b/testsuite/pidof.tests new file mode 100755 index 000000000..bfde26e2c --- /dev/null +++ b/testsuite/pidof.tests @@ -0,0 +1,29 @@ +#!/bin/sh + +# pidof tests. +# Copyright 2005 by Bernhard Fischer +# Licensed under GPL v2, see file LICENSE for details. + +# AUDIT: + +. testing.sh + +# testing "test name" "options" "expected result" "file input" "stdin" + +testing "pidof (exit with error)" \ + "pidof veryunlikelyoccuringbinaryname ; echo \$?" "1\n" "" "" +testing "pidof (exit with success)" "pidof pidof > /dev/null; echo \$?" \ + "0\n" "" "" +# We can get away with this because it says #!/bin/sh up top. + +testing "pidof this" "pidof pidof.tests | grep -o -w $$" "$$\n" "" "" + +optional FEATURE_PIDOF_SINGLE +testing "pidof -s" "pidof -s init" "1\n" "" "" + +optional FEATURE_PIDOF_OMIT +testing "pidof -o %PPID" "pidof -o %PPID pidof.tests | grep -o -w $$" "" "" "" +testing "pidof -o %PPID NOP" "pidof -o %PPID -s init" "1\n" "" "" +testing "pidof -o init" "pidof -o 1 init | grep -o -w 1" "" "" "" + +exit $FAILCOUNT diff --git a/testsuite/pwd/pwd-prints-working-directory b/testsuite/pwd/pwd-prints-working-directory new file mode 100644 index 000000000..8575347d6 --- /dev/null +++ b/testsuite/pwd/pwd-prints-working-directory @@ -0,0 +1 @@ +test $(pwd) = $(busybox pwd) diff --git a/testsuite/readlink.tests b/testsuite/readlink.tests new file mode 100755 index 000000000..0faa6ed0c --- /dev/null +++ b/testsuite/readlink.tests @@ -0,0 +1,32 @@ +#!/bin/sh + +# Readlink tests. +# Copyright 2006 by Natanael Copa <n@tanael.org> +# Licensed under GPL v2, see file LICENSE for details. + +. testing.sh + +TESTDIR=readlink_testdir +TESTFILE="$TESTDIR/testfile" +TESTLINK="testlink" +FAILLINK="$TESTDIR/$TESTDIR/testlink" + +# create the dir and test files +mkdir -p "./$TESTDIR" +touch "./$TESTFILE" +ln -s "./$TESTFILE" "./$TESTLINK" + +testing "readlink on a file" "readlink ./$TESTFILE" "" "" "" +testing "readlink on a link" "readlink ./$TESTLINK" "./$TESTFILE\n" "" "" + +optional FEATURE_READLINK_FOLLOW + +testing "readlink -f on a file" "readlink -f ./$TESTFILE" "$PWD/$TESTFILE\n" "" "" +testing "readlink -f on a link" "readlink -f ./$TESTLINK" "$PWD/$TESTFILE\n" "" "" +testing "readlink -f on an invalid link" "readlink -f ./$FAILLINK" "" "" "" +testing "readlink -f on a wierd dir" "readlink -f $TESTDIR/../$TESTFILE" "$PWD/$TESTFILE\n" "" "" + + +# clean up +rm -r "$TESTLINK" "$TESTDIR" + diff --git a/testsuite/rm/rm-removes-file b/testsuite/rm/rm-removes-file new file mode 100644 index 000000000..46571a98a --- /dev/null +++ b/testsuite/rm/rm-removes-file @@ -0,0 +1,3 @@ +touch foo +busybox rm foo +test ! -f foo diff --git a/testsuite/rmdir/rmdir-removes-parent-directories b/testsuite/rmdir/rmdir-removes-parent-directories new file mode 100644 index 000000000..222f5dec7 --- /dev/null +++ b/testsuite/rmdir/rmdir-removes-parent-directories @@ -0,0 +1,3 @@ +mkdir -p foo/bar +busybox rmdir -p foo/bar +test ! -d foo diff --git a/testsuite/runtest b/testsuite/runtest new file mode 100755 index 000000000..84cd6a7f3 --- /dev/null +++ b/testsuite/runtest @@ -0,0 +1,140 @@ +#!/bin/sh + +[ -n "$srcdir" ] || srcdir=$(pwd) +[ -n "$bindir" ] || bindir=$(dirname $(pwd)) +PATH=$bindir:$PATH + +# Run old-style test. + +function run_applet_testcase +{ + local applet=$1 + local testcase=$2 + + local status=0 + local RES= + + local uc_applet=$(echo $applet | tr a-z A-Z) + local testname=$(basename $testcase) + + if grep -q "^# CONFIG_${uc_applet} is not set$" $bindir/.config; then + echo UNTESTED: $testname + return 0 + fi + + if grep -q "^# FEATURE: " $testcase; then + local feature=`sed -ne 's/^# FEATURE: //p' $testcase` + + if grep -q "^# ${feature} is not set$" $bindir/.config; then + echo UNTESTED: $testname + return 0 + fi + fi + + rm -rf tmp + mkdir -p tmp + pushd tmp > /dev/null + + d=$srcdir sh -x -e $testcase >.logfile.txt 2>&1 || status=$? + + if [ $status -ne 0 ] ; then + echo FAIL: $testname + if [ $verbose -gt 0 ]; then + cat .logfile.txt + fi + status=$? + else + echo PASS: $testname + rm -f .logfile.txt + status=$? + fi + + popd > /dev/null + rm -rf tmp + + return $status +} + +run_applet_tests () +{ + local applet=$1 + + local status=0 + + for testcase in $srcdir/$applet/*; do + if [ "$testcase" = "$srcdir/$applet/CVS" ]; then + continue + fi + + if run_applet_testcase $applet $testcase; then + : + else + status=1 + fi + done + + return $status +} + + +status=0 +verbose=0 + +if [ x"$1" = x"-v" ]; then + verbose=1 + export VERBOSE=$verbose + shift +fi + +if [ $# -ne 0 ]; then + applets=$(cd $srcdir ; for i in $@; do ls ${i}* ; done) +else + applets=$(ls $srcdir) +fi + +# Populate a directory with links to all busybox applets + +LINKSDIR="${bindir}/runtest-tempdir-links" +rm -rf "$LINKSDIR" 2>/dev/null +mkdir "$LINKSDIR" +for i in $(sed 's@/[a-z0-9/\[]*/@@' $bindir/busybox.links 2>/dev/null) +do + ln -s $bindir/busybox "$LINKSDIR"/$i +done + +# Set up option flags so tests can be selective. + +configfile=${bindir:-../../}/.config +export OPTIONFLAGS=:$(echo $(sed -nr 's/^CONFIG_(.*)=.*/\1/p' $configfile) | sed 's/ /:/g') + +for applet in $applets; do + if [ "$applet" = "links" ]; then continue; fi + if [ "$applet" != "CVS" -a -d "$srcdir/$applet" ]; then + if run_applet_tests $applet; then + : + else + status=1 + fi + fi + + # Is this a new-style test? + applet=$(echo "$applet" | sed -n 's/\.tests$//p') + if [ ${#applet} -ne 0 ] + then + if [ ! -h "$LINKSDIR/$applet" ] && [ "${applet:0:4}" != "all_" ] + then + echo "SKIPPED: $applet (not built)" + continue + fi + if PATH="$LINKSDIR":$srcdir:$bindir:$PATH \ + "${srcdir:-.}/$applet".tests + then + : + else + status=1 + fi + fi + +done +rm -rf "$LINKSDIR" +exit $status diff --git a/testsuite/sed.tests b/testsuite/sed.tests new file mode 100755 index 000000000..2a0d4eacf --- /dev/null +++ b/testsuite/sed.tests @@ -0,0 +1,187 @@ +#!/bin/sh + +# SUSv3 compliant sed tests. +# Copyright 2005 by Rob Landley <rob@landley.net> +# Licensed under GPL v2, see file LICENSE for details. + +. testing.sh + +# testing "description" "arguments" "result" "infile" "stdin" + +# Corner cases +testing "sed no files (stdin)" 'sed ""' "hello\n" "" "hello\n" +testing "sed explicit stdin" 'sed "" -' "hello\n" "" "hello\n" +testing "sed handles empty lines" "sed -e 's/\$/@/'" "@\n" "" "\n" +testing "sed stdin twice" 'sed "" - -' "hello" "" "hello" + +# Trailing EOF. +# Match $, at end of each file or all files? + +# -e corner cases +# without -e +# multiple -e +# interact with a +# -eee arg1 arg2 arg3 +# -f corner cases +# -e -f -e +# -n corner cases +# no newline at EOF? +# -r corner cases +# Just make sure it works. +# -i corner cases: +# sed -i - +# permissions +# -i on a symlink +# on a directory +# With $ last-line test +# Continue with \ +# End of script with trailing \ + +# command list +testing "sed accepts blanks before command" "sed -e '1 d'" "" "" "" +testing "sed accepts newlines in -e" "sed -e 'i\ +1 +a\ +3'" "1\n2\n3\n" "" "2\n" +testing "sed accepts multiple -e" "sed -e 'i\' -e '1' -e 'a\' -e '3'" \ + "1\n2\n3\n" "" "2\n" + +# substitutions +testing "sed -n" "sed -n -e s/foo/bar/ -e s/bar/baz/" "" "" "foo\n" +testing "sed s//p" "sed -e s/foo/bar/p -e s/bar/baz/p" "bar\nbaz\nbaz\n" \ + "" "foo\n" +testing "sed -n s//p" "sed -ne s/abc/def/p" "def\n" "" "abc\n" +testing "sed s//g (exhaustive)" "sed -e 's/[[:space:]]*/,/g'" ",1,2,3,4,5,\n" \ + "" "12345\n" +testing "sed s arbitrary delimiter" "sed -e 's woo boing '" "boing\n" "" "woo\n" +testing "sed s chains" "sed -e s/foo/bar/ -e s/bar/baz/" "baz\n" "" "foo\n" +testing "sed s chains2" "sed -e s/foo/bar/ -e s/baz/nee/" "bar\n" "" "foo\n" +testing "sed s [delimiter]" "sed -e 's@[@]@@'" "onetwo" "" "one@two" + +# branch +testing "sed b (branch)" "sed -e 'b one;p;: one'" "foo\n" "" "foo\n" +testing "sed b (branch with no label jumps to end)" "sed -e 'b;p'" \ + "foo\n" "" "foo\n" + +# test and branch +testing "sed t (test/branch)" "sed -e 's/a/1/;t one;p;: one;p'" \ + "1\n1\nb\nb\nb\nc\nc\nc\n" "" "a\nb\nc\n" +testing "sed t (test/branch clears test bit)" "sed -e 's/a/b/;:loop;t loop'" \ + "b\nb\nc\n" "" "a\nb\nc\n" +testing "sed T (!test/branch)" "sed -e 's/a/1/;T notone;p;: notone;p'" \ + "1\n1\n1\nb\nb\nc\nc\n" "" "a\nb\nc\n" + +# Normal sed end-of-script doesn't print "c" because n flushed the pattern +# space. If n hits EOF, pattern space is empty when script ends. +# Query: how does this interact with no newline at EOF? +testing "sed n (flushes pattern space, terminates early)" "sed -e 'n;p'" \ + "a\nb\nb\nc\n" "" "a\nb\nc\n" +# N does _not_ flush pattern space, therefore c is still in there @ script end. +testing "sed N (doesn't flush pattern space when terminating)" "sed -e 'N;p'" \ + "a\nb\na\nb\nc\n" "" "a\nb\nc\n" +testing "sed address match newline" 'sed "/b/N;/b\\nc/i woo"' \ + "a\nwoo\nb\nc\nd\n" "" "a\nb\nc\nd\n" + +# Multiple lines in pattern space +testing "sed N (stops at end of input) and P (prints to first newline only)" \ + "sed -n 'N;P;p'" "a\na\nb\n" "" "a\nb\nc\n" + +# Hold space +testing "sed G (append hold space to pattern space)" 'sed G' "a\n\nb\n\nc\n\n" \ + "" "a\nb\nc\n" +#testing "sed g/G (swap/append hold and patter space)" +#testing "sed g (swap hold/pattern space)" + +testing "sed d ends script iteration" \ + "sed -e '/ook/d;s/ook/ping/p;i woot'" "" "" "ook\n" +testing "sed d ends script iteration (2)" \ + "sed -e '/ook/d;a\' -e 'bang'" "woot\nbang\n" "" "ook\nwoot\n" + +# Multiple files, with varying newlines and NUL bytes +testing "sed embedded NUL" "sed -e 's/woo/bang/'" "\0bang\0woo\0" "" \ + "\0woo\0woo\0" +testing "sed embedded NUL g" "sed -e 's/woo/bang/g'" "bang\0bang\0" "" \ + "woo\0woo\0" +echo -e "/woo/a he\0llo" > sed.commands +testing "sed NUL in command" "sed -f sed.commands" "woo\nhe\0llo\n" "" "woo" +rm sed.commands + +# sed has funky behavior with newlines at the end of file. Test lots of +# corner cases with the optional newline appending behavior. + +testing "sed normal newlines" "sed -e 's/woo/bang/' input -" "bang\nbang\n" \ + "woo\n" "woo\n" +testing "sed leave off trailing newline" "sed -e 's/woo/bang/' input -" \ + "bang\nbang" "woo\n" "woo" +testing "sed autoinsert newline" "sed -e 's/woo/bang/' input -" "bang\nbang" \ + "woo" "woo" +testing "sed empty file plus cat" "sed -e 's/nohit//' input -" "one\ntwo" \ + "" "one\ntwo" +testing "sed cat plus empty file" "sed -e 's/nohit//' input -" "one\ntwo" \ + "one\ntwo" "" +testing "sed append autoinserts newline" "sed -e '/woot/a woo' -" \ + "woot\nwoo\n" "" "woot" +testing "sed insert doesn't autoinsert newline" "sed -e '/woot/i woo' -" \ + "woo\nwoot" "" "woot" +testing "sed print autoinsert newlines" "sed -e 'p' -" "one\none" "" "one" +testing "sed print autoinsert newlines two files" "sed -e 'p' input -" \ + "one\none\ntwo\ntwo" "one" "two" +testing "sed noprint, no match, no newline" "sed -ne 's/woo/bang/' input" \ + "" "no\n" "" +testing "sed selective matches with one nl" "sed -ne 's/woo/bang/p' input -" \ + "a bang\nc bang\n" "a woo\nb no" "c woo\nd no" +testing "sed selective matches insert newline" \ + "sed -ne 's/woo/bang/p' input -" "a bang\nb bang\nd bang" \ + "a woo\nb woo" "c no\nd woo" +testing "sed selective matches noinsert newline" \ + "sed -ne 's/woo/bang/p' input -" "a bang\nb bang" "a woo\nb woo" \ + "c no\nd no" +testing "sed clusternewline" \ + "sed -e '/one/a 111' -e '/two/i 222' -e p input -" \ + "one\none\n111\n222\ntwo\ntwo" "one" "two" + +# Test end-of-file matching behavior + +testing "sed match EOF" "sed -e '"'$p'"'" "hello\nthere\nthere" "" \ + "hello\nthere" +testing "sed match EOF two files" "sed -e '"'$p'"' input -" \ + "one\ntwo\nthree\nfour\nfour" "one\ntwo" "three\nfour" +echo -ne "three\nfour" > input2 +testing "sed match EOF inline" \ + "sed -e '"'$i ook'"' -i input input2 && cat input input2" \ + "one\nook\ntwothree\nook\nfour" "one\ntwo" "" +rm input2 + +# Test lie-to-autoconf + +testing "sed lie-to-autoconf" "sed --version | grep -o 'GNU sed version '" \ + "GNU sed version \n" "" "" + +# Jump to nonexistent label +testing "sed nonexistent label" "sed -e 'b walrus' 2> /dev/null || echo yes" \ + "yes\n" "" "" + +testing "sed backref from empty s uses range regex" \ + "sed -e '/woot/s//eep \0 eep/'" "eep woot eep" "" "woot" + +testing "sed backref from empty s uses range regex with newline" \ + "sed -e '/woot/s//eep \0 eep/'" "eep woot eep\n" "" "woot\n" + +# -i with no filename + +touch ./- # Detect gnu failure mode here. +testing "sed -i with no arg [GNUFAIL]" "sed -e '' -i 2> /dev/null || echo yes" \ + "yes\n" "" "" +rm ./- # Clean up + +testing "sed s/xxx/[/" "sed -e 's/xxx/[/'" "[\n" "" "xxx\n" + +# Ponder this a bit more, why "woo not found" from gnu version? +#testing "sed doesn't substitute in deleted line" \ +# "sed -e '/ook/d;s/ook//;t woo;a bang;'" "bang" "" "ook\n" + +# This makes both seds very unhappy. Why? +#testing "sed -g (exhaustive)" "sed -e 's/[[:space:]]*/,/g'" ",1,2,3,4,5," \ +# "" "12345" + +exit $FAILCOUNT diff --git a/testsuite/seq.tests b/testsuite/seq.tests new file mode 100755 index 000000000..ebb44e7a6 --- /dev/null +++ b/testsuite/seq.tests @@ -0,0 +1,36 @@ +#!/bin/sh + +# SUSv3 compliant seq tests. +# Copyright 2006 by Rob Landley <rob@landley.net> +# Licensed under GPL v2, see file LICENSE for details. + +# AUDIT: Full SUSv3 coverage (except internationalization). + +. testing.sh + +# testing "test name" "options" "expected result" "file input" "stdin" +# file input will be file called "input" +# test can create a file "actual" instead of writing to stdout + +# Test exit status + +testing "seq (exit with error)" "seq 2> /dev/null || echo yes" "yes\n" "" "" +testing "seq (exit with error)" "seq 1 2 3 4 2> /dev/null || echo yes" \ + "yes\n" "" "" +testing "seq one argument" "seq 3" "1\n2\n3\n" "" "" +testing "seq two arguments" "seq 5 7" "5\n6\n7\n" "" "" +testing "seq two arguments reversed" "seq 7 5" "" "" "" +testing "seq two arguments equal" "seq 3 3" "3\n" "" "" +testing "seq two arguments equal, arbitrary negative step" "seq 1 -15 1" \ + "1\n" "" "" +testing "seq two arguments equal, arbitrary positive step" "seq 1 +15 1" \ + "1\n" "" "" +testing "seq count up by 2" "seq 4 2 8" "4\n6\n8\n" "" "" +testing "seq count down by 2" "seq 8 -2 4" "8\n6\n4\n" "" "" +testing "seq count wrong way #1" "seq 4 -2 8" "" "" "" +testing "seq count wrong way #2" "seq 8 2 4" "" "" "" +testing "seq count by .3" "seq 3 .3 4" "3\n3.3\n3.6\n3.9\n" "" "" +testing "seq count by -.9" "seq .7 -.9 -2.2" "0.7\n-0.2\n-1.1\n-2\n" "" "" +testing "seq count by zero" "seq 4 0 8 | head -n 10" "" "" "" + +exit $FAILCOUNT diff --git a/testsuite/sort.tests b/testsuite/sort.tests new file mode 100755 index 000000000..5a4937b58 --- /dev/null +++ b/testsuite/sort.tests @@ -0,0 +1,83 @@ +#!/bin/bash + +# SUSv3 compliant sort tests. +# Copyright 2005 by Rob Landley <rob@landley.net> +# Licensed under GPL v2, see file LICENSE for details. + +. testing.sh + +# The basic tests. These should work even with the small busybox. + +testing "sort" "sort input" "a\nb\nc\n" "c\na\nb\n" "" +testing "sort #2" "sort input" "010\n1\n3\n" "3\n1\n010\n" "" +testing "sort stdin" "sort" "a\nb\nc\n" "" "b\na\nc\n" +testing "sort numeric" "sort -n input" "1\n3\n010\n" "3\n1\n010\n" "" +testing "sort reverse" "sort -r input" "wook\nwalrus\npoint\npabst\naargh\n" \ + "point\nwook\npabst\naargh\nwalrus\n" "" + +# These tests require the full option set. + +optional FEATURE_SORT_BIG +# Longish chunk of data re-used by the next few tests + +data="42 1 3 woot +42 1 010 zoology +egg 1 2 papyrus +7 3 42 soup +999 3 0 algebra +" + +# Sorting with keys + +testing "sort one key" "sort -k4,4 input" \ +"999 3 0 algebra +egg 1 2 papyrus +7 3 42 soup +42 1 3 woot +42 1 010 zoology +" "$data" "" + +testing "sort key range with numeric option" "sort -k2,3n input" \ +"42 1 010 zoology +42 1 3 woot +egg 1 2 papyrus +7 3 42 soup +999 3 0 algebra +" "$data" "" + +# Busybox is definitely doing this one wrong just now. FIXME + +testing "sort key range with numeric option and global reverse" \ +"sort -k2,3n -r input" \ +"egg 1 2 papyrus +42 1 3 woot +42 1 010 zoology +999 3 0 algebra +7 3 42 soup +" "$data" "" + +# + +testing "sort key range with multiple options" "sort -k2,3rn input" \ +"7 3 42 soup +999 3 0 algebra +42 1 010 zoology +42 1 3 woot +egg 1 2 papyrus +" "$data" "" + +testing "sort key doesn't strip leading blanks, disables fallback global sort" \ +"sort -n -k2 -t ' '" " a \n 1 \n 2 \n" "" " 2 \n 1 \n a \n" + +testing "sort key edge case with -t" "sort -n -k4 -t/" \ +"/usr/lib/finish-install.d/1 +/usr/lib/finish-install.d/4 +/usr/lib/prebaseconfig.d/2 +/usr/lib/prebaseconfig.d/6 +" "" "/usr/lib/finish-install.d/1 +/usr/lib/prebaseconfig.d/2 +/usr/lib/finish-install.d/4 +/usr/lib/prebaseconfig.d/6 +" + +exit $FAILCOUNT diff --git a/testsuite/strings/strings-works-like-GNU b/testsuite/strings/strings-works-like-GNU new file mode 100644 index 000000000..2d6471033 --- /dev/null +++ b/testsuite/strings/strings-works-like-GNU @@ -0,0 +1,9 @@ +rm -f foo bar +strings -af ../../busybox > foo +busybox strings -af ../../busybox > bar +set +e +test ! -f foo -a -f bar +if [ $? = 0 ] ; then + set -e + diff -q foo bar +fi diff --git a/testsuite/tail/tail-n-works b/testsuite/tail/tail-n-works new file mode 100644 index 000000000..27a905f88 --- /dev/null +++ b/testsuite/tail/tail-n-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +tail -n 2 "$d/README" > logfile.gnu +busybox tail -n 2 "$d/README" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/tail/tail-works b/testsuite/tail/tail-works new file mode 100644 index 000000000..27a905f88 --- /dev/null +++ b/testsuite/tail/tail-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +tail -n 2 "$d/README" > logfile.gnu +busybox tail -n 2 "$d/README" > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/tar/tar-archives-multiple-files b/testsuite/tar/tar-archives-multiple-files new file mode 100644 index 000000000..245d9e989 --- /dev/null +++ b/testsuite/tar/tar-archives-multiple-files @@ -0,0 +1,6 @@ +# FEATURE: CONFIG_FEATURE_TAR_CREATE +touch foo bar +busybox tar cf foo.tar foo bar +rm foo bar +tar xf foo.tar +test -f foo -a -f bar diff --git a/testsuite/tar/tar-complains-about-missing-file b/testsuite/tar/tar-complains-about-missing-file new file mode 100644 index 000000000..26e8cbb36 --- /dev/null +++ b/testsuite/tar/tar-complains-about-missing-file @@ -0,0 +1,3 @@ +touch foo +tar cf foo.tar foo +! busybox tar xf foo.tar bar diff --git a/testsuite/tar/tar-demands-at-least-one-ctx b/testsuite/tar/tar-demands-at-least-one-ctx new file mode 100644 index 000000000..85e7f6059 --- /dev/null +++ b/testsuite/tar/tar-demands-at-least-one-ctx @@ -0,0 +1 @@ +! busybox tar v diff --git a/testsuite/tar/tar-demands-at-most-one-ctx b/testsuite/tar/tar-demands-at-most-one-ctx new file mode 100644 index 000000000..130d0e7f9 --- /dev/null +++ b/testsuite/tar/tar-demands-at-most-one-ctx @@ -0,0 +1 @@ +! busybox tar tx diff --git a/testsuite/tar/tar-extracts-all-subdirs b/testsuite/tar/tar-extracts-all-subdirs new file mode 100644 index 000000000..886c37ce9 --- /dev/null +++ b/testsuite/tar/tar-extracts-all-subdirs @@ -0,0 +1,12 @@ +# FEATURE: CONFIG_FEATURE_TAR_CREATE +mkdir -p foo/{1,2,3} +mkdir -p foo/1/{10,11} +mkdir -p foo/1/10/{100,101,102} +tar cf foo.tar -C foo . +rm -rf foo/* +busybox tar xf foo.tar -C foo ./1/10 +find foo | sort >logfile.bb +rm -rf foo/* +tar xf foo.tar -C foo ./1/10 +find foo | sort >logfile.gnu +cmp logfile.gnu logfile.bb diff --git a/testsuite/tar/tar-extracts-file b/testsuite/tar/tar-extracts-file new file mode 100644 index 000000000..ca72f2489 --- /dev/null +++ b/testsuite/tar/tar-extracts-file @@ -0,0 +1,5 @@ +touch foo +tar cf foo.tar foo +rm foo +busybox tar xf foo.tar +test -f foo diff --git a/testsuite/tar/tar-extracts-from-standard-input b/testsuite/tar/tar-extracts-from-standard-input new file mode 100644 index 000000000..a30e9f0b9 --- /dev/null +++ b/testsuite/tar/tar-extracts-from-standard-input @@ -0,0 +1,5 @@ +touch foo +tar cf foo.tar foo +rm foo +cat foo.tar | busybox tar x +test -f foo diff --git a/testsuite/tar/tar-extracts-multiple-files b/testsuite/tar/tar-extracts-multiple-files new file mode 100644 index 000000000..8ae8cdda5 --- /dev/null +++ b/testsuite/tar/tar-extracts-multiple-files @@ -0,0 +1,6 @@ +touch foo bar +busybox tar cf foo.tar foo bar +rm foo bar +busybox tar -xf foo.tar +test -f foo +test -f bar diff --git a/testsuite/tar/tar-extracts-to-standard-output b/testsuite/tar/tar-extracts-to-standard-output new file mode 100644 index 000000000..ca48e364e --- /dev/null +++ b/testsuite/tar/tar-extracts-to-standard-output @@ -0,0 +1,3 @@ +echo foo > foo +tar cf foo.tar foo +cat foo.tar | busybox tar Ox | cmp foo - diff --git a/testsuite/tar/tar-handles-cz-options b/testsuite/tar/tar-handles-cz-options new file mode 100644 index 000000000..5b55e46d3 --- /dev/null +++ b/testsuite/tar/tar-handles-cz-options @@ -0,0 +1,5 @@ +# FEATURE: CONFIG_FEATURE_TAR_CREATE +# FEATURE: CONFIG_FEATURE_TAR_GZIP +touch foo +busybox tar czf foo.tar.gz foo +gzip -d foo.tar.gz diff --git a/testsuite/tar/tar-handles-empty-include-and-non-empty-exclude-list b/testsuite/tar/tar-handles-empty-include-and-non-empty-exclude-list new file mode 100644 index 000000000..503364230 --- /dev/null +++ b/testsuite/tar/tar-handles-empty-include-and-non-empty-exclude-list @@ -0,0 +1,6 @@ +# FEATURE: CONFIG_FEATURE_TAR_FROM +# FEATURE: CONFIG_FEATURE_TAR_CREATE +touch foo +tar cf foo.tar foo +echo foo >foo.exclude +busybox tar xf foo.tar -X foo.exclude diff --git a/testsuite/tar/tar-handles-exclude-and-extract-lists b/testsuite/tar/tar-handles-exclude-and-extract-lists new file mode 100644 index 000000000..2de0f0e91 --- /dev/null +++ b/testsuite/tar/tar-handles-exclude-and-extract-lists @@ -0,0 +1,8 @@ +# FEATURE: CONFIG_FEATURE_TAR_FROM +# FEATURE: CONFIG_FEATURE_TAR_CREATE +touch foo bar baz +tar cf foo.tar foo bar baz +echo foo >foo.exclude +rm foo bar baz +busybox tar xf foo.tar foo bar -X foo.exclude +test ! -f foo -a -f bar -a ! -f baz diff --git a/testsuite/tar/tar-handles-multiple-X-options b/testsuite/tar/tar-handles-multiple-X-options new file mode 100644 index 000000000..155b27e68 --- /dev/null +++ b/testsuite/tar/tar-handles-multiple-X-options @@ -0,0 +1,10 @@ +# FEATURE: CONFIG_FEATURE_TAR_FROM +# FEATURE: CONFIG_FEATURE_TAR_CREATE +touch foo +touch bar +tar cf foo.tar foo bar +echo foo > foo.exclude +echo bar > bar.exclude +rm foo bar +busybox tar xf foo.tar -X foo.exclude -X bar.exclude +test ! -f foo -a ! -f bar diff --git a/testsuite/tar/tar-handles-nested-exclude b/testsuite/tar/tar-handles-nested-exclude new file mode 100644 index 000000000..39013a105 --- /dev/null +++ b/testsuite/tar/tar-handles-nested-exclude @@ -0,0 +1,9 @@ +# FEATURE: CONFIG_FEATURE_TAR_FROM +# FEATURE: CONFIG_FEATURE_TAR_CREATE +mkdir foo +touch foo/bar +tar cf foo.tar foo +rm -rf foo +echo foo/bar >foobar.exclude +busybox tar xf foo.tar foo -X foobar.exclude +test -d foo -a ! -f foo/bar diff --git a/testsuite/tee/tee-appends-input b/testsuite/tee/tee-appends-input new file mode 100644 index 000000000..cff20bf7f --- /dev/null +++ b/testsuite/tee/tee-appends-input @@ -0,0 +1,5 @@ +echo i\'m a little teapot >foo +cp foo bar +echo i\'m a little teapot >>foo +echo i\'m a little teapot | busybox tee -a bar >/dev/null +cmp foo bar diff --git a/testsuite/tee/tee-tees-input b/testsuite/tee/tee-tees-input new file mode 100644 index 000000000..26e217384 --- /dev/null +++ b/testsuite/tee/tee-tees-input @@ -0,0 +1,3 @@ +echo i\'m a little teapot >foo +echo i\'m a little teapot | busybox tee bar >baz +cmp foo bar && cmp foo baz diff --git a/testsuite/testing.sh b/testsuite/testing.sh new file mode 100755 index 000000000..e253e1aa6 --- /dev/null +++ b/testsuite/testing.sh @@ -0,0 +1,154 @@ +# Simple test harness infrastructurei for BusyBox +# +# Copyright 2005 by Rob Landley +# +# License is GPLv2, see LICENSE in the busybox tarball for full license text. + +# This file defines two functions, "testing" and "optionflag" + +# The following environment variables may be set to enable optional behavior +# in "testing": +# VERBOSE - Print the diff -u of each failed test case. +# DEBUG - Enable command tracing. +# SKIP - do not perform this test (this is set by "optionflag") +# +# The "testing" function takes five arguments: +# $1) Description to display when running command +# $2) Command line arguments to command +# $3) Expected result (on stdout) +# $4) Data written to file "input" +# $5) Data written to stdin +# +# The exit value of testing is the exit value of the command it ran. +# +# The environment variable "FAILCOUNT" contains a cumulative total of the +# number of failed tests. + +# The "optional" function is used to skip certain tests, ala: +# optionflag CONFIG_FEATURE_THINGY +# +# The "optional" function checks the environment variable "OPTIONFLAGS", +# which is either empty (in which case it always clears SKIP) or +# else contains a colon-separated list of features (in which case the function +# clears SKIP if the flag was found, or sets it to 1 if the flag was not found). + +export FAILCOUNT=0 +export SKIP= + +# Helper functions + +optional() +{ + option=`echo "$OPTIONFLAGS" | egrep "(^|:)$1(:|\$)"` + # Not set? + if [ -z "$1" ] || [ -z "$OPTIONFLAGS" ] || [ ${#option} -ne 0 ] + then + SKIP="" + return + fi + SKIP=1 +} + +# The testing function + +testing () +{ + NAME="$1" + [ -z "$1" ] && NAME=$2 + + if [ $# -ne 5 ] + then + echo "Test $NAME has the wrong number of arguments ($# $*)" >&2 + exit + fi + + [ -n "$DEBUG" ] && set -x + + if [ -n "$SKIP" ] + then + echo "SKIPPED: $NAME" + return 0 + fi + + echo -ne "$3" > expected + echo -ne "$4" > input + [ -z "$VERBOSE" ] || echo "echo '$5' | $2" + echo -ne "$5" | eval "$2" > actual + RETVAL=$? + + cmp expected actual > /dev/null + if [ $? -ne 0 ] + then + FAILCOUNT=$[$FAILCOUNT+1] + echo "FAIL: $NAME" + [ -n "$VERBOSE" ] && diff -u expected actual + else + echo "PASS: $NAME" + fi + rm -f input expected actual + + [ -n "$DEBUG" ] && set +x + + return $RETVAL +} + +# Recursively grab an executable and all the libraries needed to run it. +# Source paths beginning with / will be copied into destpath, otherwise +# the file is assumed to already be there and only its library dependencies +# are copied. + +function mkchroot +{ + [ $# -lt 2 ] && return + + echo -n . + + dest=$1 + shift + for i in "$@" + do + [ "${i:0:1}" == "/" ] || i=$(which $i) + [ -f "$dest/$i" ] && continue + if [ -e "$i" ] + then + d=`echo "$i" | grep -o '.*/'` && + mkdir -p "$dest/$d" && + cat "$i" > "$dest/$i" && + chmod +x "$dest/$i" + else + echo "Not found: $i" + fi + mkchroot "$dest" $(ldd "$i" | egrep -o '/.* ') + done +} + +# Set up a chroot environment and run commands within it. +# Needed commands listed on command line +# Script fed to stdin. + +function dochroot +{ + mkdir tmpdir4chroot + mount -t ramfs tmpdir4chroot tmpdir4chroot + mkdir -p tmpdir4chroot/{etc,sys,proc,tmp,dev} + cp -L testing.sh tmpdir4chroot + + # Copy utilities from command line arguments + + echo -n "Setup chroot" + mkchroot tmpdir4chroot $* + echo + + mknod tmpdir4chroot/dev/tty c 5 0 + mknod tmpdir4chroot/dev/null c 1 3 + mknod tmpdir4chroot/dev/zero c 1 5 + + # Copy script from stdin + + cat > tmpdir4chroot/test.sh + chmod +x tmpdir4chroot/test.sh + chroot tmpdir4chroot /test.sh + umount -l tmpdir4chroot + rmdir tmpdir4chroot +} + diff --git a/testsuite/touch/touch-creates-file b/testsuite/touch/touch-creates-file new file mode 100644 index 000000000..4b4935421 --- /dev/null +++ b/testsuite/touch/touch-creates-file @@ -0,0 +1,2 @@ +busybox touch foo +test -f foo diff --git a/testsuite/touch/touch-does-not-create-file b/testsuite/touch/touch-does-not-create-file new file mode 100644 index 000000000..885259286 --- /dev/null +++ b/testsuite/touch/touch-does-not-create-file @@ -0,0 +1,2 @@ +busybox touch -c foo +test ! -f foo diff --git a/testsuite/touch/touch-touches-files-after-non-existent-file b/testsuite/touch/touch-touches-files-after-non-existent-file new file mode 100644 index 000000000..a869ec267 --- /dev/null +++ b/testsuite/touch/touch-touches-files-after-non-existent-file @@ -0,0 +1,3 @@ +touch -t 198001010000 bar +busybox touch -c foo bar +test x"`find bar -mtime -1`" = xbar diff --git a/testsuite/tr/tr-d-works b/testsuite/tr/tr-d-works new file mode 100644 index 000000000..d939e8b0f --- /dev/null +++ b/testsuite/tr/tr-d-works @@ -0,0 +1,4 @@ +echo testing | tr -d aeiou > logfile.gnu +echo testing | busybox tr -d aeiou > logfile.bb + +cmp logfile.gnu logfile.bb diff --git a/testsuite/tr/tr-non-gnu b/testsuite/tr/tr-non-gnu new file mode 100644 index 000000000..ffa6951ae --- /dev/null +++ b/testsuite/tr/tr-non-gnu @@ -0,0 +1 @@ +echo fdhrnzvfu bffvsentr | busybox tr '[a-z]' '[n-z][a-m]' diff --git a/testsuite/tr/tr-works b/testsuite/tr/tr-works new file mode 100644 index 000000000..9b2e90e42 --- /dev/null +++ b/testsuite/tr/tr-works @@ -0,0 +1,24 @@ +run_tr () +{ + echo -n "echo '$1' | tr '$2' '$3': " + echo "$1" | $bb tr "$2" "$3" + echo +} +tr_test () +{ + run_tr "cbaab" abc zyx + run_tr "TESTING A B C" '[A-Z]' '[a-z]' + run_tr "abc[]" "a[b" AXB + run_tr abc '[:alpha:]' A-ZA-Z + run_tr abc56 '[:alnum:]' A-ZA-Zxxxxxxxxxx + run_tr 012 '[:digit:]' abcdefghi + run_tr abc56 '[:lower:]' '[:upper:]' + run_tr " " '[:space:]' 12345 + run_tr " " '[:blank:]' 12 + run_tr 'a b' '[= =]' X + run_tr "[:" '[:' ab +} + +bb= tr_test > logfile.gnu +bb=busybox tr_test > logfile.bb +cmp logfile.gnu logfile.bb diff --git a/testsuite/true/true-is-silent b/testsuite/true/true-is-silent new file mode 100644 index 000000000..1d1bdb22f --- /dev/null +++ b/testsuite/true/true-is-silent @@ -0,0 +1 @@ +busybox true 2>&1 | cmp - /dev/null diff --git a/testsuite/true/true-returns-success b/testsuite/true/true-returns-success new file mode 100644 index 000000000..cdf2d55e5 --- /dev/null +++ b/testsuite/true/true-returns-success @@ -0,0 +1 @@ +busybox true diff --git a/testsuite/umlwrapper.sh b/testsuite/umlwrapper.sh new file mode 100755 index 000000000..e55e4dbd3 --- /dev/null +++ b/testsuite/umlwrapper.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +# Wrapper for User Mode Linux emulation environment + +RUNFILE="$(pwd)/${1}.testroot" +if [ -z "$RUNFILE" ] || [ ! -x "$RUNFILE" ] +then + echo "Can't run '$RUNFILE'" + exit 1 +fi + +shift + +if [ -z $(which linux) ] +then + echo "No User Mode Linux." + exit 1; +fi + +linux rootfstype=hostfs rw init="$RUNFILE" TESTDIR=`pwd` PATH="$PATH" $* quiet diff --git a/testsuite/uniq.tests b/testsuite/uniq.tests new file mode 100755 index 000000000..49d4bed9c --- /dev/null +++ b/testsuite/uniq.tests @@ -0,0 +1,70 @@ +#!/bin/sh + +# SUSv3 compliant uniq tests. +# Copyright 2005 by Rob Landley <rob@landley.net> +# Licensed under GPL v2, see file LICENSE for details. + +# AUDIT: Full SUSv3 coverage (except internationalization). + +. testing.sh + +# testing "test name" "options" "expected result" "file input" "stdin" +# file input will be file called "input" +# test can create a file "actual" instead of writing to stdout + +# Test exit status + +testing "uniq (exit with error)" "uniq nonexistent 2> /dev/null || echo yes" \ + "yes\n" "" "" +testing "uniq (exit success)" "uniq /dev/null && echo yes" "yes\n" "" "" + +# Test various data sources and destinations + +testing "uniq (default to stdin)" "uniq" "one\ntwo\nthree\n" "" \ + "one\ntwo\ntwo\nthree\nthree\nthree\n" +testing "uniq - (specify stdin)" "uniq -" "one\ntwo\nthree\n" "" \ + "one\ntwo\ntwo\nthree\nthree\nthree\n" +testing "uniq input (specify file)" "uniq input" "one\ntwo\nthree\n" \ + "one\ntwo\ntwo\nthree\nthree\nthree\n" "" + +testing "uniq input outfile (two files)" "uniq input actual > /dev/null" \ + "one\ntwo\nthree\n" "one\ntwo\ntwo\nthree\nthree\nthree\n" "" +testing "uniq (stdin) outfile" "uniq - actual" \ + "one\ntwo\nthree\n" "" "one\ntwo\ntwo\nthree\nthree\nthree\n" +# Note: SUSv3 doesn't seem to require support for "-" output, but we do anyway. +testing "uniq input - (specify stdout)" "uniq input -" \ + "one\ntwo\nthree\n" "one\ntwo\ntwo\nthree\nthree\nthree\n" "" + + +#-f skip fields +#-s skip chars +#-c occurrences +#-d dups only +#-u + +# Test various command line options + +# Leading whitespace is a minor technical violation of the spec, +# but since gnu does it... +testing "uniq -c (occurrence count)" "uniq -c | sed 's/^[ \t]*//'" \ + "1 one\n2 two\n3 three\n" "" \ + "one\ntwo\ntwo\nthree\nthree\nthree\n" +testing "uniq -d (dups only) " "uniq -d" "two\nthree\n" "" \ + "one\ntwo\ntwo\nthree\nthree\nthree\n" + +testing "uniq -f -s (skip fields and chars)" "uniq -f2 -s 3" \ +"cc dd ee8 +aa bb cc9 +" "" \ +"cc dd ee8 +bb cc dd8 +aa bb cc9 +" + +# -d is "Suppress the writing fo lines that are not repeated in the input." +# -u is "Suppress the writing of lines that are repeated in the input." +# Therefore, together this means they should produce no output. +testing "uniq -u and -d produce no output" "uniq -d -u" "" "" \ + "one\ntwo\ntwo\nthree\nthree\nthree\n" + +exit $FAILCOUNT diff --git a/testsuite/unzip.tests b/testsuite/unzip.tests new file mode 100755 index 000000000..77814886f --- /dev/null +++ b/testsuite/unzip.tests @@ -0,0 +1,38 @@ +#!/bin/sh + +# Tests for unzip. +# Copyright 2006 Rob Landley <rob@landley.net> +# Copyright 2006 Glenn McGrath <bug1@ihug.co.nz> +# Licensed under GPL v2, see file LICENSE for details. + +. testing.sh + +# testing "test name" "options" "expected result" "file input" "stdin" +# file input will be file called "input" +# test can create a file "actual" instead of writing to stdout + +# Create a scratch directory + +mkdir temp +cd temp + +# Create test file to work with. + +mkdir foo +touch foo/bar +zip foo.zip foo foo/bar > /dev/null +rm -f foo/bar +rmdir foo + +# Test that unzipping just foo doesn't create bar. +testing "unzip (subdir only)" "unzip -q foo.zip foo/ && test -d foo && test ! -f foo/bar && echo yes" "yes\n" "" "" + +rmdir foo +rm foo.zip + +# Clean up scratch directory. + +cd .. +rm -rf temp + +exit $FAILCOUNT diff --git a/testsuite/uptime/uptime-works b/testsuite/uptime/uptime-works new file mode 100644 index 000000000..80e578778 --- /dev/null +++ b/testsuite/uptime/uptime-works @@ -0,0 +1,2 @@ +busybox uptime + diff --git a/testsuite/uuencode.tests b/testsuite/uuencode.tests new file mode 100755 index 000000000..cb658db3b --- /dev/null +++ b/testsuite/uuencode.tests @@ -0,0 +1,28 @@ +#!/bin/sh + +# unit test for uuencode to test functionality. +# Copyright 2006 by Erik Hovland <erik@hovland.org> +# Licensed under GPL v2, see file LICENSE for details. + +# AUDIT: Unit tests for uuencode + +. testing.sh + +# testing "test name" "options" "expected result" "file input" "stdin" +# file input will be file called "input" +# test can create a file "actual" instead of writing to stdout + +# Test setup of standard input +saved_umask=$(umask) +umask 0 +testing "uuencode sets standard input mode correctly" \ + "uuencode foo </dev/null | head -n 1 | grep -q 666 && echo yes" "yes\n" "" "" +umask $saved_umask + +testing "uuencode correct encoding" "uuencode bb_uuenc_test.out" \ +"begin 644 bb_uuenc_test.out\nM5&AE(&9A<W0@9W)E>2!F;W@@:G5M<&5D(&]V97(@=&AE(&QA>GD@8G)O=VX@\n%9&]G+@H\`\n\`\nend\n" \ + "" "The fast grey fox jumped over the lazy brown dog.\n" +testing "uuencode correct base64 encoding" "uuencode -m bb_uuenc_test.out" \ +"begin-base64 644 bb_uuenc_test.out\nVGhlIGZhc3QgZ3JleSBmb3gganVtcGVkIG92ZXIgdGhlIGxhenkgYnJvd24g\nZG9nLgo=\n====\n" \ + "" "The fast grey fox jumped over the lazy brown dog.\n" +exit $FAILCOUNT diff --git a/testsuite/wc/wc-counts-all b/testsuite/wc/wc-counts-all new file mode 100644 index 000000000..5e2cb6e4a --- /dev/null +++ b/testsuite/wc/wc-counts-all @@ -0,0 +1 @@ +test "`echo i\'m a little teapot | busybox wc`" = ' 1 4 20' diff --git a/testsuite/wc/wc-counts-characters b/testsuite/wc/wc-counts-characters new file mode 100644 index 000000000..755864684 --- /dev/null +++ b/testsuite/wc/wc-counts-characters @@ -0,0 +1 @@ +test `echo i\'m a little teapot | busybox wc -c` -eq 20 diff --git a/testsuite/wc/wc-counts-lines b/testsuite/wc/wc-counts-lines new file mode 100644 index 000000000..5be6ed089 --- /dev/null +++ b/testsuite/wc/wc-counts-lines @@ -0,0 +1 @@ +test `echo i\'m a little teapot | busybox wc -l` -eq 1 diff --git a/testsuite/wc/wc-counts-words b/testsuite/wc/wc-counts-words new file mode 100644 index 000000000..331650e95 --- /dev/null +++ b/testsuite/wc/wc-counts-words @@ -0,0 +1 @@ +test `echo i\'m a little teapot | busybox wc -w` -eq 4 diff --git a/testsuite/wc/wc-prints-longest-line-length b/testsuite/wc/wc-prints-longest-line-length new file mode 100644 index 000000000..78831fc13 --- /dev/null +++ b/testsuite/wc/wc-prints-longest-line-length @@ -0,0 +1 @@ +test `echo i\'m a little teapot | busybox wc -L` -eq 19 diff --git a/testsuite/wget/wget--O-overrides--P b/testsuite/wget/wget--O-overrides--P new file mode 100644 index 000000000..fdb5d47c0 --- /dev/null +++ b/testsuite/wget/wget--O-overrides--P @@ -0,0 +1,3 @@ +mkdir foo +busybox wget -q -O index.html -P foo http://www.google.com/ +test -s index.html diff --git a/testsuite/wget/wget-handles-empty-path b/testsuite/wget/wget-handles-empty-path new file mode 100644 index 000000000..5b591837a --- /dev/null +++ b/testsuite/wget/wget-handles-empty-path @@ -0,0 +1 @@ +busybox wget http://www.google.com diff --git a/testsuite/wget/wget-retrieves-google-index b/testsuite/wget/wget-retrieves-google-index new file mode 100644 index 000000000..7be9a8087 --- /dev/null +++ b/testsuite/wget/wget-retrieves-google-index @@ -0,0 +1,2 @@ +busybox wget -q -O foo http://www.google.com/ +test -s foo diff --git a/testsuite/wget/wget-supports--P b/testsuite/wget/wget-supports--P new file mode 100644 index 000000000..9b4d095e6 --- /dev/null +++ b/testsuite/wget/wget-supports--P @@ -0,0 +1,3 @@ +mkdir foo +busybox wget -q -P foo http://www.google.com/ +test -s foo/index.html diff --git a/testsuite/which/which-uses-default-path b/testsuite/which/which-uses-default-path new file mode 100644 index 000000000..63ceb9f8f --- /dev/null +++ b/testsuite/which/which-uses-default-path @@ -0,0 +1,4 @@ +BUSYBOX=$(type -p busybox) +SAVED_PATH=$PATH +unset PATH +$BUSYBOX which ls diff --git a/testsuite/xargs/xargs-works b/testsuite/xargs/xargs-works new file mode 100644 index 000000000..c95869e89 --- /dev/null +++ b/testsuite/xargs/xargs-works @@ -0,0 +1,4 @@ +[ -n "$d" ] || d=.. +find "$d" -name \*works -type f | xargs md5sum > logfile.gnu +find "$d" -name \*works -type f | busybox xargs md5sum > logfile.bb +diff -u logfile.gnu logfile.bb diff --git a/util-linux/Config.in b/util-linux/Config.in new file mode 100644 index 000000000..848914d6f --- /dev/null +++ b/util-linux/Config.in @@ -0,0 +1,526 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Linux System Utilities" + +config DMESG + bool "dmesg" + default n + help + dmesg is used to examine or control the kernel ring buffer. When the + Linux kernel prints messages to the system log, they are stored in + the kernel ring buffer. You can use dmesg to print the kernel's ring + buffer, clear the kernel ring buffer, change the size of the kernel + ring buffer, and change the priority level at which kernel messages + are also logged to the system console. Enable this option if you + wish to enable the 'dmesg' utility. + +config FEATURE_DMESG_PRETTY + bool "pretty dmesg output" + default y + depends on DMESG + help + If you wish to scrub the syslog level from the output, say 'Y' here. + The syslog level is a string prefixed to every line with the form "<#>". + + With this option you will see: + # dmesg + Linux version 2.6.17.4 ..... + BIOS-provided physical RAM map: + BIOS-e820: 0000000000000000 - 000000000009f000 (usable) + + Without this option you will see: + # dmesg + <5>Linux version 2.6.17.4 ..... + <6>BIOS-provided physical RAM map: + <6> BIOS-e820: 0000000000000000 - 000000000009f000 (usable) + +config FBSET + bool "fbset" + default n + help + fbset is used to show or change the settings of a Linux frame buffer + device. The frame buffer device provides a simple and unique + interface to access a graphics display. Enable this option + if you wish to enable the 'fbset' utility. + +config FEATURE_FBSET_FANCY + bool "Turn on extra fbset options" + default n + depends on FBSET + help + This option enables extended fbset options, allowing one to set the + framebuffer size, color depth, etc. interface to access a graphics + display. Enable this option if you wish to enable extended fbset + options. + +config FEATURE_FBSET_READMODE + bool "Turn on fbset readmode support" + default n + depends on FBSET + help + This option allows fbset to read the video mode database stored by + default as /etc/fb.modes, which can be used to set frame buffer + device to pre-defined video modes. + +config FDFLUSH + bool "fdflush" + default n + help + fdflush is only needed when changing media on slightly-broken + removable media drives. It is used to make Linux believe that a + hardware disk-change switch has been actuated, which causes Linux to + forget anything it has cached from the previous media. If you have + such a slightly-broken drive, you will need to run fdflush every time + you change a disk. Most people have working hardware and can safely + leave this disabled. + +config FDFORMAT + bool "fdformat" + default n + help + fdformat is used to low-level format a floppy disk. + +config FDISK + bool "fdisk" + default n + help + The fdisk utility is used to divide hard disks into one or more + logical disks, which are generally called partitions. This utility + can be used to list and edit the set of partitions or BSD style + 'disk slices' that are defined on a hard drive. + +config FDISK_SUPPORT_LARGE_DISKS + bool "support over 4GB disks" + default y + depends on FDISK + help + Enable this option to support large disks > 4GB. + +config FEATURE_FDISK_WRITABLE + bool "Write support" + default y + depends on FDISK + help + Enabling this option allows you to create or change a partition table + and write those changes out to disk. If you leave this option + disabled, you will only be able to view the partition table. + +config FEATURE_AIX_LABEL + bool "Support AIX disklabels" + default n + depends on FDISK && FEATURE_FDISK_WRITABLE + help + Enabling this option allows you to create or change AIX disklabels. + Most people can safely leave this option disabled. + +config FEATURE_SGI_LABEL + bool "Support SGI disklabels" + default n + depends on FDISK && FEATURE_FDISK_WRITABLE + help + Enabling this option allows you to create or change SGI disklabels. + Most people can safely leave this option disabled. + +config FEATURE_SUN_LABEL + bool "Support SUN disklabels" + default n + depends on FDISK && FEATURE_FDISK_WRITABLE + help + Enabling this option allows you to create or change SUN disklabels. + Most people can safely leave this option disabled. + +config FEATURE_OSF_LABEL + bool "Support BSD disklabels" + default n + depends on FDISK && FEATURE_FDISK_WRITABLE + help + Enabling this option allows you to create or change BSD disklabels + and define and edit BSD disk slices. + +config FEATURE_FDISK_ADVANCED + bool "Support expert mode" + default n + depends on FDISK && FEATURE_FDISK_WRITABLE + help + Enabling this option allows you to do terribly unsafe things like + define arbitrary drive geometry, move the beginning of data in a + partition, and similarly evil things. Unless you have a very good + reason you would be wise to leave this disabled. + +config FREERAMDISK + bool "freeramdisk" + default n + help + Linux allows you to create ramdisks. This utility allows you to + delete them and completely free all memory that was used for the + ramdisk. For example, if you boot Linux into a ramdisk and later + pivot_root, you may want to free the memory that is allocated to the + ramdisk. If you have no use for freeing memory from a ramdisk, leave + this disabled. + +config FSCK_MINIX + bool "fsck_minix" + default n + help + The minix filesystem is a nice, small, compact, read-write filesystem + with little overhead. It is not a journaling filesystem however and + can experience corruption if it is not properly unmounted or if the + power goes off in the middle of a write. This utility allows you to + check for and attempt to repair any corruption that occurs to a minix + filesystem. + +config MKFS_MINIX + bool "mkfs_minix" + default n + help + The minix filesystem is a nice, small, compact, read-write filesystem + with little overhead. If you wish to be able to create minix filesystems + this utility will do the job for you. + +comment "Minix filesystem support" + depends on FSCK_MINIX || MKFS_MINIX + +config FEATURE_MINIX2 + bool "Support Minix fs v2 (fsck_minix/mkfs_minix)" + default y + depends on FSCK_MINIX || MKFS_MINIX + help + If you wish to be able to create version 2 minix filesystems, enable this. + If you enabled 'mkfs_minix' then you almost certainly want to be using the + version 2 filesystem support. + +config GETOPT + bool "getopt" + default n + help + The getopt utility is used to break up (parse) options in command + lines to make it easy to write complex shell scripts that also check + for legal (and illegal) options. If you want to write horribly + complex shell scripts, or use some horribly complex shell script + written by others, this utility may be for you. Most people will + wisely leave this disabled. + +config HEXDUMP + bool "hexdump" + default n + help + The hexdump utility is used to display binary data in a readable + way that is comparable to the output from most hex editors. + +config HWCLOCK + bool "hwclock" + default n + help + The hwclock utility is used to read and set the hardware clock + on a system. This is primarily used to set the current time on + shutdown in the hardware clock, so the hardware will keep the + correct time when Linux is _not_ running. + +config FEATURE_HWCLOCK_LONG_OPTIONS + bool "Support long options (--hctosys,...)" + default n + depends on HWCLOCK && GETOPT_LONG + help + By default, the hwclock utility only uses short options. If you + are overly fond of its long options, such as --hctosys, --utc, etc) + then enable this option. + +config FEATURE_HWCLOCK_ADJTIME_FHS + bool "Use FHS /var/lib/hwclock/adjtime" + default y + depends on HWCLOCK + help + Starting with FHS 2.3, the adjtime state file is supposed to exist + at /var/lib/hwclock/adjtime instead of /etc/adjtime. If you wish + to use the FHS behavior, answer Y here, otherwise answer N for the + classic /etc/adjtime path. + + http://www.pathname.com/fhs/pub/fhs-2.3.html#VARLIBHWCLOCKSTATEDIRECTORYFORHWCLO + +config IPCRM + bool "ipcrm" + default n + select FEATURE_SUID + help + The ipcrm utility allows the removal of System V interprocess + communication (IPC) objects and the associated data structures + from the system. + +config IPCS + bool "ipcs" + default n + select FEATURE_SUID + help + The ipcs utility is used to provide information on the currently + allocated System V interprocess (IPC) objects in the system. + +config LOSETUP + bool "losetup" + default n + help + losetup is used to associate or detach a loop device with a regular + file or block device, and to query the status of a loop device. This + version does not currently support enabling data encryption. + +config MDEV + bool "mdev" + default n + help + mdev is a mini-udev implementation: call it with -s to populate + /dev from /sys, then "echo /sbin/mdev > /proc/sys/kernel/hotplug" to + have it handle hotplug events afterwards. Device names are taken + from sysfs. + +config FEATURE_MDEV_CONF + bool "Support /etc/mdev.conf" + default n + depends on MDEV + help + The mdev config file contains lines that look like: + + hd[a-z][0-9]* 0:3 660 + + That's device name (with regex match), uid:gid, and permissions. + + Config file parsing stops on the first matching line. If no config + entry is matched, devices are created with default 0:0 660. (Make + the last line match .* to override this.) + +config FEATURE_MDEV_EXEC + bool "Support command execution at device addition/removal" + default n + depends on FEATURE_MDEV_CONF + help + This adds support for an optional field to /etc/mdev.conf, consisting + of a special character and a command line to run after creating the + corresponding device(s) and before removing, ala: + + hdc root:cdrom 660 *ln -s $MDEV cdrom + + The $MDEV environment variable is set to the name of the device. + + The special characters and their meanings are: + @ Run after creating the device. + $ Run before removing the device. + * Run both after creating and before removing the device. + + Commands are executed via system() so you need /bin/sh, meaning you + probably want to select a default shell in the Shells menu. + +config MKSWAP + bool "mkswap" + default n + help + The mkswap utility is used to configure a file or disk partition as + Linux swap space. This allows Linux to use the entire file or + partition as if it were additional RAM, which can greatly increase + the capability of low-memory machines. This additional memory is + much slower than real RAM, but can be very helpful at preventing your + applications being killed by the Linux out of memory (OOM) killer. + Once you have created swap space using 'mkswap' you need to enable + the swap space using the 'swapon' utility. + +config FEATURE_MKSWAP_V0 + bool "version 0 support" + default n + depends on MKSWAP +# depends on MKSWAP && DEPRECATED + help + Enable support for the old v0 style. + If your kernel is older than 2.1.117, then v0 support is the + only option. + +config MORE + bool "more" + default n + help + more is a simple utility which allows you to read text one screen + sized page at a time. If you want to read text that is larger than + the screen, and you are using anything faster than a 300 baud modem, + you will probably find this utility very helpful. If you don't have + any need to reading text files, you can leave this disabled. + +config FEATURE_USE_TERMIOS + bool "Use termios to manipulate the screen" + default y + depends on MORE + help + This option allows utilities such as 'more' and 'top' to determine + the size of the screen. If you leave this disabled, your utilities + that display things on the screen will be especially primitive and + will be unable to determine the current screen size, and will be + unable to move the cursor. + +config MOUNT + bool "mount" + default n + help + All files and filesystems in Unix are arranged into one big directory + tree. The 'mount' utility is used to graft a filesystem onto a + particular part of the tree. A filesystem can either live on a block + device, or it can be accessible over the network, as is the case with + NFS filesystems. Most people using BusyBox will also want to enable + the 'mount' utility. + +config FEATURE_MOUNT_NFS + bool "Support mounting NFS file systems" + default n + depends on MOUNT + depends on FEATURE_HAVE_RPC + select FEATURE_SYSLOG + help + Enable mounting of NFS file systems. + +config FEATURE_MOUNT_CIFS + bool "Support mounting CIFS/SMB file systems" + default n + depends on MOUNT + help + Enable support for samba mounts. + +config FEATURE_MOUNT_FLAGS + depends on MOUNT + bool "Support lots of -o flags in mount" + default y + help + Without this, mount only supports ro/rw/remount. With this, it + supports nosuid, suid, dev, nodev, exec, noexec, sync, async, atime, + noatime, diratime, nodiratime, loud, bind, move, shared, slave, + private, unbindable, rshared, rslave, rprivate, and runbindable. + +config FEATURE_MOUNT_FSTAB + depends on MOUNT + bool "Support /etc/fstab and -a" + default y + help + Support mount all and looking for files in /etc/fstab. + +config PIVOT_ROOT + bool "pivot_root" + default n + help + The pivot_root utility swaps the mount points for the root filesystem + with some other mounted filesystem. This allows you to do all sorts + of wild and crazy things with your Linux system and is far more + powerful than 'chroot'. + + Note: This is for initrd in linux 2.4. Under initramfs (introduced + in linux 2.6) use switch_root instead. + +config RDATE + bool "rdate" + default n + help + The rdate utility allows you to synchronize the date and time of your + system clock with the date and time of a remote networked system using + the RFC868 protocol, which is built into the inetd daemon on most + systems. + +config READPROFILE + bool "readprofile" + default n + help + This allows you to parse /proc/profile for basic profiling. + +config SETARCH + bool "setarch" + default n + help + The linux32 utility is used to create a 32bit environment for the + specified program (usually a shell). It only makes sense to have + this util on a system that supports both 64bit and 32bit userland + (like amd64/x86, ppc64/ppc, sparc64/sparc, etc...). + +config SWAPONOFF + bool "swaponoff" + default n + help + This option enables both the 'swapon' and the 'swapoff' utilities. + Once you have created some swap space using 'mkswap', you also need + to enable your swap space with the 'swapon' utility. The 'swapoff' + utility is used, typically at system shutdown, to disable any swap + space. If you are not using any swap space, you can leave this + option disabled. + +config SWITCH_ROOT + bool "switch_root" + default n + help + The switch_root utility is used from initramfs to select a new + root device. Under initramfs, you have to use this instead of + pivot_root. (Stop reading here if you don't care why.) + + Booting with initramfs extracts a gzipped cpio archive into rootfs + (which is a variant of ramfs/tmpfs). Because rootfs can't be moved + or unmounted*, pivot_root will not work from initramfs. Instead, + switch_root deletes everything out of rootfs (including itself), + does a mount --move that overmounts rootfs with the new root, and + then execs the specified init program. + + * Because the Linux kernel uses rootfs internally as the starting + and ending point for searching through the kernel's doubly linked + list of active mount points. That's why. + +config UMOUNT + bool "umount" + default n + help + When you want to remove a mounted filesystem from its current mount point, + for example when you are shutting down the system, the 'umount' utility is + the tool to use. If you enabled the 'mount' utility, you almost certainly + also want to enable 'umount'. + +config FEATURE_UMOUNT_ALL + bool "umount -a option" + default n + depends on UMOUNT + help + Support -a option to unmount all currently mounted filesystems. + +comment "Common options for mount/umount" + depends on MOUNT || UMOUNT + +config FEATURE_MOUNT_LOOP + bool "Support loopback mounts" + default n + depends on MOUNT || UMOUNT + help + Enabling this feature allows automatic mounting of files (containing + filesystem images) via the linux kernel's loopback devices. The mount + command will detect you are trying to mount a file instead of a block + device, and transparently associate the file with a loopback device. + The umount command will also free that loopback device. + + You can still use the 'losetup' utility (to manually associate files + with loop devices) if you need to do something advanced, such as + specify an offset or cryptographic options to the loopback device. + (If you don't want umount to free the loop device, use "umount -D".) + +config FEATURE_MTAB_SUPPORT + bool "Support for the old /etc/mtab file" + default n + depends on MOUNT || UMOUNT + help + Historically, Unix systems kept track of the currently mounted + partitions in the file "/etc/mtab". These days, the kernel exports + the list of currently mounted partitions in "/proc/mounts", rendering + the old mtab file obsolete. (In modern systems, /etc/mtab should be + a symlink to /proc/mounts.) + + The only reason to have mount maintain an /etc/mtab file itself is if + your stripped-down embedded system does not have a /proc directory. + If you must use this, keep in mind it's inherently brittle (for + example a mount under chroot won't update it), can't handle modern + features like separate per-process filesystem namespaces, requires + that your /etc directory be writeable, tends to get easily confused + by --bind or --move mounts, won't update if you rename a directory + that contains a mount point, and so on. (In brief: avoid.) + + About the only reason to use this is if you've removed /proc from + your kernel. + +endmenu + diff --git a/util-linux/Kbuild b/util-linux/Kbuild new file mode 100644 index 000000000..cc1d0e05d --- /dev/null +++ b/util-linux/Kbuild @@ -0,0 +1,32 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen <andersen@codepoet.org> +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_DMESG) +=dmesg.o +lib-$(CONFIG_FBSET) +=fbset.o +lib-$(CONFIG_FDFLUSH) +=freeramdisk.o +lib-$(CONFIG_FDFORMAT) +=fdformat.o +lib-$(CONFIG_FDISK) +=fdisk.o +lib-$(CONFIG_FREERAMDISK) +=freeramdisk.o +lib-$(CONFIG_FSCK_MINIX) +=fsck_minix.o +lib-$(CONFIG_GETOPT) +=getopt.o +lib-$(CONFIG_HEXDUMP) +=hexdump.o +lib-$(CONFIG_HWCLOCK) +=hwclock.o +lib-$(CONFIG_IPCRM) +=ipcrm.o +lib-$(CONFIG_IPCS) +=ipcs.o +lib-$(CONFIG_LOSETUP) +=losetup.o +lib-$(CONFIG_MDEV) +=mdev.o +lib-$(CONFIG_MKFS_MINIX) +=mkfs_minix.o +lib-$(CONFIG_MKSWAP) +=mkswap.o +lib-$(CONFIG_MORE) +=more.o +lib-$(CONFIG_MOUNT) +=mount.o +lib-$(CONFIG_PIVOT_ROOT) +=pivot_root.o +lib-$(CONFIG_RDATE) +=rdate.o +lib-$(CONFIG_READPROFILE) +=readprofile.o +lib-$(CONFIG_SETARCH) +=setarch.o +lib-$(CONFIG_SWAPONOFF) +=swaponoff.o +lib-$(CONFIG_SWITCH_ROOT) +=switch_root.o +lib-$(CONFIG_UMOUNT) +=umount.o diff --git a/util-linux/dmesg.c b/util-linux/dmesg.c new file mode 100644 index 000000000..658cddc38 --- /dev/null +++ b/util-linux/dmesg.c @@ -0,0 +1,53 @@ +/* vi: set sw=4 ts=4: */ +/* + * + * dmesg - display/control kernel ring buffer. + * + * Copyright 2006 Rob Landley <rob@landley.net> + * Copyright 2006 Bernhard Fischer <rep.nop@aon.at> + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include <unistd.h> +#include <sys/klog.h> + +int dmesg_main(int argc, char *argv[]) +{ + char *size, *level; + int flags = getopt32(argc, argv, "cs:n:", &size, &level); + + if (flags & 4) { + if (klogctl(8, NULL, xatoul_range(level, 0, 10))) + bb_perror_msg_and_die("klogctl"); + } else { + int len; + char *buf; + + len = (flags & 2) ? xatoul_range(size, 2, INT_MAX) : 16384; + buf = xmalloc(len); + if (0 > (len = klogctl(3 + (flags & 1), buf, len))) + bb_perror_msg_and_die("klogctl"); + + // Skip <#> at the start of lines, and make sure we end with a newline. + + if (ENABLE_FEATURE_DMESG_PRETTY) { + int last = '\n'; + int in; + + for (in = 0; in<len;) { + if (last == '\n' && buf[in] == '<') in += 3; + else putchar(last = buf[in++]); + } + if (last != '\n') putchar('\n'); + } else { + write(1,buf,len); + if (len && buf[len-1]!='\n') putchar('\n'); + } + + if (ENABLE_FEATURE_CLEAN_UP) free(buf); + } + + return 0; +} diff --git a/util-linux/fbset.c b/util-linux/fbset.c new file mode 100644 index 000000000..1aa0a0ac1 --- /dev/null +++ b/util-linux/fbset.c @@ -0,0 +1,407 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini fbset implementation for busybox + * + * Copyright (C) 1999 by Randolph Chung <tausq@debian.org> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * This is a from-scratch implementation of fbset; but the de facto fbset + * implementation was a good reference. fbset (original) is released under + * the GPL, and is (c) 1995-1999 by: + * Geert Uytterhoeven (Geert.Uytterhoeven@cs.kuleuven.ac.be) + */ + +#include "busybox.h" + +#define DEFAULTFBDEV FB_0 +#define DEFAULTFBMODE "/etc/fb.modes" + +enum { + OPT_CHANGE = (1 << 0), + OPT_INFO = (1 << 1), + OPT_READMODE = (1 << 2), + OPT_ALL = (1 << 9), + + CMD_FB = 1, + CMD_DB = 2, + CMD_GEOMETRY = 3, + CMD_TIMING = 4, + CMD_ACCEL = 5, + CMD_HSYNC = 6, + CMD_VSYNC = 7, + CMD_LACED = 8, + CMD_DOUBLE = 9, +/* CMD_XCOMPAT = 10, */ + CMD_ALL = 11, + CMD_INFO = 12, + CMD_CHANGE = 13, + +#ifdef CONFIG_FEATURE_FBSET_FANCY + CMD_XRES = 100, + CMD_YRES = 101, + CMD_VXRES = 102, + CMD_VYRES = 103, + CMD_DEPTH = 104, + CMD_MATCH = 105, + CMD_PIXCLOCK = 106, + CMD_LEFT = 107, + CMD_RIGHT = 108, + CMD_UPPER = 109, + CMD_LOWER = 110, + CMD_HSLEN = 111, + CMD_VSLEN = 112, + CMD_CSYNC = 113, + CMD_GSYNC = 114, + CMD_EXTSYNC = 115, + CMD_BCAST = 116, + CMD_RGBA = 117, + CMD_STEP = 118, + CMD_MOVE = 119, +#endif +}; + +static unsigned int g_options = 0; + +/* Stuff stolen from the kernel's fb.h */ +#define FB_ACTIVATE_ALL 64 +enum { + FBIOGET_VSCREENINFO = 0x4600, + FBIOPUT_VSCREENINFO = 0x4601 +}; +struct fb_bitfield { + uint32_t offset; /* beginning of bitfield */ + uint32_t length; /* length of bitfield */ + uint32_t msb_right; /* !=0: Most significant bit is right */ +}; +struct fb_var_screeninfo { + uint32_t xres; /* visible resolution */ + uint32_t yres; + uint32_t xres_virtual; /* virtual resolution */ + uint32_t yres_virtual; + uint32_t xoffset; /* offset from virtual to visible */ + uint32_t yoffset; /* resolution */ + + uint32_t bits_per_pixel; + uint32_t grayscale; /* !=0 Graylevels instead of colors */ + + struct fb_bitfield red; /* bitfield in fb mem if true color, */ + struct fb_bitfield green; /* else only length is significant */ + struct fb_bitfield blue; + struct fb_bitfield transp; /* transparency */ + + uint32_t nonstd; /* !=0 Non standard pixel format */ + + uint32_t activate; /* see FB_ACTIVATE_x */ + + uint32_t height; /* height of picture in mm */ + uint32_t width; /* width of picture in mm */ + + uint32_t accel_flags; /* acceleration flags (hints) */ + + /* Timing: All values in pixclocks, except pixclock (of course) */ + uint32_t pixclock; /* pixel clock in ps (pico seconds) */ + uint32_t left_margin; /* time from sync to picture */ + uint32_t right_margin; /* time from picture to sync */ + uint32_t upper_margin; /* time from sync to picture */ + uint32_t lower_margin; + uint32_t hsync_len; /* length of horizontal sync */ + uint32_t vsync_len; /* length of vertical sync */ + uint32_t sync; /* see FB_SYNC_x */ + uint32_t vmode; /* see FB_VMODE_x */ + uint32_t reserved[6]; /* Reserved for future compatibility */ +}; + + +static const struct cmdoptions_t { + const char name[10]; + const unsigned char param_count; + const unsigned char code; +} g_cmdoptions[] = { + { "-fb", 1, CMD_FB }, + { "-db", 1, CMD_DB }, + { "-a", 0, CMD_ALL }, + { "-i", 0, CMD_INFO }, + { "-g", 5, CMD_GEOMETRY }, + { "-t", 7, CMD_TIMING }, + { "-accel", 1, CMD_ACCEL }, + { "-hsync", 1, CMD_HSYNC }, + { "-vsync", 1, CMD_VSYNC }, + { "-laced", 1, CMD_LACED }, + { "-double", 1, CMD_DOUBLE }, + { "-n", 0, CMD_CHANGE }, +#ifdef CONFIG_FEATURE_FBSET_FANCY + { "-all", 0, CMD_ALL }, + { "-xres", 1, CMD_XRES }, + { "-yres", 1, CMD_YRES }, + { "-vxres", 1, CMD_VXRES }, + { "-vyres", 1, CMD_VYRES }, + { "-depth", 1, CMD_DEPTH }, + { "-match", 0, CMD_MATCH }, + { "-geometry", 5, CMD_GEOMETRY }, + { "-pixclock", 1, CMD_PIXCLOCK }, + { "-left", 1, CMD_LEFT }, + { "-right", 1, CMD_RIGHT }, + { "-upper", 1, CMD_UPPER }, + { "-lower", 1, CMD_LOWER }, + { "-hslen", 1, CMD_HSLEN }, + { "-vslen", 1, CMD_VSLEN }, + { "-timings", 7, CMD_TIMING }, + { "-csync", 1, CMD_CSYNC }, + { "-gsync", 1, CMD_GSYNC }, + { "-extsync", 1, CMD_EXTSYNC }, + { "-bcast", 1, CMD_BCAST }, + { "-rgba", 1, CMD_RGBA }, + { "-step", 1, CMD_STEP }, + { "-move", 1, CMD_MOVE }, +#endif + { "", 0, 0 } +}; + +#ifdef CONFIG_FEATURE_FBSET_READMODE +/* taken from linux/fb.h */ +enum { + FB_VMODE_INTERLACED = 1, /* interlaced */ + FB_VMODE_DOUBLE = 2, /* double scan */ + FB_SYNC_HOR_HIGH_ACT = 1, /* horizontal sync high active */ + FB_SYNC_VERT_HIGH_ACT = 2, /* vertical sync high active */ + FB_SYNC_EXT = 4, /* external sync */ + FB_SYNC_COMP_HIGH_ACT = 8 /* composite sync high active */ +}; +#endif + +static int readmode(struct fb_var_screeninfo *base, const char *fn, + const char *mode) +{ +#ifdef CONFIG_FEATURE_FBSET_READMODE + FILE *f; + char buf[256]; + char *p = buf; + + f = xfopen(fn, "r"); + while (!feof(f)) { + fgets(buf, sizeof(buf), f); + if (!(p = strstr(buf, "mode ")) && !(p = strstr(buf, "mode\t"))) + continue; + p += 5; + if (!(p = strstr(buf, mode))) + continue; + p += strlen(mode); + if (!isspace(*p) && (*p != 0) && (*p != '"') + && (*p != '\r') && (*p != '\n')) + continue; /* almost, but not quite */ + + while (!feof(f)) { + fgets(buf, sizeof(buf), f); + if ((p = strstr(buf, "geometry "))) { + p += 9; + /* FIXME: catastrophic on arches with 64bit ints */ + sscanf(p, "%d %d %d %d %d", + &(base->xres), &(base->yres), + &(base->xres_virtual), &(base->yres_virtual), + &(base->bits_per_pixel)); + } else if ((p = strstr(buf, "timings "))) { + p += 8; + sscanf(p, "%d %d %d %d %d %d %d", + &(base->pixclock), + &(base->left_margin), &(base->right_margin), + &(base->upper_margin), &(base->lower_margin), + &(base->hsync_len), &(base->vsync_len)); + } else if ((p = strstr(buf, "laced "))) { + //p += 6; + if (strstr(buf, "false")) { + base->vmode &= ~FB_VMODE_INTERLACED; + } else { + base->vmode |= FB_VMODE_INTERLACED; + } + } else if ((p = strstr(buf, "double "))) { + //p += 7; + if (strstr(buf, "false")) { + base->vmode &= ~FB_VMODE_DOUBLE; + } else { + base->vmode |= FB_VMODE_DOUBLE; + } + } else if ((p = strstr(buf, "vsync "))) { + //p += 6; + if (strstr(buf, "low")) { + base->sync &= ~FB_SYNC_VERT_HIGH_ACT; + } else { + base->sync |= FB_SYNC_VERT_HIGH_ACT; + } + } else if ((p = strstr(buf, "hsync "))) { + //p += 6; + if (strstr(buf, "low")) { + base->sync &= ~FB_SYNC_HOR_HIGH_ACT; + } else { + base->sync |= FB_SYNC_HOR_HIGH_ACT; + } + } else if ((p = strstr(buf, "csync "))) { + //p += 6; + if (strstr(buf, "low")) { + base->sync &= ~FB_SYNC_COMP_HIGH_ACT; + } else { + base->sync |= FB_SYNC_COMP_HIGH_ACT; + } + } else if ((p = strstr(buf, "extsync "))) { + //p += 8; + if (strstr(buf, "false")) { + base->sync &= ~FB_SYNC_EXT; + } else { + base->sync |= FB_SYNC_EXT; + } + } + + if (strstr(buf, "endmode")) + return 1; + } + } +#else + bb_error_msg("mode reading not compiled in"); +#endif + return 0; +} + +static inline void setmode(struct fb_var_screeninfo *base, + struct fb_var_screeninfo *set) +{ + if ((int) set->xres > 0) + base->xres = set->xres; + if ((int) set->yres > 0) + base->yres = set->yres; + if ((int) set->xres_virtual > 0) + base->xres_virtual = set->xres_virtual; + if ((int) set->yres_virtual > 0) + base->yres_virtual = set->yres_virtual; + if ((int) set->bits_per_pixel > 0) + base->bits_per_pixel = set->bits_per_pixel; +} + +static inline void showmode(struct fb_var_screeninfo *v) +{ + double drate = 0, hrate = 0, vrate = 0; + + if (v->pixclock) { + drate = 1e12 / v->pixclock; + hrate = drate / (v->left_margin + v->xres + v->right_margin + v->hsync_len); + vrate = hrate / (v->upper_margin + v->yres + v->lower_margin + v->vsync_len); + } + printf("\nmode \"%ux%u-%u\"\n" +#ifdef CONFIG_FEATURE_FBSET_FANCY + "\t# D: %.3f MHz, H: %.3f kHz, V: %.3f Hz\n" +#endif + "\tgeometry %u %u %u %u %u\n" + "\ttimings %u %u %u %u %u %u %u\n" + "\taccel %s\n" + "\trgba %u/%u,%u/%u,%u/%u,%u/%u\n" + "endmode\n\n", + v->xres, v->yres, (int) (vrate + 0.5), +#ifdef CONFIG_FEATURE_FBSET_FANCY + drate / 1e6, hrate / 1e3, vrate, +#endif + v->xres, v->yres, v->xres_virtual, v->yres_virtual, v->bits_per_pixel, + v->pixclock, v->left_margin, v->right_margin, v->upper_margin, v->lower_margin, + v->hsync_len, v->vsync_len, + (v->accel_flags > 0 ? "true" : "false"), + v->red.length, v->red.offset, v->green.length, v->green.offset, + v->blue.length, v->blue.offset, v->transp.length, v->transp.offset); +} + +#ifdef STANDALONE +int main(int argc, char **argv) +#else +int fbset_main(int argc, char **argv) +#endif +{ + struct fb_var_screeninfo var, varset; + int fh, i; + char *fbdev = DEFAULTFBDEV; + char *modefile = DEFAULTFBMODE; + char *thisarg, *mode = NULL; + + memset(&varset, 0xFF, sizeof(varset)); + + /* parse cmd args.... why do they have to make things so difficult? */ + argv++; + argc--; + for (; argc > 0 && (thisarg = *argv); argc--, argv++) { + for (i = 0; g_cmdoptions[i].name[0]; i++) { + if (strcmp(thisarg, g_cmdoptions[i].name)) + continue; + if (argc-1 < g_cmdoptions[i].param_count) + bb_show_usage(); + + switch (g_cmdoptions[i].code) { + case CMD_FB: + fbdev = argv[1]; + break; + case CMD_DB: + modefile = argv[1]; + break; + case CMD_GEOMETRY: + varset.xres = xatou32(argv[1]); + varset.yres = xatou32(argv[2]); + varset.xres_virtual = xatou32(argv[3]); + varset.yres_virtual = xatou32(argv[4]); + varset.bits_per_pixel = xatou32(argv[5]); + break; + case CMD_TIMING: + varset.pixclock = xatou32(argv[1]); + varset.left_margin = xatou32(argv[2]); + varset.right_margin = xatou32(argv[3]); + varset.upper_margin = xatou32(argv[4]); + varset.lower_margin = xatou32(argv[5]); + varset.hsync_len = xatou32(argv[6]); + varset.vsync_len = xatou32(argv[7]); + break; + case CMD_ALL: + g_options |= OPT_ALL; + break; + case CMD_CHANGE: + g_options |= OPT_CHANGE; + break; +#ifdef CONFIG_FEATURE_FBSET_FANCY + case CMD_XRES: + varset.xres = xatou32(argv[1]); + break; + case CMD_YRES: + varset.yres = xatou32(argv[1]); + break; + case CMD_DEPTH: + varset.bits_per_pixel = xatou32(argv[1]); + break; +#endif + } + argc -= g_cmdoptions[i].param_count; + argv += g_cmdoptions[i].param_count; + break; + } + if (!g_cmdoptions[i].name[0]) { + if (argc != 1) + bb_show_usage(); + mode = *argv; + g_options |= OPT_READMODE; + } + } + + fh = xopen(fbdev, O_RDONLY); + if (ioctl(fh, FBIOGET_VSCREENINFO, &var)) + bb_perror_msg_and_die("ioctl(%sT_VSCREENINFO)", "GE"); + if (g_options & OPT_READMODE) { + if (!readmode(&var, modefile, mode)) { + bb_error_msg_and_die("unknown video mode '%s'", mode); + } + } + + setmode(&var, &varset); + if (g_options & OPT_CHANGE) { + if (g_options & OPT_ALL) + var.activate = FB_ACTIVATE_ALL; + if (ioctl(fh, FBIOPUT_VSCREENINFO, &var)) + bb_perror_msg_and_die("ioctl(%sT_VSCREENINFO)", "PU"); + } + showmode(&var); + /* Don't close the file, as exiting will take care of that */ + /* close(fh); */ + + return EXIT_SUCCESS; +} diff --git a/util-linux/fdformat.c b/util-linux/fdformat.c new file mode 100644 index 000000000..0242d8d3a --- /dev/null +++ b/util-linux/fdformat.c @@ -0,0 +1,143 @@ +/* vi: set sw=4 ts=4: */ +/* fdformat.c - Low-level formats a floppy disk - Werner Almesberger */ + +/* 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL> + * - added Native Language Support + * 1999-03-20 Arnaldo Carvalho de Melo <acme@conectiva.com.br> + * - more i18n/nls translatable strings marked + * + * 5 July 2003 -- modified for Busybox by Erik Andersen + */ + +#include "busybox.h" + + +/* Stuff extracted from linux/fd.h */ +struct floppy_struct { + unsigned int size, /* nr of sectors total */ + sect, /* sectors per track */ + head, /* nr of heads */ + track, /* nr of tracks */ + stretch; /* !=0 means double track steps */ +#define FD_STRETCH 1 +#define FD_SWAPSIDES 2 + + unsigned char gap, /* gap1 size */ + + rate, /* data rate. |= 0x40 for perpendicular */ +#define FD_2M 0x4 +#define FD_SIZECODEMASK 0x38 +#define FD_SIZECODE(floppy) (((((floppy)->rate&FD_SIZECODEMASK)>> 3)+ 2) %8) +#define FD_SECTSIZE(floppy) ( (floppy)->rate & FD_2M ? \ + 512 : 128 << FD_SIZECODE(floppy) ) +#define FD_PERP 0x40 + + spec1, /* stepping rate, head unload time */ + fmt_gap; /* gap2 size */ + const char * name; /* used only for predefined formats */ +}; +struct format_descr { + unsigned int device,head,track; +}; +#define FDFMTBEG _IO(2,0x47) +#define FDFMTTRK _IOW(2,0x48, struct format_descr) +#define FDFMTEND _IO(2,0x49) +#define FDGETPRM _IOR(2, 0x04, struct floppy_struct) +#define FD_FILL_BYTE 0xF6 /* format fill byte. */ + +static void xioctl(int fd, int request, void *argp, const char *string) +{ + if (ioctl(fd, request, argp) < 0) { + bb_perror_msg_and_die(string); + } +} + +int fdformat_main(int argc,char **argv) +{ + int fd, n, cyl, read_bytes, verify; + unsigned char *data; + struct stat st; + struct floppy_struct param; + struct format_descr descr; + + if (argc < 2) { + bb_show_usage(); + } + verify = !getopt32(argc, argv, "n"); + argv += optind; + + xstat(*argv, &st); + if (!S_ISBLK(st.st_mode)) { + bb_error_msg_and_die("%s: not a block device", *argv); + /* do not test major - perhaps this was an USB floppy */ + } + + /* O_RDWR for formatting and verifying */ + fd = xopen(*argv, O_RDWR); + + /* original message was: "Could not determine current format type" */ + xioctl(fd, FDGETPRM, ¶m, "FDGETPRM"); + + printf("%s-sided, %d tracks, %d sec/track. Total capacity %d kB\n", + (param.head == 2) ? "Double" : "Single", + param.track, param.sect, param.size >> 1); + + /* FORMAT */ + printf("Formatting... "); + xioctl(fd, FDFMTBEG, NULL, "FDFMTBEG"); + + /* n == track */ + for (n = 0; n < param.track; n++) { + descr.head = 0; + descr.track = n; + xioctl(fd, FDFMTTRK, &descr, "FDFMTTRK"); + printf("%3d\b\b\b", n); + if (param.head == 2) { + descr.head = 1; + xioctl(fd, FDFMTTRK, &descr, "FDFMTTRK"); + } + } + + xioctl(fd, FDFMTEND, NULL, "FDFMTEND"); + printf("done\n"); + + /* VERIFY */ + if (verify) { + /* n == cyl_size */ + n = param.sect*param.head*512; + + data = xmalloc(n); + printf("Verifying... "); + for (cyl = 0; cyl < param.track; cyl++) { + printf("%3d\b\b\b", cyl); + read_bytes = safe_read(fd, data, n); + if (read_bytes != n) { + if (read_bytes < 0) { + bb_perror_msg(bb_msg_read_error); + } + bb_error_msg_and_die("problem reading cylinder %d, " + "expected %d, read %d", cyl, n, read_bytes); + // FIXME: maybe better seek & continue?? + } + /* Check backwards so we don't need a counter */ + while (--read_bytes >= 0) { + if (data[read_bytes] != FD_FILL_BYTE) { + printf("bad data in cyl %d\nContinuing... ",cyl); + } + } + } + /* There is no point in freeing blocks at the end of a program, because + all of the program's space is given back to the system when the process + terminates.*/ + + if (ENABLE_FEATURE_CLEAN_UP) free(data); + + printf("done\n"); + } + + if (ENABLE_FEATURE_CLEAN_UP) close(fd); + + /* Don't bother closing. Exit does + * that, so we can save a few bytes */ + return EXIT_SUCCESS; +} diff --git a/util-linux/fdisk.c b/util-linux/fdisk.c new file mode 100644 index 000000000..2f87f1c60 --- /dev/null +++ b/util-linux/fdisk.c @@ -0,0 +1,3043 @@ +/* vi: set sw=4 ts=4: */ +/* fdisk.c -- Partition table manipulator for Linux. + * + * Copyright (C) 1992 A. V. Le Blanc (LeBlanc@mcc.ac.uk) + * Copyright (C) 2001,2002 Vladimir Oleynik <dzo@simtreas.ru> (initial bb port) + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#include <assert.h> /* assert */ +#include "busybox.h" +#define _(x) x + +/* Looks like someone forgot to add this to config system */ +#ifndef ENABLE_FEATURE_FDISK_BLKSIZE +# define ENABLE_FEATURE_FDISK_BLKSIZE 0 +# define USE_FEATURE_FDISK_BLKSIZE(a) +#endif + +#define DEFAULT_SECTOR_SIZE 512 +#define MAX_SECTOR_SIZE 2048 +#define SECTOR_SIZE 512 /* still used in osf/sgi/sun code */ +#define MAXIMUM_PARTS 60 + +#define ACTIVE_FLAG 0x80 + +#define EXTENDED 0x05 +#define WIN98_EXTENDED 0x0f +#define LINUX_PARTITION 0x81 +#define LINUX_SWAP 0x82 +#define LINUX_NATIVE 0x83 +#define LINUX_EXTENDED 0x85 +#define LINUX_LVM 0x8e +#define LINUX_RAID 0xfd + +#define IS_EXTENDED(i) \ + ((i) == EXTENDED || (i) == WIN98_EXTENDED || (i) == LINUX_EXTENDED) + +#define SIZE(a) (sizeof(a)/sizeof((a)[0])) + +#define cround(n) (display_in_cyl_units ? ((n)/units_per_sector)+1 : (n)) +#define scround(x) (((x)+units_per_sector-1)/units_per_sector) + +struct hd_geometry { + unsigned char heads; + unsigned char sectors; + unsigned short cylinders; + unsigned long start; +}; + +#define HDIO_GETGEO 0x0301 /* get device geometry */ + +struct systypes { + const char *name; +}; + +static unsigned sector_size = DEFAULT_SECTOR_SIZE; +static unsigned user_set_sector_size; +static unsigned sector_offset = 1; + +/* + * Raw disk label. For DOS-type partition tables the MBR, + * with descriptions of the primary partitions. + */ +#if (MAX_SECTOR_SIZE) > (BUFSIZ+1) +static char MBRbuffer[MAX_SECTOR_SIZE]; +#else +# define MBRbuffer bb_common_bufsiz1 +#endif + +#if ENABLE_FEATURE_OSF_LABEL +static int possibly_osf_label; +#endif + +static unsigned heads, sectors, cylinders; +static void update_units(void); + + +/* + * return partition name - uses static storage unless buf is supplied + */ +static const char * +partname(const char *dev, int pno, int lth) +{ + static char buffer[80]; + const char *p; + int w, wp; + int bufsiz; + char *bufp; + + bufp = buffer; + bufsiz = sizeof(buffer); + + w = strlen(dev); + p = ""; + + if (isdigit(dev[w-1])) + p = "p"; + + /* devfs kludge - note: fdisk partition names are not supposed + to equal kernel names, so there is no reason to do this */ + if (strcmp(dev + w - 4, "disc") == 0) { + w -= 4; + p = "part"; + } + + wp = strlen(p); + + if (lth) { + snprintf(bufp, bufsiz, "%*.*s%s%-2u", + lth-wp-2, w, dev, p, pno); + } else { + snprintf(bufp, bufsiz, "%.*s%s%-2u", w, dev, p, pno); + } + return bufp; +} + +struct partition { + unsigned char boot_ind; /* 0x80 - active */ + unsigned char head; /* starting head */ + unsigned char sector; /* starting sector */ + unsigned char cyl; /* starting cylinder */ + unsigned char sys_ind; /* What partition type */ + unsigned char end_head; /* end head */ + unsigned char end_sector; /* end sector */ + unsigned char end_cyl; /* end cylinder */ + unsigned char start4[4]; /* starting sector counting from 0 */ + unsigned char size4[4]; /* nr of sectors in partition */ +} ATTRIBUTE_PACKED; + +enum failure { + ioctl_error, unable_to_open, unable_to_read, unable_to_seek, + unable_to_write +}; + +enum label_type { + label_dos, label_sun, label_sgi, label_aix, label_osf +}; +#define LABEL_IS_DOS (label_dos == current_label_type) + +#if ENABLE_FEATURE_SUN_LABEL +#define LABEL_IS_SUN (label_sun == current_label_type) +#define STATIC_SUN static +#else +#define LABEL_IS_SUN 0 +#define STATIC_SUN extern +#endif + +#if ENABLE_FEATURE_SGI_LABEL +#define LABEL_IS_SGI (label_sgi == current_label_type) +#define STATIC_SGI static +#else +#define LABEL_IS_SGI 0 +#define STATIC_SGI extern +#endif + +#if ENABLE_FEATURE_AIX_LABEL +#define LABEL_IS_AIX (label_aix == current_label_type) +#define STATIC_AIX static +#else +#define LABEL_IS_AIX 0 +#define STATIC_AIX extern +#endif + +#if ENABLE_FEATURE_OSF_LABEL +#define LABEL_IS_OSF (label_osf == current_label_type) +#define STATIC_OSF static +#else +#define LABEL_IS_OSF 0 +#define STATIC_OSF extern +#endif + +enum action { fdisk, require, try_only, create_empty_dos, create_empty_sun }; + +static enum label_type current_label_type; + +static const char *disk_device; +static int fd; /* the disk */ +static int partitions = 4; /* maximum partition + 1 */ +static int display_in_cyl_units = 1; +static unsigned units_per_sector = 1; +#if ENABLE_FEATURE_FDISK_WRITABLE +static void change_units(void); +static void reread_partition_table(int leave); +static void delete_partition(int i); +static int get_partition(int warn, int max); +static void list_types(const struct systypes *sys); +static unsigned read_int(unsigned low, unsigned dflt, unsigned high, unsigned base, char *mesg); +#endif +static const char *partition_type(unsigned char type); +static void fdisk_fatal(enum failure why) ATTRIBUTE_NORETURN; +static void get_geometry(void); +static int get_boot(enum action what); + +#define PLURAL 0 +#define SINGULAR 1 + +#define hex_val(c) ({ \ + char _c = (c); \ + isdigit(_c) ? _c - '0' : \ + tolower(_c) + 10 - 'a'; \ + }) + + +#define LINE_LENGTH 800 +#define pt_offset(b, n) ((struct partition *)((b) + 0x1be + \ + (n) * sizeof(struct partition))) +#define sector(s) ((s) & 0x3f) +#define cylinder(s, c) ((c) | (((s) & 0xc0) << 2)) + +#define hsc2sector(h,s,c) (sector(s) - 1 + sectors * \ + ((h) + heads * cylinder(s,c))) +#define set_hsc(h,s,c,sector) { \ + s = sector % sectors + 1; \ + sector /= sectors; \ + h = sector % heads; \ + sector /= heads; \ + c = sector & 0xff; \ + s |= (sector >> 2) & 0xc0; \ + } + + +static int32_t get_start_sect(const struct partition *p); +static int32_t get_nr_sects(const struct partition *p); + +/* + * per partition table entry data + * + * The four primary partitions have the same sectorbuffer (MBRbuffer) + * and have NULL ext_pointer. + * Each logical partition table entry has two pointers, one for the + * partition and one link to the next one. + */ +static struct pte { + struct partition *part_table; /* points into sectorbuffer */ + struct partition *ext_pointer; /* points into sectorbuffer */ +#if ENABLE_FEATURE_FDISK_WRITABLE + char changed; /* boolean */ +#endif + off_t offset; /* disk sector number */ + char *sectorbuffer; /* disk sector contents */ +} ptes[MAXIMUM_PARTS]; + + +#if ENABLE_FEATURE_FDISK_WRITABLE +static void +set_all_unchanged(void) +{ + int i; + + for (i = 0; i < MAXIMUM_PARTS; i++) + ptes[i].changed = 0; +} + +extern inline void +set_changed(int i) +{ + ptes[i].changed = 1; +} +#endif /* CONFIG_FEATURE_FDISK_WRITABLE */ + +extern inline struct partition * +get_part_table(int i) +{ + return ptes[i].part_table; +} + +static const char * +str_units(int n) +{ /* n==1: use singular */ + if (n == 1) + return display_in_cyl_units ? _("cylinder") : _("sector"); + else + return display_in_cyl_units ? _("cylinders") : _("sectors"); +} + +static int +valid_part_table_flag(const char *mbuffer) +{ + return (mbuffer[510] == 0x55 && (uint8_t)mbuffer[511] == 0xaa); +} + +#if ENABLE_FEATURE_FDISK_WRITABLE +extern inline void +write_part_table_flag(char *b) +{ + b[510] = 0x55; + b[511] = 0xaa; +} + +static char line_buffer[LINE_LENGTH]; +static char *line_ptr; + +/* read line; return 0 or first char */ +static int +read_line(void) +{ + fflush(stdout); /* requested by niles@scyld.com */ + line_ptr = line_buffer; + if (!fgets(line_buffer, LINE_LENGTH, stdin)) { + /* error or eof */ + bb_error_msg_and_die("\ngot EOF, exiting"); + } + while (*line_ptr && !isgraph(*line_ptr)) + line_ptr++; + return *line_ptr; +} + +static char +read_nonempty(const char *mesg) +{ + do { + fputs(mesg, stdout); + } while (!read_line()); + return *line_ptr; +} + +static char +read_maybe_empty(const char *mesg) +{ + fputs(mesg, stdout); + if (!read_line()) { + line_ptr = line_buffer; + *line_ptr = '\n'; + line_ptr[1] = 0; + } + return *line_ptr; +} + +static int +read_hex(const struct systypes *sys) +{ + unsigned long v; + while (1) { + read_nonempty(_("Hex code (type L to list codes): ")); + if (*line_ptr == 'l' || *line_ptr == 'L') { + list_types(sys); + continue; + } + v = bb_strtoul(line_ptr, NULL, 16); + if (errno || v > 0xff) continue; + return v; + } +} +#endif /* CONFIG_FEATURE_FDISK_WRITABLE */ + +#include "fdisk_aix.c" + +typedef struct { + unsigned char info[128]; /* Informative text string */ + unsigned char spare0[14]; + struct sun_info { + unsigned char spare1; + unsigned char id; + unsigned char spare2; + unsigned char flags; + } infos[8]; + unsigned char spare1[246]; /* Boot information etc. */ + unsigned short rspeed; /* Disk rotational speed */ + unsigned short pcylcount; /* Physical cylinder count */ + unsigned short sparecyl; /* extra sects per cylinder */ + unsigned char spare2[4]; /* More magic... */ + unsigned short ilfact; /* Interleave factor */ + unsigned short ncyl; /* Data cylinder count */ + unsigned short nacyl; /* Alt. cylinder count */ + unsigned short ntrks; /* Tracks per cylinder */ + unsigned short nsect; /* Sectors per track */ + unsigned char spare3[4]; /* Even more magic... */ + struct sun_partinfo { + uint32_t start_cylinder; + uint32_t num_sectors; + } partitions[8]; + unsigned short magic; /* Magic number */ + unsigned short csum; /* Label xor'd checksum */ +} sun_partition; +#define sunlabel ((sun_partition *)MBRbuffer) +#define SUNOS_SWAP 3 +#define SUN_WHOLE_DISK 5 +STATIC_OSF void bsd_select(void); +STATIC_OSF void xbsd_print_disklabel(int); +#include "fdisk_osf.c" + +#define SGI_VOLHDR 0x00 +/* 1 and 2 were used for drive types no longer supported by SGI */ +#define SGI_SWAP 0x03 +/* 4 and 5 were for filesystem types SGI haven't ever supported on MIPS CPUs */ +#define SGI_VOLUME 0x06 +#define SGI_EFS 0x07 +#define SGI_LVOL 0x08 +#define SGI_RLVOL 0x09 +#define SGI_XFS 0x0a +#define SGI_XFSLOG 0x0b +#define SGI_XLV 0x0c +#define SGI_XVM 0x0d +#define SGI_ENTIRE_DISK SGI_VOLUME +#if defined(CONFIG_FEATURE_SGI_LABEL) || defined(CONFIG_FEATURE_SUN_LABEL) +static uint16_t +__swap16(uint16_t x) +{ + return (x << 8) | (x >> 8); +} + +static uint32_t +__swap32(uint32_t x) +{ + return (x << 24) | + ((x & 0xFF00) << 8) | + ((x & 0xFF0000) >> 8) | + (x >> 24); +} +#endif + +STATIC_SGI const struct systypes sgi_sys_types[]; +STATIC_SGI unsigned sgi_get_num_sectors(int i); +STATIC_SGI int sgi_get_sysid(int i); +STATIC_SGI void sgi_delete_partition(int i); +STATIC_SGI void sgi_change_sysid(int i, int sys); +STATIC_SGI void sgi_list_table(int xtra); +STATIC_SGI void sgi_set_xcyl(void); +STATIC_SGI int verify_sgi(int verbose); +STATIC_SGI void sgi_add_partition(int n, int sys); +STATIC_SGI void sgi_set_swappartition(int i); +STATIC_SGI const char *sgi_get_bootfile(void); +STATIC_SGI void sgi_set_bootfile(const char* aFile); +STATIC_SGI void create_sgiinfo(void); +STATIC_SGI void sgi_write_table(void); +STATIC_SGI void sgi_set_bootpartition(int i); + +#include "fdisk_sgi.c" + +STATIC_SUN const struct systypes sun_sys_types[]; +STATIC_SUN void sun_delete_partition(int i); +STATIC_SUN void sun_change_sysid(int i, int sys); +STATIC_SUN void sun_list_table(int xtra); +STATIC_SUN void sun_set_xcyl(void); +STATIC_SUN void add_sun_partition(int n, int sys); +STATIC_SUN void sun_set_alt_cyl(void); +STATIC_SUN void sun_set_ncyl(int cyl); +STATIC_SUN void sun_set_xcyl(void); +STATIC_SUN void sun_set_ilfact(void); +STATIC_SUN void sun_set_rspeed(void); +STATIC_SUN void sun_set_pcylcount(void); +STATIC_SUN void toggle_sunflags(int i, unsigned char mask); +STATIC_SUN void verify_sun(void); +STATIC_SUN void sun_write_table(void); +#include "fdisk_sun.c" + +/* DOS partition types */ + +static const struct systypes i386_sys_types[] = { + { "\x00" "Empty" }, + { "\x01" "FAT12" }, + { "\x04" "FAT16 <32M" }, + { "\x05" "Extended" }, /* DOS 3.3+ extended partition */ + { "\x06" "FAT16" }, /* DOS 16-bit >=32M */ + { "\x07" "HPFS/NTFS" }, /* OS/2 IFS, eg, HPFS or NTFS or QNX */ + { "\x0a" "OS/2 Boot Manager" },/* OS/2 Boot Manager */ + { "\x0b" "Win95 FAT32" }, + { "\x0c" "Win95 FAT32 (LBA)" },/* LBA really is 'Extended Int 13h' */ + { "\x0e" "Win95 FAT16 (LBA)" }, + { "\x0f" "Win95 Ext'd (LBA)" }, + { "\x11" "Hidden FAT12" }, + { "\x12" "Compaq diagnostics" }, + { "\x14" "Hidden FAT16 <32M" }, + { "\x16" "Hidden FAT16" }, + { "\x17" "Hidden HPFS/NTFS" }, + { "\x1b" "Hidden Win95 FAT32" }, + { "\x1c" "Hidden Win95 FAT32 (LBA)" }, + { "\x1e" "Hidden Win95 FAT16 (LBA)" }, + { "\x3c" "PartitionMagic recovery" }, + { "\x41" "PPC PReP Boot" }, + { "\x42" "SFS" }, + { "\x63" "GNU HURD or SysV" }, /* GNU HURD or Mach or Sys V/386 (such as ISC UNIX) */ + { "\x80" "Old Minix" }, /* Minix 1.4a and earlier */ + { "\x81" "Minix / old Linux" },/* Minix 1.4b and later */ + { "\x82" "Linux swap" }, /* also Solaris */ + { "\x83" "Linux" }, + { "\x84" "OS/2 hidden C: drive" }, + { "\x85" "Linux extended" }, + { "\x86" "NTFS volume set" }, + { "\x87" "NTFS volume set" }, + { "\x8e" "Linux LVM" }, + { "\x9f" "BSD/OS" }, /* BSDI */ + { "\xa0" "IBM Thinkpad hibernation" }, + { "\xa5" "FreeBSD" }, /* various BSD flavours */ + { "\xa6" "OpenBSD" }, + { "\xa8" "Darwin UFS" }, + { "\xa9" "NetBSD" }, + { "\xab" "Darwin boot" }, + { "\xb7" "BSDI fs" }, + { "\xb8" "BSDI swap" }, + { "\xbe" "Solaris boot" }, + { "\xeb" "BeOS fs" }, + { "\xee" "EFI GPT" }, /* Intel EFI GUID Partition Table */ + { "\xef" "EFI (FAT-12/16/32)" },/* Intel EFI System Partition */ + { "\xf0" "Linux/PA-RISC boot" },/* Linux/PA-RISC boot loader */ + { "\xf2" "DOS secondary" }, /* DOS 3.3+ secondary */ + { "\xfd" "Linux raid autodetect" },/* New (2.2.x) raid partition with + autodetect using persistent + superblock */ +#if 0 /* ENABLE_WEIRD_PARTITION_TYPES */ + { "\x02" "XENIX root" }, + { "\x03" "XENIX usr" }, + { "\x08" "AIX" }, /* AIX boot (AIX -- PS/2 port) or SplitDrive */ + { "\x09" "AIX bootable" }, /* AIX data or Coherent */ + { "\x10" "OPUS" }, + { "\x18" "AST SmartSleep" }, + { "\x24" "NEC DOS" }, + { "\x39" "Plan 9" }, + { "\x40" "Venix 80286" }, + { "\x4d" "QNX4.x" }, + { "\x4e" "QNX4.x 2nd part" }, + { "\x4f" "QNX4.x 3rd part" }, + { "\x50" "OnTrack DM" }, + { "\x51" "OnTrack DM6 Aux1" }, /* (or Novell) */ + { "\x52" "CP/M" }, /* CP/M or Microport SysV/AT */ + { "\x53" "OnTrack DM6 Aux3" }, + { "\x54" "OnTrackDM6" }, + { "\x55" "EZ-Drive" }, + { "\x56" "Golden Bow" }, + { "\x5c" "Priam Edisk" }, + { "\x61" "SpeedStor" }, + { "\x64" "Novell Netware 286" }, + { "\x65" "Novell Netware 386" }, + { "\x70" "DiskSecure Multi-Boot" }, + { "\x75" "PC/IX" }, + { "\x93" "Amoeba" }, + { "\x94" "Amoeba BBT" }, /* (bad block table) */ + { "\xa7" "NeXTSTEP" }, + { "\xbb" "Boot Wizard hidden" }, + { "\xc1" "DRDOS/sec (FAT-12)" }, + { "\xc4" "DRDOS/sec (FAT-16 < 32M)" }, + { "\xc6" "DRDOS/sec (FAT-16)" }, + { "\xc7" "Syrinx" }, + { "\xda" "Non-FS data" }, + { "\xdb" "CP/M / CTOS / ..." },/* CP/M or Concurrent CP/M or + Concurrent DOS or CTOS */ + { "\xde" "Dell Utility" }, /* Dell PowerEdge Server utilities */ + { "\xdf" "BootIt" }, /* BootIt EMBRM */ + { "\xe1" "DOS access" }, /* DOS access or SpeedStor 12-bit FAT + extended partition */ + { "\xe3" "DOS R/O" }, /* DOS R/O or SpeedStor */ + { "\xe4" "SpeedStor" }, /* SpeedStor 16-bit FAT extended + partition < 1024 cyl. */ + { "\xf1" "SpeedStor" }, + { "\xf4" "SpeedStor" }, /* SpeedStor large partition */ + { "\xfe" "LANstep" }, /* SpeedStor >1024 cyl. or LANstep */ + { "\xff" "BBT" }, /* Xenix Bad Block Table */ +#endif + { 0 } +}; + + +#if ENABLE_FEATURE_FDISK_WRITABLE +/* start_sect and nr_sects are stored little endian on all machines */ +/* moreover, they are not aligned correctly */ +static void +store4_little_endian(unsigned char *cp, unsigned val) +{ + cp[0] = val; + cp[1] = val >> 8; + cp[2] = val >> 16; + cp[3] = val >> 24; +} +#endif /* CONFIG_FEATURE_FDISK_WRITABLE */ + +static unsigned +read4_little_endian(const unsigned char *cp) +{ + return cp[0] + (cp[1] << 8) + (cp[2] << 16) + (cp[3] << 24); +} + +#if ENABLE_FEATURE_FDISK_WRITABLE +static void +set_start_sect(struct partition *p, unsigned start_sect) +{ + store4_little_endian(p->start4, start_sect); +} +#endif + +static int32_t +get_start_sect(const struct partition *p) +{ + return read4_little_endian(p->start4); +} + +#if ENABLE_FEATURE_FDISK_WRITABLE +static void +set_nr_sects(struct partition *p, int32_t nr_sects) +{ + store4_little_endian(p->size4, nr_sects); +} +#endif + +static int32_t +get_nr_sects(const struct partition *p) +{ + return read4_little_endian(p->size4); +} + +/* normally O_RDWR, -l option gives O_RDONLY */ +static int type_open = O_RDWR; + + +static int ext_index; /* the prime extended partition */ +static int listing; /* no aborts for fdisk -l */ +static int dos_compatible_flag = ~0; +#if ENABLE_FEATURE_FDISK_WRITABLE +static int dos_changed; +static int nowarn; /* no warnings for fdisk -l/-s */ +#endif + + + +static unsigned user_cylinders, user_heads, user_sectors; +static unsigned pt_heads, pt_sectors; +static unsigned kern_heads, kern_sectors; + +static off_t extended_offset; /* offset of link pointers */ + +static unsigned long long total_number_of_sectors; + + +static jmp_buf listingbuf; + +static void fdisk_fatal(enum failure why) +{ + const char *message; + + if (listing) { + close(fd); + longjmp(listingbuf, 1); + } + + switch (why) { + case unable_to_open: + message = "\nUnable to open %s"; + break; + case unable_to_read: + message = "\nUnable to read %s"; + break; + case unable_to_seek: + message = "\nUnable to seek on %s"; + break; + case unable_to_write: + message = "\nUnable to write %s"; + break; + case ioctl_error: + message = "\nBLKGETSIZE ioctl failed on %s"; + break; + default: + message = "\nFatal error"; + } + + bb_error_msg_and_die(message, disk_device); +} + +static void +seek_sector(off_t secno) +{ + off_t offset = secno * sector_size; + if (lseek(fd, offset, SEEK_SET) == (off_t) -1) + fdisk_fatal(unable_to_seek); +} + +#if ENABLE_FEATURE_FDISK_WRITABLE +static void +write_sector(off_t secno, char *buf) +{ + seek_sector(secno); + if (write(fd, buf, sector_size) != sector_size) + fdisk_fatal(unable_to_write); +} +#endif + +/* Allocate a buffer and read a partition table sector */ +static void +read_pte(struct pte *pe, off_t offset) +{ + pe->offset = offset; + pe->sectorbuffer = (char *) xmalloc(sector_size); + seek_sector(offset); + if (read(fd, pe->sectorbuffer, sector_size) != sector_size) + fdisk_fatal(unable_to_read); +#if ENABLE_FEATURE_FDISK_WRITABLE + pe->changed = 0; +#endif + pe->part_table = pe->ext_pointer = NULL; +} + +static unsigned +get_partition_start(const struct pte *pe) +{ + return pe->offset + get_start_sect(pe->part_table); +} + +#if ENABLE_FEATURE_FDISK_WRITABLE +/* + * Avoid warning about DOS partitions when no DOS partition was changed. + * Here a heuristic "is probably dos partition". + * We might also do the opposite and warn in all cases except + * for "is probably nondos partition". + */ +static int +is_dos_partition(int t) +{ + return (t == 1 || t == 4 || t == 6 || + t == 0x0b || t == 0x0c || t == 0x0e || + t == 0x11 || t == 0x12 || t == 0x14 || t == 0x16 || + t == 0x1b || t == 0x1c || t == 0x1e || t == 0x24 || + t == 0xc1 || t == 0xc4 || t == 0xc6); +} + +static void +menu(void) +{ + if (LABEL_IS_SUN) { + puts(_("Command action")); + puts(_("\ta\ttoggle a read only flag")); /* sun */ + puts(_("\tb\tedit bsd disklabel")); + puts(_("\tc\ttoggle the mountable flag")); /* sun */ + puts(_("\td\tdelete a partition")); + puts(_("\tl\tlist known partition types")); + puts(_("\tm\tprint this menu")); + puts(_("\tn\tadd a new partition")); + puts(_("\to\tcreate a new empty DOS partition table")); + puts(_("\tp\tprint the partition table")); + puts(_("\tq\tquit without saving changes")); + puts(_("\ts\tcreate a new empty Sun disklabel")); /* sun */ + puts(_("\tt\tchange a partition's system id")); + puts(_("\tu\tchange display/entry units")); + puts(_("\tv\tverify the partition table")); + puts(_("\tw\twrite table to disk and exit")); +#if ENABLE_FEATURE_FDISK_ADVANCED + puts(_("\tx\textra functionality (experts only)")); +#endif + } else + if (LABEL_IS_SGI) { + puts(_("Command action")); + puts(_("\ta\tselect bootable partition")); /* sgi flavour */ + puts(_("\tb\tedit bootfile entry")); /* sgi */ + puts(_("\tc\tselect sgi swap partition")); /* sgi flavour */ + puts(_("\td\tdelete a partition")); + puts(_("\tl\tlist known partition types")); + puts(_("\tm\tprint this menu")); + puts(_("\tn\tadd a new partition")); + puts(_("\to\tcreate a new empty DOS partition table")); + puts(_("\tp\tprint the partition table")); + puts(_("\tq\tquit without saving changes")); + puts(_("\ts\tcreate a new empty Sun disklabel")); /* sun */ + puts(_("\tt\tchange a partition's system id")); + puts(_("\tu\tchange display/entry units")); + puts(_("\tv\tverify the partition table")); + puts(_("\tw\twrite table to disk and exit")); + } else + if (LABEL_IS_AIX) { + puts(_("Command action")); + puts(_("\tm\tprint this menu")); + puts(_("\to\tcreate a new empty DOS partition table")); + puts(_("\tq\tquit without saving changes")); + puts(_("\ts\tcreate a new empty Sun disklabel")); /* sun */ + } else + { + puts(_("Command action")); + puts(_("\ta\ttoggle a bootable flag")); + puts(_("\tb\tedit bsd disklabel")); + puts(_("\tc\ttoggle the dos compatibility flag")); + puts(_("\td\tdelete a partition")); + puts(_("\tl\tlist known partition types")); + puts(_("\tm\tprint this menu")); + puts(_("\tn\tadd a new partition")); + puts(_("\to\tcreate a new empty DOS partition table")); + puts(_("\tp\tprint the partition table")); + puts(_("\tq\tquit without saving changes")); + puts(_("\ts\tcreate a new empty Sun disklabel")); /* sun */ + puts(_("\tt\tchange a partition's system id")); + puts(_("\tu\tchange display/entry units")); + puts(_("\tv\tverify the partition table")); + puts(_("\tw\twrite table to disk and exit")); +#if ENABLE_FEATURE_FDISK_ADVANCED + puts(_("\tx\textra functionality (experts only)")); +#endif + } +} +#endif /* CONFIG_FEATURE_FDISK_WRITABLE */ + + +#if ENABLE_FEATURE_FDISK_ADVANCED +static void +xmenu(void) +{ + if (LABEL_IS_SUN) { + puts(_("Command action")); + puts(_("\ta\tchange number of alternate cylinders")); /*sun*/ + puts(_("\tc\tchange number of cylinders")); + puts(_("\td\tprint the raw data in the partition table")); + puts(_("\te\tchange number of extra sectors per cylinder"));/*sun*/ + puts(_("\th\tchange number of heads")); + puts(_("\ti\tchange interleave factor")); /*sun*/ + puts(_("\to\tchange rotation speed (rpm)")); /*sun*/ + puts(_("\tm\tprint this menu")); + puts(_("\tp\tprint the partition table")); + puts(_("\tq\tquit without saving changes")); + puts(_("\tr\treturn to main menu")); + puts(_("\ts\tchange number of sectors/track")); + puts(_("\tv\tverify the partition table")); + puts(_("\tw\twrite table to disk and exit")); + puts(_("\ty\tchange number of physical cylinders")); /*sun*/ + } else + if (LABEL_IS_SGI) { + puts(_("Command action")); + puts(_("\tb\tmove beginning of data in a partition")); /* !sun */ + puts(_("\tc\tchange number of cylinders")); + puts(_("\td\tprint the raw data in the partition table")); + puts(_("\te\tlist extended partitions")); /* !sun */ + puts(_("\tg\tcreate an IRIX (SGI) partition table"));/* sgi */ + puts(_("\th\tchange number of heads")); + puts(_("\tm\tprint this menu")); + puts(_("\tp\tprint the partition table")); + puts(_("\tq\tquit without saving changes")); + puts(_("\tr\treturn to main menu")); + puts(_("\ts\tchange number of sectors/track")); + puts(_("\tv\tverify the partition table")); + puts(_("\tw\twrite table to disk and exit")); + } else + if (LABEL_IS_AIX) { + puts(_("Command action")); + puts(_("\tb\tmove beginning of data in a partition")); /* !sun */ + puts(_("\tc\tchange number of cylinders")); + puts(_("\td\tprint the raw data in the partition table")); + puts(_("\te\tlist extended partitions")); /* !sun */ + puts(_("\tg\tcreate an IRIX (SGI) partition table"));/* sgi */ + puts(_("\th\tchange number of heads")); + puts(_("\tm\tprint this menu")); + puts(_("\tp\tprint the partition table")); + puts(_("\tq\tquit without saving changes")); + puts(_("\tr\treturn to main menu")); + puts(_("\ts\tchange number of sectors/track")); + puts(_("\tv\tverify the partition table")); + puts(_("\tw\twrite table to disk and exit")); + } else { + puts(_("Command action")); + puts(_("\tb\tmove beginning of data in a partition")); /* !sun */ + puts(_("\tc\tchange number of cylinders")); + puts(_("\td\tprint the raw data in the partition table")); + puts(_("\te\tlist extended partitions")); /* !sun */ + puts(_("\tf\tfix partition order")); /* !sun, !aix, !sgi */ +#if ENABLE_FEATURE_SGI_LABEL + puts(_("\tg\tcreate an IRIX (SGI) partition table"));/* sgi */ +#endif + puts(_("\th\tchange number of heads")); + puts(_("\tm\tprint this menu")); + puts(_("\tp\tprint the partition table")); + puts(_("\tq\tquit without saving changes")); + puts(_("\tr\treturn to main menu")); + puts(_("\ts\tchange number of sectors/track")); + puts(_("\tv\tverify the partition table")); + puts(_("\tw\twrite table to disk and exit")); + } +} +#endif /* ADVANCED mode */ + +#if ENABLE_FEATURE_FDISK_WRITABLE +static const struct systypes * +get_sys_types(void) +{ + return ( + LABEL_IS_SUN ? sun_sys_types : + LABEL_IS_SGI ? sgi_sys_types : + i386_sys_types); +} +#else +#define get_sys_types() i386_sys_types +#endif /* CONFIG_FEATURE_FDISK_WRITABLE */ + +static const char *partition_type(unsigned char type) +{ + int i; + const struct systypes *types = get_sys_types(); + + for (i = 0; types[i].name; i++) + if ((unsigned char )types[i].name[0] == type) + return types[i].name + 1; + + return _("Unknown"); +} + + +#if ENABLE_FEATURE_FDISK_WRITABLE +static int +get_sysid(int i) +{ + return LABEL_IS_SUN ? sunlabel->infos[i].id : + (LABEL_IS_SGI ? sgi_get_sysid(i) : + ptes[i].part_table->sys_ind); +} + +void list_types(const struct systypes *sys) +{ + unsigned last[4], done = 0, next = 0, size; + int i; + + for (i = 0; sys[i].name; i++); + size = i; + + for (i = 3; i >= 0; i--) + last[3 - i] = done += (size + i - done) / (i + 1); + i = done = 0; + + do { + printf("%c%2x %-15.15s", i ? ' ' : '\n', + (unsigned char)sys[next].name[0], + partition_type((unsigned char)sys[next].name[0])); + next = last[i++] + done; + if (i > 3 || next >= last[i]) { + i = 0; + next = ++done; + } + } while (done < last[0]); + putchar('\n'); +} +#endif /* CONFIG_FEATURE_FDISK_WRITABLE */ + +static int +is_cleared_partition(const struct partition *p) +{ + return !(!p || p->boot_ind || p->head || p->sector || p->cyl || + p->sys_ind || p->end_head || p->end_sector || p->end_cyl || + get_start_sect(p) || get_nr_sects(p)); +} + +static void +clear_partition(struct partition *p) +{ + if (!p) + return; + memset(p, 0, sizeof(struct partition)); +} + +#if ENABLE_FEATURE_FDISK_WRITABLE +static void +set_partition(int i, int doext, off_t start, off_t stop, int sysid) +{ + struct partition *p; + off_t offset; + + if (doext) { + p = ptes[i].ext_pointer; + offset = extended_offset; + } else { + p = ptes[i].part_table; + offset = ptes[i].offset; + } + p->boot_ind = 0; + p->sys_ind = sysid; + set_start_sect(p, start - offset); + set_nr_sects(p, stop - start + 1); + if (dos_compatible_flag && (start/(sectors*heads) > 1023)) + start = heads*sectors*1024 - 1; + set_hsc(p->head, p->sector, p->cyl, start); + if (dos_compatible_flag && (stop/(sectors*heads) > 1023)) + stop = heads*sectors*1024 - 1; + set_hsc(p->end_head, p->end_sector, p->end_cyl, stop); + ptes[i].changed = 1; +} +#endif + +static int +test_c(const char **m, const char *mesg) +{ + int val = 0; + if (!*m) + printf(_("You must set")); + else { + printf(" %s", *m); + val = 1; + } + *m = mesg; + return val; +} + +static int +warn_geometry(void) +{ + const char *m = NULL; + int prev = 0; + + if (!heads) + prev = test_c(&m, _("heads")); + if (!sectors) + prev = test_c(&m, _("sectors")); + if (!cylinders) + prev = test_c(&m, _("cylinders")); + if (!m) + return 0; + + printf("%s%s.\n" +#if ENABLE_FEATURE_FDISK_WRITABLE + "You can do this from the extra functions menu.\n" +#endif + , prev ? _(" and ") : " ", m); + + return 1; +} + +static void update_units(void) +{ + int cyl_units = heads * sectors; + + if (display_in_cyl_units && cyl_units) + units_per_sector = cyl_units; + else + units_per_sector = 1; /* in sectors */ +} + +#if ENABLE_FEATURE_FDISK_WRITABLE +static void +warn_cylinders(void) +{ + if (LABEL_IS_DOS && cylinders > 1024 && !nowarn) + printf(_("\n" +"The number of cylinders for this disk is set to %d.\n" +"There is nothing wrong with that, but this is larger than 1024,\n" +"and could in certain setups cause problems with:\n" +"1) software that runs at boot time (e.g., old versions of LILO)\n" +"2) booting and partitioning software from other OSs\n" +" (e.g., DOS FDISK, OS/2 FDISK)\n"), + cylinders); +} +#endif + +static void +read_extended(int ext) +{ + int i; + struct pte *pex; + struct partition *p, *q; + + ext_index = ext; + pex = &ptes[ext]; + pex->ext_pointer = pex->part_table; + + p = pex->part_table; + if (!get_start_sect(p)) { + printf(_("Bad offset in primary extended partition\n")); + return; + } + + while (IS_EXTENDED(p->sys_ind)) { + struct pte *pe = &ptes[partitions]; + + if (partitions >= MAXIMUM_PARTS) { + /* This is not a Linux restriction, but + this program uses arrays of size MAXIMUM_PARTS. + Do not try to 'improve' this test. */ + struct pte *pre = &ptes[partitions-1]; +#if ENABLE_FEATURE_FDISK_WRITABLE + printf(_("Warning: deleting partitions after %d\n"), + partitions); + pre->changed = 1; +#endif + clear_partition(pre->ext_pointer); + return; + } + + read_pte(pe, extended_offset + get_start_sect(p)); + + if (!extended_offset) + extended_offset = get_start_sect(p); + + q = p = pt_offset(pe->sectorbuffer, 0); + for (i = 0; i < 4; i++, p++) if (get_nr_sects(p)) { + if (IS_EXTENDED(p->sys_ind)) { + if (pe->ext_pointer) + printf(_("Warning: extra link " + "pointer in partition table" + " %d\n"), partitions + 1); + else + pe->ext_pointer = p; + } else if (p->sys_ind) { + if (pe->part_table) + printf(_("Warning: ignoring extra " + "data in partition table" + " %d\n"), partitions + 1); + else + pe->part_table = p; + } + } + + /* very strange code here... */ + if (!pe->part_table) { + if (q != pe->ext_pointer) + pe->part_table = q; + else + pe->part_table = q + 1; + } + if (!pe->ext_pointer) { + if (q != pe->part_table) + pe->ext_pointer = q; + else + pe->ext_pointer = q + 1; + } + + p = pe->ext_pointer; + partitions++; + } + +#if ENABLE_FEATURE_FDISK_WRITABLE + /* remove empty links */ + remove: + for (i = 4; i < partitions; i++) { + struct pte *pe = &ptes[i]; + + if (!get_nr_sects(pe->part_table) && + (partitions > 5 || ptes[4].part_table->sys_ind)) { + printf("omitting empty partition (%d)\n", i+1); + delete_partition(i); + goto remove; /* numbering changed */ + } + } +#endif +} + +#if ENABLE_FEATURE_FDISK_WRITABLE +static void +create_doslabel(void) +{ + int i; + + printf( + _("Building a new DOS disklabel. Changes will remain in memory only,\n" + "until you decide to write them. After that, of course, the previous\n" + "content won't be recoverable.\n\n")); + + current_label_type = label_dos; + +#if ENABLE_FEATURE_OSF_LABEL + possibly_osf_label = 0; +#endif + partitions = 4; + + for (i = 510-64; i < 510; i++) + MBRbuffer[i] = 0; + write_part_table_flag(MBRbuffer); + extended_offset = 0; + set_all_unchanged(); + set_changed(0); + get_boot(create_empty_dos); +} +#endif /* CONFIG_FEATURE_FDISK_WRITABLE */ + +static void +get_sectorsize(void) +{ + if (!user_set_sector_size) { + int arg; + if (ioctl(fd, BLKSSZGET, &arg) == 0) + sector_size = arg; + if (sector_size != DEFAULT_SECTOR_SIZE) + printf(_("Note: sector size is %d (not %d)\n"), + sector_size, DEFAULT_SECTOR_SIZE); + } +} + +static void +get_kernel_geometry(void) +{ + struct hd_geometry geometry; + + if (!ioctl(fd, HDIO_GETGEO, &geometry)) { + kern_heads = geometry.heads; + kern_sectors = geometry.sectors; + /* never use geometry.cylinders - it is truncated */ + } +} + +static void +get_partition_table_geometry(void) +{ + const unsigned char *bufp = (const unsigned char *)MBRbuffer; + struct partition *p; + int i, h, s, hh, ss; + int first = 1; + int bad = 0; + + if (!(valid_part_table_flag((char*)bufp))) + return; + + hh = ss = 0; + for (i = 0; i < 4; i++) { + p = pt_offset(bufp, i); + if (p->sys_ind != 0) { + h = p->end_head + 1; + s = (p->end_sector & 077); + if (first) { + hh = h; + ss = s; + first = 0; + } else if (hh != h || ss != s) + bad = 1; + } + } + + if (!first && !bad) { + pt_heads = hh; + pt_sectors = ss; + } +} + +static void +get_geometry(void) +{ + int sec_fac; + unsigned long long bytes; /* really u64 */ + + get_sectorsize(); + sec_fac = sector_size / 512; +#if ENABLE_FEATURE_SUN_LABEL + guess_device_type(); +#endif + heads = cylinders = sectors = 0; + kern_heads = kern_sectors = 0; + pt_heads = pt_sectors = 0; + + get_kernel_geometry(); + get_partition_table_geometry(); + + heads = user_heads ? user_heads : + pt_heads ? pt_heads : + kern_heads ? kern_heads : 255; + sectors = user_sectors ? user_sectors : + pt_sectors ? pt_sectors : + kern_sectors ? kern_sectors : 63; + if (ioctl(fd, BLKGETSIZE64, &bytes) == 0) { + /* got bytes */ + } else { + unsigned long longsectors; + + if (ioctl(fd, BLKGETSIZE, &longsectors)) + longsectors = 0; + bytes = ((unsigned long long) longsectors) << 9; + } + + total_number_of_sectors = (bytes >> 9); + + sector_offset = 1; + if (dos_compatible_flag) + sector_offset = sectors; + + cylinders = total_number_of_sectors / (heads * sectors * sec_fac); + if (!cylinders) + cylinders = user_cylinders; +} + +/* + * Read MBR. Returns: + * -1: no 0xaa55 flag present (possibly entire disk BSD) + * 0: found or created label + * 1: I/O error + */ +static int +get_boot(enum action what) +{ + int i; + + partitions = 4; + + for (i = 0; i < 4; i++) { + struct pte *pe = &ptes[i]; + + pe->part_table = pt_offset(MBRbuffer, i); + pe->ext_pointer = NULL; + pe->offset = 0; + pe->sectorbuffer = MBRbuffer; +#if ENABLE_FEATURE_FDISK_WRITABLE + pe->changed = (what == create_empty_dos); +#endif + } + +#if ENABLE_FEATURE_SUN_LABEL + if (what == create_empty_sun && check_sun_label()) + return 0; +#endif + + memset(MBRbuffer, 0, 512); + +#if ENABLE_FEATURE_FDISK_WRITABLE + if (what == create_empty_dos) + goto got_dos_table; /* skip reading disk */ + + if ((fd = open(disk_device, type_open)) < 0) { + if ((fd = open(disk_device, O_RDONLY)) < 0) { + if (what == try_only) + return 1; + fdisk_fatal(unable_to_open); + } else + printf(_("You will not be able to write " + "the partition table.\n")); + } + + if (512 != read(fd, MBRbuffer, 512)) { + if (what == try_only) + return 1; + fdisk_fatal(unable_to_read); + } +#else + if ((fd = open(disk_device, O_RDONLY)) < 0) + return 1; + if (512 != read(fd, MBRbuffer, 512)) + return 1; +#endif + + get_geometry(); + + update_units(); + +#if ENABLE_FEATURE_SUN_LABEL + if (check_sun_label()) + return 0; +#endif + +#if ENABLE_FEATURE_SGI_LABEL + if (check_sgi_label()) + return 0; +#endif + +#if ENABLE_FEATURE_AIX_LABEL + if (check_aix_label()) + return 0; +#endif + +#if ENABLE_FEATURE_OSF_LABEL + if (check_osf_label()) { + possibly_osf_label = 1; + if (!valid_part_table_flag(MBRbuffer)) { + current_label_type = label_osf; + return 0; + } + printf(_("This disk has both DOS and BSD magic.\n" + "Give the 'b' command to go to BSD mode.\n")); + } +#endif + +#if ENABLE_FEATURE_FDISK_WRITABLE + got_dos_table: +#endif + + if (!valid_part_table_flag(MBRbuffer)) { +#ifndef CONFIG_FEATURE_FDISK_WRITABLE + return -1; +#else + switch (what) { + case fdisk: + printf(_("Device contains neither a valid DOS " + "partition table, nor Sun, SGI or OSF " + "disklabel\n")); +#ifdef __sparc__ +#if ENABLE_FEATURE_SUN_LABEL + create_sunlabel(); +#endif +#else + create_doslabel(); +#endif + return 0; + case try_only: + return -1; + case create_empty_dos: +#if ENABLE_FEATURE_SUN_LABEL + case create_empty_sun: +#endif + break; + default: + bb_error_msg_and_die(_("internal error")); + } +#endif /* CONFIG_FEATURE_FDISK_WRITABLE */ + } + +#if ENABLE_FEATURE_FDISK_WRITABLE + warn_cylinders(); +#endif + warn_geometry(); + + for (i = 0; i < 4; i++) { + struct pte *pe = &ptes[i]; + + if (IS_EXTENDED(pe->part_table->sys_ind)) { + if (partitions != 4) + printf(_("Ignoring extra extended " + "partition %d\n"), i + 1); + else + read_extended(i); + } + } + + for (i = 3; i < partitions; i++) { + struct pte *pe = &ptes[i]; + + if (!valid_part_table_flag(pe->sectorbuffer)) { + printf(_("Warning: invalid flag 0x%02x,0x%02x of partition " + "table %d will be corrected by w(rite)\n"), + pe->sectorbuffer[510], + pe->sectorbuffer[511], + i + 1); +#if ENABLE_FEATURE_FDISK_WRITABLE + pe->changed = 1; +#endif + } + } + + return 0; +} + +#if ENABLE_FEATURE_FDISK_WRITABLE +/* + * Print the message MESG, then read an integer between LOW and HIGH (inclusive). + * If the user hits Enter, DFLT is returned. + * Answers like +10 are interpreted as offsets from BASE. + * + * There is no default if DFLT is not between LOW and HIGH. + */ +static unsigned +read_int(unsigned low, unsigned dflt, unsigned high, unsigned base, char *mesg) +{ + unsigned i; + int default_ok = 1; + const char *fmt = "%s (%u-%u, default %u): "; + + if (dflt < low || dflt > high) { + fmt = "%s (%u-%u): "; + default_ok = 0; + } + + while (1) { + int use_default = default_ok; + + /* ask question and read answer */ + do { + printf(fmt, mesg, low, high, dflt); + read_maybe_empty(""); + } while (*line_ptr != '\n' && !isdigit(*line_ptr) + && *line_ptr != '-' && *line_ptr != '+'); + + if (*line_ptr == '+' || *line_ptr == '-') { + int minus = (*line_ptr == '-'); + int absolute = 0; + + i = atoi(line_ptr + 1); + + while (isdigit(*++line_ptr)) + use_default = 0; + + switch (*line_ptr) { + case 'c': + case 'C': + if (!display_in_cyl_units) + i *= heads * sectors; + break; + case 'K': + absolute = 1024; + break; + case 'k': + absolute = 1000; + break; + case 'm': + case 'M': + absolute = 1000000; + break; + case 'g': + case 'G': + absolute = 1000000000; + break; + default: + break; + } + if (absolute) { + unsigned long long bytes; + unsigned long unit; + + bytes = (unsigned long long) i * absolute; + unit = sector_size * units_per_sector; + bytes += unit/2; /* round */ + bytes /= unit; + i = bytes; + } + if (minus) + i = -i; + i += base; + } else { + i = atoi(line_ptr); + while (isdigit(*line_ptr)) { + line_ptr++; + use_default = 0; + } + } + if (use_default) + printf(_("Using default value %u\n"), i = dflt); + if (i >= low && i <= high) + break; + else + printf(_("Value is out of range\n")); + } + return i; +} + +static int +get_partition(int warn, int max) +{ + struct pte *pe; + int i; + + i = read_int(1, 0, max, 0, _("Partition number")) - 1; + pe = &ptes[i]; + + if (warn) { + if ((!LABEL_IS_SUN && !LABEL_IS_SGI && !pe->part_table->sys_ind) + || (LABEL_IS_SUN && (!sunlabel->partitions[i].num_sectors || !sunlabel->infos[i].id)) + || (LABEL_IS_SGI && !sgi_get_num_sectors(i)) + ) { + printf(_("Warning: partition %d has empty type\n"), i+1); + } + } + return i; +} + +static int +get_existing_partition(int warn, int max) +{ + int pno = -1; + int i; + + for (i = 0; i < max; i++) { + struct pte *pe = &ptes[i]; + struct partition *p = pe->part_table; + + if (p && !is_cleared_partition(p)) { + if (pno >= 0) + goto not_unique; + pno = i; + } + } + if (pno >= 0) { + printf(_("Selected partition %d\n"), pno+1); + return pno; + } + printf(_("No partition is defined yet!\n")); + return -1; + + not_unique: + return get_partition(warn, max); +} + +static int +get_nonexisting_partition(int warn, int max) +{ + int pno = -1; + int i; + + for (i = 0; i < max; i++) { + struct pte *pe = &ptes[i]; + struct partition *p = pe->part_table; + + if (p && is_cleared_partition(p)) { + if (pno >= 0) + goto not_unique; + pno = i; + } + } + if (pno >= 0) { + printf(_("Selected partition %d\n"), pno+1); + return pno; + } + printf(_("All primary partitions have been defined already!\n")); + return -1; + + not_unique: + return get_partition(warn, max); +} + + +static void +change_units(void) +{ + display_in_cyl_units = !display_in_cyl_units; + update_units(); + printf(_("Changing display/entry units to %s\n"), + str_units(PLURAL)); +} + +static void +toggle_active(int i) +{ + struct pte *pe = &ptes[i]; + struct partition *p = pe->part_table; + + if (IS_EXTENDED(p->sys_ind) && !p->boot_ind) + printf(_("WARNING: Partition %d is an extended partition\n"), i + 1); + p->boot_ind = (p->boot_ind ? 0 : ACTIVE_FLAG); + pe->changed = 1; +} + +static void +toggle_dos_compatibility_flag(void) +{ + dos_compatible_flag = ~dos_compatible_flag; + if (dos_compatible_flag) { + sector_offset = sectors; + printf(_("DOS Compatibility flag is set\n")); + } + else { + sector_offset = 1; + printf(_("DOS Compatibility flag is not set\n")); + } +} + +static void +delete_partition(int i) +{ + struct pte *pe = &ptes[i]; + struct partition *p = pe->part_table; + struct partition *q = pe->ext_pointer; + +/* Note that for the fifth partition (i == 4) we don't actually + * decrement partitions. + */ + + if (warn_geometry()) + return; /* C/H/S not set */ + pe->changed = 1; + + if (LABEL_IS_SUN) { + sun_delete_partition(i); + return; + } + if (LABEL_IS_SGI) { + sgi_delete_partition(i); + return; + } + + if (i < 4) { + if (IS_EXTENDED(p->sys_ind) && i == ext_index) { + partitions = 4; + ptes[ext_index].ext_pointer = NULL; + extended_offset = 0; + } + clear_partition(p); + return; + } + + if (!q->sys_ind && i > 4) { + /* the last one in the chain - just delete */ + --partitions; + --i; + clear_partition(ptes[i].ext_pointer); + ptes[i].changed = 1; + } else { + /* not the last one - further ones will be moved down */ + if (i > 4) { + /* delete this link in the chain */ + p = ptes[i-1].ext_pointer; + *p = *q; + set_start_sect(p, get_start_sect(q)); + set_nr_sects(p, get_nr_sects(q)); + ptes[i-1].changed = 1; + } else if (partitions > 5) { /* 5 will be moved to 4 */ + /* the first logical in a longer chain */ + pe = &ptes[5]; + + if (pe->part_table) /* prevent SEGFAULT */ + set_start_sect(pe->part_table, + get_partition_start(pe) - + extended_offset); + pe->offset = extended_offset; + pe->changed = 1; + } + + if (partitions > 5) { + partitions--; + while (i < partitions) { + ptes[i] = ptes[i+1]; + i++; + } + } else + /* the only logical: clear only */ + clear_partition(ptes[i].part_table); + } +} + +static void +change_sysid(void) +{ + int i, sys, origsys; + struct partition *p; + + /* If sgi_label then don't use get_existing_partition, + let the user select a partition, since get_existing_partition() + only works for Linux like partition tables. */ + if (!LABEL_IS_SGI) { + i = get_existing_partition(0, partitions); + } else { + i = get_partition(0, partitions); + } + if (i == -1) + return; + p = ptes[i].part_table; + origsys = sys = get_sysid(i); + + /* if changing types T to 0 is allowed, then + the reverse change must be allowed, too */ + if (!sys && !LABEL_IS_SGI && !LABEL_IS_SUN && !get_nr_sects(p)) { + printf(_("Partition %d does not exist yet!\n"), i + 1); + return; + } + while (1) { + sys = read_hex (get_sys_types()); + + if (!sys && !LABEL_IS_SGI && !LABEL_IS_SUN) { + printf(_("Type 0 means free space to many systems\n" + "(but not to Linux). Having partitions of\n" + "type 0 is probably unwise. You can delete\n" + "a partition using the 'd' command.\n")); + /* break; */ + } + + if (!LABEL_IS_SUN && !LABEL_IS_SGI) { + if (IS_EXTENDED(sys) != IS_EXTENDED(p->sys_ind)) { + printf(_("You cannot change a partition into" + " an extended one or vice versa\n" + "Delete it first.\n")); + break; + } + } + + if (sys < 256) { + if (LABEL_IS_SUN && i == 2 && sys != SUN_WHOLE_DISK) + printf(_("Consider leaving partition 3 " + "as Whole disk (5),\n" + "as SunOS/Solaris expects it and " + "even Linux likes it.\n\n")); + if (LABEL_IS_SGI && + ( + (i == 10 && sys != SGI_ENTIRE_DISK) || + (i == 8 && sys != 0) + ) + ){ + printf(_("Consider leaving partition 9 " + "as volume header (0),\nand " + "partition 11 as entire volume (6)" + "as IRIX expects it.\n\n")); + } + if (sys == origsys) + break; + if (LABEL_IS_SUN) { + sun_change_sysid(i, sys); + } else if (LABEL_IS_SGI) { + sgi_change_sysid(i, sys); + } else + p->sys_ind = sys; + + printf(_("Changed system type of partition %d " + "to %x (%s)\n"), i + 1, sys, + partition_type(sys)); + ptes[i].changed = 1; + if (is_dos_partition(origsys) || + is_dos_partition(sys)) + dos_changed = 1; + break; + } + } +} +#endif /* CONFIG_FEATURE_FDISK_WRITABLE */ + + +/* check_consistency() and long2chs() added Sat Mar 6 12:28:16 1993, + * faith@cs.unc.edu, based on code fragments from pfdisk by Gordon W. Ross, + * Jan. 1990 (version 1.2.1 by Gordon W. Ross Aug. 1990; Modified by S. + * Lubkin Oct. 1991). */ + +static void +long2chs(ulong ls, unsigned *c, unsigned *h, unsigned *s) +{ + int spc = heads * sectors; + + *c = ls / spc; + ls = ls % spc; + *h = ls / sectors; + *s = ls % sectors + 1; /* sectors count from 1 */ +} + +static void +check_consistency(const struct partition *p, int partition) +{ + unsigned pbc, pbh, pbs; /* physical beginning c, h, s */ + unsigned pec, peh, pes; /* physical ending c, h, s */ + unsigned lbc, lbh, lbs; /* logical beginning c, h, s */ + unsigned lec, leh, les; /* logical ending c, h, s */ + + if (!heads || !sectors || (partition >= 4)) + return; /* do not check extended partitions */ + +/* physical beginning c, h, s */ + pbc = (p->cyl & 0xff) | ((p->sector << 2) & 0x300); + pbh = p->head; + pbs = p->sector & 0x3f; + +/* physical ending c, h, s */ + pec = (p->end_cyl & 0xff) | ((p->end_sector << 2) & 0x300); + peh = p->end_head; + pes = p->end_sector & 0x3f; + +/* compute logical beginning (c, h, s) */ + long2chs(get_start_sect(p), &lbc, &lbh, &lbs); + +/* compute logical ending (c, h, s) */ + long2chs(get_start_sect(p) + get_nr_sects(p) - 1, &lec, &leh, &les); + +/* Same physical / logical beginning? */ + if (cylinders <= 1024 && (pbc != lbc || pbh != lbh || pbs != lbs)) { + printf(_("Partition %d has different physical/logical " + "beginnings (non-Linux?):\n"), partition + 1); + printf(_(" phys=(%d, %d, %d) "), pbc, pbh, pbs); + printf(_("logical=(%d, %d, %d)\n"),lbc, lbh, lbs); + } + +/* Same physical / logical ending? */ + if (cylinders <= 1024 && (pec != lec || peh != leh || pes != les)) { + printf(_("Partition %d has different physical/logical " + "endings:\n"), partition + 1); + printf(_(" phys=(%d, %d, %d) "), pec, peh, pes); + printf(_("logical=(%d, %d, %d)\n"),lec, leh, les); + } + +/* Ending on cylinder boundary? */ + if (peh != (heads - 1) || pes != sectors) { + printf(_("Partition %i does not end on cylinder boundary.\n"), + partition + 1); + } +} + +static void +list_disk_geometry(void) +{ + long long bytes = (total_number_of_sectors << 9); + long megabytes = bytes/1000000; + + if (megabytes < 10000) + printf(_("\nDisk %s: %ld MB, %lld bytes\n"), + disk_device, megabytes, bytes); + else + printf(_("\nDisk %s: %ld.%ld GB, %lld bytes\n"), + disk_device, megabytes/1000, (megabytes/100)%10, bytes); + printf(_("%d heads, %d sectors/track, %d cylinders"), + heads, sectors, cylinders); + if (units_per_sector == 1) + printf(_(", total %llu sectors"), + total_number_of_sectors / (sector_size/512)); + printf(_("\nUnits = %s of %d * %d = %d bytes\n\n"), + str_units(PLURAL), + units_per_sector, sector_size, units_per_sector * sector_size); +} + +/* + * Check whether partition entries are ordered by their starting positions. + * Return 0 if OK. Return i if partition i should have been earlier. + * Two separate checks: primary and logical partitions. + */ +static int +wrong_p_order(int *prev) +{ + const struct pte *pe; + const struct partition *p; + off_t last_p_start_pos = 0, p_start_pos; + int i, last_i = 0; + + for (i = 0 ; i < partitions; i++) { + if (i == 4) { + last_i = 4; + last_p_start_pos = 0; + } + pe = &ptes[i]; + if ((p = pe->part_table)->sys_ind) { + p_start_pos = get_partition_start(pe); + + if (last_p_start_pos > p_start_pos) { + if (prev) + *prev = last_i; + return i; + } + + last_p_start_pos = p_start_pos; + last_i = i; + } + } + return 0; +} + +#if ENABLE_FEATURE_FDISK_ADVANCED +/* + * Fix the chain of logicals. + * extended_offset is unchanged, the set of sectors used is unchanged + * The chain is sorted so that sectors increase, and so that + * starting sectors increase. + * + * After this it may still be that cfdisk doesnt like the table. + * (This is because cfdisk considers expanded parts, from link to + * end of partition, and these may still overlap.) + * Now + * sfdisk /dev/hda > ohda; sfdisk /dev/hda < ohda + * may help. + */ +static void +fix_chain_of_logicals(void) +{ + int j, oj, ojj, sj, sjj; + struct partition *pj,*pjj,tmp; + + /* Stage 1: sort sectors but leave sector of part 4 */ + /* (Its sector is the global extended_offset.) */ + stage1: + for (j = 5; j < partitions-1; j++) { + oj = ptes[j].offset; + ojj = ptes[j+1].offset; + if (oj > ojj) { + ptes[j].offset = ojj; + ptes[j+1].offset = oj; + pj = ptes[j].part_table; + set_start_sect(pj, get_start_sect(pj)+oj-ojj); + pjj = ptes[j+1].part_table; + set_start_sect(pjj, get_start_sect(pjj)+ojj-oj); + set_start_sect(ptes[j-1].ext_pointer, + ojj-extended_offset); + set_start_sect(ptes[j].ext_pointer, + oj-extended_offset); + goto stage1; + } + } + + /* Stage 2: sort starting sectors */ + stage2: + for (j = 4; j < partitions-1; j++) { + pj = ptes[j].part_table; + pjj = ptes[j+1].part_table; + sj = get_start_sect(pj); + sjj = get_start_sect(pjj); + oj = ptes[j].offset; + ojj = ptes[j+1].offset; + if (oj+sj > ojj+sjj) { + tmp = *pj; + *pj = *pjj; + *pjj = tmp; + set_start_sect(pj, ojj+sjj-oj); + set_start_sect(pjj, oj+sj-ojj); + goto stage2; + } + } + + /* Probably something was changed */ + for (j = 4; j < partitions; j++) + ptes[j].changed = 1; +} + + +static void +fix_partition_table_order(void) +{ + struct pte *pei, *pek; + int i,k; + + if (!wrong_p_order(NULL)) { + printf(_("Nothing to do. Ordering is correct already.\n\n")); + return; + } + + while ((i = wrong_p_order(&k)) != 0 && i < 4) { + /* partition i should have come earlier, move it */ + /* We have to move data in the MBR */ + struct partition *pi, *pk, *pe, pbuf; + pei = &ptes[i]; + pek = &ptes[k]; + + pe = pei->ext_pointer; + pei->ext_pointer = pek->ext_pointer; + pek->ext_pointer = pe; + + pi = pei->part_table; + pk = pek->part_table; + + memmove(&pbuf, pi, sizeof(struct partition)); + memmove(pi, pk, sizeof(struct partition)); + memmove(pk, &pbuf, sizeof(struct partition)); + + pei->changed = pek->changed = 1; + } + + if (i) + fix_chain_of_logicals(); + + printf("Done.\n"); + +} +#endif + +static void +list_table(int xtra) +{ + const struct partition *p; + int i, w; + + if (LABEL_IS_SUN) { + sun_list_table(xtra); + return; + } + if (LABEL_IS_SUN) { + sgi_list_table(xtra); + return; + } + + list_disk_geometry(); + + if (LABEL_IS_OSF) { + xbsd_print_disklabel(xtra); + return; + } + + /* Heuristic: we list partition 3 of /dev/foo as /dev/foo3, + but if the device name ends in a digit, say /dev/foo1, + then the partition is called /dev/foo1p3. */ + w = strlen(disk_device); + if (w && isdigit(disk_device[w-1])) + w++; + if (w < 5) + w = 5; + + // 1 12345678901 12345678901 12345678901 12 + printf(_("%*s Boot Start End Blocks Id System\n"), + w+1, _("Device")); + + for (i = 0; i < partitions; i++) { + const struct pte *pe = &ptes[i]; + off_t psects; + off_t pblocks; + unsigned podd; + + p = pe->part_table; + if (!p || is_cleared_partition(p)) + continue; + + psects = get_nr_sects(p); + pblocks = psects; + podd = 0; + + if (sector_size < 1024) { + pblocks /= (1024 / sector_size); + podd = psects % (1024 / sector_size); + } + if (sector_size > 1024) + pblocks *= (sector_size / 1024); + + printf("%s %c %11llu %11llu %11llu%c %2x %s\n", + partname(disk_device, i+1, w+2), + !p->boot_ind ? ' ' : p->boot_ind == ACTIVE_FLAG /* boot flag */ + ? '*' : '?', + (unsigned long long) cround(get_partition_start(pe)), /* start */ + (unsigned long long) cround(get_partition_start(pe) + psects /* end */ + - (psects ? 1 : 0)), + (unsigned long long) pblocks, podd ? '+' : ' ', /* odd flag on end */ + p->sys_ind, /* type id */ + partition_type(p->sys_ind)); /* type name */ + + check_consistency(p, i); + } + + /* Is partition table in disk order? It need not be, but... */ + /* partition table entries are not checked for correct order if this + is a sgi, sun or aix labeled disk... */ + if (LABEL_IS_DOS && wrong_p_order(NULL)) { + /* FIXME */ + printf(_("\nPartition table entries are not in disk order\n")); + } +} + +#if ENABLE_FEATURE_FDISK_ADVANCED +static void +x_list_table(int extend) +{ + const struct pte *pe; + const struct partition *p; + int i; + + printf(_("\nDisk %s: %d heads, %d sectors, %d cylinders\n\n"), + disk_device, heads, sectors, cylinders); + printf(_("Nr AF Hd Sec Cyl Hd Sec Cyl Start Size ID\n")); + for (i = 0 ; i < partitions; i++) { + pe = &ptes[i]; + p = (extend ? pe->ext_pointer : pe->part_table); + if (p != NULL) { + printf("%2d %02x%4d%4d%5d%4d%4d%5d%11u%11u %02x\n", + i + 1, p->boot_ind, p->head, + sector(p->sector), + cylinder(p->sector, p->cyl), p->end_head, + sector(p->end_sector), + cylinder(p->end_sector, p->end_cyl), + get_start_sect(p), get_nr_sects(p), p->sys_ind); + if (p->sys_ind) + check_consistency(p, i); + } + } +} +#endif + +#if ENABLE_FEATURE_FDISK_WRITABLE +static void +fill_bounds(off_t *first, off_t *last) +{ + int i; + const struct pte *pe = &ptes[0]; + const struct partition *p; + + for (i = 0; i < partitions; pe++,i++) { + p = pe->part_table; + if (!p->sys_ind || IS_EXTENDED(p->sys_ind)) { + first[i] = 0xffffffff; + last[i] = 0; + } else { + first[i] = get_partition_start(pe); + last[i] = first[i] + get_nr_sects(p) - 1; + } + } +} + +static void +check(int n, unsigned h, unsigned s, unsigned c, off_t start) +{ + off_t total, real_s, real_c; + + real_s = sector(s) - 1; + real_c = cylinder(s, c); + total = (real_c * sectors + real_s) * heads + h; + if (!total) + printf(_("Warning: partition %d contains sector 0\n"), n); + if (h >= heads) + printf(_("Partition %d: head %d greater than maximum %d\n"), + n, h + 1, heads); + if (real_s >= sectors) + printf(_("Partition %d: sector %d greater than " + "maximum %d\n"), n, s, sectors); + if (real_c >= cylinders) + printf(_("Partitions %d: cylinder %llu greater than " + "maximum %d\n"), n, (unsigned long long)real_c + 1, cylinders); + if (cylinders <= 1024 && start != total) + printf(_("Partition %d: previous sectors %llu disagrees with " + "total %llu\n"), n, (unsigned long long)start, (unsigned long long)total); +} + +static void +verify(void) +{ + int i, j; + unsigned total = 1; + off_t first[partitions], last[partitions]; + struct partition *p; + + if (warn_geometry()) + return; + + if (LABEL_IS_SUN) { + verify_sun(); + return; + } + if (LABEL_IS_SGI) { + verify_sgi(1); + return; + } + + fill_bounds(first, last); + for (i = 0; i < partitions; i++) { + struct pte *pe = &ptes[i]; + + p = pe->part_table; + if (p->sys_ind && !IS_EXTENDED(p->sys_ind)) { + check_consistency(p, i); + if (get_partition_start(pe) < first[i]) + printf(_("Warning: bad start-of-data in " + "partition %d\n"), i + 1); + check(i + 1, p->end_head, p->end_sector, p->end_cyl, + last[i]); + total += last[i] + 1 - first[i]; + for (j = 0; j < i; j++) + if ((first[i] >= first[j] && first[i] <= last[j]) + || ((last[i] <= last[j] && last[i] >= first[j]))) { + printf(_("Warning: partition %d overlaps " + "partition %d.\n"), j + 1, i + 1); + total += first[i] >= first[j] ? + first[i] : first[j]; + total -= last[i] <= last[j] ? + last[i] : last[j]; + } + } + } + + if (extended_offset) { + struct pte *pex = &ptes[ext_index]; + off_t e_last = get_start_sect(pex->part_table) + + get_nr_sects(pex->part_table) - 1; + + for (i = 4; i < partitions; i++) { + total++; + p = ptes[i].part_table; + if (!p->sys_ind) { + if (i != 4 || i + 1 < partitions) + printf(_("Warning: partition %d " + "is empty\n"), i + 1); + } + else if (first[i] < extended_offset || + last[i] > e_last) + printf(_("Logical partition %d not entirely in " + "partition %d\n"), i + 1, ext_index + 1); + } + } + + if (total > heads * sectors * cylinders) + printf(_("Total allocated sectors %d greater than the maximum " + "%d\n"), total, heads * sectors * cylinders); + else if ((total = heads * sectors * cylinders - total) != 0) + printf(_("%d unallocated sectors\n"), total); +} + +static void +add_partition(int n, int sys) +{ + char mesg[256]; /* 48 does not suffice in Japanese */ + int i, num_read = 0; + struct partition *p = ptes[n].part_table; + struct partition *q = ptes[ext_index].part_table; + long long llimit; + off_t start, stop = 0, limit, temp, + first[partitions], last[partitions]; + + if (p && p->sys_ind) { + printf(_("Partition %d is already defined. Delete " + "it before re-adding it.\n"), n + 1); + return; + } + fill_bounds(first, last); + if (n < 4) { + start = sector_offset; + if (display_in_cyl_units || !total_number_of_sectors) + llimit = heads * sectors * cylinders - 1; + else + llimit = total_number_of_sectors - 1; + limit = llimit; + if (limit != llimit) + limit = 0x7fffffff; + if (extended_offset) { + first[ext_index] = extended_offset; + last[ext_index] = get_start_sect(q) + + get_nr_sects(q) - 1; + } + } else { + start = extended_offset + sector_offset; + limit = get_start_sect(q) + get_nr_sects(q) - 1; + } + if (display_in_cyl_units) + for (i = 0; i < partitions; i++) + first[i] = (cround(first[i]) - 1) * units_per_sector; + + snprintf(mesg, sizeof(mesg), _("First %s"), str_units(SINGULAR)); + do { + temp = start; + for (i = 0; i < partitions; i++) { + int lastplusoff; + + if (start == ptes[i].offset) + start += sector_offset; + lastplusoff = last[i] + ((n < 4) ? 0 : sector_offset); + if (start >= first[i] && start <= lastplusoff) + start = lastplusoff + 1; + } + if (start > limit) + break; + if (start >= temp+units_per_sector && num_read) { + printf(_("Sector %"OFF_FMT"d is already allocated\n"), temp); + temp = start; + num_read = 0; + } + if (!num_read && start == temp) { + off_t saved_start; + + saved_start = start; + start = read_int(cround(saved_start), cround(saved_start), cround(limit), + 0, mesg); + if (display_in_cyl_units) { + start = (start - 1) * units_per_sector; + if (start < saved_start) start = saved_start; + } + num_read = 1; + } + } while (start != temp || !num_read); + if (n > 4) { /* NOT for fifth partition */ + struct pte *pe = &ptes[n]; + + pe->offset = start - sector_offset; + if (pe->offset == extended_offset) { /* must be corrected */ + pe->offset++; + if (sector_offset == 1) + start++; + } + } + + for (i = 0; i < partitions; i++) { + struct pte *pe = &ptes[i]; + + if (start < pe->offset && limit >= pe->offset) + limit = pe->offset - 1; + if (start < first[i] && limit >= first[i]) + limit = first[i] - 1; + } + if (start > limit) { + printf(_("No free sectors available\n")); + if (n > 4) + partitions--; + return; + } + if (cround(start) == cround(limit)) { + stop = limit; + } else { + snprintf(mesg, sizeof(mesg), + _("Last %s or +size or +sizeM or +sizeK"), + str_units(SINGULAR)); + stop = read_int(cround(start), cround(limit), cround(limit), + cround(start), mesg); + if (display_in_cyl_units) { + stop = stop * units_per_sector - 1; + if (stop >limit) + stop = limit; + } + } + + set_partition(n, 0, start, stop, sys); + if (n > 4) + set_partition(n - 1, 1, ptes[n].offset, stop, EXTENDED); + + if (IS_EXTENDED(sys)) { + struct pte *pe4 = &ptes[4]; + struct pte *pen = &ptes[n]; + + ext_index = n; + pen->ext_pointer = p; + pe4->offset = extended_offset = start; + pe4->sectorbuffer = xzalloc(sector_size); + pe4->part_table = pt_offset(pe4->sectorbuffer, 0); + pe4->ext_pointer = pe4->part_table + 1; + pe4->changed = 1; + partitions = 5; + } +} + +static void +add_logical(void) +{ + if (partitions > 5 || ptes[4].part_table->sys_ind) { + struct pte *pe = &ptes[partitions]; + + pe->sectorbuffer = xzalloc(sector_size); + pe->part_table = pt_offset(pe->sectorbuffer, 0); + pe->ext_pointer = pe->part_table + 1; + pe->offset = 0; + pe->changed = 1; + partitions++; + } + add_partition(partitions - 1, LINUX_NATIVE); +} + +static void +new_partition(void) +{ + int i, free_primary = 0; + + if (warn_geometry()) + return; + + if (LABEL_IS_SUN) { + add_sun_partition(get_partition(0, partitions), LINUX_NATIVE); + return; + } + if (LABEL_IS_SGI) { + sgi_add_partition(get_partition(0, partitions), LINUX_NATIVE); + return; + } + if (LABEL_IS_AIX) { + printf(_("\tSorry - this fdisk cannot handle AIX disk labels." + "\n\tIf you want to add DOS-type partitions, create" + "\n\ta new empty DOS partition table first. (Use o.)" + "\n\tWARNING: " + "This will destroy the present disk contents.\n")); + return; + } + + for (i = 0; i < 4; i++) + free_primary += !ptes[i].part_table->sys_ind; + + if (!free_primary && partitions >= MAXIMUM_PARTS) { + printf(_("The maximum number of partitions has been created\n")); + return; + } + + if (!free_primary) { + if (extended_offset) + add_logical(); + else + printf(_("You must delete some partition and add " + "an extended partition first\n")); + } else { + char c, line[LINE_LENGTH]; + snprintf(line, sizeof(line), "%s\n %s\n p primary " + "partition (1-4)\n", + "Command action", (extended_offset ? + "l logical (5 or over)" : "e extended")); + while (1) { + c = read_nonempty(line); + if (c == 'p' || c == 'P') { + i = get_nonexisting_partition(0, 4); + if (i >= 0) + add_partition(i, LINUX_NATIVE); + return; + } + else if (c == 'l' && extended_offset) { + add_logical(); + return; + } + else if (c == 'e' && !extended_offset) { + i = get_nonexisting_partition(0, 4); + if (i >= 0) + add_partition(i, EXTENDED); + return; + } + else + printf(_("Invalid partition number " + "for type '%c'\n"), c); + } + } +} + +static void +write_table(void) +{ + int i; + + if (LABEL_IS_DOS) { + for (i = 0; i < 3; i++) + if (ptes[i].changed) + ptes[3].changed = 1; + for (i = 3; i < partitions; i++) { + struct pte *pe = &ptes[i]; + + if (pe->changed) { + write_part_table_flag(pe->sectorbuffer); + write_sector(pe->offset, pe->sectorbuffer); + } + } + } + else if (LABEL_IS_SGI) { + /* no test on change? the printf below might be mistaken */ + sgi_write_table(); + } + else if (LABEL_IS_SUN) { + int needw = 0; + + for (i = 0; i < 8; i++) + if (ptes[i].changed) + needw = 1; + if (needw) + sun_write_table(); + } + + printf(_("The partition table has been altered!\n\n")); + reread_partition_table(1); +} + +static void +reread_partition_table(int leave) +{ + int error = 0; + int i; + + printf(_("Calling ioctl() to re-read partition table.\n")); + sync(); + sleep(2); + if ((i = ioctl(fd, BLKRRPART)) != 0) { + error = errno; + } else { + /* some kernel versions (1.2.x) seem to have trouble + rereading the partition table, but if asked to do it + twice, the second time works. - biro@yggdrasil.com */ + sync(); + sleep(2); + if ((i = ioctl(fd, BLKRRPART)) != 0) + error = errno; + } + + if (i) { + printf(_("\nWARNING: Re-reading the partition table " + "failed with error %d: %s.\n" + "The kernel still uses the old table.\n" + "The new table will be used " + "at the next reboot.\n"), + error, strerror(error)); + } + + if (dos_changed) + printf( + _("\nWARNING: If you have created or modified any DOS 6.x\n" + "partitions, please see the fdisk manual page for additional\n" + "information.\n")); + + if (leave) { + close(fd); + + printf(_("Syncing disks.\n")); + sync(); + sleep(4); /* for sync() */ + exit(!!i); + } +} +#endif /* CONFIG_FEATURE_FDISK_WRITABLE */ + +#if ENABLE_FEATURE_FDISK_ADVANCED +#define MAX_PER_LINE 16 +static void +print_buffer(char *pbuffer) +{ + int i,l; + + for (i = 0, l = 0; i < sector_size; i++, l++) { + if (l == 0) + printf("0x%03X:", i); + printf(" %02X", (unsigned char) pbuffer[i]); + if (l == MAX_PER_LINE - 1) { + puts(""); + l = -1; + } + } + if (l > 0) + puts(""); + puts(""); +} + + +static void +print_raw(void) +{ + int i; + + printf(_("Device: %s\n"), disk_device); + if (LABEL_IS_SGI || LABEL_IS_SUN) + print_buffer(MBRbuffer); + else { + for (i = 3; i < partitions; i++) + print_buffer(ptes[i].sectorbuffer); + } +} + +static void +move_begin(int i) +{ + struct pte *pe = &ptes[i]; + struct partition *p = pe->part_table; + off_t new, first; + + if (warn_geometry()) + return; + if (!p->sys_ind || !get_nr_sects(p) || IS_EXTENDED(p->sys_ind)) { + printf(_("Partition %d has no data area\n"), i + 1); + return; + } + first = get_partition_start(pe); + new = read_int(first, first, first + get_nr_sects(p) - 1, first, + _("New beginning of data")) - pe->offset; + + if (new != get_nr_sects(p)) { + first = get_nr_sects(p) + get_start_sect(p) - new; + set_nr_sects(p, first); + set_start_sect(p, new); + pe->changed = 1; + } +} + +static void +xselect(void) +{ + char c; + + while (1) { + putchar('\n'); + c = tolower(read_nonempty(_("Expert command (m for help): "))); + switch (c) { + case 'a': + if (LABEL_IS_SUN) + sun_set_alt_cyl(); + break; + case 'b': + if (LABEL_IS_DOS) + move_begin(get_partition(0, partitions)); + break; + case 'c': + user_cylinders = cylinders = + read_int(1, cylinders, 1048576, 0, + _("Number of cylinders")); + if (LABEL_IS_SUN) + sun_set_ncyl(cylinders); + if (LABEL_IS_DOS) + warn_cylinders(); + break; + case 'd': + print_raw(); + break; + case 'e': + if (LABEL_IS_SGI) + sgi_set_xcyl(); + else if (LABEL_IS_SUN) + sun_set_xcyl(); + else if (LABEL_IS_DOS) + x_list_table(1); + break; + case 'f': + if (LABEL_IS_DOS) + fix_partition_table_order(); + break; + case 'g': +#if ENABLE_FEATURE_SGI_LABEL + create_sgilabel(); +#endif + break; + case 'h': + user_heads = heads = read_int(1, heads, 256, 0, + _("Number of heads")); + update_units(); + break; + case 'i': + if (LABEL_IS_SUN) + sun_set_ilfact(); + break; + case 'o': + if (LABEL_IS_SUN) + sun_set_rspeed(); + break; + case 'p': + if (LABEL_IS_SUN) + list_table(1); + else + x_list_table(0); + break; + case 'q': + close(fd); + puts(""); + exit(0); + case 'r': + return; + case 's': + user_sectors = sectors = read_int(1, sectors, 63, 0, + _("Number of sectors")); + if (dos_compatible_flag) { + sector_offset = sectors; + printf(_("Warning: setting sector offset for DOS " + "compatiblity\n")); + } + update_units(); + break; + case 'v': + verify(); + break; + case 'w': + write_table(); /* does not return */ + break; + case 'y': + if (LABEL_IS_SUN) + sun_set_pcylcount(); + break; + default: + xmenu(); + } + } +} +#endif /* ADVANCED mode */ + +static int +is_ide_cdrom_or_tape(const char *device) +{ + FILE *procf; + char buf[100]; + struct stat statbuf; + int is_ide = 0; + + /* No device was given explicitly, and we are trying some + likely things. But opening /dev/hdc may produce errors like + "hdc: tray open or drive not ready" + if it happens to be a CD-ROM drive. It even happens that + the process hangs on the attempt to read a music CD. + So try to be careful. This only works since 2.1.73. */ + + if (strncmp("/dev/hd", device, 7)) + return 0; + + snprintf(buf, sizeof(buf), "/proc/ide/%s/media", device+5); + procf = fopen(buf, "r"); + if (procf != NULL && fgets(buf, sizeof(buf), procf)) + is_ide = (!strncmp(buf, "cdrom", 5) || + !strncmp(buf, "tape", 4)); + else + /* Now when this proc file does not exist, skip the + device when it is read-only. */ + if (stat(device, &statbuf) == 0) + is_ide = ((statbuf.st_mode & 0222) == 0); + + if (procf) + fclose(procf); + return is_ide; +} + + +static void +try(const char *device, int user_specified) +{ + int gb; + + disk_device = device; + if (setjmp(listingbuf)) + return; + if (!user_specified) + if (is_ide_cdrom_or_tape(device)) + return; + if ((fd = open(disk_device, type_open)) >= 0) { + gb = get_boot(try_only); + if (gb > 0) { /* I/O error */ + close(fd); + } else if (gb < 0) { /* no DOS signature */ + list_disk_geometry(); + if (LABEL_IS_AIX) { + return; + } +#if ENABLE_FEATURE_OSF_LABEL + if (btrydev(device) < 0) +#endif + printf(_("Disk %s doesn't contain a valid " + "partition table\n"), device); + close(fd); + } else { + close(fd); + list_table(0); +#if ENABLE_FEATURE_FDISK_WRITABLE + if (!LABEL_IS_SUN && partitions > 4){ + delete_partition(ext_index); + } +#endif + } + } else { + /* Ignore other errors, since we try IDE + and SCSI hard disks which may not be + installed on the system. */ + if (errno == EACCES) { + printf(_("Cannot open %s\n"), device); + return; + } + } +} + +/* for fdisk -l: try all things in /proc/partitions + that look like a partition name (do not end in a digit) */ +static void +tryprocpt(void) +{ + FILE *procpt; + char line[100], ptname[100], devname[120], *s; + int ma, mi, sz; + + procpt = fopen_or_warn("/proc/partitions", "r"); + + while (fgets(line, sizeof(line), procpt)) { + if (sscanf(line, " %d %d %d %[^\n ]", + &ma, &mi, &sz, ptname) != 4) + continue; + for (s = ptname; *s; s++); + if (isdigit(s[-1])) + continue; + sprintf(devname, "/dev/%s", ptname); + try(devname, 0); + } +#if ENABLE_FEATURE_CLEAN_UP + fclose(procpt); +#endif +} + +#if ENABLE_FEATURE_FDISK_WRITABLE +static void +unknown_command(int c) +{ + printf(_("%c: unknown command\n"), c); +} +#endif + +int fdisk_main(int argc, char **argv) +{ + char *str_b, *str_C, *str_H, *str_S; + unsigned opt; + int c; + /* + * fdisk -v + * fdisk -l [-b sectorsize] [-u] device ... + * fdisk -s [partition] ... + * fdisk [-b sectorsize] [-u] device + * + * Options -C, -H, -S set the geometry. + */ + enum { + OPT_b = 1 << 0, + OPT_C = 1 << 1, + OPT_H = 1 << 2, + OPT_l = 1 << 3, + OPT_S = 1 << 4, + OPT_u = 1 << 5, + OPT_s = (1 << 6) * ENABLE_FEATURE_FDISK_BLKSIZE, + }; + opt = getopt32(argc, argv, "b:C:H:lS:u" USE_FEATURE_FDISK_BLKSIZE("s"), + &str_b, &str_C, &str_H, &str_S); + argc -= optind; + argv += optind; + if (opt & OPT_b) { // -b + /* Ugly: this sector size is really per device, + so cannot be combined with multiple disks, + and the same goes for the C/H/S options. + */ + sector_size = xatoi_u(str_b); + if (sector_size != 512 && sector_size != 1024 && + sector_size != 2048) + bb_show_usage(); + sector_offset = 2; + user_set_sector_size = 1; + } + if (opt & OPT_C) user_cylinders = xatoi_u(str_C); // -C + if (opt & OPT_H) { // -H + user_heads = xatoi_u(str_H); + if (user_heads <= 0 || user_heads >= 256) + user_heads = 0; + } + //if (opt & OPT_l) // -l + if (opt & OPT_S) { // -S + user_sectors = xatoi_u(str_S); + if (user_sectors <= 0 || user_sectors >= 64) + user_sectors = 0; + } + if (opt & OPT_u) display_in_cyl_units = 0; // -u + //if (opt & OPT_s) // -s + + if (user_set_sector_size && argc != 1) + printf(_("Warning: the -b (set sector size) option should" + " be used with one specified device\n")); + +#if ENABLE_FEATURE_FDISK_WRITABLE + if (opt & OPT_l) { + nowarn = 1; +#endif + type_open = O_RDONLY; + if (argc > 0) { + int k; +#if __GNUC__ + /* avoid gcc warning: + variable `k' might be clobbered by `longjmp' */ + (void)&k; +#endif + listing = 1; + for (k = 0; k < argc; k++) + try(argv[k], 1); + } else { + /* we no longer have default device names */ + /* but, we can use /proc/partitions instead */ + tryprocpt(); + } + return 0; +#if ENABLE_FEATURE_FDISK_WRITABLE + } +#endif + +#if ENABLE_FEATURE_FDISK_BLKSIZE + if (opt & OPT_s) { + long size; + int j; + + nowarn = 1; + type_open = O_RDONLY; + + if (argc <= 0) + bb_show_usage(); + + for (j = 0; j < argc; j++) { + disk_device = argv[j]; + fd = open(disk_device, type_open); + if (fd < 0) + fdisk_fatal(unable_to_open); + if (ioctl(fd, BLKGETSIZE, &size)) + fdisk_fatal(ioctl_error); + close(fd); + if (argc == 1) + printf("%ld\n", size/2); + else + printf("%s: %ld\n", argv[j], size/2); + } + return 0; + } +#endif + +#if ENABLE_FEATURE_FDISK_WRITABLE + if (argc != 1) + bb_show_usage(); + + disk_device = argv[0]; + get_boot(fdisk); + + if (LABEL_IS_OSF) { + /* OSF label, and no DOS label */ + printf(_("Detected an OSF/1 disklabel on %s, entering " + "disklabel mode.\n"), disk_device); + bsd_select(); + /*Why do we do this? It seems to be counter-intuitive*/ + current_label_type = label_dos; + /* If we return we may want to make an empty DOS label? */ + } + + while (1) { + putchar('\n'); + c = tolower(read_nonempty(_("Command (m for help): "))); + switch (c) { + case 'a': + if (LABEL_IS_DOS) + toggle_active(get_partition(1, partitions)); + else if (LABEL_IS_SUN) + toggle_sunflags(get_partition(1, partitions), + 0x01); + else if (LABEL_IS_SGI) + sgi_set_bootpartition( + get_partition(1, partitions)); + else + unknown_command(c); + break; + case 'b': + if (LABEL_IS_SGI) { + printf(_("\nThe current boot file is: %s\n"), + sgi_get_bootfile()); + if (read_maybe_empty(_("Please enter the name of the " + "new boot file: ")) == '\n') + printf(_("Boot file unchanged\n")); + else + sgi_set_bootfile(line_ptr); + } +#if ENABLE_FEATURE_OSF_LABEL + else + bsd_select(); +#endif + break; + case 'c': + if (LABEL_IS_DOS) + toggle_dos_compatibility_flag(); + else if (LABEL_IS_SUN) + toggle_sunflags(get_partition(1, partitions), + 0x10); + else if (LABEL_IS_SGI) + sgi_set_swappartition( + get_partition(1, partitions)); + else + unknown_command(c); + break; + case 'd': + { + int j; + /* If sgi_label then don't use get_existing_partition, + let the user select a partition, since + get_existing_partition() only works for Linux-like + partition tables */ + if (!LABEL_IS_SGI) { + j = get_existing_partition(1, partitions); + } else { + j = get_partition(1, partitions); + } + if (j >= 0) + delete_partition(j); + } + break; + case 'i': + if (LABEL_IS_SGI) + create_sgiinfo(); + else + unknown_command(c); + case 'l': + list_types(get_sys_types()); + break; + case 'm': + menu(); + break; + case 'n': + new_partition(); + break; + case 'o': + create_doslabel(); + break; + case 'p': + list_table(0); + break; + case 'q': + close(fd); + puts(""); + return 0; + case 's': +#if ENABLE_FEATURE_SUN_LABEL + create_sunlabel(); +#endif + break; + case 't': + change_sysid(); + break; + case 'u': + change_units(); + break; + case 'v': + verify(); + break; + case 'w': + write_table(); /* does not return */ + break; +#if ENABLE_FEATURE_FDISK_ADVANCED + case 'x': + if (LABEL_IS_SGI) { + printf(_("\n\tSorry, no experts menu for SGI " + "partition tables available.\n\n")); + } else + xselect(); + break; +#endif + default: + unknown_command(c); + menu(); + } + } + return 0; +#endif /* CONFIG_FEATURE_FDISK_WRITABLE */ +} diff --git a/util-linux/fdisk_aix.c b/util-linux/fdisk_aix.c new file mode 100644 index 000000000..a3d5fe15f --- /dev/null +++ b/util-linux/fdisk_aix.c @@ -0,0 +1,76 @@ +#ifdef CONFIG_FEATURE_AIX_LABEL +/* + * Copyright (C) Andreas Neuper, Sep 1998. + * This file may be redistributed under + * the terms of the GNU Public License. + */ + +typedef struct { + unsigned int magic; /* expect AIX_LABEL_MAGIC */ + unsigned int fillbytes1[124]; + unsigned int physical_volume_id; + unsigned int fillbytes2[124]; +} aix_partition; + +#define AIX_LABEL_MAGIC 0xc9c2d4c1 +#define AIX_LABEL_MAGIC_SWAPPED 0xc1d4c2c9 +#define AIX_INFO_MAGIC 0x00072959 +#define AIX_INFO_MAGIC_SWAPPED 0x59290700 + +#define aixlabel ((aix_partition *)MBRbuffer) + + +/* + Changes: + * 1999-03-20 Arnaldo Carvalho de Melo <acme@conectiva.com.br> + * Internationalization + * + * 2003-03-20 Phillip Kesling <pkesling@sgi.com> + * Some fixes +*/ + +static int aix_other_endian; +static short aix_volumes = 1; + +/* + * only dealing with free blocks here + */ + +static void +aix_info(void) +{ + puts( + _("\n\tThere is a valid AIX label on this disk.\n" + "\tUnfortunately Linux cannot handle these\n" + "\tdisks at the moment. Nevertheless some\n" + "\tadvice:\n" + "\t1. fdisk will destroy its contents on write.\n" + "\t2. Be sure that this disk is NOT a still vital\n" + "\t part of a volume group. (Otherwise you may\n" + "\t erase the other disks as well, if unmirrored.)\n" + "\t3. Before deleting this physical volume be sure\n" + "\t to remove the disk logically from your AIX\n" + "\t machine. (Otherwise you become an AIXpert).") + ); +} + +static int +check_aix_label(void) +{ + if (aixlabel->magic != AIX_LABEL_MAGIC && + aixlabel->magic != AIX_LABEL_MAGIC_SWAPPED) { + current_label_type = 0; + aix_other_endian = 0; + return 0; + } + aix_other_endian = (aixlabel->magic == AIX_LABEL_MAGIC_SWAPPED); + update_units(); + current_label_type = label_aix; + partitions = 1016; + aix_volumes = 15; + aix_info(); + /*aix_nolabel();*/ /* %% */ + /*aix_label = 1;*/ /* %% */ + return 1; +} +#endif /* AIX_LABEL */ diff --git a/util-linux/fdisk_osf.c b/util-linux/fdisk_osf.c new file mode 100644 index 000000000..bff2371e4 --- /dev/null +++ b/util-linux/fdisk_osf.c @@ -0,0 +1,1046 @@ +#ifdef CONFIG_FEATURE_OSF_LABEL +/* + * Copyright (c) 1987, 1988 Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgment: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + + +#ifndef BSD_DISKMAGIC +#define BSD_DISKMAGIC ((uint32_t) 0x82564557) +#endif + +#ifndef BSD_MAXPARTITIONS +#define BSD_MAXPARTITIONS 16 +#endif + +#define BSD_LINUX_BOOTDIR "/usr/ucb/mdec" + +#if defined (i386) || defined (__sparc__) || defined (__arm__) || defined (__m68k__) || defined (__mips__) || defined (__s390__) || defined (__sh__) || defined(__x86_64__) +#define BSD_LABELSECTOR 1 +#define BSD_LABELOFFSET 0 +#elif defined (__alpha__) || defined (__powerpc__) || defined (__ia64__) || defined (__hppa__) +#define BSD_LABELSECTOR 0 +#define BSD_LABELOFFSET 64 +#elif defined (__s390__) || defined (__s390x__) +#define BSD_LABELSECTOR 1 +#define BSD_LABELOFFSET 0 +#else +#error unknown architecture +#endif + +#define BSD_BBSIZE 8192 /* size of boot area, with label */ +#define BSD_SBSIZE 8192 /* max size of fs superblock */ + +struct xbsd_disklabel { + uint32_t d_magic; /* the magic number */ + int16_t d_type; /* drive type */ + int16_t d_subtype; /* controller/d_type specific */ + char d_typename[16]; /* type name, e.g. "eagle" */ + char d_packname[16]; /* pack identifier */ + /* disk geometry: */ + uint32_t d_secsize; /* # of bytes per sector */ + uint32_t d_nsectors; /* # of data sectors per track */ + uint32_t d_ntracks; /* # of tracks per cylinder */ + uint32_t d_ncylinders; /* # of data cylinders per unit */ + uint32_t d_secpercyl; /* # of data sectors per cylinder */ + uint32_t d_secperunit; /* # of data sectors per unit */ + /* + * Spares (bad sector replacements) below + * are not counted in d_nsectors or d_secpercyl. + * Spare sectors are assumed to be physical sectors + * which occupy space at the end of each track and/or cylinder. + */ + uint16_t d_sparespertrack; /* # of spare sectors per track */ + uint16_t d_sparespercyl; /* # of spare sectors per cylinder */ + /* + * Alternate cylinders include maintenance, replacement, + * configuration description areas, etc. + */ + uint32_t d_acylinders; /* # of alt. cylinders per unit */ + + /* hardware characteristics: */ + /* + * d_interleave, d_trackskew and d_cylskew describe perturbations + * in the media format used to compensate for a slow controller. + * Interleave is physical sector interleave, set up by the formatter + * or controller when formatting. When interleaving is in use, + * logically adjacent sectors are not physically contiguous, + * but instead are separated by some number of sectors. + * It is specified as the ratio of physical sectors traversed + * per logical sector. Thus an interleave of 1:1 implies contiguous + * layout, while 2:1 implies that logical sector 0 is separated + * by one sector from logical sector 1. + * d_trackskew is the offset of sector 0 on track N + * relative to sector 0 on track N-1 on the same cylinder. + * Finally, d_cylskew is the offset of sector 0 on cylinder N + * relative to sector 0 on cylinder N-1. + */ + uint16_t d_rpm; /* rotational speed */ + uint16_t d_interleave; /* hardware sector interleave */ + uint16_t d_trackskew; /* sector 0 skew, per track */ + uint16_t d_cylskew; /* sector 0 skew, per cylinder */ + uint32_t d_headswitch; /* head switch time, usec */ + uint32_t d_trkseek; /* track-to-track seek, usec */ + uint32_t d_flags; /* generic flags */ +#define NDDATA 5 + uint32_t d_drivedata[NDDATA]; /* drive-type specific information */ +#define NSPARE 5 + uint32_t d_spare[NSPARE]; /* reserved for future use */ + uint32_t d_magic2; /* the magic number (again) */ + uint16_t d_checksum; /* xor of data incl. partitions */ + /* filesystem and partition information: */ + uint16_t d_npartitions; /* number of partitions in following */ + uint32_t d_bbsize; /* size of boot area at sn0, bytes */ + uint32_t d_sbsize; /* max size of fs superblock, bytes */ + struct xbsd_partition { /* the partition table */ + uint32_t p_size; /* number of sectors in partition */ + uint32_t p_offset; /* starting sector */ + uint32_t p_fsize; /* filesystem basic fragment size */ + uint8_t p_fstype; /* filesystem type, see below */ + uint8_t p_frag; /* filesystem fragments per block */ + uint16_t p_cpg; /* filesystem cylinders per group */ + } d_partitions[BSD_MAXPARTITIONS]; /* actually may be more */ +}; + +/* d_type values: */ +#define BSD_DTYPE_SMD 1 /* SMD, XSMD; VAX hp/up */ +#define BSD_DTYPE_MSCP 2 /* MSCP */ +#define BSD_DTYPE_DEC 3 /* other DEC (rk, rl) */ +#define BSD_DTYPE_SCSI 4 /* SCSI */ +#define BSD_DTYPE_ESDI 5 /* ESDI interface */ +#define BSD_DTYPE_ST506 6 /* ST506 etc. */ +#define BSD_DTYPE_HPIB 7 /* CS/80 on HP-IB */ +#define BSD_DTYPE_HPFL 8 /* HP Fiber-link */ +#define BSD_DTYPE_FLOPPY 10 /* floppy */ + +/* d_subtype values: */ +#define BSD_DSTYPE_INDOSPART 0x8 /* is inside dos partition */ +#define BSD_DSTYPE_DOSPART(s) ((s) & 3) /* dos partition number */ +#define BSD_DSTYPE_GEOMETRY 0x10 /* drive params in label */ + +static const char * const xbsd_dktypenames[] = { + "unknown", + "SMD", + "MSCP", + "old DEC", + "SCSI", + "ESDI", + "ST506", + "HP-IB", + "HP-FL", + "type 9", + "floppy", + 0 +}; +#define BSD_DKMAXTYPES (sizeof(xbsd_dktypenames) / sizeof(xbsd_dktypenames[0]) - 1) + +/* + * Filesystem type and version. + * Used to interpret other filesystem-specific + * per-partition information. + */ +#define BSD_FS_UNUSED 0 /* unused */ +#define BSD_FS_SWAP 1 /* swap */ +#define BSD_FS_V6 2 /* Sixth Edition */ +#define BSD_FS_V7 3 /* Seventh Edition */ +#define BSD_FS_SYSV 4 /* System V */ +#define BSD_FS_V71K 5 /* V7 with 1K blocks (4.1, 2.9) */ +#define BSD_FS_V8 6 /* Eighth Edition, 4K blocks */ +#define BSD_FS_BSDFFS 7 /* 4.2BSD fast file system */ +#define BSD_FS_BSDLFS 9 /* 4.4BSD log-structured file system */ +#define BSD_FS_OTHER 10 /* in use, but unknown/unsupported */ +#define BSD_FS_HPFS 11 /* OS/2 high-performance file system */ +#define BSD_FS_ISO9660 12 /* ISO-9660 filesystem (cdrom) */ +#define BSD_FS_ISOFS BSD_FS_ISO9660 +#define BSD_FS_BOOT 13 /* partition contains bootstrap */ +#define BSD_FS_ADOS 14 /* AmigaDOS fast file system */ +#define BSD_FS_HFS 15 /* Macintosh HFS */ +#define BSD_FS_ADVFS 16 /* Digital Unix AdvFS */ + +/* this is annoying, but it's also the way it is :-( */ +#ifdef __alpha__ +#define BSD_FS_EXT2 8 /* ext2 file system */ +#else +#define BSD_FS_MSDOS 8 /* MS-DOS file system */ +#endif + +static const struct systypes xbsd_fstypes[] = { + { "\x00" "unused" }, /* BSD_FS_UNUSED */ + { "\x01" "swap" }, /* BSD_FS_SWAP */ + { "\x02" "Version 6" }, /* BSD_FS_V6 */ + { "\x03" "Version 7" }, /* BSD_FS_V7 */ + { "\x04" "System V" }, /* BSD_FS_SYSV */ + { "\x05" "4.1BSD" }, /* BSD_FS_V71K */ + { "\x06" "Eighth Edition" }, /* BSD_FS_V8 */ + { "\x07" "4.2BSD" }, /* BSD_FS_BSDFFS */ +#ifdef __alpha__ + { "\x08" "ext2" }, /* BSD_FS_EXT2 */ +#else + { "\x08" "MS-DOS" }, /* BSD_FS_MSDOS */ +#endif + { "\x09" "4.4LFS" }, /* BSD_FS_BSDLFS */ + { "\x0a" "unknown" }, /* BSD_FS_OTHER */ + { "\x0b" "HPFS" }, /* BSD_FS_HPFS */ + { "\x0c" "ISO-9660" }, /* BSD_FS_ISO9660 */ + { "\x0d" "boot" }, /* BSD_FS_BOOT */ + { "\x0e" "ADOS" }, /* BSD_FS_ADOS */ + { "\x0f" "HFS" }, /* BSD_FS_HFS */ + { "\x10" "AdvFS" }, /* BSD_FS_ADVFS */ + { NULL } +}; +#define BSD_FSMAXTYPES (SIZE(xbsd_fstypes)-1) + + +/* + * flags shared by various drives: + */ +#define BSD_D_REMOVABLE 0x01 /* removable media */ +#define BSD_D_ECC 0x02 /* supports ECC */ +#define BSD_D_BADSECT 0x04 /* supports bad sector forw. */ +#define BSD_D_RAMDISK 0x08 /* disk emulator */ +#define BSD_D_CHAIN 0x10 /* can do back-back transfers */ +#define BSD_D_DOSPART 0x20 /* within MSDOS partition */ + +/* + Changes: + 19990319 - Arnaldo Carvalho de Melo <acme@conectiva.com.br> - i18n/nls + + 20000101 - David Huggins-Daines <dhuggins@linuxcare.com> - Better + support for OSF/1 disklabels on Alpha. + Also fixed unaligned accesses in alpha_bootblock_checksum() +*/ + +static int possibly_osf_label; + +#define FREEBSD_PARTITION 0xa5 +#define NETBSD_PARTITION 0xa9 + +static void xbsd_delete_part(void); +static void xbsd_new_part(void); +static void xbsd_write_disklabel(void); +static int xbsd_create_disklabel(void); +static void xbsd_edit_disklabel(void); +static void xbsd_write_bootstrap(void); +static void xbsd_change_fstype(void); +static int xbsd_get_part_index(int max); +static int xbsd_check_new_partition(int *i); +static void xbsd_list_types(void); +static u_short xbsd_dkcksum(struct xbsd_disklabel *lp); +static int xbsd_initlabel(struct partition *p, struct xbsd_disklabel *d); +static int xbsd_readlabel(struct partition *p, struct xbsd_disklabel *d); +static int xbsd_writelabel(struct partition *p, struct xbsd_disklabel *d); + +#if defined (__alpha__) +static void alpha_bootblock_checksum(char *boot); +#endif + +#if !defined (__alpha__) +static int xbsd_translate_fstype(int linux_type); +static void xbsd_link_part(void); +static struct partition *xbsd_part; +static int xbsd_part_index; +#endif + +#if defined (__alpha__) +/* We access this through a uint64_t * when checksumming */ +static char disklabelbuffer[BSD_BBSIZE] ATTRIBUTE_ALIGNED(8); +#else +static char disklabelbuffer[BSD_BBSIZE]; +#endif + +static struct xbsd_disklabel xbsd_dlabel; + +#define bsd_cround(n) \ + (display_in_cyl_units ? ((n)/xbsd_dlabel.d_secpercyl) + 1 : (n)) + +/* + * Test whether the whole disk has BSD disk label magic. + * + * Note: often reformatting with DOS-type label leaves the BSD magic, + * so this does not mean that there is a BSD disk label. + */ +static int +check_osf_label(void) +{ + if (xbsd_readlabel(NULL, &xbsd_dlabel) == 0) + return 0; + return 1; +} + +static int +btrydev(const char * dev) +{ + if (xbsd_readlabel (NULL, &xbsd_dlabel) == 0) + return -1; + printf(_("\nBSD label for device: %s\n"), dev); + xbsd_print_disklabel (0); + return 0; +} + +static void +bmenu(void) +{ + puts (_("Command action")); + puts (_("\td\tdelete a BSD partition")); + puts (_("\te\tedit drive data")); + puts (_("\ti\tinstall bootstrap")); + puts (_("\tl\tlist known filesystem types")); + puts (_("\tm\tprint this menu")); + puts (_("\tn\tadd a new BSD partition")); + puts (_("\tp\tprint BSD partition table")); + puts (_("\tq\tquit without saving changes")); + puts (_("\tr\treturn to main menu")); + puts (_("\ts\tshow complete disklabel")); + puts (_("\tt\tchange a partition's filesystem id")); + puts (_("\tu\tchange units (cylinders/sectors)")); + puts (_("\tw\twrite disklabel to disk")); +#if !defined (__alpha__) + puts (_("\tx\tlink BSD partition to non-BSD partition")); +#endif +} + +#if !defined (__alpha__) +static int +hidden(int type) +{ + return type ^ 0x10; +} + +static int +is_bsd_partition_type(int type) +{ + return (type == FREEBSD_PARTITION || + type == hidden(FREEBSD_PARTITION) || + type == NETBSD_PARTITION || + type == hidden(NETBSD_PARTITION)); +} +#endif + +static void +bsd_select(void) +{ +#if !defined (__alpha__) + int t, ss; + struct partition *p; + + for (t = 0; t < 4; t++) { + p = get_part_table(t); + if (p && is_bsd_partition_type(p->sys_ind)) { + xbsd_part = p; + xbsd_part_index = t; + ss = get_start_sect(xbsd_part); + if (ss == 0) { + fprintf(stderr, _("Partition %s has invalid starting sector 0.\n"), + partname(disk_device, t+1, 0)); + return; + } + printf(_("Reading disklabel of %s at sector %d.\n"), + partname(disk_device, t+1, 0), ss + BSD_LABELSECTOR); + if (xbsd_readlabel(xbsd_part, &xbsd_dlabel) == 0) + if (xbsd_create_disklabel() == 0) + return; + break; + } + } + + if (t == 4) { + printf(_("There is no *BSD partition on %s.\n"), disk_device); + return; + } + +#elif defined (__alpha__) + + if (xbsd_readlabel(NULL, &xbsd_dlabel) == 0) + if (xbsd_create_disklabel() == 0) + exit (EXIT_SUCCESS); + +#endif + + while (1) { + putchar('\n'); + switch (tolower(read_nonempty(_("BSD disklabel command (m for help): ")))) { + case 'd': + xbsd_delete_part(); + break; + case 'e': + xbsd_edit_disklabel(); + break; + case 'i': + xbsd_write_bootstrap(); + break; + case 'l': + xbsd_list_types(); + break; + case 'n': + xbsd_new_part(); + break; + case 'p': + xbsd_print_disklabel(0); + break; + case 'q': + close(fd); + exit(EXIT_SUCCESS); + case 'r': + return; + case 's': + xbsd_print_disklabel(1); + break; + case 't': + xbsd_change_fstype(); + break; + case 'u': + change_units(); + break; + case 'w': + xbsd_write_disklabel(); + break; +#if !defined (__alpha__) + case 'x': + xbsd_link_part(); + break; +#endif + default: + bmenu(); + break; + } + } +} + +static void +xbsd_delete_part(void) +{ + int i; + + i = xbsd_get_part_index(xbsd_dlabel.d_npartitions); + xbsd_dlabel.d_partitions[i].p_size = 0; + xbsd_dlabel.d_partitions[i].p_offset = 0; + xbsd_dlabel.d_partitions[i].p_fstype = BSD_FS_UNUSED; + if (xbsd_dlabel.d_npartitions == i + 1) + while (xbsd_dlabel.d_partitions[xbsd_dlabel.d_npartitions-1].p_size == 0) + xbsd_dlabel.d_npartitions--; +} + +static void +xbsd_new_part(void) +{ + off_t begin, end; + char mesg[256]; + int i; + + if (!xbsd_check_new_partition(&i)) + return; + +#if !defined (__alpha__) && !defined (__powerpc__) && !defined (__hppa__) + begin = get_start_sect(xbsd_part); + end = begin + get_nr_sects(xbsd_part) - 1; +#else + begin = 0; + end = xbsd_dlabel.d_secperunit - 1; +#endif + + snprintf(mesg, sizeof(mesg), _("First %s"), str_units(SINGULAR)); + begin = read_int(bsd_cround(begin), bsd_cround(begin), bsd_cround(end), + 0, mesg); + + if (display_in_cyl_units) + begin = (begin - 1) * xbsd_dlabel.d_secpercyl; + + snprintf(mesg, sizeof(mesg), _("Last %s or +size or +sizeM or +sizeK"), + str_units(SINGULAR)); + end = read_int(bsd_cround (begin), bsd_cround (end), bsd_cround (end), + bsd_cround (begin), mesg); + + if (display_in_cyl_units) + end = end * xbsd_dlabel.d_secpercyl - 1; + + xbsd_dlabel.d_partitions[i].p_size = end - begin + 1; + xbsd_dlabel.d_partitions[i].p_offset = begin; + xbsd_dlabel.d_partitions[i].p_fstype = BSD_FS_UNUSED; +} + +static void +xbsd_print_disklabel(int show_all) +{ + struct xbsd_disklabel *lp = &xbsd_dlabel; + struct xbsd_partition *pp; + int i, j; + + if (show_all) { +#if defined (__alpha__) + printf("# %s:\n", disk_device); +#else + printf("# %s:\n", partname(disk_device, xbsd_part_index+1, 0)); +#endif + if ((unsigned) lp->d_type < BSD_DKMAXTYPES) + printf(_("type: %s\n"), xbsd_dktypenames[lp->d_type]); + else + printf(_("type: %d\n"), lp->d_type); + printf(_("disk: %.*s\n"), (int) sizeof(lp->d_typename), lp->d_typename); + printf(_("label: %.*s\n"), (int) sizeof(lp->d_packname), lp->d_packname); + printf(_("flags:")); + if (lp->d_flags & BSD_D_REMOVABLE) + printf(_(" removable")); + if (lp->d_flags & BSD_D_ECC) + printf(_(" ecc")); + if (lp->d_flags & BSD_D_BADSECT) + printf(_(" badsect")); + puts(""); + /* On various machines the fields of *lp are short/int/long */ + /* In order to avoid problems, we cast them all to long. */ + printf(_("bytes/sector: %ld\n"), (long) lp->d_secsize); + printf(_("sectors/track: %ld\n"), (long) lp->d_nsectors); + printf(_("tracks/cylinder: %ld\n"), (long) lp->d_ntracks); + printf(_("sectors/cylinder: %ld\n"), (long) lp->d_secpercyl); + printf(_("cylinders: %ld\n"), (long) lp->d_ncylinders); + printf(_("rpm: %d\n"), lp->d_rpm); + printf(_("interleave: %d\n"), lp->d_interleave); + printf(_("trackskew: %d\n"), lp->d_trackskew); + printf(_("cylinderskew: %d\n"), lp->d_cylskew); + printf(_("headswitch: %ld\t\t# milliseconds\n"), + (long) lp->d_headswitch); + printf(_("track-to-track seek: %ld\t# milliseconds\n"), + (long) lp->d_trkseek); + printf(_("drivedata: ")); + for (i = NDDATA - 1; i >= 0; i--) + if (lp->d_drivedata[i]) + break; + if (i < 0) + i = 0; + for (j = 0; j <= i; j++) + printf("%ld ", (long) lp->d_drivedata[j]); + } + printf(_("\n%d partitions:\n"), lp->d_npartitions); + printf(_("# start end size fstype [fsize bsize cpg]\n")); + pp = lp->d_partitions; + for (i = 0; i < lp->d_npartitions; i++, pp++) { + if (pp->p_size) { + if (display_in_cyl_units && lp->d_secpercyl) { + printf(" %c: %8ld%c %8ld%c %8ld%c ", + 'a' + i, + (long) pp->p_offset / lp->d_secpercyl + 1, + (pp->p_offset % lp->d_secpercyl) ? '*' : ' ', + (long) (pp->p_offset + pp->p_size + lp->d_secpercyl - 1) / lp->d_secpercyl, + ((pp->p_offset + pp->p_size) % lp->d_secpercyl) ? '*' : ' ', + (long) pp->p_size / lp->d_secpercyl, + (pp->p_size % lp->d_secpercyl) ? '*' : ' ' + ); + } else { + printf(" %c: %8ld %8ld %8ld ", + 'a' + i, + (long) pp->p_offset, + (long) pp->p_offset + pp->p_size - 1, + (long) pp->p_size + ); + } + + if ((unsigned) pp->p_fstype < BSD_FSMAXTYPES) + printf("%8.8s", xbsd_fstypes[pp->p_fstype].name); + else + printf("%8x", pp->p_fstype); + + switch (pp->p_fstype) { + case BSD_FS_UNUSED: + printf(" %5ld %5ld %5.5s ", + (long) pp->p_fsize, (long) pp->p_fsize * pp->p_frag, ""); + break; + case BSD_FS_BSDFFS: + printf(" %5ld %5ld %5d ", + (long) pp->p_fsize, (long) pp->p_fsize * pp->p_frag, pp->p_cpg); + break; + default: + printf("%22.22s", ""); + break; + } + puts(""); + } + } +} + +static void +xbsd_write_disklabel(void) +{ +#if defined (__alpha__) + printf(_("Writing disklabel to %s.\n"), disk_device); + xbsd_writelabel(NULL, &xbsd_dlabel); +#else + printf(_("Writing disklabel to %s.\n"), + partname(disk_device, xbsd_part_index + 1, 0)); + xbsd_writelabel(xbsd_part, &xbsd_dlabel); +#endif + reread_partition_table(0); /* no exit yet */ +} + +static int +xbsd_create_disklabel(void) +{ + char c; + +#if defined (__alpha__) + fprintf(stderr, _("%s contains no disklabel.\n"), disk_device); +#else + fprintf(stderr, _("%s contains no disklabel.\n"), + partname(disk_device, xbsd_part_index + 1, 0)); +#endif + + while (1) { + c = read_nonempty(_("Do you want to create a disklabel? (y/n) ")); + if (c == 'y' || c == 'Y') { + if (xbsd_initlabel( +#if defined (__alpha__) || defined (__powerpc__) || defined (__hppa__) || \ + defined (__s390__) || defined (__s390x__) + NULL, &xbsd_dlabel +#else + xbsd_part, &xbsd_dlabel/* not used, xbsd_part_index*/ +#endif + ) == 1) { + xbsd_print_disklabel (1); + return 1; + } else + return 0; + } else if (c == 'n') + return 0; + } +} + +static int +edit_int(int def, char *mesg) +{ + do { + fputs(mesg, stdout); + printf(" (%d): ", def); + if (!read_line()) + return def; + } while (!isdigit(*line_ptr)); + return atoi(line_ptr); +} + +static void +xbsd_edit_disklabel(void) +{ + struct xbsd_disklabel *d; + + d = &xbsd_dlabel; + +#if defined (__alpha__) || defined (__ia64__) + d->d_secsize = (u_long) edit_int((u_long) d->d_secsize ,_("bytes/sector")); + d->d_nsectors = (u_long) edit_int((u_long) d->d_nsectors ,_("sectors/track")); + d->d_ntracks = (u_long) edit_int((u_long) d->d_ntracks ,_("tracks/cylinder")); + d->d_ncylinders = (u_long) edit_int((u_long) d->d_ncylinders ,_("cylinders")); +#endif + + /* d->d_secpercyl can be != d->d_nsectors * d->d_ntracks */ + while (1) { + d->d_secpercyl = (u_long) edit_int((u_long) d->d_nsectors * d->d_ntracks, + _("sectors/cylinder")); + if (d->d_secpercyl <= d->d_nsectors * d->d_ntracks) + break; + + printf(_("Must be <= sectors/track * tracks/cylinder (default).\n")); + } + d->d_rpm = (u_short) edit_int((u_short) d->d_rpm ,_("rpm")); + d->d_interleave = (u_short) edit_int((u_short) d->d_interleave,_("interleave")); + d->d_trackskew = (u_short) edit_int((u_short) d->d_trackskew ,_("trackskew")); + d->d_cylskew = (u_short) edit_int((u_short) d->d_cylskew ,_("cylinderskew")); + d->d_headswitch = (u_long) edit_int((u_long) d->d_headswitch ,_("headswitch")); + d->d_trkseek = (u_long) edit_int((u_long) d->d_trkseek ,_("track-to-track seek")); + + d->d_secperunit = d->d_secpercyl * d->d_ncylinders; +} + +static int +xbsd_get_bootstrap (char *path, void *ptr, int size) +{ + int fdb; + + if ((fdb = open (path, O_RDONLY)) < 0) { + perror(path); + return 0; + } + if (read(fdb, ptr, size) < 0) { + perror(path); + close(fdb); + return 0; + } + printf(" ... %s\n", path); + close(fdb); + return 1; +} + +static void +sync_disks(void) +{ + printf(_("\nSyncing disks.\n")); + sync(); + sleep(4); /* What? */ +} + +static void +xbsd_write_bootstrap(void) +{ + char *bootdir = BSD_LINUX_BOOTDIR; + char path[MAXPATHLEN]; + char *dkbasename; + struct xbsd_disklabel dl; + char *d, *p, *e; + int sector; + + if (xbsd_dlabel.d_type == BSD_DTYPE_SCSI) + dkbasename = "sd"; + else + dkbasename = "wd"; + + printf(_("Bootstrap: %sboot -> boot%s (%s): "), + dkbasename, dkbasename, dkbasename); + if (read_line()) { + line_ptr[strlen(line_ptr)-1] = '\0'; + dkbasename = line_ptr; + } + snprintf(path, sizeof(path), "%s/%sboot", bootdir, dkbasename); + if (!xbsd_get_bootstrap(path, disklabelbuffer, (int) xbsd_dlabel.d_secsize)) + return; + +/* We need a backup of the disklabel (xbsd_dlabel might have changed). */ + d = &disklabelbuffer[BSD_LABELSECTOR * SECTOR_SIZE]; + memmove(&dl, d, sizeof(struct xbsd_disklabel)); + +/* The disklabel will be overwritten by 0's from bootxx anyway */ + memset(d, 0, sizeof(struct xbsd_disklabel)); + + snprintf(path, sizeof(path), "%s/boot%s", bootdir, dkbasename); + if (!xbsd_get_bootstrap(path, &disklabelbuffer[xbsd_dlabel.d_secsize], + (int) xbsd_dlabel.d_bbsize - xbsd_dlabel.d_secsize)) + return; + + e = d + sizeof(struct xbsd_disklabel); + for (p = d; p < e; p++) + if (*p) { + fprintf(stderr, _("Bootstrap overlaps with disk label!\n")); + exit(EXIT_FAILURE); + } + + memmove(d, &dl, sizeof(struct xbsd_disklabel)); + +#if defined (__powerpc__) || defined (__hppa__) + sector = 0; +#elif defined (__alpha__) + sector = 0; + alpha_bootblock_checksum(disklabelbuffer); +#else + sector = get_start_sect(xbsd_part); +#endif + + if (lseek(fd, sector * SECTOR_SIZE, SEEK_SET) == -1) + fdisk_fatal(unable_to_seek); + if (BSD_BBSIZE != write(fd, disklabelbuffer, BSD_BBSIZE)) + fdisk_fatal(unable_to_write); + +#if defined (__alpha__) + printf(_("Bootstrap installed on %s.\n"), disk_device); +#else + printf(_("Bootstrap installed on %s.\n"), + partname (disk_device, xbsd_part_index+1, 0)); +#endif + + sync_disks(); +} + +static void +xbsd_change_fstype(void) +{ + int i; + + i = xbsd_get_part_index(xbsd_dlabel.d_npartitions); + xbsd_dlabel.d_partitions[i].p_fstype = read_hex(xbsd_fstypes); +} + +static int +xbsd_get_part_index(int max) +{ + char prompt[256]; + char l; + + snprintf(prompt, sizeof(prompt), _("Partition (a-%c): "), 'a' + max - 1); + do + l = tolower(read_nonempty(prompt)); + while (l < 'a' || l > 'a' + max - 1); + return l - 'a'; +} + +static int +xbsd_check_new_partition(int *i) +{ + /* room for more? various BSD flavours have different maxima */ + if (xbsd_dlabel.d_npartitions == BSD_MAXPARTITIONS) { + int t; + + for (t = 0; t < BSD_MAXPARTITIONS; t++) + if (xbsd_dlabel.d_partitions[t].p_size == 0) + break; + + if (t == BSD_MAXPARTITIONS) { + fprintf(stderr, _("The maximum number of partitions " + "has been created\n")); + return 0; + } + } + + *i = xbsd_get_part_index (BSD_MAXPARTITIONS); + + if (*i >= xbsd_dlabel.d_npartitions) + xbsd_dlabel.d_npartitions = (*i) + 1; + + if (xbsd_dlabel.d_partitions[*i].p_size != 0) { + fprintf(stderr, _("This partition already exists.\n")); + return 0; + } + + return 1; +} + +static void +xbsd_list_types(void) +{ + list_types(xbsd_fstypes); +} + +static u_short +xbsd_dkcksum(struct xbsd_disklabel *lp) +{ + u_short *start, *end; + u_short sum = 0; + + start = (u_short *) lp; + end = (u_short *) &lp->d_partitions[lp->d_npartitions]; + while (start < end) + sum ^= *start++; + return sum; +} + +static int +xbsd_initlabel(struct partition *p, struct xbsd_disklabel *d) +{ + struct xbsd_partition *pp; + + get_geometry(); + memset(d, 0, sizeof(struct xbsd_disklabel)); + + d->d_magic = BSD_DISKMAGIC; + + if (strncmp(disk_device, "/dev/sd", 7) == 0) + d->d_type = BSD_DTYPE_SCSI; + else + d->d_type = BSD_DTYPE_ST506; + +#if !defined (__alpha__) + d->d_flags = BSD_D_DOSPART; +#else + d->d_flags = 0; +#endif + d->d_secsize = SECTOR_SIZE; /* bytes/sector */ + d->d_nsectors = sectors; /* sectors/track */ + d->d_ntracks = heads; /* tracks/cylinder (heads) */ + d->d_ncylinders = cylinders; + d->d_secpercyl = sectors * heads;/* sectors/cylinder */ + if (d->d_secpercyl == 0) + d->d_secpercyl = 1; /* avoid segfaults */ + d->d_secperunit = d->d_secpercyl * d->d_ncylinders; + + d->d_rpm = 3600; + d->d_interleave = 1; + d->d_trackskew = 0; + d->d_cylskew = 0; + d->d_headswitch = 0; + d->d_trkseek = 0; + + d->d_magic2 = BSD_DISKMAGIC; + d->d_bbsize = BSD_BBSIZE; + d->d_sbsize = BSD_SBSIZE; + +#if !defined (__alpha__) + d->d_npartitions = 4; + pp = &d->d_partitions[2]; /* Partition C should be + the NetBSD partition */ + pp->p_offset = get_start_sect(p); + pp->p_size = get_nr_sects(p); + pp->p_fstype = BSD_FS_UNUSED; + pp = &d->d_partitions[3]; /* Partition D should be + the whole disk */ + pp->p_offset = 0; + pp->p_size = d->d_secperunit; + pp->p_fstype = BSD_FS_UNUSED; +#elif defined (__alpha__) + d->d_npartitions = 3; + pp = &d->d_partitions[2]; /* Partition C should be + the whole disk */ + pp->p_offset = 0; + pp->p_size = d->d_secperunit; + pp->p_fstype = BSD_FS_UNUSED; +#endif + + return 1; +} + +/* + * Read a xbsd_disklabel from sector 0 or from the starting sector of p. + * If it has the right magic, return 1. + */ +static int +xbsd_readlabel (struct partition *p, struct xbsd_disklabel *d) +{ + int t, sector; + + /* p is used only to get the starting sector */ +#if !defined (__alpha__) + sector = (p ? get_start_sect(p) : 0); +#elif defined (__alpha__) + sector = 0; +#endif + + if (lseek(fd, sector * SECTOR_SIZE, SEEK_SET) == -1) + fdisk_fatal(unable_to_seek); + if (BSD_BBSIZE != read(fd, disklabelbuffer, BSD_BBSIZE)) + fdisk_fatal(unable_to_read); + + memmove(d, &disklabelbuffer[BSD_LABELSECTOR * SECTOR_SIZE + BSD_LABELOFFSET], + sizeof(struct xbsd_disklabel)); + + if (d->d_magic != BSD_DISKMAGIC || d->d_magic2 != BSD_DISKMAGIC) + return 0; + + for (t = d->d_npartitions; t < BSD_MAXPARTITIONS; t++) { + d->d_partitions[t].p_size = 0; + d->d_partitions[t].p_offset = 0; + d->d_partitions[t].p_fstype = BSD_FS_UNUSED; + } + + if (d->d_npartitions > BSD_MAXPARTITIONS) + fprintf(stderr, _("Warning: too many partitions " + "(%d, maximum is %d).\n"), + d->d_npartitions, BSD_MAXPARTITIONS); + return 1; +} + +static int +xbsd_writelabel (struct partition *p, struct xbsd_disklabel *d) +{ + unsigned int sector; + +#if !defined (__alpha__) && !defined (__powerpc__) && !defined (__hppa__) + sector = get_start_sect(p) + BSD_LABELSECTOR; +#else + sector = BSD_LABELSECTOR; +#endif + + d->d_checksum = 0; + d->d_checksum = xbsd_dkcksum (d); + + /* This is necessary if we want to write the bootstrap later, + otherwise we'd write the old disklabel with the bootstrap. + */ + memmove(&disklabelbuffer[BSD_LABELSECTOR * SECTOR_SIZE + BSD_LABELOFFSET], + d, sizeof(struct xbsd_disklabel)); + +#if defined (__alpha__) && BSD_LABELSECTOR == 0 + alpha_bootblock_checksum(disklabelbuffer); + if (lseek(fd, 0, SEEK_SET) == -1) + fdisk_fatal(unable_to_seek); + if (BSD_BBSIZE != write(fd, disklabelbuffer, BSD_BBSIZE)) + fdisk_fatal(unable_to_write); +#else + if (lseek(fd, sector * SECTOR_SIZE + BSD_LABELOFFSET, SEEK_SET) == -1) + fdisk_fatal(unable_to_seek); + if (sizeof(struct xbsd_disklabel) != write(fd, d, sizeof(struct xbsd_disklabel))) + fdisk_fatal(unable_to_write); +#endif + sync_disks(); + return 1; +} + + +#if !defined (__alpha__) +static int +xbsd_translate_fstype(int linux_type) +{ + switch (linux_type) { + case 0x01: /* DOS 12-bit FAT */ + case 0x04: /* DOS 16-bit <32M */ + case 0x06: /* DOS 16-bit >=32M */ + case 0xe1: /* DOS access */ + case 0xe3: /* DOS R/O */ + case 0xf2: /* DOS secondary */ + return BSD_FS_MSDOS; + case 0x07: /* OS/2 HPFS */ + return BSD_FS_HPFS; + default: + return BSD_FS_OTHER; + } +} + +static void +xbsd_link_part(void) +{ + int k, i; + struct partition *p; + + k = get_partition(1, partitions); + + if (!xbsd_check_new_partition(&i)) + return; + + p = get_part_table(k); + + xbsd_dlabel.d_partitions[i].p_size = get_nr_sects(p); + xbsd_dlabel.d_partitions[i].p_offset = get_start_sect(p); + xbsd_dlabel.d_partitions[i].p_fstype = xbsd_translate_fstype(p->sys_ind); +} +#endif + +#if defined (__alpha__) + +#if !defined(__GLIBC__) +typedef unsigned long long uint64_t; +#endif + +static void +alpha_bootblock_checksum(char *boot) +{ + uint64_t *dp, sum; + int i; + + dp = (uint64_t *)boot; + sum = 0; + for (i = 0; i < 63; i++) + sum += dp[i]; + dp[63] = sum; +} +#endif /* __alpha__ */ + +#endif /* OSF_LABEL */ diff --git a/util-linux/fdisk_sgi.c b/util-linux/fdisk_sgi.c new file mode 100644 index 000000000..548a70bdc --- /dev/null +++ b/util-linux/fdisk_sgi.c @@ -0,0 +1,885 @@ +#ifdef CONFIG_FEATURE_SGI_LABEL + +/* + * Copyright (C) Andreas Neuper, Sep 1998. + * This file may be modified and redistributed under + * the terms of the GNU Public License. + */ + +struct device_parameter { /* 48 bytes */ + unsigned char skew; + unsigned char gap1; + unsigned char gap2; + unsigned char sparecyl; + unsigned short pcylcount; + unsigned short head_vol0; + unsigned short ntrks; /* tracks in cyl 0 or vol 0 */ + unsigned char cmd_tag_queue_depth; + unsigned char unused0; + unsigned short unused1; + unsigned short nsect; /* sectors/tracks in cyl 0 or vol 0 */ + unsigned short bytes; + unsigned short ilfact; + unsigned int flags; /* controller flags */ + unsigned int datarate; + unsigned int retries_on_error; + unsigned int ms_per_word; + unsigned short xylogics_gap1; + unsigned short xylogics_syncdelay; + unsigned short xylogics_readdelay; + unsigned short xylogics_gap2; + unsigned short xylogics_readgate; + unsigned short xylogics_writecont; +}; + +/* + * controller flags + */ +#define SECTOR_SLIP 0x01 +#define SECTOR_FWD 0x02 +#define TRACK_FWD 0x04 +#define TRACK_MULTIVOL 0x08 +#define IGNORE_ERRORS 0x10 +#define RESEEK 0x20 +#define ENABLE_CMDTAGQ 0x40 + +typedef struct { + unsigned int magic; /* expect SGI_LABEL_MAGIC */ + unsigned short boot_part; /* active boot partition */ + unsigned short swap_part; /* active swap partition */ + unsigned char boot_file[16]; /* name of the bootfile */ + struct device_parameter devparam; /* 1 * 48 bytes */ + struct volume_directory { /* 15 * 16 bytes */ + unsigned char vol_file_name[8]; /* a character array */ + unsigned int vol_file_start; /* number of logical block */ + unsigned int vol_file_size; /* number of bytes */ + } directory[15]; + struct sgi_partinfo { /* 16 * 12 bytes */ + unsigned int num_sectors; /* number of blocks */ + unsigned int start_sector; /* must be cylinder aligned */ + unsigned int id; + } partitions[16]; + unsigned int csum; + unsigned int fillbytes; +} sgi_partition; + +typedef struct { + unsigned int magic; /* looks like a magic number */ + unsigned int a2; + unsigned int a3; + unsigned int a4; + unsigned int b1; + unsigned short b2; + unsigned short b3; + unsigned int c[16]; + unsigned short d[3]; + unsigned char scsi_string[50]; + unsigned char serial[137]; + unsigned short check1816; + unsigned char installer[225]; +} sgiinfo; + +#define SGI_LABEL_MAGIC 0x0be5a941 +#define SGI_LABEL_MAGIC_SWAPPED 0x41a9e50b +#define SGI_INFO_MAGIC 0x00072959 +#define SGI_INFO_MAGIC_SWAPPED 0x59290700 + +#define SGI_SSWAP16(x) (sgi_other_endian ? __swap16(x) : (uint16_t)(x)) +#define SGI_SSWAP32(x) (sgi_other_endian ? __swap32(x) : (uint32_t)(x)) + +#define sgilabel ((sgi_partition *)MBRbuffer) +#define sgiparam (sgilabel->devparam) + +/* + * + * fdisksgilabel.c + * + * Copyright (C) Andreas Neuper, Sep 1998. + * This file may be modified and redistributed under + * the terms of the GNU Public License. + * + * Sat Mar 20 EST 1999 Arnaldo Carvalho de Melo <acme@conectiva.com.br> + * Internationalization + */ + + +static int sgi_other_endian; +static int debug; +static short sgi_volumes = 1; + +/* + * only dealing with free blocks here + */ + +typedef struct { + unsigned int first; + unsigned int last; +} freeblocks; +static freeblocks freelist[17]; /* 16 partitions can produce 17 vacant slots */ + +static void +setfreelist(int i, unsigned int f, unsigned int l) +{ + freelist[i].first = f; + freelist[i].last = l; +} + +static void +add2freelist(unsigned int f, unsigned int l) +{ + int i; + for (i = 0; i < 17 ; i++) + if (freelist[i].last == 0) + break; + setfreelist(i, f, l); +} + +static void +clearfreelist(void) +{ + int i; + + for (i = 0; i < 17 ; i++) + setfreelist(i, 0, 0); +} + +static unsigned int +isinfreelist(unsigned int b) +{ + int i; + + for (i = 0; i < 17 ; i++) + if (freelist[i].first <= b && freelist[i].last >= b) + return freelist[i].last; + return 0; +} + /* return last vacant block of this stride (never 0). */ + /* the '>=' is not quite correct, but simplifies the code */ +/* + * end of free blocks section + */ + +static const struct systypes sgi_sys_types[] = { +/* SGI_VOLHDR */ { "\x00" "SGI volhdr" }, +/* 0x01 */ { "\x01" "SGI trkrepl" }, +/* 0x02 */ { "\x02" "SGI secrepl" }, +/* SGI_SWAP */ { "\x03" "SGI raw" }, +/* 0x04 */ { "\x04" "SGI bsd" }, +/* 0x05 */ { "\x05" "SGI sysv" }, +/* SGI_ENTIRE_DISK */ { "\x06" "SGI volume" }, +/* SGI_EFS */ { "\x07" "SGI efs" }, +/* 0x08 */ { "\x08" "SGI lvol" }, +/* 0x09 */ { "\x09" "SGI rlvol" }, +/* SGI_XFS */ { "\x0a" "SGI xfs" }, +/* SGI_XFSLOG */ { "\x0b" "SGI xfslog" }, +/* SGI_XLV */ { "\x0c" "SGI xlv" }, +/* SGI_XVM */ { "\x0d" "SGI xvm" }, +/* LINUX_SWAP */ { "\x82" "Linux swap" }, +/* LINUX_NATIVE */ { "\x83" "Linux native" }, +/* LINUX_LVM */ { "\x8d" "Linux LVM" }, +/* LINUX_RAID */ { "\xfd" "Linux RAID" }, + { NULL } +}; + + +static int +sgi_get_nsect(void) +{ + return SGI_SSWAP16(sgilabel->devparam.nsect); +} + +static int +sgi_get_ntrks(void) +{ + return SGI_SSWAP16(sgilabel->devparam.ntrks); +} + +static unsigned int +two_s_complement_32bit_sum(unsigned int* base, int size /* in bytes */) +{ + int i = 0; + unsigned int sum = 0; + + size /= sizeof(unsigned int); + for (i = 0; i < size; i++) + sum -= SGI_SSWAP32(base[i]); + return sum; +} + +static int +check_sgi_label(void) +{ + if (sizeof(sgilabel) > 512) { + fprintf(stderr, + _("According to MIPS Computer Systems, Inc the " + "Label must not contain more than 512 bytes\n")); + exit(1); + } + + if (sgilabel->magic != SGI_LABEL_MAGIC + && sgilabel->magic != SGI_LABEL_MAGIC_SWAPPED) { + current_label_type = label_dos; + return 0; + } + + sgi_other_endian = (sgilabel->magic == SGI_LABEL_MAGIC_SWAPPED); + /* + * test for correct checksum + */ + if (two_s_complement_32bit_sum((unsigned int*)sgilabel, + sizeof(*sgilabel))) { + fprintf(stderr, + _("Detected sgi disklabel with wrong checksum.\n")); + } + update_units(); + current_label_type = label_sgi; + partitions = 16; + sgi_volumes = 15; + return 1; +} + +static unsigned int +sgi_get_start_sector(int i) +{ + return SGI_SSWAP32(sgilabel->partitions[i].start_sector); +} + +static unsigned int +sgi_get_num_sectors(int i) +{ + return SGI_SSWAP32(sgilabel->partitions[i].num_sectors); +} + +static int +sgi_get_sysid(int i) +{ + return SGI_SSWAP32(sgilabel->partitions[i].id); +} + +static int +sgi_get_bootpartition(void) +{ + return SGI_SSWAP16(sgilabel->boot_part); +} + +static int +sgi_get_swappartition(void) +{ + return SGI_SSWAP16(sgilabel->swap_part); +} + +static void +sgi_list_table(int xtra) +{ + int i, w, wd; + int kpi = 0; /* kernel partition ID */ + + if(xtra) { + printf(_("\nDisk %s (SGI disk label): %d heads, %d sectors\n" + "%d cylinders, %d physical cylinders\n" + "%d extra sects/cyl, interleave %d:1\n" + "%s\n" + "Units = %s of %d * 512 bytes\n\n"), + disk_device, heads, sectors, cylinders, + SGI_SSWAP16(sgiparam.pcylcount), + SGI_SSWAP16(sgiparam.sparecyl), + SGI_SSWAP16(sgiparam.ilfact), + (char *)sgilabel, + str_units(PLURAL), units_per_sector); + } else { + printf( _("\nDisk %s (SGI disk label): " + "%d heads, %d sectors, %d cylinders\n" + "Units = %s of %d * 512 bytes\n\n"), + disk_device, heads, sectors, cylinders, + str_units(PLURAL), units_per_sector ); + } + + w = strlen(disk_device); + wd = strlen(_("Device")); + if (w < wd) + w = wd; + + printf(_("----- partitions -----\n" + "Pt# %*s Info Start End Sectors Id System\n"), + w + 2, _("Device")); + for (i = 0 ; i < partitions; i++) { + if( sgi_get_num_sectors(i) || debug ) { + uint32_t start = sgi_get_start_sector(i); + uint32_t len = sgi_get_num_sectors(i); + kpi++; /* only count nonempty partitions */ + printf( + "%2d: %s %4s %9ld %9ld %9ld %2x %s\n", +/* fdisk part number */ i+1, +/* device */ partname(disk_device, kpi, w+3), +/* flags */ (sgi_get_swappartition() == i) ? "swap" : +/* flags */ (sgi_get_bootpartition() == i) ? "boot" : " ", +/* start */ (long) scround(start), +/* end */ (long) scround(start+len)-1, +/* no odd flag on end */(long) len, +/* type id */ sgi_get_sysid(i), +/* type name */ partition_type(sgi_get_sysid(i))); + } + } + printf(_("----- Bootinfo -----\nBootfile: %s\n" + "----- Directory Entries -----\n"), + sgilabel->boot_file); + for (i = 0 ; i < sgi_volumes; i++) { + if (sgilabel->directory[i].vol_file_size) { + uint32_t start = SGI_SSWAP32(sgilabel->directory[i].vol_file_start); + uint32_t len = SGI_SSWAP32(sgilabel->directory[i].vol_file_size); + unsigned char *name = sgilabel->directory[i].vol_file_name; + + printf(_("%2d: %-10s sector%5u size%8u\n"), + i, (char*)name, (unsigned int) start, (unsigned int) len); + } + } +} + +static void +sgi_set_bootpartition(int i) +{ + sgilabel->boot_part = SGI_SSWAP16(((short)i)); +} + +static unsigned int +sgi_get_lastblock(void) +{ + return heads * sectors * cylinders; +} + +static void +sgi_set_swappartition(int i) +{ + sgilabel->swap_part = SGI_SSWAP16(((short)i)); +} + +static int +sgi_check_bootfile(const char* aFile) +{ + if (strlen(aFile) < 3) /* "/a\n" is minimum */ { + printf(_("\nInvalid Bootfile!\n" + "\tThe bootfile must be an absolute non-zero pathname,\n" + "\te.g. \"/unix\" or \"/unix.save\".\n")); + return 0; + } else { + if (strlen(aFile) > 16) { + printf(_("\n\tName of Bootfile too long: " + "16 bytes maximum.\n")); + return 0; + } else { + if (aFile[0] != '/') { + printf(_("\n\tBootfile must have a " + "fully qualified pathname.\n")); + return 0; + } + } + } + if (strncmp(aFile, (char*)sgilabel->boot_file, 16)) { + printf(_("\n\tBe aware, that the bootfile is not checked for existence.\n\t" + "SGI's default is \"/unix\" and for backup \"/unix.save\".\n")); + /* filename is correct and did change */ + return 1; + } + return 0; /* filename did not change */ +} + +static const char * +sgi_get_bootfile(void) +{ + return (char*)sgilabel->boot_file; +} + +static void +sgi_set_bootfile(const char* aFile) +{ + int i = 0; + + if (sgi_check_bootfile(aFile)) { + while (i < 16) { + if ((aFile[i] != '\n') /* in principle caught again by next line */ + && (strlen(aFile) > i)) + sgilabel->boot_file[i] = aFile[i]; + else + sgilabel->boot_file[i] = 0; + i++; + } + printf(_("\n\tBootfile is changed to \"%s\".\n"), sgilabel->boot_file); + } +} + +static void +create_sgiinfo(void) +{ + /* I keep SGI's habit to write the sgilabel to the second block */ + sgilabel->directory[0].vol_file_start = SGI_SSWAP32(2); + sgilabel->directory[0].vol_file_size = SGI_SSWAP32(sizeof(sgiinfo)); + strncpy((char*)sgilabel->directory[0].vol_file_name, "sgilabel", 8); +} + +static sgiinfo *fill_sgiinfo(void); + +static void +sgi_write_table(void) +{ + sgilabel->csum = 0; + sgilabel->csum = SGI_SSWAP32(two_s_complement_32bit_sum( + (unsigned int*)sgilabel, sizeof(*sgilabel))); + assert(two_s_complement_32bit_sum( + (unsigned int*)sgilabel, sizeof(*sgilabel)) == 0); + + if (lseek(fd, 0, SEEK_SET) < 0) + fdisk_fatal(unable_to_seek); + if (write(fd, sgilabel, SECTOR_SIZE) != SECTOR_SIZE) + fdisk_fatal(unable_to_write); + if (!strncmp((char*)sgilabel->directory[0].vol_file_name, "sgilabel", 8)) { + /* + * keep this habit of first writing the "sgilabel". + * I never tested whether it works without (AN 981002). + */ + sgiinfo *info = fill_sgiinfo(); + int infostartblock = SGI_SSWAP32(sgilabel->directory[0].vol_file_start); + if (lseek(fd, infostartblock*SECTOR_SIZE, SEEK_SET) < 0) + fdisk_fatal(unable_to_seek); + if (write(fd, info, SECTOR_SIZE) != SECTOR_SIZE) + fdisk_fatal(unable_to_write); + free(info); + } +} + +static int +compare_start(int *x, int *y) +{ + /* + * sort according to start sectors + * and prefers largest partition: + * entry zero is entire disk entry + */ + unsigned int i = *x; + unsigned int j = *y; + unsigned int a = sgi_get_start_sector(i); + unsigned int b = sgi_get_start_sector(j); + unsigned int c = sgi_get_num_sectors(i); + unsigned int d = sgi_get_num_sectors(j); + + if (a == b) + return (d > c) ? 1 : (d == c) ? 0 : -1; + return (a > b) ? 1 : -1; +} + + +static int +verify_sgi(int verbose) +{ + int Index[16]; /* list of valid partitions */ + int sortcount = 0; /* number of used partitions, i.e. non-zero lengths */ + int entire = 0, i = 0; + unsigned int start = 0; + long long gap = 0; /* count unused blocks */ + unsigned int lastblock = sgi_get_lastblock(); + + clearfreelist(); + for (i = 0; i < 16; i++) { + if (sgi_get_num_sectors(i) != 0) { + Index[sortcount++] = i; + if (sgi_get_sysid(i) == SGI_ENTIRE_DISK) { + if (entire++ == 1) { + if (verbose) + printf(_("More than one entire disk entry present.\n")); + } + } + } + } + if (sortcount == 0) { + if (verbose) + printf(_("No partitions defined\n")); + return (lastblock > 0) ? 1 : (lastblock == 0) ? 0 : -1; + } + qsort(Index, sortcount, sizeof(Index[0]), (void*)compare_start); + if (sgi_get_sysid(Index[0]) == SGI_ENTIRE_DISK) { + if ((Index[0] != 10) && verbose) + printf(_("IRIX likes when Partition 11 covers the entire disk.\n")); + if ((sgi_get_start_sector(Index[0]) != 0) && verbose) + printf(_("The entire disk partition should start " + "at block 0,\n" + "not at diskblock %d.\n"), + sgi_get_start_sector(Index[0])); + if (debug) /* I do not understand how some disks fulfil it */ + if ((sgi_get_num_sectors(Index[0]) != lastblock) && verbose) + printf(_("The entire disk partition is only %d diskblock large,\n" + "but the disk is %d diskblocks long.\n"), + sgi_get_num_sectors(Index[0]), lastblock); + lastblock = sgi_get_num_sectors(Index[0]); + } else { + if (verbose) + printf(_("One Partition (#11) should cover the entire disk.\n")); + if (debug > 2) + printf("sysid=%d\tpartition=%d\n", + sgi_get_sysid(Index[0]), Index[0]+1); + } + for (i = 1, start = 0; i < sortcount; i++) { + int cylsize = sgi_get_nsect() * sgi_get_ntrks(); + + if ((sgi_get_start_sector(Index[i]) % cylsize) != 0) { + if (debug) /* I do not understand how some disks fulfil it */ + if (verbose) + printf(_("Partition %d does not start on cylinder boundary.\n"), + Index[i]+1); + } + if (sgi_get_num_sectors(Index[i]) % cylsize != 0) { + if (debug) /* I do not understand how some disks fulfil it */ + if (verbose) + printf(_("Partition %d does not end on cylinder boundary.\n"), + Index[i]+1); + } + /* We cannot handle several "entire disk" entries. */ + if (sgi_get_sysid(Index[i]) == SGI_ENTIRE_DISK) continue; + if (start > sgi_get_start_sector(Index[i])) { + if (verbose) + printf(_("The Partition %d and %d overlap by %d sectors.\n"), + Index[i-1]+1, Index[i]+1, + start - sgi_get_start_sector(Index[i])); + if (gap > 0) gap = -gap; + if (gap == 0) gap = -1; + } + if (start < sgi_get_start_sector(Index[i])) { + if (verbose) + printf(_("Unused gap of %8u sectors - sectors %8u-%u\n"), + sgi_get_start_sector(Index[i]) - start, + start, sgi_get_start_sector(Index[i])-1); + gap += sgi_get_start_sector(Index[i]) - start; + add2freelist(start, sgi_get_start_sector(Index[i])); + } + start = sgi_get_start_sector(Index[i]) + + sgi_get_num_sectors(Index[i]); + if (debug > 1) { + if (verbose) + printf("%2d:%12d\t%12d\t%12d\n", Index[i], + sgi_get_start_sector(Index[i]), + sgi_get_num_sectors(Index[i]), + sgi_get_sysid(Index[i])); + } + } + if (start < lastblock) { + if (verbose) + printf(_("Unused gap of %8u sectors - sectors %8u-%u\n"), + lastblock - start, start, lastblock-1); + gap += lastblock - start; + add2freelist(start, lastblock); + } + /* + * Done with arithmetics + * Go for details now + */ + if (verbose) { + if (!sgi_get_num_sectors(sgi_get_bootpartition())) { + printf(_("\nThe boot partition does not exist.\n")); + } + if (!sgi_get_num_sectors(sgi_get_swappartition())) { + printf(_("\nThe swap partition does not exist.\n")); + } else { + if ((sgi_get_sysid(sgi_get_swappartition()) != SGI_SWAP) + && (sgi_get_sysid(sgi_get_swappartition()) != LINUX_SWAP)) + printf(_("\nThe swap partition has no swap type.\n")); + } + if (sgi_check_bootfile("/unix")) + printf(_("\tYou have chosen an unusual boot file name.\n")); + } + return (gap > 0) ? 1 : (gap == 0) ? 0 : -1; +} + +static int +sgi_gaps(void) +{ + /* + * returned value is: + * = 0 : disk is properly filled to the rim + * < 0 : there is an overlap + * > 0 : there is still some vacant space + */ + return verify_sgi(0); +} + +static void +sgi_change_sysid(int i, int sys) +{ + if( sgi_get_num_sectors(i) == 0 ) { /* caught already before, ... */ + printf(_("Sorry You may change the Tag of non-empty partitions.\n")); + return; + } + if (((sys != SGI_ENTIRE_DISK) && (sys != SGI_VOLHDR)) + && (sgi_get_start_sector(i) < 1) ) { + read_maybe_empty( + _("It is highly recommended that the partition at offset 0\n" + "is of type \"SGI volhdr\", the IRIX system will rely on it to\n" + "retrieve from its directory standalone tools like sash and fx.\n" + "Only the \"SGI volume\" entire disk section may violate this.\n" + "Type YES if you are sure about tagging this partition differently.\n")); + if (strcmp(line_ptr, _("YES\n"))) + return; + } + sgilabel->partitions[i].id = SGI_SSWAP32(sys); +} + +/* returns partition index of first entry marked as entire disk */ +static int +sgi_entire(void) +{ + int i; + + for (i = 0; i < 16; i++) + if (sgi_get_sysid(i) == SGI_VOLUME) + return i; + return -1; +} + +static void +sgi_set_partition(int i, unsigned int start, unsigned int length, int sys) +{ + sgilabel->partitions[i].id = SGI_SSWAP32(sys); + sgilabel->partitions[i].num_sectors = SGI_SSWAP32(length); + sgilabel->partitions[i].start_sector = SGI_SSWAP32(start); + set_changed(i); + if (sgi_gaps() < 0) /* rebuild freelist */ + printf(_("Do You know, You got a partition overlap on the disk?\n")); +} + +static void +sgi_set_entire(void) +{ + int n; + + for (n = 10; n < partitions; n++) { + if(!sgi_get_num_sectors(n) ) { + sgi_set_partition(n, 0, sgi_get_lastblock(), SGI_VOLUME); + break; + } + } +} + +static void +sgi_set_volhdr(void) +{ + int n; + + for (n = 8; n < partitions; n++) { + if (!sgi_get_num_sectors(n)) { + /* + * 5 cylinders is an arbitrary value I like + * IRIX 5.3 stored files in the volume header + * (like sash, symmon, fx, ide) with ca. 3200 + * sectors. + */ + if (heads * sectors * 5 < sgi_get_lastblock()) + sgi_set_partition(n, 0, heads * sectors * 5, SGI_VOLHDR); + break; + } + } +} + +static void +sgi_delete_partition(int i) +{ + sgi_set_partition(i, 0, 0, 0); +} + +static void +sgi_add_partition(int n, int sys) +{ + char mesg[256]; + unsigned int first = 0, last = 0; + + if (n == 10) { + sys = SGI_VOLUME; + } else if (n == 8) { + sys = 0; + } + if(sgi_get_num_sectors(n)) { + printf(_("Partition %d is already defined. Delete " + "it before re-adding it.\n"), n + 1); + return; + } + if ((sgi_entire() == -1) && (sys != SGI_VOLUME)) { + printf(_("Attempting to generate entire disk entry automatically.\n")); + sgi_set_entire(); + sgi_set_volhdr(); + } + if ((sgi_gaps() == 0) && (sys != SGI_VOLUME)) { + printf(_("The entire disk is already covered with partitions.\n")); + return; + } + if (sgi_gaps() < 0) { + printf(_("You got a partition overlap on the disk. Fix it first!\n")); + return; + } + snprintf(mesg, sizeof(mesg), _("First %s"), str_units(SINGULAR)); + while (1) { + if(sys == SGI_VOLUME) { + last = sgi_get_lastblock(); + first = read_int(0, 0, last-1, 0, mesg); + if (first != 0) { + printf(_("It is highly recommended that eleventh partition\n" + "covers the entire disk and is of type 'SGI volume'\n")); + } + } else { + first = freelist[0].first; + last = freelist[0].last; + first = read_int(scround(first), scround(first), scround(last)-1, + 0, mesg); + } + if (display_in_cyl_units) + first *= units_per_sector; + else + first = first; /* align to cylinder if you know how ... */ + if(!last ) + last = isinfreelist(first); + if(last == 0) { + printf(_("You will get a partition overlap on the disk. " + "Fix it first!\n")); + } else + break; + } + snprintf(mesg, sizeof(mesg), _(" Last %s"), str_units(SINGULAR)); + last = read_int(scround(first), scround(last)-1, scround(last)-1, + scround(first), mesg)+1; + if (display_in_cyl_units) + last *= units_per_sector; + else + last = last; /* align to cylinder if You know how ... */ + if ( (sys == SGI_VOLUME) && (first != 0 || last != sgi_get_lastblock() ) ) + printf(_("It is highly recommended that eleventh partition\n" + "covers the entire disk and is of type 'SGI volume'\n")); + sgi_set_partition(n, first, last-first, sys); +} + +#ifdef CONFIG_FEATURE_FDISK_ADVANCED +static void +create_sgilabel(void) +{ + struct hd_geometry geometry; + struct { + unsigned int start; + unsigned int nsect; + int sysid; + } old[4]; + int i = 0; + long longsectors; /* the number of sectors on the device */ + int res; /* the result from the ioctl */ + int sec_fac; /* the sector factor */ + + sec_fac = sector_size / 512; /* determine the sector factor */ + + fprintf( stderr, + _("Building a new SGI disklabel. Changes will remain in memory only,\n" + "until you decide to write them. After that, of course, the previous\n" + "content will be unrecoverably lost.\n\n")); + + sgi_other_endian = (BYTE_ORDER == LITTLE_ENDIAN); + res = ioctl(fd, BLKGETSIZE, &longsectors); + if (!ioctl(fd, HDIO_GETGEO, &geometry)) { + heads = geometry.heads; + sectors = geometry.sectors; + if (res == 0) { + /* the get device size ioctl was successful */ + cylinders = longsectors / (heads * sectors); + cylinders /= sec_fac; + } else { + /* otherwise print error and use truncated version */ + cylinders = geometry.cylinders; + fprintf(stderr, + _("Warning: BLKGETSIZE ioctl failed on %s. " + "Using geometry cylinder value of %d.\n" + "This value may be truncated for devices" + " > 33.8 GB.\n"), disk_device, cylinders); + } + } + for (i = 0; i < 4; i++) { + old[i].sysid = 0; + if (valid_part_table_flag(MBRbuffer)) { + if(get_part_table(i)->sys_ind) { + old[i].sysid = get_part_table(i)->sys_ind; + old[i].start = get_start_sect(get_part_table(i)); + old[i].nsect = get_nr_sects(get_part_table(i)); + printf(_("Trying to keep parameters of partition %d.\n"), i); + if (debug) + printf(_("ID=%02x\tSTART=%d\tLENGTH=%d\n"), + old[i].sysid, old[i].start, old[i].nsect); + } + } + } + + memset(MBRbuffer, 0, sizeof(MBRbuffer)); + sgilabel->magic = SGI_SSWAP32(SGI_LABEL_MAGIC); + sgilabel->boot_part = SGI_SSWAP16(0); + sgilabel->swap_part = SGI_SSWAP16(1); + + /* sizeof(sgilabel->boot_file) = 16 > 6 */ + memset(sgilabel->boot_file, 0, 16); + strcpy((char*)sgilabel->boot_file, "/unix"); + + sgilabel->devparam.skew = (0); + sgilabel->devparam.gap1 = (0); + sgilabel->devparam.gap2 = (0); + sgilabel->devparam.sparecyl = (0); + sgilabel->devparam.pcylcount = SGI_SSWAP16(geometry.cylinders); + sgilabel->devparam.head_vol0 = SGI_SSWAP16(0); + sgilabel->devparam.ntrks = SGI_SSWAP16(geometry.heads); + /* tracks/cylinder (heads) */ + sgilabel->devparam.cmd_tag_queue_depth = (0); + sgilabel->devparam.unused0 = (0); + sgilabel->devparam.unused1 = SGI_SSWAP16(0); + sgilabel->devparam.nsect = SGI_SSWAP16(geometry.sectors); + /* sectors/track */ + sgilabel->devparam.bytes = SGI_SSWAP16(512); + sgilabel->devparam.ilfact = SGI_SSWAP16(1); + sgilabel->devparam.flags = SGI_SSWAP32(TRACK_FWD| + IGNORE_ERRORS|RESEEK); + sgilabel->devparam.datarate = SGI_SSWAP32(0); + sgilabel->devparam.retries_on_error = SGI_SSWAP32(1); + sgilabel->devparam.ms_per_word = SGI_SSWAP32(0); + sgilabel->devparam.xylogics_gap1 = SGI_SSWAP16(0); + sgilabel->devparam.xylogics_syncdelay = SGI_SSWAP16(0); + sgilabel->devparam.xylogics_readdelay = SGI_SSWAP16(0); + sgilabel->devparam.xylogics_gap2 = SGI_SSWAP16(0); + sgilabel->devparam.xylogics_readgate = SGI_SSWAP16(0); + sgilabel->devparam.xylogics_writecont = SGI_SSWAP16(0); + memset( &(sgilabel->directory), 0, sizeof(struct volume_directory)*15 ); + memset( &(sgilabel->partitions), 0, sizeof(struct sgi_partinfo)*16 ); + current_label_type = label_sgi; + partitions = 16; + sgi_volumes = 15; + sgi_set_entire(); + sgi_set_volhdr(); + for (i = 0; i < 4; i++) { + if(old[i].sysid) { + sgi_set_partition(i, old[i].start, old[i].nsect, old[i].sysid); + } + } +} + +static void +sgi_set_xcyl(void) +{ + /* do nothing in the beginning */ +} +#endif /* CONFIG_FEATURE_FDISK_ADVANCED */ + +/* _____________________________________________________________ + */ + +static sgiinfo * +fill_sgiinfo(void) +{ + sgiinfo *info = calloc(1, sizeof(sgiinfo)); + + info->magic = SGI_SSWAP32(SGI_INFO_MAGIC); + info->b1 = SGI_SSWAP32(-1); + info->b2 = SGI_SSWAP16(-1); + info->b3 = SGI_SSWAP16(1); + /* You may want to replace this string !!!!!!! */ + strcpy( (char*)info->scsi_string, "IBM OEM 0662S12 3 30" ); + strcpy( (char*)info->serial, "0000" ); + info->check1816 = SGI_SSWAP16(18*256 +16 ); + strcpy( (char*)info->installer, "Sfx version 5.3, Oct 18, 1994" ); + return info; +} +#endif /* SGI_LABEL */ diff --git a/util-linux/fdisk_sun.c b/util-linux/fdisk_sun.c new file mode 100644 index 000000000..1e8f2e525 --- /dev/null +++ b/util-linux/fdisk_sun.c @@ -0,0 +1,729 @@ +#ifdef CONFIG_FEATURE_SUN_LABEL + +#define SUN_LABEL_MAGIC 0xDABE +#define SUN_LABEL_MAGIC_SWAPPED 0xBEDA +#define SUN_SSWAP16(x) (sun_other_endian ? __swap16(x) : (uint16_t)(x)) +#define SUN_SSWAP32(x) (sun_other_endian ? __swap32(x) : (uint32_t)(x)) + +/* Copied from linux/major.h */ +#define FLOPPY_MAJOR 2 + +#define SCSI_IOCTL_GET_IDLUN 0x5382 + +/* + * fdisksunlabel.c + * + * I think this is mostly, or entirely, due to + * Jakub Jelinek (jj@sunsite.mff.cuni.cz), July 1996 + * + * Merged with fdisk for other architectures, aeb, June 1998. + * + * Sat Mar 20 EST 1999 Arnaldo Carvalho de Melo <acme@conectiva.com.br> + * Internationalization + */ + + +static int sun_other_endian; +static int scsi_disk; +static int floppy; + +#ifndef IDE0_MAJOR +#define IDE0_MAJOR 3 +#endif +#ifndef IDE1_MAJOR +#define IDE1_MAJOR 22 +#endif + +static void +guess_device_type(void) +{ + struct stat bootstat; + + if (fstat(fd, &bootstat) < 0) { + scsi_disk = 0; + floppy = 0; + } else if (S_ISBLK(bootstat.st_mode) + && (major(bootstat.st_rdev) == IDE0_MAJOR || + major(bootstat.st_rdev) == IDE1_MAJOR)) { + scsi_disk = 0; + floppy = 0; + } else if (S_ISBLK(bootstat.st_mode) + && major(bootstat.st_rdev) == FLOPPY_MAJOR) { + scsi_disk = 0; + floppy = 1; + } else { + scsi_disk = 1; + floppy = 0; + } +} + +static const struct systypes sun_sys_types[] = { + { "\x00" "Empty" }, /* 0 */ + { "\x01" "Boot" }, /* 1 */ + { "\x02" "SunOS root" }, /* 2 */ + { "\x03" "SunOS swap" }, /* SUNOS_SWAP */ + { "\x04" "SunOS usr" }, /* 4 */ + { "\x05" "Whole disk" }, /* SUN_WHOLE_DISK */ + { "\x06" "SunOS stand" }, /* 6 */ + { "\x07" "SunOS var" }, /* 7 */ + { "\x08" "SunOS home" }, /* 8 */ + { "\x82" "Linux swap" }, /* LINUX_SWAP */ + { "\x83" "Linux native" }, /* LINUX_NATIVE */ + { "\x8e" "Linux LVM" }, /* 0x8e */ +/* New (2.2.x) raid partition with autodetect using persistent superblock */ + { "\xfd" "Linux raid autodetect" }, /* 0xfd */ + { NULL } +}; + + +static void +set_sun_partition(int i, uint start, uint stop, int sysid) +{ + sunlabel->infos[i].id = sysid; + sunlabel->partitions[i].start_cylinder = + SUN_SSWAP32(start / (heads * sectors)); + sunlabel->partitions[i].num_sectors = + SUN_SSWAP32(stop - start); + set_changed(i); +} + +static int +check_sun_label(void) +{ + unsigned short *ush; + int csum; + + if (sunlabel->magic != SUN_LABEL_MAGIC + && sunlabel->magic != SUN_LABEL_MAGIC_SWAPPED) { + current_label_type = label_dos; + sun_other_endian = 0; + return 0; + } + sun_other_endian = (sunlabel->magic == SUN_LABEL_MAGIC_SWAPPED); + ush = ((unsigned short *) (sunlabel + 1)) - 1; + for (csum = 0; ush >= (unsigned short *)sunlabel;) csum ^= *ush--; + if (csum) { + fprintf(stderr,_("Detected sun disklabel with wrong checksum.\n" + "Probably you'll have to set all the values,\n" + "e.g. heads, sectors, cylinders and partitions\n" + "or force a fresh label (s command in main menu)\n")); + } else { + heads = SUN_SSWAP16(sunlabel->ntrks); + cylinders = SUN_SSWAP16(sunlabel->ncyl); + sectors = SUN_SSWAP16(sunlabel->nsect); + } + update_units(); + current_label_type = label_sun; + partitions = 8; + return 1; +} + +static const struct sun_predefined_drives { + const char *vendor; + const char *model; + unsigned short sparecyl; + unsigned short ncyl; + unsigned short nacyl; + unsigned short pcylcount; + unsigned short ntrks; + unsigned short nsect; + unsigned short rspeed; +} sun_drives[] = { + { "Quantum","ProDrive 80S",1,832,2,834,6,34,3662}, + { "Quantum","ProDrive 105S",1,974,2,1019,6,35,3662}, + { "CDC","Wren IV 94171-344",3,1545,2,1549,9,46,3600}, + { "IBM","DPES-31080",0,4901,2,4903,4,108,5400}, + { "IBM","DORS-32160",0,1015,2,1017,67,62,5400}, + { "IBM","DNES-318350",0,11199,2,11474,10,320,7200}, + { "SEAGATE","ST34371",0,3880,2,3882,16,135,7228}, + { "","SUN0104",1,974,2,1019,6,35,3662}, + { "","SUN0207",4,1254,2,1272,9,36,3600}, + { "","SUN0327",3,1545,2,1549,9,46,3600}, + { "","SUN0340",0,1538,2,1544,6,72,4200}, + { "","SUN0424",2,1151,2,2500,9,80,4400}, + { "","SUN0535",0,1866,2,2500,7,80,5400}, + { "","SUN0669",5,1614,2,1632,15,54,3600}, + { "","SUN1.0G",5,1703,2,1931,15,80,3597}, + { "","SUN1.05",0,2036,2,2038,14,72,5400}, + { "","SUN1.3G",6,1965,2,3500,17,80,5400}, + { "","SUN2.1G",0,2733,2,3500,19,80,5400}, + { "IOMEGA","Jaz",0,1019,2,1021,64,32,5394}, +}; + +static const struct sun_predefined_drives * +sun_autoconfigure_scsi(void) +{ + const struct sun_predefined_drives *p = NULL; + +#ifdef SCSI_IOCTL_GET_IDLUN + unsigned int id[2]; + char buffer[2048]; + char buffer2[2048]; + FILE *pfd; + char *vendor; + char *model; + char *q; + int i; + + if (ioctl(fd, SCSI_IOCTL_GET_IDLUN, &id)) + return NULL; + + sprintf(buffer, + "Host: scsi%d Channel: %02d Id: %02d Lun: %02d\n", + /* This is very wrong (works only if you have one HBA), + but I haven't found a way how to get hostno + from the current kernel */ + 0, + (id[0]>>16) & 0xff, + id[0] & 0xff, + (id[0]>>8) & 0xff + ); + pfd = fopen("/proc/scsi/scsi", "r"); + if (!pfd) { + return NULL; + } + while (fgets(buffer2, 2048, pfd)) { + if (strcmp(buffer, buffer2)) + continue; + if (!fgets(buffer2, 2048, pfd)) + break; + q = strstr(buffer2, "Vendor: "); + if (!q) + break; + q += 8; + vendor = q; + q = strstr(q, " "); + *q++ = '\0'; /* truncate vendor name */ + q = strstr(q, "Model: "); + if (!q) + break; + *q = '\0'; + q += 7; + model = q; + q = strstr(q, " Rev: "); + if (!q) + break; + *q = '\0'; + for (i = 0; i < SIZE(sun_drives); i++) { + if (*sun_drives[i].vendor && strcasecmp(sun_drives[i].vendor, vendor)) + continue; + if (!strstr(model, sun_drives[i].model)) + continue; + printf(_("Autoconfigure found a %s%s%s\n"), + sun_drives[i].vendor, + (*sun_drives[i].vendor) ? " " : "", + sun_drives[i].model); + p = sun_drives + i; + break; + } + break; + } + fclose(pfd); +#endif + return p; +} + +static void +create_sunlabel(void) +{ + struct hd_geometry geometry; + unsigned int ndiv; + int i; + unsigned char c; + const struct sun_predefined_drives *p = NULL; + + fprintf(stderr, + _("Building a new sun disklabel. Changes will remain in memory only,\n" + "until you decide to write them. After that, of course, the previous\n" + "content won't be recoverable.\n\n")); + sun_other_endian = BB_LITTLE_ENDIAN; + memset(MBRbuffer, 0, sizeof(MBRbuffer)); + sunlabel->magic = SUN_SSWAP16(SUN_LABEL_MAGIC); + if (!floppy) { + puts(_("Drive type\n" + " ? auto configure\n" + " 0 custom (with hardware detected defaults)")); + for (i = 0; i < SIZE(sun_drives); i++) { + printf(" %c %s%s%s\n", + i + 'a', sun_drives[i].vendor, + (*sun_drives[i].vendor) ? " " : "", + sun_drives[i].model); + } + while (1) { + c = read_nonempty(_("Select type (? for auto, 0 for custom): ")); + if (c >= 'a' && c < 'a' + SIZE(sun_drives)) { + p = sun_drives + c - 'a'; + break; + } else if (c >= 'A' && c < 'A' + SIZE(sun_drives)) { + p = sun_drives + c - 'A'; + break; + } else if (c == '0') { + break; + } else if (c == '?' && scsi_disk) { + p = sun_autoconfigure_scsi(); + if (!p) + printf(_("Autoconfigure failed.\n")); + else + break; + } + } + } + if (!p || floppy) { + if (!ioctl(fd, HDIO_GETGEO, &geometry)) { + heads = geometry.heads; + sectors = geometry.sectors; + cylinders = geometry.cylinders; + } else { + heads = 0; + sectors = 0; + cylinders = 0; + } + if (floppy) { + sunlabel->nacyl = 0; + sunlabel->pcylcount = SUN_SSWAP16(cylinders); + sunlabel->rspeed = SUN_SSWAP16(300); + sunlabel->ilfact = SUN_SSWAP16(1); + sunlabel->sparecyl = 0; + } else { + heads = read_int(1,heads,1024,0,_("Heads")); + sectors = read_int(1,sectors,1024,0,_("Sectors/track")); + if (cylinders) + cylinders = read_int(1,cylinders-2,65535,0,_("Cylinders")); + else + cylinders = read_int(1,0,65535,0,_("Cylinders")); + sunlabel->nacyl = SUN_SSWAP16(read_int(0,2,65535,0, _("Alternate cylinders"))); + sunlabel->pcylcount = SUN_SSWAP16(read_int(0,cylinders+SUN_SSWAP16(sunlabel->nacyl), 65535,0, _("Physical cylinders"))); + sunlabel->rspeed = SUN_SSWAP16(read_int(1,5400,100000,0, _("Rotation speed (rpm)"))); + sunlabel->ilfact = SUN_SSWAP16(read_int(1,1,32,0, _("Interleave factor"))); + sunlabel->sparecyl = SUN_SSWAP16(read_int(0,0,sectors,0, _("Extra sectors per cylinder"))); + } + } else { + sunlabel->sparecyl = SUN_SSWAP16(p->sparecyl); + sunlabel->ncyl = SUN_SSWAP16(p->ncyl); + sunlabel->nacyl = SUN_SSWAP16(p->nacyl); + sunlabel->pcylcount = SUN_SSWAP16(p->pcylcount); + sunlabel->ntrks = SUN_SSWAP16(p->ntrks); + sunlabel->nsect = SUN_SSWAP16(p->nsect); + sunlabel->rspeed = SUN_SSWAP16(p->rspeed); + sunlabel->ilfact = SUN_SSWAP16(1); + cylinders = p->ncyl; + heads = p->ntrks; + sectors = p->nsect; + puts(_("You may change all the disk params from the x menu")); + } + + snprintf((char *)(sunlabel->info), sizeof(sunlabel->info), + "%s%s%s cyl %d alt %d hd %d sec %d", + p ? p->vendor : "", (p && *p->vendor) ? " " : "", + p ? p->model : (floppy ? _("3,5\" floppy") : _("Linux custom")), + cylinders, SUN_SSWAP16(sunlabel->nacyl), heads, sectors); + + sunlabel->ntrks = SUN_SSWAP16(heads); + sunlabel->nsect = SUN_SSWAP16(sectors); + sunlabel->ncyl = SUN_SSWAP16(cylinders); + if (floppy) + set_sun_partition(0, 0, cylinders * heads * sectors, LINUX_NATIVE); + else { + if (cylinders * heads * sectors >= 150 * 2048) { + ndiv = cylinders - (50 * 2048 / (heads * sectors)); /* 50M swap */ + } else + ndiv = cylinders * 2 / 3; + set_sun_partition(0, 0, ndiv * heads * sectors, LINUX_NATIVE); + set_sun_partition(1, ndiv * heads * sectors, cylinders * heads * sectors, LINUX_SWAP); + sunlabel->infos[1].flags |= 0x01; /* Not mountable */ + } + set_sun_partition(2, 0, cylinders * heads * sectors, SUN_WHOLE_DISK); + { + unsigned short *ush = (unsigned short *)sunlabel; + unsigned short csum = 0; + while (ush < (unsigned short *)(&sunlabel->csum)) + csum ^= *ush++; + sunlabel->csum = csum; + } + + set_all_unchanged(); + set_changed(0); + get_boot(create_empty_sun); +} + +static void +toggle_sunflags(int i, unsigned char mask) +{ + if (sunlabel->infos[i].flags & mask) + sunlabel->infos[i].flags &= ~mask; + else + sunlabel->infos[i].flags |= mask; + set_changed(i); +} + +static void +fetch_sun(uint *starts, uint *lens, uint *start, uint *stop) +{ + int i, continuous = 1; + + *start = 0; + *stop = cylinders * heads * sectors; + for (i = 0; i < partitions; i++) { + if (sunlabel->partitions[i].num_sectors + && sunlabel->infos[i].id + && sunlabel->infos[i].id != SUN_WHOLE_DISK) { + starts[i] = SUN_SSWAP32(sunlabel->partitions[i].start_cylinder) * heads * sectors; + lens[i] = SUN_SSWAP32(sunlabel->partitions[i].num_sectors); + if (continuous) { + if (starts[i] == *start) + *start += lens[i]; + else if (starts[i] + lens[i] >= *stop) + *stop = starts[i]; + else + continuous = 0; + /* There will be probably more gaps + than one, so lets check afterwards */ + } + } else { + starts[i] = 0; + lens[i] = 0; + } + } +} + +static uint *verify_sun_starts; + +static int +verify_sun_cmp(int *a, int *b) +{ + if (*a == -1) return 1; + if (*b == -1) return -1; + if (verify_sun_starts[*a] > verify_sun_starts[*b]) return 1; + return -1; +} + +static void +verify_sun(void) +{ + uint starts[8], lens[8], start, stop; + int i,j,k,starto,endo; + int array[8]; + + verify_sun_starts = starts; + fetch_sun(starts,lens,&start,&stop); + for (k = 0; k < 7; k++) { + for (i = 0; i < 8; i++) { + if (k && (lens[i] % (heads * sectors))) { + printf(_("Partition %d doesn't end on cylinder boundary\n"), i+1); + } + if (lens[i]) { + for (j = 0; j < i; j++) + if (lens[j]) { + if (starts[j] == starts[i]+lens[i]) { + starts[j] = starts[i]; lens[j] += lens[i]; + lens[i] = 0; + } else if (starts[i] == starts[j]+lens[j]){ + lens[j] += lens[i]; + lens[i] = 0; + } else if (!k) { + if (starts[i] < starts[j]+lens[j] + && starts[j] < starts[i]+lens[i]) { + starto = starts[i]; + if (starts[j] > starto) + starto = starts[j]; + endo = starts[i]+lens[i]; + if (starts[j]+lens[j] < endo) + endo = starts[j]+lens[j]; + printf(_("Partition %d overlaps with others in " + "sectors %d-%d\n"), i+1, starto, endo); + } + } + } + } + } + } + for (i = 0; i < 8; i++) { + if (lens[i]) + array[i] = i; + else + array[i] = -1; + } + qsort(array,SIZE(array),sizeof(array[0]), + (int (*)(const void *,const void *)) verify_sun_cmp); + if (array[0] == -1) { + printf(_("No partitions defined\n")); + return; + } + stop = cylinders * heads * sectors; + if (starts[array[0]]) + printf(_("Unused gap - sectors 0-%d\n"),starts[array[0]]); + for (i = 0; i < 7 && array[i+1] != -1; i++) { + printf(_("Unused gap - sectors %d-%d\n"),starts[array[i]]+lens[array[i]],starts[array[i+1]]); + } + start = starts[array[i]] + lens[array[i]]; + if (start < stop) + printf(_("Unused gap - sectors %d-%d\n"),start,stop); +} + +static void +add_sun_partition(int n, int sys) +{ + uint start, stop, stop2; + uint starts[8], lens[8]; + int whole_disk = 0; + + char mesg[256]; + int i, first, last; + + if (sunlabel->partitions[n].num_sectors && sunlabel->infos[n].id) { + printf(_("Partition %d is already defined. Delete " + "it before re-adding it.\n"), n + 1); + return; + } + + fetch_sun(starts,lens,&start,&stop); + if (stop <= start) { + if (n == 2) + whole_disk = 1; + else { + printf(_("Other partitions already cover the whole disk.\nDelete " + "some/shrink them before retry.\n")); + return; + } + } + snprintf(mesg, sizeof(mesg), _("First %s"), str_units(SINGULAR)); + while (1) { + if (whole_disk) + first = read_int(0, 0, 0, 0, mesg); + else + first = read_int(scround(start), scround(stop)+1, + scround(stop), 0, mesg); + if (display_in_cyl_units) + first *= units_per_sector; + else + /* Starting sector has to be properly aligned */ + first = (first + heads * sectors - 1) / (heads * sectors); + if (n == 2 && first != 0) + printf("\ +It is highly recommended that the third partition covers the whole disk\n\ +and is of type `Whole disk'\n"); + /* ewt asks to add: "don't start a partition at cyl 0" + However, edmundo@rano.demon.co.uk writes: + "In addition to having a Sun partition table, to be able to + boot from the disc, the first partition, /dev/sdX1, must + start at cylinder 0. This means that /dev/sdX1 contains + the partition table and the boot block, as these are the + first two sectors of the disc. Therefore you must be + careful what you use /dev/sdX1 for. In particular, you must + not use a partition starting at cylinder 0 for Linux swap, + as that would overwrite the partition table and the boot + block. You may, however, use such a partition for a UFS + or EXT2 file system, as these file systems leave the first + 1024 bytes undisturbed. */ + /* On the other hand, one should not use partitions + starting at block 0 in an md, or the label will + be trashed. */ + for (i = 0; i < partitions; i++) + if (lens[i] && starts[i] <= first && starts[i] + lens[i] > first) + break; + if (i < partitions && !whole_disk) { + if (n == 2 && !first) { + whole_disk = 1; + break; + } + printf(_("Sector %d is already allocated\n"), first); + } else + break; + } + stop = cylinders * heads * sectors; + stop2 = stop; + for (i = 0; i < partitions; i++) { + if (starts[i] > first && starts[i] < stop) + stop = starts[i]; + } + snprintf(mesg, sizeof(mesg), + _("Last %s or +size or +sizeM or +sizeK"), + str_units(SINGULAR)); + if (whole_disk) + last = read_int(scround(stop2), scround(stop2), scround(stop2), + 0, mesg); + else if (n == 2 && !first) + last = read_int(scround(first), scround(stop2), scround(stop2), + scround(first), mesg); + else + last = read_int(scround(first), scround(stop), scround(stop), + scround(first), mesg); + if (display_in_cyl_units) + last *= units_per_sector; + if (n == 2 && !first) { + if (last >= stop2) { + whole_disk = 1; + last = stop2; + } else if (last > stop) { + printf(_("You haven't covered the whole disk with " + "the 3rd partition, but your value\n" + "%d %s covers some other partition. " + "Your entry has been changed\n" + "to %d %s\n"), + scround(last), str_units(SINGULAR), + scround(stop), str_units(SINGULAR)); + last = stop; + } + } else if (!whole_disk && last > stop) + last = stop; + + if (whole_disk) + sys = SUN_WHOLE_DISK; + set_sun_partition(n, first, last, sys); +} + +static void +sun_delete_partition(int i) +{ + unsigned int nsec; + + if (i == 2 + && sunlabel->infos[i].id == SUN_WHOLE_DISK + && !sunlabel->partitions[i].start_cylinder + && (nsec = SUN_SSWAP32(sunlabel->partitions[i].num_sectors)) == heads * sectors * cylinders) + printf(_("If you want to maintain SunOS/Solaris compatibility, " + "consider leaving this\n" + "partition as Whole disk (5), starting at 0, with %u " + "sectors\n"), nsec); + sunlabel->infos[i].id = 0; + sunlabel->partitions[i].num_sectors = 0; +} + +static void +sun_change_sysid(int i, int sys) +{ + if (sys == LINUX_SWAP && !sunlabel->partitions[i].start_cylinder) { + read_maybe_empty( + _("It is highly recommended that the partition at offset 0\n" + "is UFS, EXT2FS filesystem or SunOS swap. Putting Linux swap\n" + "there may destroy your partition table and bootblock.\n" + "Type YES if you're very sure you would like that partition\n" + "tagged with 82 (Linux swap): ")); + if (strcmp (line_ptr, _("YES\n"))) + return; + } + switch (sys) { + case SUNOS_SWAP: + case LINUX_SWAP: + /* swaps are not mountable by default */ + sunlabel->infos[i].flags |= 0x01; + break; + default: + /* assume other types are mountable; + user can change it anyway */ + sunlabel->infos[i].flags &= ~0x01; + break; + } + sunlabel->infos[i].id = sys; +} + +static void +sun_list_table(int xtra) +{ + int i, w; + + w = strlen(disk_device); + if (xtra) + printf( + _("\nDisk %s (Sun disk label): %d heads, %d sectors, %d rpm\n" + "%d cylinders, %d alternate cylinders, %d physical cylinders\n" + "%d extra sects/cyl, interleave %d:1\n" + "%s\n" + "Units = %s of %d * 512 bytes\n\n"), + disk_device, heads, sectors, SUN_SSWAP16(sunlabel->rspeed), + cylinders, SUN_SSWAP16(sunlabel->nacyl), + SUN_SSWAP16(sunlabel->pcylcount), + SUN_SSWAP16(sunlabel->sparecyl), + SUN_SSWAP16(sunlabel->ilfact), + (char *)sunlabel, + str_units(PLURAL), units_per_sector); + else + printf( + _("\nDisk %s (Sun disk label): %d heads, %d sectors, %d cylinders\n" + "Units = %s of %d * 512 bytes\n\n"), + disk_device, heads, sectors, cylinders, + str_units(PLURAL), units_per_sector); + + printf(_("%*s Flag Start End Blocks Id System\n"), + w + 1, _("Device")); + for (i = 0 ; i < partitions; i++) { + if (sunlabel->partitions[i].num_sectors) { + uint32_t start = SUN_SSWAP32(sunlabel->partitions[i].start_cylinder) * heads * sectors; + uint32_t len = SUN_SSWAP32(sunlabel->partitions[i].num_sectors); + printf("%s %c%c %9ld %9ld %9ld%c %2x %s\n", + partname(disk_device, i+1, w), /* device */ + (sunlabel->infos[i].flags & 0x01) ? 'u' : ' ', /* flags */ + (sunlabel->infos[i].flags & 0x10) ? 'r' : ' ', + (long) scround(start), /* start */ + (long) scround(start+len), /* end */ + (long) len / 2, len & 1 ? '+' : ' ', /* odd flag on end */ + sunlabel->infos[i].id, /* type id */ + partition_type(sunlabel->infos[i].id)); /* type name */ + } + } +} + +#ifdef CONFIG_FEATURE_FDISK_ADVANCED + +static void +sun_set_alt_cyl(void) +{ + sunlabel->nacyl = + SUN_SSWAP16(read_int(0,SUN_SSWAP16(sunlabel->nacyl), 65535, 0, + _("Number of alternate cylinders"))); +} + +static void +sun_set_ncyl(int cyl) +{ + sunlabel->ncyl = SUN_SSWAP16(cyl); +} + +static void +sun_set_xcyl(void) +{ + sunlabel->sparecyl = + SUN_SSWAP16(read_int(0, SUN_SSWAP16(sunlabel->sparecyl), sectors, 0, + _("Extra sectors per cylinder"))); +} + +static void +sun_set_ilfact(void) +{ + sunlabel->ilfact = + SUN_SSWAP16(read_int(1, SUN_SSWAP16(sunlabel->ilfact), 32, 0, + _("Interleave factor"))); +} + +static void +sun_set_rspeed(void) +{ + sunlabel->rspeed = + SUN_SSWAP16(read_int(1, SUN_SSWAP16(sunlabel->rspeed), 100000, 0, + _("Rotation speed (rpm)"))); +} + +static void +sun_set_pcylcount(void) +{ + sunlabel->pcylcount = + SUN_SSWAP16(read_int(0, SUN_SSWAP16(sunlabel->pcylcount), 65535, 0, + _("Number of physical cylinders"))); +} +#endif /* CONFIG_FEATURE_FDISK_ADVANCED */ + +static void +sun_write_table(void) +{ + unsigned short *ush = (unsigned short *)sunlabel; + unsigned short csum = 0; + + while (ush < (unsigned short *)(&sunlabel->csum)) + csum ^= *ush++; + sunlabel->csum = csum; + if (lseek(fd, 0, SEEK_SET) < 0) + fdisk_fatal(unable_to_seek); + if (write(fd, sunlabel, SECTOR_SIZE) != SECTOR_SIZE) + fdisk_fatal(unable_to_write); +} +#endif /* SUN_LABEL */ diff --git a/util-linux/freeramdisk.c b/util-linux/freeramdisk.c new file mode 100644 index 000000000..2293d3ee6 --- /dev/null +++ b/util-linux/freeramdisk.c @@ -0,0 +1,34 @@ +/* vi: set sw=4 ts=4: */ +/* + * freeramdisk and fdflush implementations for busybox + * + * Copyright (C) 2000 and written by Emanuele Caratti <wiz@iol.it> + * Adjusted a bit by Erik Andersen <andersen@codepoet.org> + * Unified with fdflush by Tito Ragusa <farmatito@tiscali.it> + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +/* From <linux/fd.h> */ +#define FDFLUSH _IO(2,0x4b) + +int freeramdisk_main(int argc, char **argv) +{ + int result; + int fd; + + if (argc != 2) bb_show_usage(); + + fd = xopen(argv[1], O_RDWR); + + // Act like freeramdisk, fdflush, or both depending on configuration. + result = ioctl(fd, (ENABLE_FREERAMDISK && applet_name[1]=='r') + || !ENABLE_FDFLUSH ? BLKFLSBUF : FDFLUSH); + + if (ENABLE_FEATURE_CLEAN_UP) close(fd); + + if (result) bb_perror_msg_and_die("%s", argv[1]); + return EXIT_SUCCESS; +} diff --git a/util-linux/fsck_minix.c b/util-linux/fsck_minix.c new file mode 100644 index 000000000..9c831bd59 --- /dev/null +++ b/util-linux/fsck_minix.c @@ -0,0 +1,1417 @@ +/* vi: set sw=4 ts=4: */ +/* + * fsck.c - a file system consistency checker for Linux. + * + * (C) 1991, 1992 Linus Torvalds. + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + */ + +/* + * 09.11.91 - made the first rudimentary functions + * + * 10.11.91 - updated, does checking, no repairs yet. + * Sent out to the mailing-list for testing. + * + * 14.11.91 - Testing seems to have gone well. Added some + * correction-code, and changed some functions. + * + * 15.11.91 - More correction code. Hopefully it notices most + * cases now, and tries to do something about them. + * + * 16.11.91 - More corrections (thanks to Mika Jalava). Most + * things seem to work now. Yeah, sure. + * + * + * 19.04.92 - Had to start over again from this old version, as a + * kernel bug ate my enhanced fsck in february. + * + * 28.02.93 - added support for different directory entry sizes.. + * + * Sat Mar 6 18:59:42 1993, faith@cs.unc.edu: Output namelen with + * super-block information + * + * Sat Oct 9 11:17:11 1993, faith@cs.unc.edu: make exit status conform + * to that required by fsutil + * + * Mon Jan 3 11:06:52 1994 - Dr. Wettstein (greg%wind.uucp@plains.nodak.edu) + * Added support for file system valid flag. Also + * added program_version variable and output of + * program name and version number when program + * is executed. + * + * 30.10.94 - added support for v2 filesystem + * (Andreas Schwab, schwab@issan.informatik.uni-dortmund.de) + * + * 10.12.94 - added test to prevent checking of mounted fs adapted + * from Theodore Ts'o's (tytso@athena.mit.edu) e2fsck + * program. (Daniel Quinlan, quinlan@yggdrasil.com) + * + * 01.07.96 - Fixed the v2 fs stuff to use the right #defines and such + * for modern libcs (janl@math.uio.no, Nicolai Langfeldt) + * + * 02.07.96 - Added C bit fiddling routines from rmk@ecs.soton.ac.uk + * (Russell King). He made them for ARM. It would seem + * that the ARM is powerful enough to do this in C whereas + * i386 and m64k must use assembly to get it fast >:-) + * This should make minix fsck system-independent. + * (janl@math.uio.no, Nicolai Langfeldt) + * + * 04.11.96 - Added minor fixes from Andreas Schwab to avoid compiler + * warnings. Added mc68k bitops from + * Joerg Dorchain <dorchain@mpi-sb.mpg.de>. + * + * 06.11.96 - Added v2 code submitted by Joerg Dorchain, but written by + * Andreas Schwab. + * + * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@misiek.eu.org> + * - added Native Language Support + * + * + * I've had no time to add comments - hopefully the function names + * are comments enough. As with all file system checkers, this assumes + * the file system is quiescent - don't use it on a mounted device + * unless you can be sure nobody is writing to it (and remember that the + * kernel can write to it when it searches for files). + * + * Usage: fsck [-larvsm] device + * -l for a listing of all the filenames + * -a for automatic repairs (not implemented) + * -r for repairs (interactive) (not implemented) + * -v for verbose (tells how many files) + * -s for super-block info + * -m for minix-like "mode not cleared" warnings + * -f force filesystem check even if filesystem marked as valid + * + * The device may be a block device or a image of one, but this isn't + * enforced (but it's not much fun on a character device :-). + */ + +#include "busybox.h" +#include <mntent.h> + +/* + * This is the original minix inode layout on disk. + * Note the 8-bit gid and atime and ctime. + */ +struct minix_inode { + uint16_t i_mode; + uint16_t i_uid; + uint32_t i_size; + uint32_t i_time; + uint8_t i_gid; + uint8_t i_nlinks; + uint16_t i_zone[9]; +}; + +/* + * The new minix inode has all the time entries, as well as + * long block numbers and a third indirect block (7+1+1+1 + * instead of 7+1+1). Also, some previously 8-bit values are + * now 16-bit. The inode is now 64 bytes instead of 32. + */ +struct minix2_inode { + uint16_t i_mode; + uint16_t i_nlinks; + uint16_t i_uid; + uint16_t i_gid; + uint32_t i_size; + uint32_t i_atime; + uint32_t i_mtime; + uint32_t i_ctime; + uint32_t i_zone[10]; +}; + +enum { + MINIX_ROOT_INO = 1, + MINIX_LINK_MAX = 250, + MINIX2_LINK_MAX = 65530, + + MINIX_I_MAP_SLOTS = 8, + MINIX_Z_MAP_SLOTS = 64, + MINIX_SUPER_MAGIC = 0x137F, /* original minix fs */ + MINIX_SUPER_MAGIC2 = 0x138F, /* minix fs, 30 char names */ + MINIX2_SUPER_MAGIC = 0x2468, /* minix V2 fs */ + MINIX2_SUPER_MAGIC2 = 0x2478, /* minix V2 fs, 30 char names */ + MINIX_VALID_FS = 0x0001, /* Clean fs. */ + MINIX_ERROR_FS = 0x0002, /* fs has errors. */ + + MINIX_INODES_PER_BLOCK = ((BLOCK_SIZE)/(sizeof (struct minix_inode))), + MINIX2_INODES_PER_BLOCK = ((BLOCK_SIZE)/(sizeof (struct minix2_inode))), + + MINIX_V1 = 0x0001, /* original minix fs */ + MINIX_V2 = 0x0002 /* minix V2 fs */ +}; + +#define INODE_VERSION(inode) inode->i_sb->u.minix_sb.s_version + +/* + * minix super-block data on disk + */ +struct minix_super_block { + uint16_t s_ninodes; + uint16_t s_nzones; + uint16_t s_imap_blocks; + uint16_t s_zmap_blocks; + uint16_t s_firstdatazone; + uint16_t s_log_zone_size; + uint32_t s_max_size; + uint16_t s_magic; + uint16_t s_state; + uint32_t s_zones; +}; + +struct minix_dir_entry { + uint16_t inode; + char name[0]; +}; + + +#define NAME_MAX 255 /* # chars in a file name */ + +#define MINIX_INODES_PER_BLOCK ((BLOCK_SIZE)/(sizeof (struct minix_inode))) + +#ifndef BLKGETSIZE +#define BLKGETSIZE _IO(0x12,96) /* return device size */ +#endif + +#ifndef __linux__ +#define volatile +#endif + +enum { ROOT_INO = 1 }; + +#define UPPER(size,n) ((size+((n)-1))/(n)) +#define INODE_SIZE (sizeof(struct minix_inode)) +#ifdef CONFIG_FEATURE_MINIX2 +#define INODE_SIZE2 (sizeof(struct minix2_inode)) +#define INODE_BLOCKS UPPER(INODES, (version2 ? MINIX2_INODES_PER_BLOCK \ + : MINIX_INODES_PER_BLOCK)) +#else +#define INODE_BLOCKS UPPER(INODES, (MINIX_INODES_PER_BLOCK)) +#endif +#define INODE_BUFFER_SIZE (INODE_BLOCKS * BLOCK_SIZE) + +#define BITS_PER_BLOCK (BLOCK_SIZE<<3) + +static char *program_version = "1.2 - 11/11/96"; +static char *device_name; +static int IN; +static int repair, automatic, verbose, list, show, warn_mode, force; +static int directory, regular, blockdev, chardev, links, symlinks, total; + +static int changed; /* flags if the filesystem has been changed */ +static int errors_uncorrected; /* flag if some error was not corrected */ +static int dirsize = 16; +static int namelen = 14; +static struct termios termios; +static int termios_set; + +static char *inode_buffer; +#define Inode (((struct minix_inode *) inode_buffer)-1) +#define Inode2 (((struct minix2_inode *) inode_buffer)-1) +static char super_block_buffer[BLOCK_SIZE]; + +#define Super (*(struct minix_super_block *)super_block_buffer) +#define INODES ((unsigned long)Super.s_ninodes) +#ifdef CONFIG_FEATURE_MINIX2 +static int version2; +#define ZONES ((unsigned long)(version2 ? Super.s_zones : Super.s_nzones)) +#else +#define ZONES ((unsigned long)(Super.s_nzones)) +#endif +#define IMAPS ((unsigned long)Super.s_imap_blocks) +#define ZMAPS ((unsigned long)Super.s_zmap_blocks) +#define FIRSTZONE ((unsigned long)Super.s_firstdatazone) +#define ZONESIZE ((unsigned long)Super.s_log_zone_size) +#define MAXSIZE ((unsigned long)Super.s_max_size) +#define MAGIC (Super.s_magic) +#define NORM_FIRSTZONE (2+IMAPS+ZMAPS+INODE_BLOCKS) + +static char *inode_map; +static char *zone_map; + +static unsigned char *inode_count; +static unsigned char *zone_count; + +static void recursive_check(unsigned int ino); +#ifdef CONFIG_FEATURE_MINIX2 +static void recursive_check2(unsigned int ino); +#endif + +static int bit(char *a, unsigned int i) +{ + return (a[i >> 3] & (1<<(i & 7))) != 0; +} +#define inode_in_use(x) (bit(inode_map,(x))) +#define zone_in_use(x) (bit(zone_map,(x)-FIRSTZONE+1)) + +#define mark_inode(x) (setbit(inode_map,(x)),changed=1) +#define unmark_inode(x) (clrbit(inode_map,(x)),changed=1) + +#define mark_zone(x) (setbit(zone_map,(x)-FIRSTZONE+1),changed=1) +#define unmark_zone(x) (clrbit(zone_map,(x)-FIRSTZONE+1),changed=1) + +static void leave(int) ATTRIBUTE_NORETURN; +static void leave(int status) +{ + if (termios_set) + tcsetattr(0, TCSANOW, &termios); + exit(status); +} + +static void die(const char *str) +{ + bb_error_msg("%s", str); + leave(8); +} + +/* File-name data */ +enum { MAX_DEPTH = 32 }; +static int name_depth; +static char *current_name; +static char *name_component[MAX_DEPTH+1]; + +/* Wed Feb 9 15:17:06 MST 2000 */ +/* dynamically allocate name_list (instead of making it static) */ +static void alloc_current_name(void) +{ + current_name = xmalloc(MAX_DEPTH * (BUFSIZ + 1)); + current_name[0] = '/'; + current_name[1] = '\0'; + name_component[0] = ¤t_name[0]; +} + +#ifdef CONFIG_FEATURE_CLEAN_UP +/* execute this atexit() to deallocate name_list[] */ +/* piptigger was here */ +static void free_current_name(void) +{ + free(current_name); +} +#endif + +static void push_filename(const char *name) +{ + // /dir/dir/dir/file + // ^ ^ ^ + // [0] [1] [2] <-name_component[i] + if (name_depth < MAX_DEPTH) { + int len; + char *p = name_component[name_depth]; + *p++ = '/'; + len = sprintf(p, "%.*s", namelen, name); + name_component[name_depth + 1] = p + len; + } + name_depth++; +} + +static void pop_filename(void) { + name_depth--; + if (name_depth < MAX_DEPTH) { + *name_component[name_depth] = '\0'; + if (!name_depth) { + current_name[0] = '/'; + current_name[1] = '\0'; + } + } +} + +static int ask(const char *string, int def) +{ + int c; + + if (!repair) { + puts(""); + errors_uncorrected = 1; + return 0; + } + if (automatic) { + puts(""); + if (!def) + errors_uncorrected = 1; + return def; + } + printf(def ? "%s (y/n)? " : "%s (n/y)? ", string); + for (;;) { + fflush(stdout); + if ((c = getchar()) == EOF) { + if (!def) + errors_uncorrected = 1; + return def; + } + c = toupper(c); + if (c == 'Y') { + def = 1; + break; + } else if (c == 'N') { + def = 0; + break; + } else if (c == ' ' || c == '\n') + break; + } + if (def) + printf("y\n"); + else { + printf("n\n"); + errors_uncorrected = 1; + } + return def; +} + +/* + * Make certain that we aren't checking a filesystem that is on a + * mounted partition. Code adapted from e2fsck, Copyright (C) 1993, + * 1994 Theodore Ts'o. Also licensed under GPL. + */ +static void check_mount(void) +{ + FILE *f; + struct mntent *mnt; + int cont; + int fd; + + if ((f = setmntent(MOUNTED, "r")) == NULL) + return; + while ((mnt = getmntent(f)) != NULL) + if (strcmp(device_name, mnt->mnt_fsname) == 0) + break; + endmntent(f); + if (!mnt) + return; + + /* + * If the root is mounted read-only, then /etc/mtab is + * probably not correct; so we won't issue a warning based on + * it. + */ + fd = open(MOUNTED, O_RDWR); + if (fd < 0 && errno == EROFS) + return; + else + close(fd); + + printf("%s is mounted. ", device_name); + cont = 0; + if (isatty(0) && isatty(1)) + cont = ask("Do you really want to continue", 0); + if (!cont) { + printf("Check aborted\n"); + exit(0); + } + return; +} + +/* + * check_zone_nr checks to see that *nr is a valid zone nr. If it + * isn't, it will possibly be repaired. Check_zone_nr sets *corrected + * if an error was corrected, and returns the zone (0 for no zone + * or a bad zone-number). + */ +static int check_zone_nr2(uint32_t *nr, int *corrected) +{ + const char *msg; + if (!*nr) + return 0; + if (*nr < FIRSTZONE) + msg = "< FIRSTZONE"; + else if (*nr >= ZONES) + msg = ">= ZONES"; + else + return *nr; + printf("Zone nr %s in file '%s'. ", msg, current_name); + if (ask("Remove block", 1)) { + *nr = 0; + *corrected = 1; + } + return 0; +} + +static int check_zone_nr(uint16_t *nr, int *corrected) +{ + uint32_t nr32 = *nr; + int r = check_zone_nr2(&nr32, corrected); + *nr = (uint16_t)nr32; + return r; +} + +/* + * read-block reads block nr into the buffer at addr. + */ +static void read_block(unsigned int nr, char *addr) +{ + if (!nr) { + memset(addr, 0, BLOCK_SIZE); + return; + } + if (BLOCK_SIZE * nr != lseek(IN, BLOCK_SIZE * nr, SEEK_SET)) { + printf("%s: cannot seek to block in file '%s'\n", + bb_msg_read_error, current_name); + errors_uncorrected = 1; + memset(addr, 0, BLOCK_SIZE); + } else if (BLOCK_SIZE != read(IN, addr, BLOCK_SIZE)) { + printf("%s: bad block in file '%s'\n", + bb_msg_read_error, current_name); + errors_uncorrected = 1; + memset(addr, 0, BLOCK_SIZE); + } +} + +/* + * write_block writes block nr to disk. + */ +static void write_block(unsigned int nr, char *addr) +{ + if (!nr) + return; + if (nr < FIRSTZONE || nr >= ZONES) { + printf("Internal error: trying to write bad block\n" + "Write request ignored\n"); + errors_uncorrected = 1; + return; + } + if (BLOCK_SIZE * nr != lseek(IN, BLOCK_SIZE * nr, SEEK_SET)) + die("Seek failed in write_block"); + if (BLOCK_SIZE != write(IN, addr, BLOCK_SIZE)) { + printf("%s: bad block in file '%s'\n", + bb_msg_write_error, current_name); + errors_uncorrected = 1; + } +} + +/* + * map_block calculates the absolute block nr of a block in a file. + * It sets 'changed' if the inode has needed changing, and re-writes + * any indirect blocks with errors. + */ +static int map_block(struct minix_inode *inode, unsigned int blknr) +{ + uint16_t ind[BLOCK_SIZE >> 1]; + uint16_t dind[BLOCK_SIZE >> 1]; + int blk_chg, block, result; + + if (blknr < 7) + return check_zone_nr(inode->i_zone + blknr, &changed); + blknr -= 7; + if (blknr < 512) { + block = check_zone_nr(inode->i_zone + 7, &changed); + read_block(block, (char *) ind); + blk_chg = 0; + result = check_zone_nr(blknr + ind, &blk_chg); + if (blk_chg) + write_block(block, (char *) ind); + return result; + } + blknr -= 512; + block = check_zone_nr(inode->i_zone + 8, &changed); + read_block(block, (char *) dind); + blk_chg = 0; + result = check_zone_nr(dind + (blknr / 512), &blk_chg); + if (blk_chg) + write_block(block, (char *) dind); + block = result; + read_block(block, (char *) ind); + blk_chg = 0; + result = check_zone_nr(ind + (blknr % 512), &blk_chg); + if (blk_chg) + write_block(block, (char *) ind); + return result; +} + +#ifdef CONFIG_FEATURE_MINIX2 +static int map_block2(struct minix2_inode *inode, unsigned int blknr) +{ + uint32_t ind[BLOCK_SIZE >> 2]; + uint32_t dind[BLOCK_SIZE >> 2]; + uint32_t tind[BLOCK_SIZE >> 2]; + int blk_chg, block, result; + + if (blknr < 7) + return check_zone_nr2(inode->i_zone + blknr, &changed); + blknr -= 7; + if (blknr < 256) { + block = check_zone_nr2(inode->i_zone + 7, &changed); + read_block(block, (char *) ind); + blk_chg = 0; + result = check_zone_nr2(blknr + ind, &blk_chg); + if (blk_chg) + write_block(block, (char *) ind); + return result; + } + blknr -= 256; + if (blknr >= 256 * 256) { + block = check_zone_nr2(inode->i_zone + 8, &changed); + read_block(block, (char *) dind); + blk_chg = 0; + result = check_zone_nr2(dind + blknr / 256, &blk_chg); + if (blk_chg) + write_block(block, (char *) dind); + block = result; + read_block(block, (char *) ind); + blk_chg = 0; + result = check_zone_nr2(ind + blknr % 256, &blk_chg); + if (blk_chg) + write_block(block, (char *) ind); + return result; + } + blknr -= 256 * 256; + block = check_zone_nr2(inode->i_zone + 9, &changed); + read_block(block, (char *) tind); + blk_chg = 0; + result = check_zone_nr2(tind + blknr / (256 * 256), &blk_chg); + if (blk_chg) + write_block(block, (char *) tind); + block = result; + read_block(block, (char *) dind); + blk_chg = 0; + result = check_zone_nr2(dind + (blknr / 256) % 256, &blk_chg); + if (blk_chg) + write_block(block, (char *) dind); + block = result; + read_block(block, (char *) ind); + blk_chg = 0; + result = check_zone_nr2(ind + blknr % 256, &blk_chg); + if (blk_chg) + write_block(block, (char *) ind); + return result; +} +#endif + +static void write_super_block(void) +{ + /* + * Set the state of the filesystem based on whether or not there + * are uncorrected errors. The filesystem valid flag is + * unconditionally set if we get this far. + */ + Super.s_state |= MINIX_VALID_FS; + if (errors_uncorrected) + Super.s_state |= MINIX_ERROR_FS; + else + Super.s_state &= ~MINIX_ERROR_FS; + + if (BLOCK_SIZE != lseek(IN, BLOCK_SIZE, SEEK_SET)) + die("Seek failed in write_super_block"); + if (BLOCK_SIZE != write(IN, super_block_buffer, BLOCK_SIZE)) + die("Unable to write super-block"); +} + +static void write_tables(void) +{ + write_super_block(); + + if (IMAPS * BLOCK_SIZE != write(IN, inode_map, IMAPS * BLOCK_SIZE)) + die("Unable to write inode map"); + if (ZMAPS * BLOCK_SIZE != write(IN, zone_map, ZMAPS * BLOCK_SIZE)) + die("Unable to write zone map"); + if (INODE_BUFFER_SIZE != write(IN, inode_buffer, INODE_BUFFER_SIZE)) + die("Unable to write inodes"); +} + +static void get_dirsize(void) +{ + int block; + char blk[BLOCK_SIZE]; + int size; + +#ifdef CONFIG_FEATURE_MINIX2 + if (version2) + block = Inode2[ROOT_INO].i_zone[0]; + else +#endif + block = Inode[ROOT_INO].i_zone[0]; + read_block(block, blk); + for (size = 16; size < BLOCK_SIZE; size <<= 1) { + if (strcmp(blk + size + 2, "..") == 0) { + dirsize = size; + namelen = size - 2; + return; + } + } + /* use defaults */ +} + +static void read_superblock(void) +{ + if (BLOCK_SIZE != lseek(IN, BLOCK_SIZE, SEEK_SET)) + die("Seek failed"); + if (BLOCK_SIZE != read(IN, super_block_buffer, BLOCK_SIZE)) + die("Unable to read super block"); + /* already initialized to: + namelen = 14; + dirsize = 16; + version2 = 0; + */ + if (MAGIC == MINIX_SUPER_MAGIC) { + } else if (MAGIC == MINIX_SUPER_MAGIC2) { + namelen = 30; + dirsize = 32; +#ifdef CONFIG_FEATURE_MINIX2 + } else if (MAGIC == MINIX2_SUPER_MAGIC) { + version2 = 1; + } else if (MAGIC == MINIX2_SUPER_MAGIC2) { + namelen = 30; + dirsize = 32; + version2 = 1; +#endif + } else + die("Bad magic number in super-block"); + if (ZONESIZE != 0 || BLOCK_SIZE != 1024) + die("Only 1k blocks/zones supported"); + if (IMAPS * BLOCK_SIZE * 8 < INODES + 1) + die("Bad s_imap_blocks field in super-block"); + if (ZMAPS * BLOCK_SIZE * 8 < ZONES - FIRSTZONE + 1) + die("Bad s_zmap_blocks field in super-block"); +} + +static void read_tables(void) +{ + inode_map = xzalloc(IMAPS * BLOCK_SIZE); + zone_map = xzalloc(ZMAPS * BLOCK_SIZE); + inode_buffer = xmalloc(INODE_BUFFER_SIZE); + inode_count = xmalloc(INODES + 1); + zone_count = xmalloc(ZONES); + if (IMAPS * BLOCK_SIZE != read(IN, inode_map, IMAPS * BLOCK_SIZE)) + die("Unable to read inode map"); + if (ZMAPS * BLOCK_SIZE != read(IN, zone_map, ZMAPS * BLOCK_SIZE)) + die("Unable to read zone map"); + if (INODE_BUFFER_SIZE != read(IN, inode_buffer, INODE_BUFFER_SIZE)) + die("Unable to read inodes"); + if (NORM_FIRSTZONE != FIRSTZONE) { + printf("Warning: Firstzone!=Norm_firstzone\n"); + errors_uncorrected = 1; + } + get_dirsize(); + if (show) { + printf("%ld inodes\n" + "%ld blocks\n" + "Firstdatazone=%ld (%ld)\n" + "Zonesize=%d\n" + "Maxsize=%ld\n" + "Filesystem state=%d\n" + "namelen=%d\n\n", + INODES, + ZONES, + FIRSTZONE, NORM_FIRSTZONE, + BLOCK_SIZE << ZONESIZE, + MAXSIZE, + Super.s_state, + namelen); + } +} + +static struct minix_inode *get_inode(unsigned int nr) +{ + struct minix_inode *inode; + + if (!nr || nr > INODES) + return NULL; + total++; + inode = Inode + nr; + if (!inode_count[nr]) { + if (!inode_in_use(nr)) { + printf("Inode %d is marked as 'unused', but it is used " + "for file '%s'\n", nr, current_name); + if (repair) { + if (ask("Mark as 'in use'", 1)) + mark_inode(nr); + } else { + errors_uncorrected = 1; + } + } + if (S_ISDIR(inode->i_mode)) + directory++; + else if (S_ISREG(inode->i_mode)) + regular++; + else if (S_ISCHR(inode->i_mode)) + chardev++; + else if (S_ISBLK(inode->i_mode)) + blockdev++; + else if (S_ISLNK(inode->i_mode)) + symlinks++; + else if (S_ISSOCK(inode->i_mode)); + else if (S_ISFIFO(inode->i_mode)); + else { + printf("%s has mode %05o\n", current_name, inode->i_mode); + } + + } else + links++; + if (!++inode_count[nr]) { + printf("Warning: inode count too big\n"); + inode_count[nr]--; + errors_uncorrected = 1; + } + return inode; +} + +#ifdef CONFIG_FEATURE_MINIX2 +static struct minix2_inode *get_inode2(unsigned int nr) +{ + struct minix2_inode *inode; + + if (!nr || nr > INODES) + return NULL; + total++; + inode = Inode2 + nr; + if (!inode_count[nr]) { + if (!inode_in_use(nr)) { + printf("Inode %d is marked as 'unused', but it is used " + "for file '%s'\n", nr, current_name); + if (repair) { + if (ask("Mark as 'in use'", 1)) + mark_inode(nr); + else + errors_uncorrected = 1; + } + } + if (S_ISDIR(inode->i_mode)) + directory++; + else if (S_ISREG(inode->i_mode)) + regular++; + else if (S_ISCHR(inode->i_mode)) + chardev++; + else if (S_ISBLK(inode->i_mode)) + blockdev++; + else if (S_ISLNK(inode->i_mode)) + symlinks++; + else if (S_ISSOCK(inode->i_mode)); + else if (S_ISFIFO(inode->i_mode)); + else { + printf("%s has mode %05o\n", current_name, inode->i_mode); + } + } else + links++; + if (!++inode_count[nr]) { + printf("Warning: inode count too big\n"); + inode_count[nr]--; + errors_uncorrected = 1; + } + return inode; +} +#endif + +static void check_root(void) +{ + struct minix_inode *inode = Inode + ROOT_INO; + + if (!inode || !S_ISDIR(inode->i_mode)) + die("Root inode isn't a directory"); +} + +#ifdef CONFIG_FEATURE_MINIX2 +static void check_root2(void) +{ + struct minix2_inode *inode = Inode2 + ROOT_INO; + + if (!inode || !S_ISDIR(inode->i_mode)) + die("Root inode isn't a directory"); +} +#endif + +static int add_zone(uint16_t *znr, int *corrected) +{ + int result; + int block; + + result = 0; + block = check_zone_nr(znr, corrected); + if (!block) + return 0; + if (zone_count[block]) { + printf("Already used block is reused in file '%s'. ", + current_name); + if (ask("Clear", 1)) { + *znr = 0; + block = 0; + *corrected = 1; + return 0; + } + } + if (!zone_in_use(block)) { + printf("Block %d in file '%s' is marked as 'unused'. ", + block, current_name); + if (ask("Correct", 1)) + mark_zone(block); + } + if (!++zone_count[block]) + zone_count[block]--; + return block; +} + +#ifdef CONFIG_FEATURE_MINIX2 +static int add_zone2(uint32_t *znr, int *corrected) +{ + int result; + int block; + + result = 0; + block = check_zone_nr2(znr, corrected); + if (!block) + return 0; + if (zone_count[block]) { + printf("Already used block is reused in file '%s'. ", + current_name); + if (ask("Clear", 1)) { + *znr = 0; + block = 0; + *corrected = 1; + return 0; + } + } + if (!zone_in_use(block)) { + printf("Block %d in file '%s' is marked as 'unused'. ", + block, current_name); + if (ask("Correct", 1)) + mark_zone(block); + } + if (!++zone_count[block]) + zone_count[block]--; + return block; +} +#endif + +static void add_zone_ind(uint16_t *znr, int *corrected) +{ + static char blk[BLOCK_SIZE]; + int i, chg_blk = 0; + int block; + + block = add_zone(znr, corrected); + if (!block) + return; + read_block(block, blk); + for (i = 0; i < (BLOCK_SIZE >> 1); i++) + add_zone(i + (uint16_t *) blk, &chg_blk); + if (chg_blk) + write_block(block, blk); +} + +#ifdef CONFIG_FEATURE_MINIX2 +static void add_zone_ind2(uint32_t *znr, int *corrected) +{ + static char blk[BLOCK_SIZE]; + int i, chg_blk = 0; + int block; + + block = add_zone2(znr, corrected); + if (!block) + return; + read_block(block, blk); + for (i = 0; i < BLOCK_SIZE >> 2; i++) + add_zone2(i + (uint32_t *) blk, &chg_blk); + if (chg_blk) + write_block(block, blk); +} +#endif + +static void add_zone_dind(uint16_t *znr, int *corrected) +{ + static char blk[BLOCK_SIZE]; + int i, blk_chg = 0; + int block; + + block = add_zone(znr, corrected); + if (!block) + return; + read_block(block, blk); + for (i = 0; i < (BLOCK_SIZE >> 1); i++) + add_zone_ind(i + (uint16_t *) blk, &blk_chg); + if (blk_chg) + write_block(block, blk); +} + +#ifdef CONFIG_FEATURE_MINIX2 +static void add_zone_dind2(uint32_t *znr, int *corrected) +{ + static char blk[BLOCK_SIZE]; + int i, blk_chg = 0; + int block; + + block = add_zone2(znr, corrected); + if (!block) + return; + read_block(block, blk); + for (i = 0; i < BLOCK_SIZE >> 2; i++) + add_zone_ind2(i + (uint32_t *) blk, &blk_chg); + if (blk_chg) + write_block(block, blk); +} + +static void add_zone_tind2(uint32_t *znr, int *corrected) +{ + static char blk[BLOCK_SIZE]; + int i, blk_chg = 0; + int block; + + block = add_zone2(znr, corrected); + if (!block) + return; + read_block(block, blk); + for (i = 0; i < BLOCK_SIZE >> 2; i++) + add_zone_dind2(i + (uint32_t *) blk, &blk_chg); + if (blk_chg) + write_block(block, blk); +} +#endif + +static void check_zones(unsigned int i) +{ + struct minix_inode *inode; + + if (!i || i > INODES) + return; + if (inode_count[i] > 1) /* have we counted this file already? */ + return; + inode = Inode + i; + if (!S_ISDIR(inode->i_mode) && !S_ISREG(inode->i_mode) && + !S_ISLNK(inode->i_mode)) return; + for (i = 0; i < 7; i++) + add_zone(i + inode->i_zone, &changed); + add_zone_ind(7 + inode->i_zone, &changed); + add_zone_dind(8 + inode->i_zone, &changed); +} + +#ifdef CONFIG_FEATURE_MINIX2 +static void check_zones2(unsigned int i) +{ + struct minix2_inode *inode; + + if (!i || i > INODES) + return; + if (inode_count[i] > 1) /* have we counted this file already? */ + return; + inode = Inode2 + i; + if (!S_ISDIR(inode->i_mode) && !S_ISREG(inode->i_mode) + && !S_ISLNK(inode->i_mode)) + return; + for (i = 0; i < 7; i++) + add_zone2(i + inode->i_zone, &changed); + add_zone_ind2(7 + inode->i_zone, &changed); + add_zone_dind2(8 + inode->i_zone, &changed); + add_zone_tind2(9 + inode->i_zone, &changed); +} +#endif + +static void check_file(struct minix_inode *dir, unsigned int offset) +{ + static char blk[BLOCK_SIZE]; + struct minix_inode *inode; + int ino; + char *name; + int block; + + block = map_block(dir, offset / BLOCK_SIZE); + read_block(block, blk); + name = blk + (offset % BLOCK_SIZE) + 2; + ino = *(uint16_t *) (name - 2); + if (ino > INODES) { + printf("%s contains a bad inode number for file '%.*s'. ", + current_name, namelen, name); + if (ask("Remove", 1)) { + *(uint16_t *) (name - 2) = 0; + write_block(block, blk); + } + ino = 0; + } + push_filename(name); + inode = get_inode(ino); + pop_filename(); + if (!offset) { + if (!inode || strcmp(".", name)) { + printf("%s: bad directory: '.' isn't first\n", current_name); + errors_uncorrected = 1; + } else + return; + } + if (offset == dirsize) { + if (!inode || strcmp("..", name)) { + printf("%s: bad directory: '..' isn't second\n", current_name); + errors_uncorrected = 1; + } else + return; + } + if (!inode) + return; + push_filename(name); + if (list) { + if (verbose) + printf("%6d %07o %3d ", ino, inode->i_mode, inode->i_nlinks); + printf("%s%s\n", current_name, S_ISDIR(inode->i_mode) ? ":" : ""); + } + check_zones(ino); + if (inode && S_ISDIR(inode->i_mode)) + recursive_check(ino); + pop_filename(); + return; +} + +#ifdef CONFIG_FEATURE_MINIX2 +static void check_file2(struct minix2_inode *dir, unsigned int offset) +{ + static char blk[BLOCK_SIZE]; + struct minix2_inode *inode; + int ino; + char *name; + int block; + + block = map_block2(dir, offset / BLOCK_SIZE); + read_block(block, blk); + name = blk + (offset % BLOCK_SIZE) + 2; + ino = *(uint16_t *) (name - 2); + if (ino > INODES) { + printf("%s contains a bad inode number for file '%.*s'. ", + current_name, namelen, name); + if (ask("Remove", 1)) { + *(uint16_t *) (name - 2) = 0; + write_block(block, blk); + } + ino = 0; + } + push_filename(name); + inode = get_inode2(ino); + pop_filename(); + if (!offset) { + if (!inode || strcmp(".", name)) { + printf("%s: bad directory: '.' isn't first\n", current_name); + errors_uncorrected = 1; + } else + return; + } + if (offset == dirsize) { + if (!inode || strcmp("..", name)) { + printf("%s: bad directory: '..' isn't second\n", current_name); + errors_uncorrected = 1; + } else + return; + } + if (!inode) + return; + push_filename(name); + if (list) { + if (verbose) + printf("%6d %07o %3d ", ino, inode->i_mode, inode->i_nlinks); + printf("%s%s\n", current_name, S_ISDIR(inode->i_mode) ? ":" : ""); + } + check_zones2(ino); + if (inode && S_ISDIR(inode->i_mode)) + recursive_check2(ino); + pop_filename(); + return; +} +#endif + +static void recursive_check(unsigned int ino) +{ + struct minix_inode *dir; + unsigned int offset; + + dir = Inode + ino; + if (!S_ISDIR(dir->i_mode)) + die("Internal error"); + if (dir->i_size < 2 * dirsize) { + printf("%s: bad directory: size<32", current_name); + errors_uncorrected = 1; + } + for (offset = 0; offset < dir->i_size; offset += dirsize) + check_file(dir, offset); +} + +#ifdef CONFIG_FEATURE_MINIX2 +static void recursive_check2(unsigned int ino) +{ + struct minix2_inode *dir; + unsigned int offset; + + dir = Inode2 + ino; + if (!S_ISDIR(dir->i_mode)) + die("Internal error"); + if (dir->i_size < 2 * dirsize) { + printf("%s: bad directory: size<32", current_name); + errors_uncorrected = 1; + } + for (offset = 0; offset < dir->i_size; offset += dirsize) + check_file2(dir, offset); +} +#endif + +static int bad_zone(int i) +{ + char buffer[1024]; + + if (BLOCK_SIZE * i != lseek(IN, BLOCK_SIZE * i, SEEK_SET)) + die("Seek failed in bad_zone"); + return (BLOCK_SIZE != read(IN, buffer, BLOCK_SIZE)); +} + +static void check_counts(void) +{ + int i; + + for (i = 1; i <= INODES; i++) { + if (warn_mode && Inode[i].i_mode && !inode_in_use(i)) { + printf("Inode %d has non-zero mode. ", i); + if (ask("Clear", 1)) { + Inode[i].i_mode = 0; + changed = 1; + } + } + if (!inode_count[i]) { + if (!inode_in_use(i)) + continue; + printf("Unused inode %d is marked as 'used' in the bitmap. ", i); + if (ask("Clear", 1)) + unmark_inode(i); + continue; + } + if (!inode_in_use(i)) { + printf("Inode %d is used, but marked as 'unused' in the bitmap. ", i); + if (ask("Set", 1)) + mark_inode(i); + } + if (Inode[i].i_nlinks != inode_count[i]) { + printf("Inode %d (mode=%07o), i_nlinks=%d, counted=%d. ", + i, Inode[i].i_mode, Inode[i].i_nlinks, inode_count[i]); + if (ask("Set i_nlinks to count", 1)) { + Inode[i].i_nlinks = inode_count[i]; + changed = 1; + } + } + } + for (i = FIRSTZONE; i < ZONES; i++) { + if (zone_in_use(i) == zone_count[i]) + continue; + if (!zone_count[i]) { + if (bad_zone(i)) + continue; + printf("Zone %d is marked 'in use', but no file uses it. ", i); + if (ask("Unmark", 1)) + unmark_zone(i); + continue; + } + printf("Zone %d: %sin use, counted=%d\n", + i, zone_in_use(i) ? "" : "not ", zone_count[i]); + } +} + +#ifdef CONFIG_FEATURE_MINIX2 +static void check_counts2(void) +{ + int i; + + for (i = 1; i <= INODES; i++) { + if (warn_mode && Inode2[i].i_mode && !inode_in_use(i)) { + printf("Inode %d has non-zero mode. ", i); + if (ask("Clear", 1)) { + Inode2[i].i_mode = 0; + changed = 1; + } + } + if (!inode_count[i]) { + if (!inode_in_use(i)) + continue; + printf("Unused inode %d is marked as 'used' in the bitmap. ", i); + if (ask("Clear", 1)) + unmark_inode(i); + continue; + } + if (!inode_in_use(i)) { + printf("Inode %d is used, but marked as 'unused' in the bitmap. ", i); + if (ask("Set", 1)) + mark_inode(i); + } + if (Inode2[i].i_nlinks != inode_count[i]) { + printf("Inode %d (mode=%07o), i_nlinks=%d, counted=%d. ", + i, Inode2[i].i_mode, Inode2[i].i_nlinks, + inode_count[i]); + if (ask("Set i_nlinks to count", 1)) { + Inode2[i].i_nlinks = inode_count[i]; + changed = 1; + } + } + } + for (i = FIRSTZONE; i < ZONES; i++) { + if (zone_in_use(i) == zone_count[i]) + continue; + if (!zone_count[i]) { + if (bad_zone(i)) + continue; + printf("Zone %d is marked 'in use', but no file uses it. ", i); + if (ask("Unmark", 1)) + unmark_zone(i); + continue; + } + printf("Zone %d: %sin use, counted=%d\n", + i, zone_in_use(i) ? "" : "not ", zone_count[i]); + } +} +#endif + +static void check(void) +{ + memset(inode_count, 0, (INODES + 1) * sizeof(*inode_count)); + memset(zone_count, 0, ZONES * sizeof(*zone_count)); + check_zones(ROOT_INO); + recursive_check(ROOT_INO); + check_counts(); +} + +#ifdef CONFIG_FEATURE_MINIX2 +static void check2(void) +{ + memset(inode_count, 0, (INODES + 1) * sizeof(*inode_count)); + memset(zone_count, 0, ZONES * sizeof(*zone_count)); + check_zones2(ROOT_INO); + recursive_check2(ROOT_INO); + check_counts2(); +} +#endif + +int fsck_minix_main(int argc, char **argv) +{ + struct termios tmp; + int retcode = 0; + + alloc_current_name(); +#ifdef CONFIG_FEATURE_CLEAN_UP + /* Don't bother to free memory. Exit does + * that automagically, so we can save a few bytes */ + atexit(free_current_name); +#endif + + if (INODE_SIZE * MINIX_INODES_PER_BLOCK != BLOCK_SIZE) + die("Bad inode size"); +#ifdef CONFIG_FEATURE_MINIX2 + if (INODE_SIZE2 * MINIX2_INODES_PER_BLOCK != BLOCK_SIZE) + die("Bad v2 inode size"); +#endif + while (argc-- > 1) { + argv++; + if (argv[0][0] != '-') { + if (device_name) + bb_show_usage(); + else + device_name = argv[0]; + } else + while (*++argv[0]) + switch (argv[0][0]) { + case 'l': + list = 1; + break; + case 'a': + automatic = 1; + repair = 1; + break; + case 'r': + automatic = 0; + repair = 1; + break; + case 'v': + verbose = 1; + break; + case 's': + show = 1; + break; + case 'm': + warn_mode = 1; + break; + case 'f': + force = 1; + break; + default: + bb_show_usage(); + } + } + if (!device_name) + bb_show_usage(); + check_mount(); /* trying to check a mounted filesystem? */ + if (repair && !automatic) { + if (!isatty(0) || !isatty(1)) + die("Need terminal for interactive repairs"); + } + IN = open(device_name, repair ? O_RDWR : O_RDONLY); + if (IN < 0){ + printf("Unable to open device '%s'\n", device_name); + leave(8); + } + sync(); /* paranoia? */ + read_superblock(); + + /* + * Determine whether or not we should continue with the checking. + * This is based on the status of the filesystem valid and error + * flags and whether or not the -f switch was specified on the + * command line. + */ + printf("%s, %s\n", applet_name, program_version); + if (!(Super.s_state & MINIX_ERROR_FS) && + (Super.s_state & MINIX_VALID_FS) && !force) { + if (repair) + printf("%s is clean, check is skipped\n", device_name); + return retcode; + } else if (force) + printf("Forcing filesystem check on %s\n", device_name); + else if (repair) + printf("Filesystem on %s is dirty, needs checking\n", + device_name); + + read_tables(); + + if (repair && !automatic) { + tcgetattr(0, &termios); + tmp = termios; + tmp.c_lflag &= ~(ICANON | ECHO); + tcsetattr(0, TCSANOW, &tmp); + termios_set = 1; + } +#ifdef CONFIG_FEATURE_MINIX2 + if (version2) { + check_root2(); + check2(); + } else +#endif + { + check_root(); + check(); + } + if (verbose) { + int i, free_cnt; + + for (i = 1, free_cnt = 0; i <= INODES; i++) + if (!inode_in_use(i)) + free_cnt++; + printf("\n%6ld inodes used (%ld%%)\n", (INODES - free_cnt), + 100 * (INODES - free_cnt) / INODES); + for (i = FIRSTZONE, free_cnt = 0; i < ZONES; i++) + if (!zone_in_use(i)) + free_cnt++; + printf("%6ld zones used (%ld%%)\n\n" + "%6d regular files\n" + "%6d directories\n" + "%6d character device files\n" + "%6d block device files\n" + "%6d links\n" + "%6d symbolic links\n" + "------\n" + "%6d files\n", + (ZONES - free_cnt), 100 * (ZONES - free_cnt) / ZONES, + regular, directory, chardev, blockdev, + links - 2 * directory + 1, symlinks, + total - 2 * directory + 1); + } + if (changed) { + write_tables(); + printf("FILE SYSTEM HAS BEEN CHANGED\n"); + sync(); + } else if (repair) + write_super_block(); + + if (repair && !automatic) + tcsetattr(0, TCSANOW, &termios); + + if (changed) + retcode += 3; + if (errors_uncorrected) + retcode += 4; + return retcode; +} diff --git a/util-linux/getopt.c b/util-linux/getopt.c new file mode 100644 index 000000000..64f568aa9 --- /dev/null +++ b/util-linux/getopt.c @@ -0,0 +1,365 @@ +/* vi: set sw=4 ts=4: */ +/* + * getopt.c - Enhanced implementation of BSD getopt(1) + * Copyright (c) 1997, 1998, 1999, 2000 Frodo Looijaard <frodol@dds.nl> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* + * Version 1.0-b4: Tue Sep 23 1997. First public release. + * Version 1.0: Wed Nov 19 1997. + * Bumped up the version number to 1.0 + * Fixed minor typo (CSH instead of TCSH) + * Version 1.0.1: Tue Jun 3 1998 + * Fixed sizeof instead of strlen bug + * Bumped up the version number to 1.0.1 + * Version 1.0.2: Thu Jun 11 1998 (not present) + * Fixed gcc-2.8.1 warnings + * Fixed --version/-V option (not present) + * Version 1.0.5: Tue Jun 22 1999 + * Make -u option work (not present) + * Version 1.0.6: Tue Jun 27 2000 + * No important changes + * Version 1.1.0: Tue Jun 30 2000 + * Added NLS support (partly written by Arkadiusz Mi<B6>kiewicz + * <misiek@misiek.eu.org>) + * Ported to Busybox - Alfred M. Szmidt <ams@trillian.itslinux.org> + * Removed --version/-V and --help/-h in + * Removed parse_error(), using bb_error_msg() from Busybox instead + * Replaced our_malloc with xmalloc and our_realloc with xrealloc + * + */ + +#include "busybox.h" +#include <getopt.h> + +/* NON_OPT is the code that is returned when a non-option is found in '+' + mode */ +enum { + NON_OPT = 1, +/* LONG_OPT is the code that is returned when a long option is found. */ + LONG_OPT = 2 +}; + +/* The shells recognized. */ +typedef enum {BASH,TCSH} shell_t; + + +/* Some global variables that tells us how to parse. */ +static shell_t shell=BASH; /* The shell we generate output for. */ +static int quiet_errors; /* 0 is not quiet. */ +static int quiet_output; /* 0 is not quiet. */ +static int quote=1; /* 1 is do quote. */ +static int alternative; /* 0 is getopt_long, 1 is getopt_long_only */ + +/* Function prototypes */ +static const char *normalize(const char *arg); +static int generate_output(char * argv[],int argc,const char *optstr, + const struct option *longopts); +static void add_long_options(char *options); +static void add_longopt(const char *name,int has_arg); +static void set_shell(const char *new_shell); + + +/* + * This function 'normalizes' a single argument: it puts single quotes around + * it and escapes other special characters. If quote is false, it just + * returns its argument. + * Bash only needs special treatment for single quotes; tcsh also recognizes + * exclamation marks within single quotes, and nukes whitespace. + * This function returns a pointer to a buffer that is overwritten by + * each call. + */ +const char *normalize(const char *arg) +{ + static char *BUFFER=NULL; + const char *argptr=arg; + char *bufptr; + + free(BUFFER); + + if (!quote) { /* Just copy arg */ + BUFFER=xstrdup(arg); + return BUFFER; + } + + /* Each character in arg may take up to four characters in the result: + For a quote we need a closing quote, a backslash, a quote and an + opening quote! We need also the global opening and closing quote, + and one extra character for '\0'. */ + BUFFER=xmalloc(strlen(arg)*4+3); + + bufptr=BUFFER; + *bufptr++='\''; + + while (*argptr) { + if (*argptr == '\'') { + /* Quote: replace it with: '\'' */ + *bufptr++='\''; + *bufptr++='\\'; + *bufptr++='\''; + *bufptr++='\''; + } else if (shell==TCSH && *argptr=='!') { + /* Exclamation mark: replace it with: \! */ + *bufptr++='\''; + *bufptr++='\\'; + *bufptr++='!'; + *bufptr++='\''; + } else if (shell==TCSH && *argptr=='\n') { + /* Newline: replace it with: \n */ + *bufptr++='\\'; + *bufptr++='n'; + } else if (shell==TCSH && isspace(*argptr)) { + /* Non-newline whitespace: replace it with \<ws> */ + *bufptr++='\''; + *bufptr++='\\'; + *bufptr++=*argptr; + *bufptr++='\''; + } else + /* Just copy */ + *bufptr++=*argptr; + argptr++; + } + *bufptr++='\''; + *bufptr++='\0'; + return BUFFER; +} + +/* + * Generate the output. argv[0] is the program name (used for reporting errors). + * argv[1..] contains the options to be parsed. argc must be the number of + * elements in argv (ie. 1 if there are no options, only the program name), + * optstr must contain the short options, and longopts the long options. + * Other settings are found in global variables. + */ +int generate_output(char * argv[],int argc,const char *optstr, + const struct option *longopts) +{ + int exit_code = 0; /* We assume everything will be OK */ + int opt; + int longindex; + const char *charptr; + + if (quiet_errors) /* No error reporting from getopt(3) */ + opterr=0; + optind=0; /* Reset getopt(3) */ + + while ((opt = (alternative? + getopt_long_only(argc,argv,optstr,longopts,&longindex): + getopt_long(argc,argv,optstr,longopts,&longindex))) + != EOF) + if (opt == '?' || opt == ':' ) + exit_code = 1; + else if (!quiet_output) { + if (opt == LONG_OPT) { + printf(" --%s",longopts[longindex].name); + if (longopts[longindex].has_arg) + printf(" %s", + normalize(optarg?optarg:"")); + } else if (opt == NON_OPT) + printf(" %s",normalize(optarg)); + else { + printf(" -%c",opt); + charptr = strchr(optstr,opt); + if (charptr != NULL && *++charptr == ':') + printf(" %s", + normalize(optarg?optarg:"")); + } + } + + if (! quiet_output) { + printf(" --"); + while (optind < argc) + printf(" %s",normalize(argv[optind++])); + puts(""); + } + return exit_code; +} + +static struct option *long_options; +static int long_options_length; /* Length of array */ +static int long_options_nr; /* Nr of used elements in array */ +enum { LONG_OPTIONS_INCR = 10 }; +#define init_longopt() add_longopt(NULL,0) + +/* Register a long option. The contents of name is copied. */ +void add_longopt(const char *name, int has_arg) +{ + if (!name) { /* init */ + free(long_options); + long_options=NULL; + long_options_length=0; + long_options_nr=0; + } + + if (long_options_nr == long_options_length) { + long_options_length += LONG_OPTIONS_INCR; + long_options=xrealloc(long_options, + sizeof(struct option) * + long_options_length); + } + + long_options[long_options_nr].name=NULL; + long_options[long_options_nr].has_arg=0; + long_options[long_options_nr].flag=NULL; + long_options[long_options_nr].val=0; + + if (long_options_nr) { /* Not for init! */ + long_options[long_options_nr-1].has_arg=has_arg; + long_options[long_options_nr-1].flag=NULL; + long_options[long_options_nr-1].val=LONG_OPT; + long_options[long_options_nr-1].name=xstrdup(name); + } + long_options_nr++; +} + + +/* + * Register several long options. options is a string of long options, + * separated by commas or whitespace. + * This nukes options! + */ +void add_long_options(char *options) +{ + int arg_opt, tlen; + char *tokptr=strtok(options,", \t\n"); + while (tokptr) { + arg_opt=no_argument; + tlen=strlen(tokptr); + if (tlen > 0) { + if (tokptr[tlen-1] == ':') { + if (tlen > 1 && tokptr[tlen-2] == ':') { + tokptr[tlen-2]='\0'; + tlen -= 2; + arg_opt=optional_argument; + } else { + tokptr[tlen-1]='\0'; + tlen -= 1; + arg_opt=required_argument; + } + if (tlen == 0) + bb_error_msg("empty long option after -l or --long argument"); + } + add_longopt(tokptr,arg_opt); + } + tokptr=strtok(NULL,", \t\n"); + } +} + +void set_shell(const char *new_shell) +{ + if (!strcmp(new_shell,"bash")) + shell=BASH; + else if (!strcmp(new_shell,"tcsh")) + shell=TCSH; + else if (!strcmp(new_shell,"sh")) + shell=BASH; + else if (!strcmp(new_shell,"csh")) + shell=TCSH; + else + bb_error_msg("unknown shell after -s or --shell argument"); +} + + +/* Exit codes: + * 0) No errors, successful operation. + * 1) getopt(3) returned an error. + * 2) A problem with parameter parsing for getopt(1). + * 3) Internal error, out of memory + * 4) Returned for -T + */ + +static const struct option longopts[]= +{ + {"options",required_argument,NULL,'o'}, + {"longoptions",required_argument,NULL,'l'}, + {"quiet",no_argument,NULL,'q'}, + {"quiet-output",no_argument,NULL,'Q'}, + {"shell",required_argument,NULL,'s'}, + {"test",no_argument,NULL,'T'}, + {"unquoted",no_argument,NULL,'u'}, + {"alternative",no_argument,NULL,'a'}, + {"name",required_argument,NULL,'n'}, + {NULL,0,NULL,0} +}; + +/* Stop scanning as soon as a non-option argument is found! */ +static const char shortopts[]="+ao:l:n:qQs:Tu"; + + +int getopt_main(int argc, char *argv[]) +{ + const char *optstr = NULL; + char *name = NULL; + int opt; + int compatible=0; + + init_longopt(); + + if (getenv("GETOPT_COMPATIBLE")) + compatible=1; + + if (argc == 1) { + if (compatible) { + /* For some reason, the original getopt gave no error + when there were no arguments. */ + printf(" --\n"); + return 0; + } else + bb_error_msg_and_die("missing optstring argument"); + } + + if (argv[1][0] != '-' || compatible) { + char *s; + + quote=0; + s=xmalloc(strlen(argv[1])+1); + strcpy(s,argv[1]+strspn(argv[1],"-+")); + argv[1]=argv[0]; + return generate_output(argv+1,argc-1,s,long_options); + } + + while ((opt = getopt_long(argc,argv,shortopts,longopts,NULL)) != EOF) + switch (opt) { + case 'a': + alternative=1; + break; + case 'o': + optstr = optarg; + break; + case 'l': + add_long_options(optarg); + break; + case 'n': + name = optarg; + break; + case 'q': + quiet_errors=1; + break; + case 'Q': + quiet_output=1; + break; + case 's': + set_shell(optarg); + break; + case 'T': + return 4; + case 'u': + quote=0; + break; + default: + bb_show_usage(); + } + + if (!optstr) { + if (optind >= argc) + bb_error_msg_and_die("missing optstring argument"); + else optstr=argv[optind++]; + } + if (name) + argv[optind-1]=name; + else + argv[optind-1]=argv[0]; + return generate_output(argv+optind-1,argc-optind+1,optstr,long_options); +} diff --git a/util-linux/hexdump.c b/util-linux/hexdump.c new file mode 100644 index 000000000..5cb245feb --- /dev/null +++ b/util-linux/hexdump.c @@ -0,0 +1,101 @@ +/* vi: set sw=4 ts=4: */ +/* + * hexdump implementation for busybox + * Based on code from util-linux v 2.11l + * + * Copyright (c) 1989 + * The Regents of the University of California. All rights reserved. + * + * Licensed under GPLv2 or later, see file License in this tarball for details. + */ + +#include "busybox.h" +#include <getopt.h> +#include "dump.h" + +static void bb_dump_addfile(char *name) +{ + char *p; + FILE *fp; + char *buf; + + fp = xfopen(name, "r"); + + while ((buf = xmalloc_getline(fp)) != NULL) { + p = skip_whitespace(buf); + + if (*p && (*p != '#')) { + bb_dump_add(p); + } + free(buf); + } + fclose(fp); +} + +static const char * const add_strings[] = { + "\"%07.7_ax \" 16/1 \"%03o \" \"\\n\"", /* b */ + "\"%07.7_ax \" 16/1 \"%3_c \" \"\\n\"", /* c */ + "\"%07.7_ax \" 8/2 \" %05u \" \"\\n\"", /* d */ + "\"%07.7_ax \" 8/2 \" %06o \" \"\\n\"", /* o */ + "\"%07.7_ax \" 8/2 \" %04x \" \"\\n\"", /* x */ +}; + +static const char add_first[] = "\"%07.7_Ax\n\""; + +static const char hexdump_opts[] = "bcdoxCe:f:n:s:v"; + +static const struct suffix_mult suffixes[] = { + {"b", 512 }, + {"k", 1024 }, + {"m", 1024*1024 }, + {NULL, 0 } +}; + +int hexdump_main(int argc, char **argv) +{ + const char *p; + int ch; + + bb_dump_vflag = FIRST; + bb_dump_length = -1; + + while ((ch = getopt(argc, argv, hexdump_opts)) > 0) { + p = strchr(hexdump_opts, ch); + if (!p) + bb_show_usage(); + if ((p - hexdump_opts) < 5) { + bb_dump_add(add_first); + bb_dump_add(add_strings[(int)(p - hexdump_opts)]); + } else if (ch == 'C') { + bb_dump_add("\"%08.8_Ax\n\""); + bb_dump_add("\"%08.8_ax \" 8/1 \"%02x \" \" \" 8/1 \"%02x \" "); + bb_dump_add("\" |\" 16/1 \"%_p\" \"|\\n\""); + } else { + /* Save a little bit of space below by omitting the 'else's. */ + if (ch == 'e') { + bb_dump_add(optarg); + } /* else */ + if (ch == 'f') { + bb_dump_addfile(optarg); + } /* else */ + if (ch == 'n') { + bb_dump_length = xatoi_u(optarg); + } /* else */ + if (ch == 's') { + bb_dump_skip = xatoul_range_sfx(optarg, 0, LONG_MAX, suffixes); + } /* else */ + if (ch == 'v') { + bb_dump_vflag = ALL; + } + } + } + + if (!bb_dump_fshead) { + bb_dump_add(add_first); + bb_dump_add("\"%07.7_ax \" 8/2 \"%04x \" \"\\n\""); + } + + argv += optind; + + return bb_dump_dump(argv); +} diff --git a/util-linux/hwclock.c b/util-linux/hwclock.c new file mode 100644 index 000000000..8fcd8c99c --- /dev/null +++ b/util-linux/hwclock.c @@ -0,0 +1,213 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini hwclock implementation for busybox + * + * Copyright (C) 2002 Robert Griebl <griebl@gmx.de> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. +*/ + + +#include <sys/ioctl.h> +#include <sys/utsname.h> +#include <getopt.h> +#include "busybox.h" + +/* Copied from linux/rtc.h to eliminate the kernel dependency */ +struct linux_rtc_time { + int tm_sec; + int tm_min; + int tm_hour; + int tm_mday; + int tm_mon; + int tm_year; + int tm_wday; + int tm_yday; + int tm_isdst; +}; + +#define RTC_SET_TIME _IOW('p', 0x0a, struct linux_rtc_time) /* Set RTC time */ +#define RTC_RD_TIME _IOR('p', 0x09, struct linux_rtc_time) /* Read RTC time */ + +#if ENABLE_FEATURE_HWCLOCK_LONG_OPTIONS +# ifndef _GNU_SOURCE +# define _GNU_SOURCE +# endif +#endif + +static int xopen_rtc(int flags) +{ + int rtc; + rtc = open("/dev/rtc", flags); + if (rtc < 0) { + rtc = open("/dev/misc/rtc", flags); + if (rtc < 0) + bb_perror_msg_and_die("cannot access RTC"); + } + return rtc; +} + +static time_t read_rtc(int utc) +{ + struct tm tm; + char *oldtz = 0; + time_t t = 0; + int rtc = xopen_rtc(O_RDONLY); + + memset(&tm, 0, sizeof(struct tm)); + if (ioctl(rtc, RTC_RD_TIME, &tm) < 0 ) + bb_perror_msg_and_die("cannot read time from RTC"); + tm.tm_isdst = -1; /* not known */ + + close(rtc); + + if (utc) { + oldtz = getenv("TZ"); + setenv("TZ", "UTC 0", 1); + tzset(); + } + + t = mktime(&tm); + + if (utc) { + if (oldtz) + setenv("TZ", oldtz, 1); + else + unsetenv("TZ"); + tzset(); + } + return t; +} + +static void write_rtc(time_t t, int utc) +{ + struct tm tm; + int rtc = xopen_rtc(O_WRONLY); + + tm = *(utc ? gmtime(&t) : localtime(&t)); + tm.tm_isdst = 0; + + if (ioctl(rtc, RTC_SET_TIME, &tm) < 0) + bb_perror_msg_and_die("cannot set the RTC time"); + + close(rtc); +} + +static int show_clock(int utc) +{ + struct tm *ptm; + time_t t; + RESERVE_CONFIG_BUFFER(buffer, 64); + + t = read_rtc(utc); + ptm = localtime(&t); /* Sets 'tzname[]' */ + + safe_strncpy(buffer, ctime(&t), 64); + if (buffer[0]) + buffer[strlen(buffer) - 1] = 0; + + //printf("%s %.6f seconds %s\n", buffer, 0.0, utc ? "" : (ptm->tm_isdst ? tzname[1] : tzname[0])); + printf( "%s %.6f seconds\n", buffer, 0.0); + RELEASE_CONFIG_BUFFER(buffer); + + return 0; +} + +static int to_sys_clock(int utc) +{ + struct timeval tv = { 0, 0 }; + const struct timezone tz = { timezone/60 - 60*daylight, 0 }; + + tv.tv_sec = read_rtc(utc); + + if (settimeofday(&tv, &tz)) + bb_perror_msg_and_die("settimeofday() failed"); + + return 0; +} + +static int from_sys_clock(int utc) +{ + struct timeval tv = { 0, 0 }; + struct timezone tz = { 0, 0 }; + + if (gettimeofday(&tv, &tz)) + bb_perror_msg_and_die("gettimeofday() failed"); + + write_rtc(tv.tv_sec, utc); + return 0; +} + +#ifdef CONFIG_FEATURE_HWCLOCK_ADJTIME_FHS +# define ADJTIME_PATH "/var/lib/hwclock/adjtime" +#else +# define ADJTIME_PATH "/etc/adjtime" +#endif +static int check_utc(void) +{ + int utc = 0; + FILE *f = fopen(ADJTIME_PATH, "r"); + + if (f) { + RESERVE_CONFIG_BUFFER(buffer, 128); + + while (fgets(buffer, sizeof(buffer), f)) { + int len = strlen(buffer); + + while (len && isspace(buffer[len - 1])) + len--; + + buffer[len] = 0; + + if (strncmp(buffer, "UTC", 3) == 0 ) { + utc = 1; + break; + } + } + fclose(f); + RELEASE_CONFIG_BUFFER(buffer); + } + return utc; +} + +#define HWCLOCK_OPT_LOCALTIME 0x01 +#define HWCLOCK_OPT_UTC 0x02 +#define HWCLOCK_OPT_SHOW 0x04 +#define HWCLOCK_OPT_HCTOSYS 0x08 +#define HWCLOCK_OPT_SYSTOHC 0x10 + +int hwclock_main(int argc, char **argv ) +{ + unsigned opt; + int utc; + +#if ENABLE_FEATURE_HWCLOCK_LONG_OPTIONS + static const struct option hwclock_long_options[] = { + { "localtime", 0, 0, 'l' }, + { "utc", 0, 0, 'u' }, + { "show", 0, 0, 'r' }, + { "hctosys", 0, 0, 's' }, + { "systohc", 0, 0, 'w' }, + { 0, 0, 0, 0 } + }; + applet_long_options = hwclock_long_options; +#endif + opt_complementary = "?:r--ws:w--rs:s--wr:l--u:u--l"; + opt = getopt32(argc, argv, "lursw"); + + /* If -u or -l wasn't given check if we are using utc */ + if (opt & (HWCLOCK_OPT_UTC | HWCLOCK_OPT_LOCALTIME)) + utc = opt & HWCLOCK_OPT_UTC; + else + utc = check_utc(); + + if (opt & HWCLOCK_OPT_HCTOSYS) { + return to_sys_clock(utc); + } + else if (opt & HWCLOCK_OPT_SYSTOHC) { + return from_sys_clock(utc); + } else { + /* default HWCLOCK_OPT_SHOW */ + return show_clock(utc); + } +} diff --git a/util-linux/ipcrm.c b/util-linux/ipcrm.c new file mode 100644 index 000000000..507e58fe3 --- /dev/null +++ b/util-linux/ipcrm.c @@ -0,0 +1,217 @@ +/* vi: set sw=4 ts=4: */ +/* + * ipcrm.c - utility to allow removal of IPC objects and data structures. + * + * 01 Sept 2004 - Rodney Radford <rradford@mindspring.com> + * Adapted for busybox from util-linux-2.12a. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +/* X/OPEN tells us to use <sys/{types,ipc,sem}.h> for semctl() */ +/* X/OPEN tells us to use <sys/{types,ipc,msg}.h> for msgctl() */ +#include <sys/ipc.h> +#include <sys/shm.h> +#include <sys/msg.h> +#include <sys/sem.h> + +#if defined (__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED) +/* union semun is defined by including <sys/sem.h> */ +#else +/* according to X/OPEN we have to define it ourselves */ +union semun { + int val; + struct semid_ds *buf; + unsigned short int *array; + struct seminfo *__buf; +}; +#endif + +#ifndef CONFIG_IPCRM_DROP_LEGACY + +typedef enum type_id { + SHM, + SEM, + MSG +} type_id; + +static int remove_ids(type_id type, int argc, char **argv) +{ + unsigned long id; + int ret = 0; /* silence gcc */ + int nb_errors = 0; + union semun arg; + + arg.val = 0; + + while (argc) { + id = bb_strtoul(argv[0], NULL, 10); + if (errno || id > INT_MAX) { + bb_error_msg("invalid id: %s", argv[0]); + nb_errors++; + } else { + if (type == SEM) + ret = semctl(id, 0, IPC_RMID, arg); + else if (type == MSG) + ret = msgctl(id, IPC_RMID, NULL); + else if (type == SHM) + ret = shmctl(id, IPC_RMID, NULL); + + if (ret) { + bb_perror_msg("cannot remove id %s", argv[0]); + nb_errors++; + } + } + argc--; + argv++; + } + + return nb_errors; +} +#endif /* #ifndef CONFIG_IPCRM_DROP_LEGACY */ + + +int ipcrm_main(int argc, char **argv) +{ + int c; + int error = 0; + + /* if the command is executed without parameters, do nothing */ + if (argc == 1) + return 0; +#ifndef CONFIG_IPCRM_DROP_LEGACY + /* check to see if the command is being invoked in the old way if so + then run the old code. Valid commands are msg, shm, sem. */ + { + type_id what = 0; /* silence gcc */ + char w; + + w=argv[1][0]; + if ( ((w == 'm' && argv[1][1] == 's' && argv[1][2] == 'g') + || (argv[1][0] == 's' + && ((w=argv[1][1]) == 'h' || w == 'e') + && argv[1][2] == 'm') + ) && argv[1][3] == '\0' + ) { + + if (argc < 3) + bb_show_usage(); + + if (w == 'h') + what = SHM; + else if (w == 'm') + what = MSG; + else if (w == 'e') + what = SEM; + + if (remove_ids(what, argc-2, &argv[2])) + fflush_stdout_and_exit(1); + printf("resource(s) deleted\n"); + return 0; + } + } +#endif /* #ifndef CONFIG_IPCRM_DROP_LEGACY */ + + /* process new syntax to conform with SYSV ipcrm */ + while ((c = getopt(argc, argv, "q:m:s:Q:M:S:h?")) != -1) { + int result; + int id = 0; + int iskey = (isupper)(c); + + /* needed to delete semaphores */ + union semun arg; + + arg.val = 0; + + if ((c == '?') || (c == 'h')) { + bb_show_usage(); + } + + /* we don't need case information any more */ + c = tolower(c); + + /* make sure the option is in range: allowed are q, m, s */ + if (c != 'q' && c != 'm' && c != 's') { + bb_show_usage(); + } + + if (iskey) { + /* keys are in hex or decimal */ + key_t key = xstrtoul(optarg, 0); + + if (key == IPC_PRIVATE) { + error++; + bb_error_msg("illegal key (%s)", optarg); + continue; + } + + /* convert key to id */ + id = ((c == 'q') ? msgget(key, 0) : + (c == 'm') ? shmget(key, 0, 0) : semget(key, 0, 0)); + + if (id < 0) { + char *errmsg; + const char * const what = "key"; + + error++; + switch (errno) { + case EACCES: + errmsg = "permission denied for"; + break; + case EIDRM: + errmsg = "already removed"; + break; + case ENOENT: + errmsg = "invalid"; + break; + default: + errmsg = "unknown error in"; + break; + } + bb_error_msg("%s %s (%s)", errmsg, what, optarg); + continue; + } + } else { + /* ids are in decimal */ + id = xatoul(optarg); + } + + result = ((c == 'q') ? msgctl(id, IPC_RMID, NULL) : + (c == 'm') ? shmctl(id, IPC_RMID, NULL) : + semctl(id, 0, IPC_RMID, arg)); + + if (result) { + char *errmsg; + const char * const what = iskey ? "key" : "id"; + + error++; + switch (errno) { + case EACCES: + case EPERM: + errmsg = "permission denied for"; + break; + case EINVAL: + errmsg = "invalid"; + break; + case EIDRM: + errmsg = "already removed"; + break; + default: + errmsg = "unknown error in"; + break; + } + bb_error_msg("%s %s (%s)", errmsg, what, optarg); + continue; + } + } + + /* print usage if we still have some arguments left over */ + if (optind != argc) { + bb_show_usage(); + } + + /* exit value reflects the number of errors encountered */ + return error; +} diff --git a/util-linux/ipcs.c b/util-linux/ipcs.c new file mode 100644 index 000000000..b81d07d6d --- /dev/null +++ b/util-linux/ipcs.c @@ -0,0 +1,630 @@ +/* vi: set sw=4 ts=4: */ +/* + * ipcs.c -- provides information on allocated ipc resources. + * + * 01 Sept 2004 - Rodney Radford <rradford@mindspring.com> + * Adapted for busybox from util-linux-2.12a. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include <errno.h> +#include <time.h> +#include <pwd.h> +#include <grp.h> + +/* X/OPEN tells us to use <sys/{types,ipc,sem}.h> for semctl() */ +/* X/OPEN tells us to use <sys/{types,ipc,msg}.h> for msgctl() */ +/* X/OPEN tells us to use <sys/{types,ipc,shm}.h> for shmctl() */ +#include <sys/types.h> +#include <sys/ipc.h> +#include <sys/sem.h> +#include <sys/msg.h> +#include <sys/shm.h> + + + +/*-------------------------------------------------------------------*/ +/* SHM_DEST and SHM_LOCKED are defined in kernel headers, + but inside #ifdef __KERNEL__ ... #endif */ +#ifndef SHM_DEST +/* shm_mode upper byte flags */ +#define SHM_DEST 01000 /* segment will be destroyed on last detach */ +#define SHM_LOCKED 02000 /* segment will not be swapped */ +#endif + +/* For older kernels the same holds for the defines below */ +#ifndef MSG_STAT +#define MSG_STAT 11 +#define MSG_INFO 12 +#endif + +#ifndef SHM_STAT +#define SHM_STAT 13 +#define SHM_INFO 14 +struct shm_info { + int used_ids; + ulong shm_tot; /* total allocated shm */ + ulong shm_rss; /* total resident shm */ + ulong shm_swp; /* total swapped shm */ + ulong swap_attempts; + ulong swap_successes; +}; +#endif + +#ifndef SEM_STAT +#define SEM_STAT 18 +#define SEM_INFO 19 +#endif + +/* Some versions of libc only define IPC_INFO when __USE_GNU is defined. */ +#ifndef IPC_INFO +#define IPC_INFO 3 +#endif +/*-------------------------------------------------------------------*/ + +/* The last arg of semctl is a union semun, but where is it defined? + X/OPEN tells us to define it ourselves, but until recently + Linux include files would also define it. */ +#if defined (__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED) +/* union semun is defined by including <sys/sem.h> */ +#else +/* according to X/OPEN we have to define it ourselves */ +union semun { + int val; + struct semid_ds *buf; + unsigned short int *array; + struct seminfo *__buf; +}; +#endif + +/* X/OPEN (Jan 1987) does not define fields key, seq in struct ipc_perm; + libc 4/5 does not mention struct ipc_term at all, but includes + <linux/ipc.h>, which defines a struct ipc_perm with such fields. + glibc-1.09 has no support for sysv ipc. + glibc 2 uses __key, __seq */ +#if defined (__GNU_LIBRARY__) && __GNU_LIBRARY__ > 1 +#define KEY __key +#else +#define KEY key +#endif + +#define LIMITS 1 +#define STATUS 2 +#define CREATOR 3 +#define TIME 4 +#define PID 5 + +static char format; + +static void print_perms(int id, struct ipc_perm *ipcp) +{ + struct passwd *pw; + struct group *gr; + + printf("%-10d %-10o", id, ipcp->mode & 0777); + + if ((pw = getpwuid(ipcp->cuid))) + printf(" %-10s", pw->pw_name); + else + printf(" %-10d", ipcp->cuid); + if ((gr = getgrgid(ipcp->cgid))) + printf(" %-10s", gr->gr_name); + else + printf(" %-10d", ipcp->cgid); + + if ((pw = getpwuid(ipcp->uid))) + printf(" %-10s", pw->pw_name); + else + printf(" %-10d", ipcp->uid); + if ((gr = getgrgid(ipcp->gid))) + printf(" %-10s\n", gr->gr_name); + else + printf(" %-10d\n", ipcp->gid); +} + + +static void do_shm(void) +{ + int maxid, shmid, id; + struct shmid_ds shmseg; + struct shm_info shm_info; + struct shminfo shminfo; + struct ipc_perm *ipcp = &shmseg.shm_perm; + struct passwd *pw; + + maxid = shmctl(0, SHM_INFO, (struct shmid_ds *) (void *) &shm_info); + if (maxid < 0) { + printf("kernel not configured for %s\n", "shared memory"); + return; + } + + switch (format) { + case LIMITS: + printf("------ Shared Memory %s --------\n", "Limits"); + if ((shmctl(0, IPC_INFO, (struct shmid_ds *) (void *) &shminfo)) < 0) + return; + /* glibc 2.1.3 and all earlier libc's have ints as fields + of struct shminfo; glibc 2.1.91 has unsigned long; ach */ + printf("max number of segments = %lu\n" + "max seg size (kbytes) = %lu\n" + "max total shared memory (pages) = %lu\n" + "min seg size (bytes) = %lu\n", + (unsigned long) shminfo.shmmni, + (unsigned long) (shminfo.shmmax >> 10), + (unsigned long) shminfo.shmall, + (unsigned long) shminfo.shmmin); + return; + + case STATUS: + printf("------ Shared Memory %s --------\n", "Status"); + printf( "segments allocated %d\n" + "pages allocated %ld\n" + "pages resident %ld\n" + "pages swapped %ld\n" + "Swap performance: %ld attempts\t%ld successes\n", + shm_info.used_ids, + shm_info.shm_tot, + shm_info.shm_rss, + shm_info.shm_swp, + shm_info.swap_attempts, shm_info.swap_successes); + return; + + case CREATOR: + printf("------ Shared Memory %s --------\n", "Segment Creators/Owners"); + printf( "%-10s %-10s %-10s %-10s %-10s %-10s\n", + "shmid", "perms", "cuid", "cgid", "uid", "gid"); + break; + + case TIME: + printf("------ Shared Memory %s --------\n", "Attach/Detach/Change Times"); + printf( "%-10s %-10s %-20s %-20s %-20s\n", + "shmid", "owner", "attached", "detached", "changed"); + break; + + case PID: + printf("------ Shared Memory %s --------\n", "Creator/Last-op"); + printf( "%-10s %-10s %-10s %-10s\n", + "shmid", "owner", "cpid", "lpid"); + break; + + default: + printf("------ Shared Memory %s --------\n", "Segments"); + printf( "%-10s %-10s %-10s %-10s %-10s %-10s %-12s\n", + "key", "shmid", "owner", "perms", "bytes", "nattch", + "status"); + break; + } + + for (id = 0; id <= maxid; id++) { + shmid = shmctl(id, SHM_STAT, &shmseg); + if (shmid < 0) + continue; + if (format == CREATOR) { + print_perms(shmid, ipcp); + continue; + } + pw = getpwuid(ipcp->uid); + switch (format) { + case TIME: + if (pw) + printf("%-10d %-10.10s", shmid, pw->pw_name); + else + printf("%-10d %-10d", shmid, ipcp->uid); + /* ctime uses static buffer: use separate calls */ + printf(" %-20.16s", shmseg.shm_atime + ? ctime(&shmseg.shm_atime) + 4 : "Not set"); + printf(" %-20.16s", shmseg.shm_dtime + ? ctime(&shmseg.shm_dtime) + 4 : "Not set"); + printf(" %-20.16s\n", shmseg.shm_ctime + ? ctime(&shmseg.shm_ctime) + 4 : "Not set"); + break; + case PID: + if (pw) + printf("%-10d %-10.10s", shmid, pw->pw_name); + else + printf("%-10d %-10d", shmid, ipcp->uid); + printf(" %-10d %-10d\n", shmseg.shm_cpid, shmseg.shm_lpid); + break; + + default: + printf("0x%08x ", ipcp->KEY); + if (pw) + printf("%-10d %-10.10s", shmid, pw->pw_name); + else + printf("%-10d %-10d", shmid, ipcp->uid); + printf(" %-10o %-10lu %-10ld %-6s %-6s\n", ipcp->mode & 0777, + /* + * earlier: int, Austin has size_t + */ + (unsigned long) shmseg.shm_segsz, + /* + * glibc-2.1.3 and earlier has unsigned short; + * Austin has shmatt_t + */ + (long) shmseg.shm_nattch, + ipcp->mode & SHM_DEST ? "dest" : " ", + ipcp->mode & SHM_LOCKED ? "locked" : " "); + break; + } + } +} + + +static void do_sem(void) +{ + int maxid, semid, id; + struct semid_ds semary; + struct seminfo seminfo; + struct ipc_perm *ipcp = &semary.sem_perm; + struct passwd *pw; + union semun arg; + + arg.array = (ushort *) (void *) &seminfo; + maxid = semctl(0, 0, SEM_INFO, arg); + if (maxid < 0) { + printf("kernel not configured for %s\n", "semaphores"); + return; + } + + switch (format) { + case LIMITS: + printf("------ Semaphore %s --------\n", "Limits"); + arg.array = (ushort *) (void *) &seminfo; /* damn union */ + if ((semctl(0, 0, IPC_INFO, arg)) < 0) + return; + printf("max number of arrays = %d\n" + "max semaphores per array = %d\n" + "max semaphores system wide = %d\n" + "max ops per semop call = %d\n" + "semaphore max value = %d\n", + seminfo.semmni, + seminfo.semmsl, + seminfo.semmns, seminfo.semopm, seminfo.semvmx); + return; + + case STATUS: + printf("------ Semaphore %s --------\n", "Status"); + printf( "used arrays = %d\n" + "allocated semaphores = %d\n", + seminfo.semusz, seminfo.semaem); + return; + + case CREATOR: + printf("------ Semaphore %s --------\n", "Arrays Creators/Owners"); + printf( "%-10s %-10s %-10s %-10s %-10s %-10s\n", + "semid", "perms", "cuid", "cgid", "uid", "gid"); + break; + + case TIME: + printf("------ Shared Memory %s --------\n", "Operation/Change Times"); + printf( "%-8s %-10s %-26.24s %-26.24s\n", + "shmid", "owner", "last-op", "last-changed"); + break; + + case PID: + break; + + default: + printf("------ Semaphore %s --------\n", "Arrays"); + printf( "%-10s %-10s %-10s %-10s %-10s\n", + "key", "semid", "owner", "perms", "nsems"); + break; + } + + for (id = 0; id <= maxid; id++) { + arg.buf = (struct semid_ds *) &semary; + semid = semctl(id, 0, SEM_STAT, arg); + if (semid < 0) + continue; + if (format == CREATOR) { + print_perms(semid, ipcp); + continue; + } + pw = getpwuid(ipcp->uid); + switch (format) { + case TIME: + if (pw) + printf("%-8d %-10.10s", semid, pw->pw_name); + else + printf("%-8d %-10d", semid, ipcp->uid); + /* ctime uses static buffer: use separate calls */ + printf(" %-26.24s", semary.sem_otime + ? ctime(&semary.sem_otime) : "Not set"); + printf(" %-26.24s\n", semary.sem_ctime + ? ctime(&semary.sem_ctime) : "Not set"); + break; + case PID: + break; + + default: + printf("0x%08x ", ipcp->KEY); + if (pw) + printf("%-10d %-10.9s", semid, pw->pw_name); + else + printf("%-10d %-9d", semid, ipcp->uid); + printf(" %-10o %-10ld\n", ipcp->mode & 0777, + /* + * glibc-2.1.3 and earlier has unsigned short; + * glibc-2.1.91 has variation between + * unsigned short and unsigned long + * Austin prescribes unsigned short. + */ + (long) semary.sem_nsems); + break; + } + } +} + + +static void do_msg(void) +{ + int maxid, msqid, id; + struct msqid_ds msgque; + struct msginfo msginfo; + struct ipc_perm *ipcp = &msgque.msg_perm; + struct passwd *pw; + + maxid = msgctl(0, MSG_INFO, (struct msqid_ds *) (void *) &msginfo); + if (maxid < 0) { + printf("kernel not configured for %s\n", "message queues"); + return; + } + + switch (format) { + case LIMITS: + if ((msgctl(0, IPC_INFO, (struct msqid_ds *) (void *) &msginfo)) < 0) + return; + printf("------ Message%s --------\n", "s: Limits"); + printf( "max queues system wide = %d\n" + "max size of message (bytes) = %d\n" + "default max size of queue (bytes) = %d\n", + msginfo.msgmni, msginfo.msgmax, msginfo.msgmnb); + return; + + case STATUS: + printf("------ Message%s --------\n", "s: Status"); + printf( "allocated queues = %d\n" + "used headers = %d\n" + "used space = %d bytes\n", + msginfo.msgpool, msginfo.msgmap, msginfo.msgtql); + return; + + case CREATOR: + printf("------ Message%s --------\n", " Queues: Creators/Owners"); + printf( "%-10s %-10s %-10s %-10s %-10s %-10s\n", + "msqid", "perms", "cuid", "cgid", "uid", "gid"); + break; + + case TIME: + printf("------ Message%s --------\n", " Queues Send/Recv/Change Times"); + printf( "%-8s %-10s %-20s %-20s %-20s\n", + "msqid", "owner", "send", "recv", "change"); + break; + + case PID: + printf("------ Message%s --------\n", " Queues PIDs"); + printf( "%-10s %-10s %-10s %-10s\n", + "msqid", "owner", "lspid", "lrpid"); + break; + + default: + printf("------ Message%s --------\n", " Queues"); + printf( "%-10s %-10s %-10s %-10s %-12s %-12s\n", + "key", "msqid", "owner", "perms", "used-bytes", "messages"); + break; + } + + for (id = 0; id <= maxid; id++) { + msqid = msgctl(id, MSG_STAT, &msgque); + if (msqid < 0) + continue; + if (format == CREATOR) { + print_perms(msqid, ipcp); + continue; + } + pw = getpwuid(ipcp->uid); + switch (format) { + case TIME: + if (pw) + printf("%-8d %-10.10s", msqid, pw->pw_name); + else + printf("%-8d %-10d", msqid, ipcp->uid); + printf(" %-20.16s", msgque.msg_stime + ? ctime(&msgque.msg_stime) + 4 : "Not set"); + printf(" %-20.16s", msgque.msg_rtime + ? ctime(&msgque.msg_rtime) + 4 : "Not set"); + printf(" %-20.16s\n", msgque.msg_ctime + ? ctime(&msgque.msg_ctime) + 4 : "Not set"); + break; + case PID: + if (pw) + printf("%-8d %-10.10s", msqid, pw->pw_name); + else + printf("%-8d %-10d", msqid, ipcp->uid); + printf(" %5d %5d\n", msgque.msg_lspid, msgque.msg_lrpid); + break; + + default: + printf("0x%08x ", ipcp->KEY); + if (pw) + printf("%-10d %-10.10s", msqid, pw->pw_name); + else + printf("%-10d %-10d", msqid, ipcp->uid); + printf(" %-10o %-12ld %-12ld\n", ipcp->mode & 0777, + /* + * glibc-2.1.3 and earlier has unsigned short; + * glibc-2.1.91 has variation between + * unsigned short, unsigned long + * Austin has msgqnum_t + */ + (long) msgque.msg_cbytes, (long) msgque.msg_qnum); + break; + } + } +} + + +static void print_shm(int shmid) +{ + struct shmid_ds shmds; + struct ipc_perm *ipcp = &shmds.shm_perm; + + if (shmctl(shmid, IPC_STAT, &shmds) == -1) { + bb_perror_msg("shmctl"); + return; + } + + printf("\nShared memory Segment shmid=%d\n" + "uid=%d\tgid=%d\tcuid=%d\tcgid=%d\n" + "mode=%#o\taccess_perms=%#o\n" + "bytes=%ld\tlpid=%d\tcpid=%d\tnattch=%ld\n", + shmid, + ipcp->uid, ipcp->gid, ipcp->cuid, ipcp->cgid, + ipcp->mode, ipcp->mode & 0777, + (long) shmds.shm_segsz, shmds.shm_lpid, shmds.shm_cpid, + (long) shmds.shm_nattch); + printf("att_time=%-26.24s\n", + shmds.shm_atime ? ctime(&shmds.shm_atime) : "Not set"); + printf("det_time=%-26.24s\n", + shmds.shm_dtime ? ctime(&shmds.shm_dtime) : "Not set"); + printf("change_time=%-26.24s\n\n", ctime(&shmds.shm_ctime)); +} + + +static void print_msg(int msqid) +{ + struct msqid_ds buf; + struct ipc_perm *ipcp = &buf.msg_perm; + + if (msgctl(msqid, IPC_STAT, &buf) == -1) { + bb_perror_msg("msgctl"); + return; + } + + printf("\nMessage Queue msqid=%d\n" + "uid=%d\tgid=%d\tcuid=%d\tcgid=%d\tmode=%#o\n" + "cbytes=%ld\tqbytes=%ld\tqnum=%ld\tlspid=%d\tlrpid=%d\n", + msqid, ipcp->uid, ipcp->gid, ipcp->cuid, ipcp->cgid, ipcp->mode, + /* + * glibc-2.1.3 and earlier has unsigned short; + * glibc-2.1.91 has variation between + * unsigned short, unsigned long + * Austin has msgqnum_t (for msg_qbytes) + */ + (long) buf.msg_cbytes, (long) buf.msg_qbytes, + (long) buf.msg_qnum, buf.msg_lspid, buf.msg_lrpid); + + printf("send_time=%-26.24s\n", + buf.msg_stime ? ctime(&buf.msg_stime) : "Not set"); + printf("rcv_time=%-26.24s\n", + buf.msg_rtime ? ctime(&buf.msg_rtime) : "Not set"); + printf("change_time=%-26.24s\n\n", + buf.msg_ctime ? ctime(&buf.msg_ctime) : "Not set"); +} + +static void print_sem(int semid) +{ + struct semid_ds semds; + struct ipc_perm *ipcp = &semds.sem_perm; + union semun arg; + unsigned int i; + + arg.buf = &semds; + if (semctl(semid, 0, IPC_STAT, arg)) { + bb_perror_msg("semctl"); + return; + } + + printf("\nSemaphore Array semid=%d\n" + "uid=%d\t gid=%d\t cuid=%d\t cgid=%d\n" + "mode=%#o, access_perms=%#o\n" + "nsems = %ld\n" + "otime = %-26.24s\n", + semid, + ipcp->uid, ipcp->gid, ipcp->cuid, ipcp->cgid, + ipcp->mode, ipcp->mode & 0777, + (long) semds.sem_nsems, + semds.sem_otime ? ctime(&semds.sem_otime) : "Not set"); + printf("ctime = %-26.24s\n" + "%-10s %-10s %-10s %-10s %-10s\n", + ctime(&semds.sem_ctime), + "semnum", "value", "ncount", "zcount", "pid"); + + arg.val = 0; + for (i = 0; i < semds.sem_nsems; i++) { + int val, ncnt, zcnt, pid; + + val = semctl(semid, i, GETVAL, arg); + ncnt = semctl(semid, i, GETNCNT, arg); + zcnt = semctl(semid, i, GETZCNT, arg); + pid = semctl(semid, i, GETPID, arg); + if (val < 0 || ncnt < 0 || zcnt < 0 || pid < 0) { + bb_perror_msg_and_die("semctl"); + } + printf("%-10d %-10d %-10d %-10d %-10d\n", i, val, ncnt, zcnt, pid); + } + puts(""); +} + +int ipcs_main(int argc, char **argv) +{ + int id = 0; + unsigned flags = 0; + unsigned opt; + char *opt_i; +#define flag_print (1<<0) +#define flag_msg (1<<1) +#define flag_sem (1<<2) +#define flag_shm (1<<3) + + opt = getopt32(argc, argv, "i:aqsmtcplu", &opt_i); + if (opt & 0x1) { // -i + id = xatoi(opt_i); + flags |= flag_print; + } + if (opt & 0x2) flags |= flag_msg | flag_sem | flag_shm; // -a + if (opt & 0x4) flags |= flag_msg; // -q + if (opt & 0x8) flags |= flag_sem; // -s + if (opt & 0x10) flags |= flag_shm; // -m + if (opt & 0x20) format = TIME; // -t + if (opt & 0x40) format = CREATOR; // -c + if (opt & 0x80) format = PID; // -p + if (opt & 0x100) format = LIMITS; // -l + if (opt & 0x200) format = STATUS; // -u + + if (flags & flag_print) { + if (flags & flag_shm) { + print_shm(id); + fflush_stdout_and_exit(0); + } + if (flags & flag_sem) { + print_sem(id); + fflush_stdout_and_exit(0); + } + if (flags & flag_msg) { + print_msg(id); + fflush_stdout_and_exit(0); + } + bb_show_usage(); + } + + if (!(flags & (flag_shm | flag_msg | flag_sem))) + flags |= flag_msg | flag_shm | flag_sem; + puts(""); + + if (flags & flag_shm) { + do_shm(); + puts(""); + } + if (flags & flag_sem) { + do_sem(); + puts(""); + } + if (flags & flag_msg) { + do_msg(); + puts(""); + } + fflush_stdout_and_exit(0); +} diff --git a/util-linux/losetup.c b/util-linux/losetup.c new file mode 100644 index 000000000..c7eb85a91 --- /dev/null +++ b/util-linux/losetup.c @@ -0,0 +1,64 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini losetup implementation for busybox + * + * Copyright (C) 2002 Matt Kraai. + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +#include <getopt.h> +#include <stdlib.h> + +#include "busybox.h" + +int losetup_main(int argc, char **argv) +{ + unsigned opt; + char *opt_o; + unsigned long long offset = 0; + + opt = getopt32(argc, argv, "do:", &opt_o); + argc -= optind; + argv += optind; + + if (opt == 0x3) // -d + -o (illegal) + bb_show_usage(); + + if (opt == 0x1) { // -d + /* detach takes exactly one argument */ + if (argc != 1) + bb_show_usage(); + if (!del_loop(argv[0])) + return EXIT_SUCCESS; + bb_perror_nomsg_and_die(); + } + + if (opt == 0x2) // -o + offset = xatoull(opt_o); + + /* -o or no option */ + + if (argc == 2) { + if (set_loop(&argv[0], argv[1], offset) < 0) + bb_perror_nomsg_and_die(); + } else if (argc == 1) { + char *s = query_loop(argv[0]); + if (!s) bb_perror_nomsg_and_die(); + printf("%s: %s\n", argv[0], s); + if (ENABLE_FEATURE_CLEAN_UP) free(s); + } else { + char dev[sizeof(LOOP_NAME"0")] = LOOP_NAME"0"; + char c; + for (c = '0'; c <= '9'; ++c) { + char *s; + dev[sizeof(LOOP_NAME"0")-2] = c; + s = query_loop(dev); + if (s) { + printf("%s: %s\n", dev, s); + if (ENABLE_FEATURE_CLEAN_UP) free(s); + } + } + } + return EXIT_SUCCESS; +} diff --git a/util-linux/mdev.c b/util-linux/mdev.c new file mode 100644 index 000000000..957316d52 --- /dev/null +++ b/util-linux/mdev.c @@ -0,0 +1,268 @@ +/* vi: set sw=4 ts=4: */ +/* + * + * mdev - Mini udev for busybox + * + * Copyright 2005 Rob Landley <rob@landley.net> + * Copyright 2005 Frank Sorenson <frank@tuxrocks.com> + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include "xregex.h" + +#define DEV_PATH "/dev" + +struct mdev_globals +{ + int root_major, root_minor; +} mdev_globals; + +#define bbg mdev_globals + +/* mknod in /dev based on a path like "/sys/block/hda/hda1" */ +static void make_device(char *path, int delete) +{ + char *device_name; + int major, minor, type, len; + int mode = 0660; + uid_t uid = 0; + gid_t gid = 0; + char *temp = path + strlen(path); + char *command = NULL; + + /* Try to read major/minor string. Note that the kernel puts \n after + * the data, so we don't need to worry about null terminating the string + * because sscanf() will stop at the first nondigit, which \n is. We + * also depend on path having writeable space after it. */ + + if (!delete) { + strcat(path, "/dev"); + len = open_read_close(path, temp + 1, 64); + *temp++ = 0; + if (len < 1) return; + } + + /* Determine device name, type, major and minor */ + + device_name = strrchr(path, '/') + 1; + type = path[5]=='c' ? S_IFCHR : S_IFBLK; + + /* If we have a config file, look up permissions for this device */ + + if (ENABLE_FEATURE_MDEV_CONF) { + char *conf, *pos, *end; + int line, fd; + + /* mmap the config file */ + fd = open("/etc/mdev.conf", O_RDONLY); + if (fd < 0) + goto end_parse; + len = xlseek(fd, 0, SEEK_END); + conf = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0); + close(fd); + if (!conf) + goto end_parse; + + line = 0; + /* Loop through lines in mmaped file*/ + for (pos=conf; pos-conf<len;) { + int field; + char *end2; + + line++; + /* find end of this line */ + for (end=pos; end-conf<len && *end!='\n'; end++) + ; + + /* Three fields: regex, uid:gid, mode */ + for (field=0; field < (3 + ENABLE_FEATURE_MDEV_EXEC); + field++) + { + /* Skip whitespace */ + while (pos<end && isspace(*pos)) pos++; + if (pos==end || *pos=='#') break; + for (end2=pos; + end2<end && !isspace(*end2) && *end2!='#'; end2++) + ; + + if (field == 0) { + /* Regex to match this device */ + + char *regex = strndupa(pos, end2-pos); + regex_t match; + regmatch_t off; + int result; + + /* Is this it? */ + xregcomp(&match,regex, REG_EXTENDED); + result = regexec(&match, device_name, 1, &off, 0); + regfree(&match); + + /* If not this device, skip rest of line */ + if (result || off.rm_so + || off.rm_eo != strlen(device_name)) + break; + } + if (field == 1) { + /* uid:gid */ + + char *s, *s2; + + /* Find : */ + for (s=pos; s<end2 && *s!=':'; s++) + ; + if (s == end2) break; + + /* Parse UID */ + uid = strtoul(pos, &s2, 10); + if (s != s2) { + struct passwd *pass; + pass = getpwnam(strndupa(pos, s-pos)); + if (!pass) break; + uid = pass->pw_uid; + } + s++; + /* parse GID */ + gid = strtoul(s, &s2, 10); + if (end2 != s2) { + struct group *grp; + grp = getgrnam(strndupa(s, end2-s)); + if (!grp) break; + gid = grp->gr_gid; + } + } + if (field == 2) { + /* mode */ + + mode = strtoul(pos, &pos, 8); + if (pos != end2) break; + } + if (ENABLE_FEATURE_MDEV_EXEC && field == 3) { + // Command to run + char *s = "@$*", *s2; + s2 = strchr(s, *pos++); + if (!s2) { + // Force error + field = 1; + break; + } + if ((s2-s+1) & (1<<delete)) + command = xstrndup(pos, end-pos); + } + + pos = end2; + } + + /* Did everything parse happily? */ + + if (field > 2) break; + if (field) bb_error_msg_and_die("bad line %d",line); + + /* Next line */ + pos = ++end; + } + munmap(conf, len); + end_parse: /* nothing */ ; + } + + umask(0); + if (!delete) { + if (sscanf(temp, "%d:%d", &major, &minor) != 2) return; + if (mknod(device_name, mode | type, makedev(major, minor)) && errno != EEXIST) + bb_perror_msg_and_die("mknod %s", device_name); + + if (major == bbg.root_major && minor == bbg.root_minor) + symlink(device_name, "root"); + + if (ENABLE_FEATURE_MDEV_CONF) chown(device_name, uid, gid); + } + if (command) { + int rc; + char *s; + + s = xasprintf("MDEV=%s", device_name); + putenv(s); + rc = system(command); + s[4] = 0; + putenv(s); + free(s); + free(command); + if (rc == -1) bb_perror_msg_and_die("cannot run %s", command); + } + if (delete) unlink(device_name); +} + +/* Recursive search of /sys/block or /sys/class. path must be a writeable + * buffer of size PATH_MAX containing the directory string to start at. */ + +static void find_dev(char *path) +{ + DIR *dir; + size_t len = strlen(path); + struct dirent *entry; + + dir = opendir(path); + if (dir == NULL) + return; + + while ((entry = readdir(dir)) != NULL) { + struct stat st; + + /* Skip "." and ".." (also skips hidden files, which is ok) */ + + if (entry->d_name[0] == '.') + continue; + + // uClibc doesn't fill out entry->d_type reliably. so we use lstat(). + + snprintf(path+len, PATH_MAX-len, "/%s", entry->d_name); + if (!lstat(path, &st) && S_ISDIR(st.st_mode)) find_dev(path); + path[len] = 0; + + /* If there's a dev entry, mknod it */ + + if (!strcmp(entry->d_name, "dev")) make_device(path, 0); + } + + closedir(dir); +} + +int mdev_main(int argc, char *argv[]) +{ + char *action; + char *env_path; + RESERVE_CONFIG_BUFFER(temp,PATH_MAX); + + xchdir(DEV_PATH); + + /* Scan */ + + if (argc == 2 && !strcmp(argv[1],"-s")) { + struct stat st; + + xstat("/", &st); + bbg.root_major = major(st.st_dev); + bbg.root_minor = minor(st.st_dev); + strcpy(temp,"/sys/block"); + find_dev(temp); + strcpy(temp,"/sys/class"); + find_dev(temp); + + /* Hotplug */ + + } else { + action = getenv("ACTION"); + env_path = getenv("DEVPATH"); + if (!action || !env_path) + bb_show_usage(); + + sprintf(temp, "/sys%s", env_path); + if (!strcmp(action, "add")) make_device(temp,0); + else if (!strcmp(action, "remove")) make_device(temp,1); + } + + if (ENABLE_FEATURE_CLEAN_UP) RELEASE_CONFIG_BUFFER(temp); + return 0; +} diff --git a/util-linux/mkfs_minix.c b/util-linux/mkfs_minix.c new file mode 100644 index 000000000..af19da68c --- /dev/null +++ b/util-linux/mkfs_minix.c @@ -0,0 +1,762 @@ +/* vi: set sw=4 ts=4: */ +/* + * mkfs.c - make a linux (minix) file-system. + * + * (C) 1991 Linus Torvalds. This file may be redistributed as per + * the Linux copyright. + */ + +/* + * DD.MM.YY + * + * 24.11.91 - Time began. Used the fsck sources to get started. + * + * 25.11.91 - Corrected some bugs. Added support for ".badblocks" + * The algorithm for ".badblocks" is a bit weird, but + * it should work. Oh, well. + * + * 25.01.92 - Added the -l option for getting the list of bad blocks + * out of a named file. (Dave Rivers, rivers@ponds.uucp) + * + * 28.02.92 - Added %-information when using -c. + * + * 28.02.93 - Added support for other namelengths than the original + * 14 characters so that I can test the new kernel routines.. + * + * 09.10.93 - Make exit status conform to that required by fsutil + * (Rik Faith, faith@cs.unc.edu) + * + * 31.10.93 - Added inode request feature, for backup floppies: use + * 32 inodes, for a news partition use more. + * (Scott Heavner, sdh@po.cwru.edu) + * + * 03.01.94 - Added support for file system valid flag. + * (Dr. Wettstein, greg%wind.uucp@plains.nodak.edu) + * + * 30.10.94 - added support for v2 filesystem + * (Andreas Schwab, schwab@issan.informatik.uni-dortmund.de) + * + * 09.11.94 - Added test to prevent overwrite of mounted fs adapted + * from Theodore Ts'o's (tytso@athena.mit.edu) mke2fs + * program. (Daniel Quinlan, quinlan@yggdrasil.com) + * + * 03.20.95 - Clear first 512 bytes of filesystem to make certain that + * the filesystem is not misidentified as a MS-DOS FAT filesystem. + * (Daniel Quinlan, quinlan@yggdrasil.com) + * + * 02.07.96 - Added small patch from Russell King to make the program a + * good deal more portable (janl@math.uio.no) + * + * Usage: mkfs [-c | -l filename ] [-v] [-nXX] [-iXX] device [size-in-blocks] + * + * -c for readability checking (SLOW!) + * -l for getting a list of bad blocks from a file. + * -n for namelength (currently the kernel only uses 14 or 30) + * -i for number of inodes + * -v for v2 filesystem + * + * The device may be a block device or a image of one, but this isn't + * enforced (but it's not much fun on a character device :-). + * + * Modified for BusyBox by Erik Andersen <andersen@debian.org> -- + * removed getopt based parser and added a hand rolled one. + */ + +#include "busybox.h" +#include <mntent.h> + +#define DEBUG 0 + +/* If debugging, store the very same times/uids/gids for image consistency */ +#if DEBUG +# define CUR_TIME 0 +# define GETUID 0 +# define GETGID 0 +#else +# define CUR_TIME time(NULL) +# define GETUID getuid() +# define GETGID getgid() +#endif + +/* + * This is the original minix inode layout on disk. + * Note the 8-bit gid and atime and ctime. + */ +struct minix1_inode { + uint16_t i_mode; + uint16_t i_uid; + uint32_t i_size; + uint32_t i_time; + uint8_t i_gid; + uint8_t i_nlinks; + uint16_t i_zone[9]; +}; + +/* + * The new minix inode has all the time entries, as well as + * long block numbers and a third indirect block (7+1+1+1 + * instead of 7+1+1). Also, some previously 8-bit values are + * now 16-bit. The inode is now 64 bytes instead of 32. + */ +struct minix2_inode { + uint16_t i_mode; + uint16_t i_nlinks; + uint16_t i_uid; + uint16_t i_gid; + uint32_t i_size; + uint32_t i_atime; + uint32_t i_mtime; + uint32_t i_ctime; + uint32_t i_zone[10]; +}; + +/* + * minix super-block data on disk + */ +struct minix_super_block { + uint16_t s_ninodes; + uint16_t s_nzones; + uint16_t s_imap_blocks; + uint16_t s_zmap_blocks; + uint16_t s_firstdatazone; + uint16_t s_log_zone_size; + uint32_t s_max_size; + uint16_t s_magic; + uint16_t s_state; + uint32_t s_zones; +}; + +struct minix_dir_entry { + uint16_t inode; + char name[0]; +}; + +/* Believe it or not, but mount.h has this one */ +#undef BLOCK_SIZE +enum { + BLOCK_SIZE = 1024, + BITS_PER_BLOCK = BLOCK_SIZE << 3, + + MINIX_ROOT_INO = 1, + MINIX_BAD_INO = 2, + MAX_GOOD_BLOCKS = 512, + + MINIX1_SUPER_MAGIC = 0x137F, /* original minix fs */ + MINIX1_SUPER_MAGIC2 = 0x138F, /* minix fs, 30 char names */ + MINIX2_SUPER_MAGIC = 0x2468, /* minix V2 fs */ + MINIX2_SUPER_MAGIC2 = 0x2478, /* minix V2 fs, 30 char names */ + MINIX_VALID_FS = 0x0001, /* clean fs */ + MINIX_ERROR_FS = 0x0002, /* fs has errors */ + + INODE_SIZE1 = sizeof(struct minix1_inode), + INODE_SIZE2 = sizeof(struct minix2_inode), + MINIX1_INODES_PER_BLOCK = BLOCK_SIZE / sizeof(struct minix1_inode), + MINIX2_INODES_PER_BLOCK = BLOCK_SIZE / sizeof(struct minix2_inode), + + TEST_BUFFER_BLOCKS = 16, +}; + +#if ENABLE_FEATURE_MINIX2 +static int version2; +#else +enum { version2 = 0 }; +#endif + +static char *device_name; +static int dev_fd = -1; +static uint32_t total_blocks; +static int badblocks; +/* default (changed to 30, per Linus's suggestion, Sun Nov 21 08:05:07 1993) */ +static int namelen = 30; +static int dirsize = 32; +static int magic = MINIX1_SUPER_MAGIC2; + +static char root_block[BLOCK_SIZE]; +static char super_block_buffer[BLOCK_SIZE]; +static char boot_block_buffer[512]; +static char *inode_buffer; + +static char *inode_map; +static char *zone_map; + +static int used_good_blocks; +static unsigned short good_blocks_table[MAX_GOOD_BLOCKS]; +static unsigned long req_nr_inodes; + +extern inline unsigned div_roundup(unsigned size, unsigned n) +{ + return (size + n-1) / n; +} + +#define INODE_BUF1 (((struct minix1_inode*)inode_buffer) - 1) +#define INODE_BUF2 (((struct minix2_inode*)inode_buffer) - 1) + +#define SB (*(struct minix_super_block*)super_block_buffer) + +#define SB_INODES (SB.s_ninodes) +#define SB_IMAPS (SB.s_imap_blocks) +#define SB_ZMAPS (SB.s_zmap_blocks) +#define SB_FIRSTZONE (SB.s_firstdatazone) +#define SB_ZONE_SIZE (SB.s_log_zone_size) +#define SB_MAXSIZE (SB.s_max_size) +#define SB_MAGIC (SB.s_magic) + +#if !ENABLE_FEATURE_MINIX2 +# define SB_ZONES (SB.s_nzones) +# define INODE_BLOCKS div_roundup(SB_INODES, MINIX1_INODES_PER_BLOCK) +#else +# define SB_ZONES (version2 ? SB.s_zones : SB.s_nzones) +# define INODE_BLOCKS div_roundup(SB_INODES, \ + version2 ? MINIX2_INODES_PER_BLOCK : MINIX1_INODES_PER_BLOCK) +#endif + +#define INODE_BUFFER_SIZE (INODE_BLOCKS * BLOCK_SIZE) +#define NORM_FIRSTZONE (2 + SB_IMAPS + SB_ZMAPS + INODE_BLOCKS) + +static int bit(const char* a, unsigned i) +{ + return a[i >> 3] & (1<<(i & 7)); +} + +/* Note: do not assume 0/1, it is 0/nonzero */ +#define inode_in_use(x) bit(inode_map,(x)) +#define zone_in_use(x) bit(zone_map,(x)-SB_FIRSTZONE+1) + +#define mark_inode(x) setbit(inode_map,(x)) +#define unmark_inode(x) clrbit(inode_map,(x)) +#define mark_zone(x) setbit(zone_map,(x)-SB_FIRSTZONE+1) +#define unmark_zone(x) clrbit(zone_map,(x)-SB_FIRSTZONE+1) + +#ifndef BLKGETSIZE +# define BLKGETSIZE _IO(0x12,96) /* return device size */ +#endif + + +static long valid_offset(int fd, int offset) +{ + char ch; + + if (lseek(fd, offset, SEEK_SET) < 0) + return 0; + if (read(fd, &ch, 1) < 1) + return 0; + return 1; +} + +static int count_blocks(int fd) +{ + int high, low; + + low = 0; + for (high = 1; valid_offset(fd, high); high *= 2) + low = high; + + while (low < high - 1) { + const int mid = (low + high) / 2; + + if (valid_offset(fd, mid)) + low = mid; + else + high = mid; + } + valid_offset(fd, 0); + return (low + 1); +} + +static int get_size(const char *file) +{ + int fd; + long size; + + fd = xopen(file, O_RDWR); + if (ioctl(fd, BLKGETSIZE, &size) >= 0) { + close(fd); + return (size * 512); + } + + size = count_blocks(fd); + close(fd); + return size; +} + +static void write_tables(void) +{ + /* Mark the super block valid. */ + SB.s_state |= MINIX_VALID_FS; + SB.s_state &= ~MINIX_ERROR_FS; + + msg_eol = "seek to 0 failed"; + xlseek(dev_fd, 0, SEEK_SET); + + msg_eol = "cannot clear boot sector"; + xwrite(dev_fd, boot_block_buffer, 512); + + msg_eol = "seek to BLOCK_SIZE failed"; + xlseek(dev_fd, BLOCK_SIZE, SEEK_SET); + + msg_eol = "cannot write superblock"; + xwrite(dev_fd, super_block_buffer, BLOCK_SIZE); + + msg_eol = "cannot write inode map"; + xwrite(dev_fd, inode_map, SB_IMAPS * BLOCK_SIZE); + + msg_eol = "cannot write zone map"; + xwrite(dev_fd, zone_map, SB_ZMAPS * BLOCK_SIZE); + + msg_eol = "cannot write inodes"; + xwrite(dev_fd, inode_buffer, INODE_BUFFER_SIZE); + + msg_eol = "\n"; +} + +static void write_block(int blk, char *buffer) +{ + xlseek(dev_fd, blk * BLOCK_SIZE, SEEK_SET); + xwrite(dev_fd, buffer, BLOCK_SIZE); +} + +static int get_free_block(void) +{ + int blk; + + if (used_good_blocks + 1 >= MAX_GOOD_BLOCKS) + bb_error_msg_and_die("too many bad blocks"); + if (used_good_blocks) + blk = good_blocks_table[used_good_blocks - 1] + 1; + else + blk = SB_FIRSTZONE; + while (blk < SB_ZONES && zone_in_use(blk)) + blk++; + if (blk >= SB_ZONES) + bb_error_msg_and_die("not enough good blocks"); + good_blocks_table[used_good_blocks] = blk; + used_good_blocks++; + return blk; +} + +static void mark_good_blocks(void) +{ + int blk; + + for (blk = 0; blk < used_good_blocks; blk++) + mark_zone(good_blocks_table[blk]); +} + +static int next(int zone) +{ + if (!zone) + zone = SB_FIRSTZONE - 1; + while (++zone < SB_ZONES) + if (zone_in_use(zone)) + return zone; + return 0; +} + +static void make_bad_inode(void) +{ + struct minix1_inode *inode = &INODE_BUF1[MINIX_BAD_INO]; + int i, j, zone; + int ind = 0, dind = 0; + unsigned short ind_block[BLOCK_SIZE >> 1]; + unsigned short dind_block[BLOCK_SIZE >> 1]; + +#define NEXT_BAD (zone = next(zone)) + + if (!badblocks) + return; + mark_inode(MINIX_BAD_INO); + inode->i_nlinks = 1; + /* BTW, setting this makes all images different */ + /* it's harder to check for bugs then - diff isn't helpful :(... */ + inode->i_time = CUR_TIME; + inode->i_mode = S_IFREG + 0000; + inode->i_size = badblocks * BLOCK_SIZE; + zone = next(0); + for (i = 0; i < 7; i++) { + inode->i_zone[i] = zone; + if (!NEXT_BAD) + goto end_bad; + } + inode->i_zone[7] = ind = get_free_block(); + memset(ind_block, 0, BLOCK_SIZE); + for (i = 0; i < 512; i++) { + ind_block[i] = zone; + if (!NEXT_BAD) + goto end_bad; + } + inode->i_zone[8] = dind = get_free_block(); + memset(dind_block, 0, BLOCK_SIZE); + for (i = 0; i < 512; i++) { + write_block(ind, (char *) ind_block); + dind_block[i] = ind = get_free_block(); + memset(ind_block, 0, BLOCK_SIZE); + for (j = 0; j < 512; j++) { + ind_block[j] = zone; + if (!NEXT_BAD) + goto end_bad; + } + } + bb_error_msg_and_die("too many bad blocks"); + end_bad: + if (ind) + write_block(ind, (char *) ind_block); + if (dind) + write_block(dind, (char *) dind_block); +} + +#if ENABLE_FEATURE_MINIX2 +static void make_bad_inode2(void) +{ + struct minix2_inode *inode = &INODE_BUF2[MINIX_BAD_INO]; + int i, j, zone; + int ind = 0, dind = 0; + unsigned long ind_block[BLOCK_SIZE >> 2]; + unsigned long dind_block[BLOCK_SIZE >> 2]; + + if (!badblocks) + return; + mark_inode(MINIX_BAD_INO); + inode->i_nlinks = 1; + inode->i_atime = inode->i_mtime = inode->i_ctime = CUR_TIME; + inode->i_mode = S_IFREG + 0000; + inode->i_size = badblocks * BLOCK_SIZE; + zone = next(0); + for (i = 0; i < 7; i++) { + inode->i_zone[i] = zone; + if (!NEXT_BAD) + goto end_bad; + } + inode->i_zone[7] = ind = get_free_block(); + memset(ind_block, 0, BLOCK_SIZE); + for (i = 0; i < 256; i++) { + ind_block[i] = zone; + if (!NEXT_BAD) + goto end_bad; + } + inode->i_zone[8] = dind = get_free_block(); + memset(dind_block, 0, BLOCK_SIZE); + for (i = 0; i < 256; i++) { + write_block(ind, (char *) ind_block); + dind_block[i] = ind = get_free_block(); + memset(ind_block, 0, BLOCK_SIZE); + for (j = 0; j < 256; j++) { + ind_block[j] = zone; + if (!NEXT_BAD) + goto end_bad; + } + } + /* Could make triple indirect block here */ + bb_error_msg_and_die("too many bad blocks"); + end_bad: + if (ind) + write_block(ind, (char *) ind_block); + if (dind) + write_block(dind, (char *) dind_block); +} +#endif + +static void make_root_inode(void) +{ + struct minix1_inode *inode = &INODE_BUF1[MINIX_ROOT_INO]; + + mark_inode(MINIX_ROOT_INO); + inode->i_zone[0] = get_free_block(); + inode->i_nlinks = 2; + inode->i_time = CUR_TIME; + if (badblocks) + inode->i_size = 3 * dirsize; + else { + root_block[2 * dirsize] = '\0'; + root_block[2 * dirsize + 1] = '\0'; + inode->i_size = 2 * dirsize; + } + inode->i_mode = S_IFDIR + 0755; + inode->i_uid = GETUID; + if (inode->i_uid) + inode->i_gid = GETGID; + write_block(inode->i_zone[0], root_block); +} + +#if ENABLE_FEATURE_MINIX2 +static void make_root_inode2(void) +{ + struct minix2_inode *inode = &INODE_BUF2[MINIX_ROOT_INO]; + + mark_inode(MINIX_ROOT_INO); + inode->i_zone[0] = get_free_block(); + inode->i_nlinks = 2; + inode->i_atime = inode->i_mtime = inode->i_ctime = CUR_TIME; + if (badblocks) + inode->i_size = 3 * dirsize; + else { + root_block[2 * dirsize] = '\0'; + root_block[2 * dirsize + 1] = '\0'; + inode->i_size = 2 * dirsize; + } + inode->i_mode = S_IFDIR + 0755; + inode->i_uid = GETUID; + if (inode->i_uid) + inode->i_gid = GETGID; + write_block(inode->i_zone[0], root_block); +} +#endif + +static void setup_tables(void) +{ + unsigned long inodes; + unsigned norm_firstzone; + unsigned sb_zmaps; + unsigned i; + + memset(super_block_buffer, 0, BLOCK_SIZE); + memset(boot_block_buffer, 0, 512); + SB_MAGIC = magic; + SB_ZONE_SIZE = 0; + SB_MAXSIZE = version2 ? 0x7fffffff : (7 + 512 + 512 * 512) * 1024; + if (version2) + SB.s_zones = total_blocks; + else + SB.s_nzones = total_blocks; + + /* some magic nrs: 1 inode / 3 blocks */ + if (req_nr_inodes == 0) + inodes = total_blocks / 3; + else + inodes = req_nr_inodes; + /* Round up inode count to fill block size */ + if (version2) + inodes = (inodes + MINIX2_INODES_PER_BLOCK - 1) & + ~(MINIX2_INODES_PER_BLOCK - 1); + else + inodes = (inodes + MINIX1_INODES_PER_BLOCK - 1) & + ~(MINIX1_INODES_PER_BLOCK - 1); + if (inodes > 65535) + inodes = 65535; + SB_INODES = inodes; + SB_IMAPS = div_roundup(SB_INODES + 1, BITS_PER_BLOCK); + + /* Real bad hack but overwise mkfs.minix can be thrown + * in infinite loop... + * try: + * dd if=/dev/zero of=test.fs count=10 bs=1024 + * mkfs.minix -i 200 test.fs + */ + /* This code is not insane: NORM_FIRSTZONE is not a constant, + * it is calculated from SB_INODES, SB_IMAPS and SB_ZMAPS */ + i = 999; + SB_ZMAPS = 0; + do { + norm_firstzone = NORM_FIRSTZONE; + sb_zmaps = div_roundup(total_blocks - norm_firstzone + 1, BITS_PER_BLOCK); + if (SB_ZMAPS == sb_zmaps) goto got_it; + SB_ZMAPS = sb_zmaps; + /* new SB_ZMAPS, need to recalc NORM_FIRSTZONE */ + } while (--i); + bb_error_msg_and_die("incompatible size/inode count, try different -i N"); + got_it: + + SB_FIRSTZONE = norm_firstzone; + inode_map = xmalloc(SB_IMAPS * BLOCK_SIZE); + zone_map = xmalloc(SB_ZMAPS * BLOCK_SIZE); + memset(inode_map, 0xff, SB_IMAPS * BLOCK_SIZE); + memset(zone_map, 0xff, SB_ZMAPS * BLOCK_SIZE); + for (i = SB_FIRSTZONE; i < SB_ZONES; i++) + unmark_zone(i); + for (i = MINIX_ROOT_INO; i <= SB_INODES; i++) + unmark_inode(i); + inode_buffer = xzalloc(INODE_BUFFER_SIZE); + printf("%ld inodes\n", (long)SB_INODES); + printf("%ld blocks\n", (long)SB_ZONES); + printf("Firstdatazone=%ld (%ld)\n", (long)SB_FIRSTZONE, (long)norm_firstzone); + printf("Zonesize=%d\n", BLOCK_SIZE << SB_ZONE_SIZE); + printf("Maxsize=%ld\n", (long)SB_MAXSIZE); +} + +/* + * Perform a test of a block; return the number of + * blocks readable/writable. + */ +static long do_check(char *buffer, int try, unsigned current_block) +{ + long got; + + /* Seek to the correct loc. */ + msg_eol = "seek failed during testing of blocks"; + xlseek(dev_fd, current_block * BLOCK_SIZE, SEEK_SET); + msg_eol = "\n"; + + /* Try the read */ + got = read(dev_fd, buffer, try * BLOCK_SIZE); + if (got < 0) + got = 0; + if (got & (BLOCK_SIZE - 1)) { + printf("Weird values in do_check: probably bugs\n"); + } + got /= BLOCK_SIZE; + return got; +} + +static unsigned currently_testing; + +static void alarm_intr(int alnum) +{ + if (currently_testing >= SB_ZONES) + return; + signal(SIGALRM, alarm_intr); + alarm(5); + if (!currently_testing) + return; + printf("%d ...", currently_testing); + fflush(stdout); +} + +static void check_blocks(void) +{ + int try, got; + /* buffer[] was the biggest static in entire bbox */ + char *buffer = xmalloc(BLOCK_SIZE * TEST_BUFFER_BLOCKS); + + currently_testing = 0; + signal(SIGALRM, alarm_intr); + alarm(5); + while (currently_testing < SB_ZONES) { + msg_eol = "seek failed in check_blocks"; + xlseek(dev_fd, currently_testing * BLOCK_SIZE, SEEK_SET); + msg_eol = "\n"; + try = TEST_BUFFER_BLOCKS; + if (currently_testing + try > SB_ZONES) + try = SB_ZONES - currently_testing; + got = do_check(buffer, try, currently_testing); + currently_testing += got; + if (got == try) + continue; + if (currently_testing < SB_FIRSTZONE) + bb_error_msg_and_die("bad blocks before data-area: cannot make fs"); + mark_zone(currently_testing); + badblocks++; + currently_testing++; + } + free(buffer); + printf("%d bad block(s)\n", badblocks); +} + +static void get_list_blocks(char *filename) +{ + FILE *listfile; + unsigned long blockno; + + listfile = xfopen(filename, "r"); + while (!feof(listfile)) { + fscanf(listfile, "%ld\n", &blockno); + mark_zone(blockno); + badblocks++; + } + printf("%d bad block(s)\n", badblocks); +} + +int mkfs_minix_main(int argc, char **argv) +{ + struct mntent *mp; + unsigned opt; + char *tmp; + struct stat statbuf; + char *str_i, *str_n; + char *listfile = NULL; + + if (INODE_SIZE1 * MINIX1_INODES_PER_BLOCK != BLOCK_SIZE) + bb_error_msg_and_die("bad inode size"); +#if ENABLE_FEATURE_MINIX2 + if (INODE_SIZE2 * MINIX2_INODES_PER_BLOCK != BLOCK_SIZE) + bb_error_msg_and_die("bad inode size"); +#endif + + opt = getopt32(argc, argv, "ci:l:n:v", &str_i, &listfile, &str_n); + argv += optind; + //if (opt & 1) -c + if (opt & 2) req_nr_inodes = xatoul(str_i); // -i + //if (opt & 4) -l + if (opt & 8) { // -n + namelen = xatoi_u(str_n); + if (namelen == 14) magic = MINIX1_SUPER_MAGIC; + else if (namelen == 30) magic = MINIX1_SUPER_MAGIC2; + else bb_show_usage(); + dirsize = namelen + 2; + } + if (opt & 0x10) { // -v +#if ENABLE_FEATURE_MINIX2 + version2 = 1; +#else + bb_error_msg_and_die("%s: not compiled with minix v2 support", + device_name); +#endif + } + + device_name = *argv++; + if (!device_name) + bb_show_usage(); + if (*argv) + total_blocks = xatou32(*argv); + else + total_blocks = get_size(device_name) / 1024; + + if (total_blocks < 10) + bb_error_msg_and_die("must have at least 10 blocks"); + + if (version2) { + magic = MINIX2_SUPER_MAGIC2; + if (namelen == 14) + magic = MINIX2_SUPER_MAGIC; + } else if (total_blocks > 65535) + total_blocks = 65535; + + /* Check if it is mounted */ + mp = find_mount_point(device_name, NULL); + if (mp && strcmp(device_name, mp->mnt_fsname) == 0) + bb_error_msg_and_die("%s is mounted on %s; " + "refusing to make a filesystem", + device_name, mp->mnt_dir); + + dev_fd = xopen(device_name, O_RDWR); + if (fstat(dev_fd, &statbuf) < 0) + bb_error_msg_and_die("cannot stat %s", device_name); + if (!S_ISBLK(statbuf.st_mode)) + opt &= ~1; // clear -c (check) + +/* I don't know why someone has special code to prevent mkfs.minix + * on IDE devices. Why IDE but not SCSI, etc?... */ +#if 0 + else if (statbuf.st_rdev == 0x0300 || statbuf.st_rdev == 0x0340) + /* what is this? */ + bb_error_msg_and_die("will not try " + "to make filesystem on '%s'", device_name); +#endif + + tmp = root_block; + *(short *) tmp = 1; + strcpy(tmp + 2, "."); + tmp += dirsize; + *(short *) tmp = 1; + strcpy(tmp + 2, ".."); + tmp += dirsize; + *(short *) tmp = 2; + strcpy(tmp + 2, ".badblocks"); + + setup_tables(); + + if (opt & 1) // -c ? + check_blocks(); + else if (listfile) + get_list_blocks(listfile); + + if (version2) { + make_root_inode2(); + make_bad_inode2(); + } else { + make_root_inode(); + make_bad_inode(); + } + + mark_good_blocks(); + write_tables(); + return 0; +} diff --git a/util-linux/mkswap.c b/util-linux/mkswap.c new file mode 100644 index 000000000..7baa3ecfb --- /dev/null +++ b/util-linux/mkswap.c @@ -0,0 +1,47 @@ +/* vi: set sw=4 ts=4: */ +/* mkswap.c - format swap device (Linux v1 only) + * + * Copyright 2006 Rob Landley <rob@landley.net> + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" + +int mkswap_main(int argc, char *argv[]) +{ + int fd, pagesize; + off_t len; + unsigned int hdr[129]; + + // No options supported. + + if (argc != 2) bb_show_usage(); + + // Figure out how big the device is and announce our intentions. + + fd = xopen(argv[1], O_RDWR); + len = fdlength(fd); + pagesize = getpagesize(); + printf("Setting up swapspace version 1, size = %"OFF_FMT"d bytes\n", + len - pagesize); + + // Make a header. + + memset(hdr, 0, sizeof(hdr)); + hdr[0] = 1; + hdr[1] = (len / pagesize) - 1; + + // Write the header. Sync to disk because some kernel versions check + // signature on disk (not in cache) during swapon. + + xlseek(fd, 1024, SEEK_SET); + xwrite(fd, hdr, sizeof(hdr)); + xlseek(fd, pagesize-10, SEEK_SET); + xwrite(fd, "SWAPSPACE2", 10); + fsync(fd); + + if (ENABLE_FEATURE_CLEAN_UP) close(fd); + + return 0; +} diff --git a/util-linux/more.c b/util-linux/more.c new file mode 100644 index 000000000..d048ace92 --- /dev/null +++ b/util-linux/more.c @@ -0,0 +1,177 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini more implementation for busybox + * + * Copyright (C) 1995, 1996 by Bruce Perens <bruce@pixar.com>. + * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> + * + * Latest version blended together by Erik Andersen <andersen@codepoet.org>, + * based on the original more implementation by Bruce, and code from the + * Debian boot-floppies team. + * + * Termios corrects by Vladimir Oleynik <dzo@simtreas.ru> + * + * Licensed under GPLv2 or later, see file License in this tarball for details. + */ + +#include "busybox.h" + + +#if ENABLE_FEATURE_USE_TERMIOS +static int cin_fileno; +#include <termios.h> +#define setTermSettings(fd, argp) tcsetattr(fd, TCSANOW, argp) +#define getTermSettings(fd, argp) tcgetattr(fd, argp); + +static struct termios initial_settings, new_settings; + +static void set_tty_to_initial_mode(void) +{ + setTermSettings(cin_fileno, &initial_settings); +} + +static void gotsig(int sig) +{ + putchar('\n'); + exit(EXIT_FAILURE); +} +#endif /* FEATURE_USE_TERMIOS */ + + +int more_main(int argc, char **argv) +{ + int c, lines, input = 0; + int please_display_more_prompt = 0; + struct stat st; + FILE *file; + FILE *cin; + int len, page_height; + int terminal_width; + int terminal_height; + + argc--; + argv++; + + cin = stdin; + /* use input from terminal unless we do "more >outfile" */ + if (isatty(STDOUT_FILENO)) { + cin = fopen(CURRENT_TTY, "r"); + if (!cin) + cin = xfopen(CONSOLE_DEV, "r"); + please_display_more_prompt = 2; +#if ENABLE_FEATURE_USE_TERMIOS + cin_fileno = fileno(cin); + getTermSettings(cin_fileno, &initial_settings); + new_settings = initial_settings; + new_settings.c_lflag &= ~ICANON; + new_settings.c_lflag &= ~ECHO; + new_settings.c_cc[VMIN] = 1; + new_settings.c_cc[VTIME] = 0; + setTermSettings(cin_fileno, &new_settings); + atexit(set_tty_to_initial_mode); + signal(SIGINT, gotsig); + signal(SIGQUIT, gotsig); + signal(SIGTERM, gotsig); +#endif + } + + do { + file = stdin; + if (argc != 0) { + file = fopen_or_warn(*argv, "r"); + if (!file) + goto loop; + } + + st.st_size = 0; + fstat(fileno(file), &st); + + please_display_more_prompt &= ~1; + + get_terminal_width_height(fileno(cin), &terminal_width, &terminal_height); + if (terminal_height > 4) + terminal_height -= 2; + if (terminal_width > 0) + terminal_width -= 1; + + len = 0; + lines = 0; + page_height = terminal_height; + while ((c = getc(file)) != EOF) { + + if ((please_display_more_prompt & 3) == 3) { + len = printf("--More-- "); + if (file != stdin && st.st_size > 0) { + len += printf("(%d%% of %"OFF_FMT"d bytes)", + (int) (ftello(file)*100 / st.st_size), + st.st_size); + } + fflush(stdout); + + /* + * We've just displayed the "--More--" prompt, so now we need + * to get input from the user. + */ + input = getc(cin); +#if !ENABLE_FEATURE_USE_TERMIOS + printf("\033[A"); /* up cursor */ +#endif + /* Erase the "More" message */ + printf("\r%*s\r", len, ""); + len = 0; + lines = 0; + page_height = terminal_height; + please_display_more_prompt &= ~1; + + if (input == 'q') + goto end; + } + + /* + * There are two input streams to worry about here: + * + * c : the character we are reading from the file being "mored" + * input : a character received from the keyboard + * + * If we hit a newline in the _file_ stream, we want to test and + * see if any characters have been hit in the _input_ stream. This + * allows the user to quit while in the middle of a file. + */ + if (c == '\n') { + /* increment by just one line if we are at + * the end of this line */ + if (input == '\n') + please_display_more_prompt |= 1; + /* Adjust the terminal height for any overlap, so that + * no lines get lost off the top. */ + if (len >= terminal_width) { + int quot, rem; + quot = len / terminal_width; + rem = len - (quot * terminal_width); + if (quot) { + if (rem) + page_height -= quot; + else + page_height -= (quot - 1); + } + } + if (++lines >= page_height) { + please_display_more_prompt |= 1; + } + len = 0; + } + /* + * If we just read a newline from the file being 'mored' and any + * key other than a return is hit, scroll by one page + */ + putc(c, stdout); + len++; + } + fclose(file); + fflush(stdout); + loop: + argv++; + } while (--argc > 0); + end: + return 0; +} diff --git a/util-linux/mount.c b/util-linux/mount.c new file mode 100644 index 000000000..f8ae1df19 --- /dev/null +++ b/util-linux/mount.c @@ -0,0 +1,1712 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini mount implementation for busybox + * + * Copyright (C) 1995, 1996 by Bruce Perens <bruce@pixar.com>. + * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> + * Copyright (C) 2005-2006 by Rob Landley <rob@landley.net> + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* Design notes: There is no spec for mount. Remind me to write one. + + mount_main() calls singlemount() which calls mount_it_now(). + + mount_main() can loop through /etc/fstab for mount -a + singlemount() can loop through /etc/filesystems for fstype detection. + mount_it_now() does the actual mount. +*/ + +#include "busybox.h" +#include <mntent.h> + +/* Needed for nfs support only... */ +#include <syslog.h> +#include <sys/utsname.h> +#undef TRUE +#undef FALSE +#include <rpc/rpc.h> +#include <rpc/pmap_prot.h> +#include <rpc/pmap_clnt.h> + + +// Not real flags, but we want to be able to check for this. +enum { + MOUNT_USERS = (1<<28)*ENABLE_DESKTOP, + MOUNT_NOAUTO = (1<<29), + MOUNT_SWAP = (1<<30), +}; +// TODO: more "user" flag compatibility. +// "user" option (from mount manpage): +// Only the user that mounted a filesystem can unmount it again. +// If any user should be able to unmount, then use users instead of user +// in the fstab line. The owner option is similar to the user option, +// with the restriction that the user must be the owner of the special file. +// This may be useful e.g. for /dev/fd if a login script makes +// the console user owner of this device. + +/* Standard mount options (from -o options or --options), with corresponding + * flags */ + +struct { + char *name; + long flags; +} static mount_options[] = { + // MS_FLAGS set a bit. ~MS_FLAGS disable that bit. 0 flags are NOPs. + + USE_FEATURE_MOUNT_LOOP( + {"loop", 0}, + ) + + USE_FEATURE_MOUNT_FSTAB( + {"defaults", 0}, + /* {"quiet", 0}, - do not filter out, vfat wants to see it */ + {"noauto", MOUNT_NOAUTO}, + {"swap", MOUNT_SWAP}, + USE_DESKTOP({"user", MOUNT_USERS},) + USE_DESKTOP({"users", MOUNT_USERS},) + ) + + USE_FEATURE_MOUNT_FLAGS( + // vfs flags + {"nosuid", MS_NOSUID}, + {"suid", ~MS_NOSUID}, + {"dev", ~MS_NODEV}, + {"nodev", MS_NODEV}, + {"exec", ~MS_NOEXEC}, + {"noexec", MS_NOEXEC}, + {"sync", MS_SYNCHRONOUS}, + {"async", ~MS_SYNCHRONOUS}, + {"atime", ~MS_NOATIME}, + {"noatime", MS_NOATIME}, + {"diratime", ~MS_NODIRATIME}, + {"nodiratime", MS_NODIRATIME}, + {"loud", ~MS_SILENT}, + + // action flags + + {"bind", MS_BIND}, + {"move", MS_MOVE}, + {"shared", MS_SHARED}, + {"slave", MS_SLAVE}, + {"private", MS_PRIVATE}, + {"unbindable", MS_UNBINDABLE}, + {"rshared", MS_SHARED|MS_RECURSIVE}, + {"rslave", MS_SLAVE|MS_RECURSIVE}, + {"rprivate", MS_SLAVE|MS_RECURSIVE}, + {"runbindable", MS_UNBINDABLE|MS_RECURSIVE}, + ) + + // Always understood. + + {"ro", MS_RDONLY}, // vfs flag + {"rw", ~MS_RDONLY}, // vfs flag + {"remount", MS_REMOUNT}, // action flag +}; + +#define VECTOR_SIZE(v) (sizeof(v) / sizeof((v)[0])) + +/* Append mount options to string */ +static void append_mount_options(char **oldopts, char *newopts) +{ + if (*oldopts && **oldopts) { + /* do not insert options which are already there */ + while (newopts[0]) { + char *p; + int len = strlen(newopts); + p = strchr(newopts, ','); + if (p) len = p - newopts; + p = *oldopts; + while (1) { + if (!strncmp(p, newopts, len) + && (p[len]==',' || p[len]==0)) + goto skip; + p = strchr(p,','); + if(!p) break; + p++; + } + p = xasprintf("%s,%.*s", *oldopts, len, newopts); + free(*oldopts); + *oldopts = p; +skip: + newopts += len; + while (newopts[0] == ',') newopts++; + } + } else { + if (ENABLE_FEATURE_CLEAN_UP) free(*oldopts); + *oldopts = xstrdup(newopts); + } +} + +/* Use the mount_options list to parse options into flags. + * Also return list of unrecognized options if unrecognized!=NULL */ +static int parse_mount_options(char *options, char **unrecognized) +{ + int flags = MS_SILENT; + + // Loop through options + for (;;) { + int i; + char *comma = strchr(options, ','); + + if (comma) *comma = 0; + + // Find this option in mount_options + for (i = 0; i < VECTOR_SIZE(mount_options); i++) { + if (!strcasecmp(mount_options[i].name, options)) { + long fl = mount_options[i].flags; + if (fl < 0) flags &= fl; + else flags |= fl; + break; + } + } + // If unrecognized not NULL, append unrecognized mount options */ + if (unrecognized && i == VECTOR_SIZE(mount_options)) { + // Add it to strflags, to pass on to kernel + i = *unrecognized ? strlen(*unrecognized) : 0; + *unrecognized = xrealloc(*unrecognized, i+strlen(options)+2); + + // Comma separated if it's not the first one + if (i) (*unrecognized)[i++] = ','; + strcpy((*unrecognized)+i, options); + } + + // Advance to next option, or finish + if (comma) { + *comma = ','; + options = ++comma; + } else break; + } + + return flags; +} + +// Return a list of all block device backed filesystems + +static llist_t *get_block_backed_filesystems(void) +{ + static const char *const filesystems[] = { + "/etc/filesystems", + "/proc/filesystems", + 0 + }; + char *fs, *buf; + llist_t *list = 0; + int i; + FILE *f; + + for (i = 0; filesystems[i]; i++) { + f = fopen(filesystems[i], "r"); + if (!f) continue; + + while ((buf = xmalloc_getline(f)) != 0) { + if (!strncmp(buf, "nodev", 5) && isspace(buf[5])) + continue; + fs = skip_whitespace(buf); + if (*fs=='#' || *fs=='*' || !*fs) continue; + + llist_add_to_end(&list, xstrdup(fs)); + free(buf); + } + if (ENABLE_FEATURE_CLEAN_UP) fclose(f); + } + + return list; +} + +llist_t *fslist = 0; + +#if ENABLE_FEATURE_CLEAN_UP +static void delete_block_backed_filesystems(void) +{ + llist_free(fslist, free); +} +#else +void delete_block_backed_filesystems(void); +#endif + +#if ENABLE_FEATURE_MTAB_SUPPORT +static int useMtab = 1; +static int fakeIt; +#else +#define useMtab 0 +#define fakeIt 0 +#endif + +// Perform actual mount of specific filesystem at specific location. +// NB: mp->xxx fields may be trashed on exit +static int mount_it_now(struct mntent *mp, int vfsflags, char *filteropts) +{ + int rc = 0; + + if (fakeIt) goto mtab; + + // Mount, with fallback to read-only if necessary. + + for (;;) { + rc = mount(mp->mnt_fsname, mp->mnt_dir, mp->mnt_type, + vfsflags, filteropts); + if (!rc || (vfsflags&MS_RDONLY) || (errno!=EACCES && errno!=EROFS)) + break; + bb_error_msg("%s is write-protected, mounting read-only", + mp->mnt_fsname); + vfsflags |= MS_RDONLY; + } + + // Abort entirely if permission denied. + + if (rc && errno == EPERM) + bb_error_msg_and_die(bb_msg_perm_denied_are_you_root); + + /* If the mount was successful, and we're maintaining an old-style + * mtab file by hand, add the new entry to it now. */ + mtab: + if (ENABLE_FEATURE_MTAB_SUPPORT && useMtab && !rc && !(vfsflags & MS_REMOUNT)) { + char *fsname; + FILE *mountTable = setmntent(bb_path_mtab_file, "a+"); + int i; + + if (!mountTable) { + bb_error_msg("no %s",bb_path_mtab_file); + goto ret; + } + + // Add vfs string flags + + for (i=0; mount_options[i].flags != MS_REMOUNT; i++) + if (mount_options[i].flags > 0 && (mount_options[i].flags & vfsflags)) + append_mount_options(&(mp->mnt_opts), mount_options[i].name); + + // Remove trailing / (if any) from directory we mounted on + + i = strlen(mp->mnt_dir) - 1; + if (i > 0 && mp->mnt_dir[i] == '/') mp->mnt_dir[i] = 0; + + // Convert to canonical pathnames as needed + + mp->mnt_dir = bb_simplify_path(mp->mnt_dir); + fsname = 0; + if (!mp->mnt_type || !*mp->mnt_type) { /* bind mount */ + mp->mnt_fsname = fsname = bb_simplify_path(mp->mnt_fsname); + mp->mnt_type = "bind"; + } + mp->mnt_freq = mp->mnt_passno = 0; + + // Write and close. + + addmntent(mountTable, mp); + endmntent(mountTable); + if (ENABLE_FEATURE_CLEAN_UP) { + free(mp->mnt_dir); + free(fsname); + } + } + ret: + return rc; +} + +#if ENABLE_FEATURE_MOUNT_NFS + +/* + * Linux NFS mount + * Copyright (C) 1993 Rick Sladkey <jrs@world.std.com> + * + * Licensed under GPLv2, see file LICENSE in this tarball for details. + * + * Wed Feb 8 12:51:48 1995, biro@yggdrasil.com (Ross Biro): allow all port + * numbers to be specified on the command line. + * + * Fri, 8 Mar 1996 18:01:39, Swen Thuemmler <swen@uni-paderborn.de>: + * Omit the call to connect() for Linux version 1.3.11 or later. + * + * Wed Oct 1 23:55:28 1997: Dick Streefland <dick_streefland@tasking.com> + * Implemented the "bg", "fg" and "retry" mount options for NFS. + * + * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@misiek.eu.org> + * - added Native Language Support + * + * Modified by Olaf Kirch and Trond Myklebust for new NFS code, + * plus NFSv3 stuff. + */ + +/* This is just a warning of a common mistake. Possibly this should be a + * uclibc faq entry rather than in busybox... */ +#if defined(__UCLIBC__) && ! defined(__UCLIBC_HAS_RPC__) +#error "You need to build uClibc with UCLIBC_HAS_RPC for NFS support." +#endif + +#define MOUNTPORT 635 +#define MNTPATHLEN 1024 +#define MNTNAMLEN 255 +#define FHSIZE 32 +#define FHSIZE3 64 + +typedef char fhandle[FHSIZE]; + +typedef struct { + unsigned int fhandle3_len; + char *fhandle3_val; +} fhandle3; + +enum mountstat3 { + MNT_OK = 0, + MNT3ERR_PERM = 1, + MNT3ERR_NOENT = 2, + MNT3ERR_IO = 5, + MNT3ERR_ACCES = 13, + MNT3ERR_NOTDIR = 20, + MNT3ERR_INVAL = 22, + MNT3ERR_NAMETOOLONG = 63, + MNT3ERR_NOTSUPP = 10004, + MNT3ERR_SERVERFAULT = 10006, +}; +typedef enum mountstat3 mountstat3; + +struct fhstatus { + unsigned int fhs_status; + union { + fhandle fhs_fhandle; + } fhstatus_u; +}; +typedef struct fhstatus fhstatus; + +struct mountres3_ok { + fhandle3 fhandle; + struct { + unsigned int auth_flavours_len; + char *auth_flavours_val; + } auth_flavours; +}; +typedef struct mountres3_ok mountres3_ok; + +struct mountres3 { + mountstat3 fhs_status; + union { + mountres3_ok mountinfo; + } mountres3_u; +}; +typedef struct mountres3 mountres3; + +typedef char *dirpath; + +typedef char *name; + +typedef struct mountbody *mountlist; + +struct mountbody { + name ml_hostname; + dirpath ml_directory; + mountlist ml_next; +}; +typedef struct mountbody mountbody; + +typedef struct groupnode *groups; + +struct groupnode { + name gr_name; + groups gr_next; +}; +typedef struct groupnode groupnode; + +typedef struct exportnode *exports; + +struct exportnode { + dirpath ex_dir; + groups ex_groups; + exports ex_next; +}; +typedef struct exportnode exportnode; + +struct ppathcnf { + int pc_link_max; + short pc_max_canon; + short pc_max_input; + short pc_name_max; + short pc_path_max; + short pc_pipe_buf; + u_char pc_vdisable; + char pc_xxx; + short pc_mask[2]; +}; +typedef struct ppathcnf ppathcnf; + +#define MOUNTPROG 100005 +#define MOUNTVERS 1 + +#define MOUNTPROC_NULL 0 +#define MOUNTPROC_MNT 1 +#define MOUNTPROC_DUMP 2 +#define MOUNTPROC_UMNT 3 +#define MOUNTPROC_UMNTALL 4 +#define MOUNTPROC_EXPORT 5 +#define MOUNTPROC_EXPORTALL 6 + +#define MOUNTVERS_POSIX 2 + +#define MOUNTPROC_PATHCONF 7 + +#define MOUNT_V3 3 + +#define MOUNTPROC3_NULL 0 +#define MOUNTPROC3_MNT 1 +#define MOUNTPROC3_DUMP 2 +#define MOUNTPROC3_UMNT 3 +#define MOUNTPROC3_UMNTALL 4 +#define MOUNTPROC3_EXPORT 5 + +enum { +#ifndef NFS_FHSIZE + NFS_FHSIZE = 32, +#endif +#ifndef NFS_PORT + NFS_PORT = 2049 +#endif +}; + +/* + * We want to be able to compile mount on old kernels in such a way + * that the binary will work well on more recent kernels. + * Thus, if necessary we teach nfsmount.c the structure of new fields + * that will come later. + * + * Moreover, the new kernel includes conflict with glibc includes + * so it is easiest to ignore the kernel altogether (at compile time). + */ + +struct nfs2_fh { + char data[32]; +}; +struct nfs3_fh { + unsigned short size; + unsigned char data[64]; +}; + +struct nfs_mount_data { + int version; /* 1 */ + int fd; /* 1 */ + struct nfs2_fh old_root; /* 1 */ + int flags; /* 1 */ + int rsize; /* 1 */ + int wsize; /* 1 */ + int timeo; /* 1 */ + int retrans; /* 1 */ + int acregmin; /* 1 */ + int acregmax; /* 1 */ + int acdirmin; /* 1 */ + int acdirmax; /* 1 */ + struct sockaddr_in addr; /* 1 */ + char hostname[256]; /* 1 */ + int namlen; /* 2 */ + unsigned int bsize; /* 3 */ + struct nfs3_fh root; /* 4 */ +}; + +/* bits in the flags field */ +enum { + NFS_MOUNT_SOFT = 0x0001, /* 1 */ + NFS_MOUNT_INTR = 0x0002, /* 1 */ + NFS_MOUNT_SECURE = 0x0004, /* 1 */ + NFS_MOUNT_POSIX = 0x0008, /* 1 */ + NFS_MOUNT_NOCTO = 0x0010, /* 1 */ + NFS_MOUNT_NOAC = 0x0020, /* 1 */ + NFS_MOUNT_TCP = 0x0040, /* 2 */ + NFS_MOUNT_VER3 = 0x0080, /* 3 */ + NFS_MOUNT_KERBEROS = 0x0100, /* 3 */ + NFS_MOUNT_NONLM = 0x0200 /* 3 */ +}; + + +/* + * We need to translate between nfs status return values and + * the local errno values which may not be the same. + * + * Andreas Schwab <schwab@LS5.informatik.uni-dortmund.de>: change errno: + * "after #include <errno.h> the symbol errno is reserved for any use, + * it cannot even be used as a struct tag or field name". + */ + +#ifndef EDQUOT +#define EDQUOT ENOSPC +#endif + +// Convert each NFSERR_BLAH into EBLAH + +static const struct { + int stat; + int errnum; +} nfs_errtbl[] = { + {0,0}, {1,EPERM}, {2,ENOENT}, {5,EIO}, {6,ENXIO}, {13,EACCES}, {17,EEXIST}, + {19,ENODEV}, {20,ENOTDIR}, {21,EISDIR}, {22,EINVAL}, {27,EFBIG}, + {28,ENOSPC}, {30,EROFS}, {63,ENAMETOOLONG}, {66,ENOTEMPTY}, {69,EDQUOT}, + {70,ESTALE}, {71,EREMOTE}, {-1,EIO} +}; + +static char *nfs_strerror(int status) +{ + int i; + static char buf[sizeof("unknown nfs status return value: ") + sizeof(int)*3]; + + for (i = 0; nfs_errtbl[i].stat != -1; i++) { + if (nfs_errtbl[i].stat == status) + return strerror(nfs_errtbl[i].errnum); + } + sprintf(buf, "unknown nfs status return value: %d", status); + return buf; +} + +static bool_t xdr_fhandle(XDR *xdrs, fhandle objp) +{ + if (!xdr_opaque(xdrs, objp, FHSIZE)) + return FALSE; + return TRUE; +} + +static bool_t xdr_fhstatus(XDR *xdrs, fhstatus *objp) +{ + if (!xdr_u_int(xdrs, &objp->fhs_status)) + return FALSE; + switch (objp->fhs_status) { + case 0: + if (!xdr_fhandle(xdrs, objp->fhstatus_u.fhs_fhandle)) + return FALSE; + break; + default: + break; + } + return TRUE; +} + +static bool_t xdr_dirpath(XDR *xdrs, dirpath *objp) +{ + if (!xdr_string(xdrs, objp, MNTPATHLEN)) + return FALSE; + return TRUE; +} + +static bool_t xdr_fhandle3(XDR *xdrs, fhandle3 *objp) +{ + if (!xdr_bytes(xdrs, (char **)&objp->fhandle3_val, (unsigned int *) &objp->fhandle3_len, FHSIZE3)) + return FALSE; + return TRUE; +} + +static bool_t xdr_mountres3_ok(XDR *xdrs, mountres3_ok *objp) +{ + if (!xdr_fhandle3(xdrs, &objp->fhandle)) + return FALSE; + if (!xdr_array(xdrs, &(objp->auth_flavours.auth_flavours_val), &(objp->auth_flavours.auth_flavours_len), ~0, + sizeof (int), (xdrproc_t) xdr_int)) + return FALSE; + return TRUE; +} + +static bool_t xdr_mountstat3(XDR *xdrs, mountstat3 *objp) +{ + if (!xdr_enum(xdrs, (enum_t *) objp)) + return FALSE; + return TRUE; +} + +static bool_t xdr_mountres3(XDR *xdrs, mountres3 *objp) +{ + if (!xdr_mountstat3(xdrs, &objp->fhs_status)) + return FALSE; + switch (objp->fhs_status) { + case MNT_OK: + if (!xdr_mountres3_ok(xdrs, &objp->mountres3_u.mountinfo)) + return FALSE; + break; + default: + break; + } + return TRUE; +} + +#define MAX_NFSPROT ((nfs_mount_version >= 4) ? 3 : 2) + +/* + * nfs_mount_version according to the sources seen at compile time. + */ +static int nfs_mount_version; +static int kernel_version; + +/* + * Unfortunately, the kernel prints annoying console messages + * in case of an unexpected nfs mount version (instead of + * just returning some error). Therefore we'll have to try + * and figure out what version the kernel expects. + * + * Variables: + * KERNEL_NFS_MOUNT_VERSION: kernel sources at compile time + * NFS_MOUNT_VERSION: these nfsmount sources at compile time + * nfs_mount_version: version this source and running kernel can handle + */ +static void +find_kernel_nfs_mount_version(void) +{ + if (kernel_version) + return; + + nfs_mount_version = 4; /* default */ + + kernel_version = get_linux_version_code(); + if (kernel_version) { + if (kernel_version < KERNEL_VERSION(2,1,32)) + nfs_mount_version = 1; + else if (kernel_version < KERNEL_VERSION(2,2,18) || + (kernel_version >= KERNEL_VERSION(2,3,0) && + kernel_version < KERNEL_VERSION(2,3,99))) + nfs_mount_version = 3; + /* else v4 since 2.3.99pre4 */ + } +} + +static struct pmap * +get_mountport(struct sockaddr_in *server_addr, + long unsigned prog, + long unsigned version, + long unsigned proto, + long unsigned port) +{ + struct pmaplist *pmap; + static struct pmap p = {0, 0, 0, 0}; + + server_addr->sin_port = PMAPPORT; + pmap = pmap_getmaps(server_addr); + + if (version > MAX_NFSPROT) + version = MAX_NFSPROT; + if (!prog) + prog = MOUNTPROG; + p.pm_prog = prog; + p.pm_vers = version; + p.pm_prot = proto; + p.pm_port = port; + + while (pmap) { + if (pmap->pml_map.pm_prog != prog) + goto next; + if (!version && p.pm_vers > pmap->pml_map.pm_vers) + goto next; + if (version > 2 && pmap->pml_map.pm_vers != version) + goto next; + if (version && version <= 2 && pmap->pml_map.pm_vers > 2) + goto next; + if (pmap->pml_map.pm_vers > MAX_NFSPROT || + (proto && p.pm_prot && pmap->pml_map.pm_prot != proto) || + (port && pmap->pml_map.pm_port != port)) + goto next; + memcpy(&p, &pmap->pml_map, sizeof(p)); +next: + pmap = pmap->pml_next; + } + if (!p.pm_vers) + p.pm_vers = MOUNTVERS; + if (!p.pm_port) + p.pm_port = MOUNTPORT; + if (!p.pm_prot) + p.pm_prot = IPPROTO_TCP; + return &p; +} + +static int daemonize(void) +{ + int fd; + int pid = fork(); + if (pid < 0) /* error */ + return -errno; + if (pid > 0) /* parent */ + return 0; + /* child */ + fd = xopen(bb_dev_null, O_RDWR); + dup2(fd, 0); + dup2(fd, 1); + dup2(fd, 2); + if (fd > 2) close(fd); + setsid(); + openlog(applet_name, LOG_PID, LOG_DAEMON); + logmode = LOGMODE_SYSLOG; + return 1; +} + +// TODO +static inline int we_saw_this_host_before(const char *hostname) +{ + return 0; +} + +/* RPC strerror analogs are terminally idiotic: + * *mandatory* prefix and \n at end. + * This hopefully helps. Usage: + * error_msg_rpc(clnt_*error*(" ")) */ +static void error_msg_rpc(const char *msg) +{ + int len; + while (msg[0] == ' ' || msg[0] == ':') msg++; + len = strlen(msg); + while (len && msg[len-1] == '\n') len--; + bb_error_msg("%.*s", len, msg); +} + +// NB: mp->xxx fields may be trashed on exit +static int nfsmount(struct mntent *mp, int vfsflags, char *filteropts) +{ + CLIENT *mclient; + char *hostname; + char *pathname; + char *mounthost; + struct nfs_mount_data data; + char *opt; + struct hostent *hp; + struct sockaddr_in server_addr; + struct sockaddr_in mount_server_addr; + int msock, fsock; + union { + struct fhstatus nfsv2; + struct mountres3 nfsv3; + } status; + int daemonized; + char *s; + int port; + int mountport; + int proto; + int bg; + int soft; + int intr; + int posix; + int nocto; + int noac; + int nolock; + int retry; + int tcp; + int mountprog; + int mountvers; + int nfsprog; + int nfsvers; + int retval; + + find_kernel_nfs_mount_version(); + + daemonized = 0; + mounthost = NULL; + retval = ETIMEDOUT; + msock = fsock = -1; + mclient = NULL; + + /* NB: hostname, mounthost, filteropts must be free()d prior to return */ + + filteropts = xstrdup(filteropts); /* going to trash it later... */ + + hostname = xstrdup(mp->mnt_fsname); + /* mount_main() guarantees that ':' is there */ + s = strchr(hostname, ':'); + pathname = s + 1; + *s = '\0'; + /* Ignore all but first hostname in replicated mounts + until they can be fully supported. (mack@sgi.com) */ + s = strchr(hostname, ','); + if (s) { + *s = '\0'; + bb_error_msg("warning: multiple hostnames not supported"); + } + + server_addr.sin_family = AF_INET; + if (!inet_aton(hostname, &server_addr.sin_addr)) { + hp = gethostbyname(hostname); + if (hp == NULL) { + bb_herror_msg("%s", hostname); + goto fail; + } + if (hp->h_length > sizeof(struct in_addr)) { + bb_error_msg("got bad hp->h_length"); + hp->h_length = sizeof(struct in_addr); + } + memcpy(&server_addr.sin_addr, + hp->h_addr, hp->h_length); + } + + memcpy(&mount_server_addr, &server_addr, sizeof(mount_server_addr)); + + /* add IP address to mtab options for use when unmounting */ + + if (!mp->mnt_opts) { /* TODO: actually mp->mnt_opts is never NULL */ + mp->mnt_opts = xasprintf("addr=%s", inet_ntoa(server_addr.sin_addr)); + } else { + char *tmp = xasprintf("%s%saddr=%s", mp->mnt_opts, + mp->mnt_opts[0] ? "," : "", + inet_ntoa(server_addr.sin_addr)); + free(mp->mnt_opts); + mp->mnt_opts = tmp; + } + + /* Set default options. + * rsize/wsize (and bsize, for ver >= 3) are left 0 in order to + * let the kernel decide. + * timeo is filled in after we know whether it'll be TCP or UDP. */ + memset(&data, 0, sizeof(data)); + data.retrans = 3; + data.acregmin = 3; + data.acregmax = 60; + data.acdirmin = 30; + data.acdirmax = 60; + data.namlen = NAME_MAX; + + bg = 0; + soft = 0; + intr = 0; + posix = 0; + nocto = 0; + nolock = 0; + noac = 0; + retry = 10000; /* 10000 minutes ~ 1 week */ + tcp = 0; + + mountprog = MOUNTPROG; + mountvers = 0; + port = 0; + mountport = 0; + nfsprog = 100003; + nfsvers = 0; + + /* parse options */ + + for (opt = strtok(filteropts, ","); opt; opt = strtok(NULL, ",")) { + char *opteq = strchr(opt, '='); + if (opteq) { + const char *const options[] = { + /* 0 */ "rsize", + /* 1 */ "wsize", + /* 2 */ "timeo", + /* 3 */ "retrans", + /* 4 */ "acregmin", + /* 5 */ "acregmax", + /* 6 */ "acdirmin", + /* 7 */ "acdirmax", + /* 8 */ "actimeo", + /* 9 */ "retry", + /* 10 */ "port", + /* 11 */ "mountport", + /* 12 */ "mounthost", + /* 13 */ "mountprog", + /* 14 */ "mountvers", + /* 15 */ "nfsprog", + /* 16 */ "nfsvers", + /* 17 */ "vers", + /* 18 */ "proto", + /* 19 */ "namlen", + /* 20 */ "addr", + NULL + }; + int val = xatoi_u(opteq + 1); + *opteq = '\0'; + switch (index_in_str_array(options, opt)) { + case 0: // "rsize" + data.rsize = val; + break; + case 1: // "wsize" + data.wsize = val; + break; + case 2: // "timeo" + data.timeo = val; + break; + case 3: // "retrans" + data.retrans = val; + break; + case 4: // "acregmin" + data.acregmin = val; + break; + case 5: // "acregmax" + data.acregmax = val; + break; + case 6: // "acdirmin" + data.acdirmin = val; + break; + case 7: // "acdirmax" + data.acdirmax = val; + break; + case 8: // "actimeo" + data.acregmin = val; + data.acregmax = val; + data.acdirmin = val; + data.acdirmax = val; + break; + case 9: // "retry" + retry = val; + break; + case 10: // "port" + port = val; + break; + case 11: // "mountport" + mountport = val; + break; + case 12: // "mounthost" + mounthost = xstrndup(opteq+1, + strcspn(opteq+1," \t\n\r,")); + break; + case 13: // "mountprog" + mountprog = val; + break; + case 14: // "mountvers" + mountvers = val; + break; + case 15: // "nfsprog" + nfsprog = val; + break; + case 16: // "nfsvers" + case 17: // "vers" + nfsvers = val; + break; + case 18: // "proto" + if (!strncmp(opteq+1, "tcp", 3)) + tcp = 1; + else if (!strncmp(opteq+1, "udp", 3)) + tcp = 0; + else + bb_error_msg("warning: unrecognized proto= option"); + break; + case 19: // "namlen" + if (nfs_mount_version >= 2) + data.namlen = val; + else + bb_error_msg("warning: option namlen is not supported\n"); + break; + case 20: // "addr" - ignore + break; + default: + bb_error_msg("unknown nfs mount parameter: %s=%d", opt, val); + goto fail; + } + } + else { + const char *const options[] = { + "bg", + "fg", + "soft", + "hard", + "intr", + "posix", + "cto", + "ac", + "tcp", + "udp", + "lock", + NULL + }; + int val = 1; + if (!strncmp(opt, "no", 2)) { + val = 0; + opt += 2; + } + switch (index_in_str_array(options, opt)) { + case 0: // "bg" + bg = val; + break; + case 1: // "fg" + bg = !val; + break; + case 2: // "soft" + soft = val; + break; + case 3: // "hard" + soft = !val; + break; + case 4: // "intr" + intr = val; + break; + case 5: // "posix" + posix = val; + break; + case 6: // "cto" + nocto = !val; + break; + case 7: // "ac" + noac = !val; + break; + case 8: // "tcp" + tcp = val; + break; + case 9: // "udp" + tcp = !val; + break; + case 10: // "lock" + if (nfs_mount_version >= 3) + nolock = !val; + else + bb_error_msg("warning: option nolock is not supported"); + break; + default: + bb_error_msg("unknown nfs mount option: %s%s", val ? "" : "no", opt); + goto fail; + } + } + } + proto = (tcp) ? IPPROTO_TCP : IPPROTO_UDP; + + data.flags = (soft ? NFS_MOUNT_SOFT : 0) + | (intr ? NFS_MOUNT_INTR : 0) + | (posix ? NFS_MOUNT_POSIX : 0) + | (nocto ? NFS_MOUNT_NOCTO : 0) + | (noac ? NFS_MOUNT_NOAC : 0); + if (nfs_mount_version >= 2) + data.flags |= (tcp ? NFS_MOUNT_TCP : 0); + if (nfs_mount_version >= 3) + data.flags |= (nolock ? NFS_MOUNT_NONLM : 0); + if (nfsvers > MAX_NFSPROT || mountvers > MAX_NFSPROT) { + bb_error_msg("NFSv%d not supported", nfsvers); + goto fail; + } + if (nfsvers && !mountvers) + mountvers = (nfsvers < 3) ? 1 : nfsvers; + if (nfsvers && nfsvers < mountvers) { + mountvers = nfsvers; + } + + /* Adjust options if none specified */ + if (!data.timeo) + data.timeo = tcp ? 70 : 7; + + data.version = nfs_mount_version; + + if (vfsflags & MS_REMOUNT) + goto do_mount; + + /* + * If the previous mount operation on the same host was + * backgrounded, and the "bg" for this mount is also set, + * give up immediately, to avoid the initial timeout. + */ + if (bg && we_saw_this_host_before(hostname)) { + daemonized = daemonize(); /* parent or error */ + if (daemonized <= 0) { /* parent or error */ + retval = -daemonized; + goto ret; + } + } + + /* create mount daemon client */ + /* See if the nfs host = mount host. */ + if (mounthost) { + if (mounthost[0] >= '0' && mounthost[0] <= '9') { + mount_server_addr.sin_family = AF_INET; + mount_server_addr.sin_addr.s_addr = inet_addr(hostname); + } else { + hp = gethostbyname(mounthost); + if (hp == NULL) { + bb_herror_msg("%s", mounthost); + goto fail; + } else { + if (hp->h_length > sizeof(struct in_addr)) { + bb_error_msg("got bad hp->h_length?"); + hp->h_length = sizeof(struct in_addr); + } + mount_server_addr.sin_family = AF_INET; + memcpy(&mount_server_addr.sin_addr, + hp->h_addr, hp->h_length); + } + } + } + + /* + * The following loop implements the mount retries. When the mount + * times out, and the "bg" option is set, we background ourself + * and continue trying. + * + * The case where the mount point is not present and the "bg" + * option is set, is treated as a timeout. This is done to + * support nested mounts. + * + * The "retry" count specified by the user is the number of + * minutes to retry before giving up. + */ + { + struct timeval total_timeout; + struct timeval retry_timeout; + struct pmap* pm_mnt; + time_t t; + time_t prevt; + time_t timeout; + + retry_timeout.tv_sec = 3; + retry_timeout.tv_usec = 0; + total_timeout.tv_sec = 20; + total_timeout.tv_usec = 0; + timeout = time(NULL) + 60 * retry; + prevt = 0; + t = 30; +retry: + /* be careful not to use too many CPU cycles */ + if (t - prevt < 30) + sleep(30); + + pm_mnt = get_mountport(&mount_server_addr, + mountprog, + mountvers, + proto, + mountport); + nfsvers = (pm_mnt->pm_vers < 2) ? 2 : pm_mnt->pm_vers; + + /* contact the mount daemon via TCP */ + mount_server_addr.sin_port = htons(pm_mnt->pm_port); + msock = RPC_ANYSOCK; + + switch (pm_mnt->pm_prot) { + case IPPROTO_UDP: + mclient = clntudp_create(&mount_server_addr, + pm_mnt->pm_prog, + pm_mnt->pm_vers, + retry_timeout, + &msock); + if (mclient) + break; + mount_server_addr.sin_port = htons(pm_mnt->pm_port); + msock = RPC_ANYSOCK; + case IPPROTO_TCP: + mclient = clnttcp_create(&mount_server_addr, + pm_mnt->pm_prog, + pm_mnt->pm_vers, + &msock, 0, 0); + break; + default: + mclient = 0; + } + if (!mclient) { + if (!daemonized && prevt == 0) + error_msg_rpc(clnt_spcreateerror(" ")); + } else { + enum clnt_stat clnt_stat; + /* try to mount hostname:pathname */ + mclient->cl_auth = authunix_create_default(); + + /* make pointers in xdr_mountres3 NULL so + * that xdr_array allocates memory for us + */ + memset(&status, 0, sizeof(status)); + + if (pm_mnt->pm_vers == 3) + clnt_stat = clnt_call(mclient, MOUNTPROC3_MNT, + (xdrproc_t) xdr_dirpath, + (caddr_t) &pathname, + (xdrproc_t) xdr_mountres3, + (caddr_t) &status, + total_timeout); + else + clnt_stat = clnt_call(mclient, MOUNTPROC_MNT, + (xdrproc_t) xdr_dirpath, + (caddr_t) &pathname, + (xdrproc_t) xdr_fhstatus, + (caddr_t) &status, + total_timeout); + + if (clnt_stat == RPC_SUCCESS) + goto prepare_kernel_data; /* we're done */ + if (errno != ECONNREFUSED) { + error_msg_rpc(clnt_sperror(mclient, " ")); + goto fail; /* don't retry */ + } + /* Connection refused */ + if (!daemonized && prevt == 0) /* print just once */ + error_msg_rpc(clnt_sperror(mclient, " ")); + auth_destroy(mclient->cl_auth); + clnt_destroy(mclient); + mclient = 0; + close(msock); + } + + /* Timeout. We are going to retry... maybe */ + + if (!bg) + goto fail; + if (!daemonized) { + daemonized = daemonize(); + if (daemonized <= 0) { /* parent or error */ + retval = -daemonized; + goto ret; + } + } + prevt = t; + t = time(NULL); + if (t >= timeout) + /* TODO error message */ + goto fail; + + goto retry; + } + +prepare_kernel_data: + + if (nfsvers == 2) { + if (status.nfsv2.fhs_status != 0) { + bb_error_msg("%s:%s failed, reason given by server: %s", + hostname, pathname, + nfs_strerror(status.nfsv2.fhs_status)); + goto fail; + } + memcpy(data.root.data, + (char *) status.nfsv2.fhstatus_u.fhs_fhandle, + NFS_FHSIZE); + data.root.size = NFS_FHSIZE; + memcpy(data.old_root.data, + (char *) status.nfsv2.fhstatus_u.fhs_fhandle, + NFS_FHSIZE); + } else { + fhandle3 *my_fhandle; + if (status.nfsv3.fhs_status != 0) { + bb_error_msg("%s:%s failed, reason given by server: %s", + hostname, pathname, + nfs_strerror(status.nfsv3.fhs_status)); + goto fail; + } + my_fhandle = &status.nfsv3.mountres3_u.mountinfo.fhandle; + memset(data.old_root.data, 0, NFS_FHSIZE); + memset(&data.root, 0, sizeof(data.root)); + data.root.size = my_fhandle->fhandle3_len; + memcpy(data.root.data, + (char *) my_fhandle->fhandle3_val, + my_fhandle->fhandle3_len); + + data.flags |= NFS_MOUNT_VER3; + } + + /* create nfs socket for kernel */ + + if (tcp) { + if (nfs_mount_version < 3) { + bb_error_msg("NFS over TCP is not supported"); + goto fail; + } + fsock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + } else + fsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (fsock < 0) { + bb_perror_msg("nfs socket"); + goto fail; + } + if (bindresvport(fsock, 0) < 0) { + bb_perror_msg("nfs bindresvport"); + goto fail; + } + if (port == 0) { + server_addr.sin_port = PMAPPORT; + port = pmap_getport(&server_addr, nfsprog, nfsvers, + tcp ? IPPROTO_TCP : IPPROTO_UDP); + if (port == 0) + port = NFS_PORT; + } + server_addr.sin_port = htons(port); + + /* prepare data structure for kernel */ + + data.fd = fsock; + memcpy((char *) &data.addr, (char *) &server_addr, sizeof(data.addr)); + strncpy(data.hostname, hostname, sizeof(data.hostname)); + + /* clean up */ + + auth_destroy(mclient->cl_auth); + clnt_destroy(mclient); + close(msock); + + if (bg) { + /* We must wait until mount directory is available */ + struct stat statbuf; + int delay = 1; + while (stat(mp->mnt_dir, &statbuf) == -1) { + if (!daemonized) { + daemonized = daemonize(); + if (daemonized <= 0) { /* parent or error */ + retval = -daemonized; + goto ret; + } + } + sleep(delay); /* 1, 2, 4, 8, 16, 30, ... */ + delay *= 2; + if (delay > 30) + delay = 30; + } + } + +do_mount: /* perform actual mount */ + + mp->mnt_type = "nfs"; + retval = mount_it_now(mp, vfsflags, (char*)&data); + goto ret; + +fail: /* abort */ + + if (msock != -1) { + if (mclient) { + auth_destroy(mclient->cl_auth); + clnt_destroy(mclient); + } + close(msock); + } + if (fsock != -1) + close(fsock); + +ret: + free(hostname); + free(mounthost); + free(filteropts); + return retval; +} + +#else /* !ENABLE_FEATURE_MOUNT_NFS */ + +/* Never called. Call should be optimized out. */ +int nfsmount(struct mntent *mp, int vfsflags, char *filteropts); + +#endif /* !ENABLE_FEATURE_MOUNT_NFS */ + +// Mount one directory. Handles CIFS, NFS, loopback, autobind, and filesystem +// type detection. Returns 0 for success, nonzero for failure. +// NB: mp->xxx fields may be trashed on exit +static int singlemount(struct mntent *mp, int ignore_busy) +{ + int rc = -1, vfsflags; + char *loopFile = 0, *filteropts = 0; + llist_t *fl = 0; + struct stat st; + + vfsflags = parse_mount_options(mp->mnt_opts, &filteropts); + + // Treat fstype "auto" as unspecified. + + if (mp->mnt_type && !strcmp(mp->mnt_type,"auto")) mp->mnt_type = 0; + + // Might this be an CIFS filesystem? + + if (ENABLE_FEATURE_MOUNT_CIFS && + (!mp->mnt_type || !strcmp(mp->mnt_type,"cifs")) && + (mp->mnt_fsname[0]==mp->mnt_fsname[1] && (mp->mnt_fsname[0]=='/' || mp->mnt_fsname[0]=='\\'))) + { + struct hostent *he; + char ip[32], *s; + + rc = 1; + // Replace '/' with '\' and verify that unc points to "//server/share". + + for (s = mp->mnt_fsname; *s; ++s) + if (*s == '/') *s = '\\'; + + // get server IP + + s = strrchr(mp->mnt_fsname, '\\'); + if (s == mp->mnt_fsname+1) goto report_error; + *s = 0; + he = gethostbyname(mp->mnt_fsname+2); + *s = '\\'; + if (!he) goto report_error; + + // Insert ip=... option into string flags. (NOTE: Add IPv6 support.) + + sprintf(ip, "ip=%d.%d.%d.%d", he->h_addr[0], he->h_addr[1], + he->h_addr[2], he->h_addr[3]); + parse_mount_options(ip, &filteropts); + + // compose new unc '\\server-ip\share' + + mp->mnt_fsname = xasprintf("\\\\%s%s", ip+3, + strchr(mp->mnt_fsname+2,'\\')); + + // lock is required + vfsflags |= MS_MANDLOCK; + + mp->mnt_type = "cifs"; + rc = mount_it_now(mp, vfsflags, filteropts); + if (ENABLE_FEATURE_CLEAN_UP) free(mp->mnt_fsname); + goto report_error; + } + + // Might this be an NFS filesystem? + + if (ENABLE_FEATURE_MOUNT_NFS && + (!mp->mnt_type || !strcmp(mp->mnt_type,"nfs")) && + strchr(mp->mnt_fsname, ':') != NULL) + { + rc = nfsmount(mp, vfsflags, filteropts); + goto report_error; + } + + // Look at the file. (Not found isn't a failure for remount, or for + // a synthetic filesystem like proc or sysfs.) + + if (!lstat(mp->mnt_fsname, &st) && !(vfsflags & (MS_REMOUNT | MS_BIND | MS_MOVE))) + { + // Do we need to allocate a loopback device for it? + + if (ENABLE_FEATURE_MOUNT_LOOP && S_ISREG(st.st_mode)) { + loopFile = bb_simplify_path(mp->mnt_fsname); + mp->mnt_fsname = 0; + switch (set_loop(&(mp->mnt_fsname), loopFile, 0)) { + case 0: + case 1: + break; + default: + bb_error_msg( errno == EPERM || errno == EACCES + ? bb_msg_perm_denied_are_you_root + : "cannot setup loop device"); + return errno; + } + + // Autodetect bind mounts + + } else if (S_ISDIR(st.st_mode) && !mp->mnt_type) + vfsflags |= MS_BIND; + } + + /* If we know the fstype (or don't need to), jump straight + * to the actual mount. */ + + if (mp->mnt_type || (vfsflags & (MS_REMOUNT | MS_BIND | MS_MOVE))) + rc = mount_it_now(mp, vfsflags, filteropts); + + // Loop through filesystem types until mount succeeds or we run out + + else { + + /* Initialize list of block backed filesystems. This has to be + * done here so that during "mount -a", mounts after /proc shows up + * can autodetect. */ + + if (!fslist) { + fslist = get_block_backed_filesystems(); + if (ENABLE_FEATURE_CLEAN_UP && fslist) + atexit(delete_block_backed_filesystems); + } + + for (fl = fslist; fl; fl = fl->link) { + mp->mnt_type = fl->data; + rc = mount_it_now(mp, vfsflags, filteropts); + if (!rc) break; + } + } + + // If mount failed, clean up loop file (if any). + + if (ENABLE_FEATURE_MOUNT_LOOP && rc && loopFile) { + del_loop(mp->mnt_fsname); + if (ENABLE_FEATURE_CLEAN_UP) { + free(loopFile); + free(mp->mnt_fsname); + } + } + +report_error: + if (ENABLE_FEATURE_CLEAN_UP) free(filteropts); + + if (rc && errno == EBUSY && ignore_busy) rc = 0; + if (rc < 0) + /* perror here sometimes says "mounting ... on ... failed: Success" */ + bb_error_msg("mounting %s on %s failed", mp->mnt_fsname, mp->mnt_dir); + + return rc; +} + +// Parse options, if necessary parse fstab/mtab, and call singlemount for +// each directory to be mounted. + +const char must_be_root[] = "you must be root"; + +int mount_main(int argc, char **argv) +{ + enum { OPT_ALL = 0x10 }; + + char *cmdopts = xstrdup(""), *fstype=0, *storage_path=0; + char *opt_o; + const char *fstabname; + FILE *fstab; + int i, j, rc = 0; + unsigned opt; + struct mntent mtpair[2], *mtcur = mtpair; + SKIP_DESKTOP(const int nonroot = 0;) + USE_DESKTOP( int nonroot = (getuid() != 0);) + + /* parse long options, like --bind and --move. Note that -o option + * and --option are synonymous. Yes, this means --remount,rw works. */ + + for (i = j = 0; i < argc; i++) { + if (argv[i][0] == '-' && argv[i][1] == '-') { + append_mount_options(&cmdopts, argv[i]+2); + } else argv[j++] = argv[i]; + } + argv[j] = 0; + argc = j; + + // Parse remaining options + + opt = getopt32(argc, argv, "o:t:rwanfvs", &opt_o, &fstype); + if (opt & 0x1) append_mount_options(&cmdopts, opt_o); // -o + //if (opt & 0x2) // -t + if (opt & 0x4) append_mount_options(&cmdopts, "ro"); // -r + if (opt & 0x8) append_mount_options(&cmdopts, "rw"); // -w + //if (opt & 0x10) // -a + if (opt & 0x20) USE_FEATURE_MTAB_SUPPORT(useMtab = 0); // -n + if (opt & 0x40) USE_FEATURE_MTAB_SUPPORT(fakeIt = 1); // -f + //if (opt & 0x80) // -v: verbose (ignore) + //if (opt & 0x100) // -s: sloppy (ignore) + argv += optind; + argc -= optind; + + // Three or more non-option arguments? Die with a usage message. + + if (argc > 2) bb_show_usage(); + + // If we have no arguments, show currently mounted filesystems + + if (!argc) { + if (!(opt & OPT_ALL)) { + FILE *mountTable = setmntent(bb_path_mtab_file, "r"); + + if (!mountTable) bb_error_msg_and_die("no %s", bb_path_mtab_file); + + while (getmntent_r(mountTable, mtpair, bb_common_bufsiz1, + sizeof(bb_common_bufsiz1))) + { + // Don't show rootfs. FIXME: why?? + if (!strcmp(mtpair->mnt_fsname, "rootfs")) continue; + + if (!fstype || !strcmp(mtpair->mnt_type, fstype)) + printf("%s on %s type %s (%s)\n", mtpair->mnt_fsname, + mtpair->mnt_dir, mtpair->mnt_type, + mtpair->mnt_opts); + } + if (ENABLE_FEATURE_CLEAN_UP) endmntent(mountTable); + return EXIT_SUCCESS; + } + } else storage_path = bb_simplify_path(argv[0]); + + // When we have two arguments, the second is the directory and we can + // skip looking at fstab entirely. We can always abspath() the directory + // argument when we get it. + + if (argc == 2) { + if (nonroot) + bb_error_msg_and_die(must_be_root); + mtpair->mnt_fsname = argv[0]; + mtpair->mnt_dir = argv[1]; + mtpair->mnt_type = fstype; + mtpair->mnt_opts = cmdopts; + rc = singlemount(mtpair, 0); + goto clean_up; + } + + i = parse_mount_options(cmdopts, 0); + if (nonroot && (i & ~MS_SILENT)) // Non-root users cannot specify flags + bb_error_msg_and_die(must_be_root); + + // If we have a shared subtree flag, don't worry about fstab or mtab. + + if (ENABLE_FEATURE_MOUNT_FLAGS && + (i & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))) + { + rc = mount("", argv[0], "", i, ""); + if (rc) bb_perror_msg_and_die("%s", argv[0]); + goto clean_up; + } + + // Open either fstab or mtab + + fstabname = "/etc/fstab"; + if (i & MS_REMOUNT) { + fstabname = bb_path_mtab_file; + } + fstab = setmntent(fstabname, "r"); + if (!fstab) + bb_perror_msg_and_die("cannot read %s", fstabname); + + // Loop through entries until we find what we're looking for. + + memset(mtpair, 0, sizeof(mtpair)); + for (;;) { + struct mntent *mtnext = (mtcur==mtpair ? mtpair+1 : mtpair); + + // Get next fstab entry + + if (!getmntent_r(fstab, mtcur, bb_common_bufsiz1 + + (mtcur==mtpair ? sizeof(bb_common_bufsiz1)/2 : 0), + sizeof(bb_common_bufsiz1)/2)) + { + // Were we looking for something specific? + + if (argc) { + + // If we didn't find anything, complain. + + if (!mtnext->mnt_fsname) + bb_error_msg_and_die("can't find %s in %s", + argv[0], fstabname); + + mtcur = mtnext; + if (nonroot) { + // fstab must have "users" or "user" + if (!(parse_mount_options(mtcur->mnt_opts, 0) & MOUNT_USERS)) + bb_error_msg_and_die(must_be_root); + } + + // Mount the last thing we found. + + mtcur->mnt_opts = xstrdup(mtcur->mnt_opts); + append_mount_options(&(mtcur->mnt_opts), cmdopts); + rc = singlemount(mtcur, 0); + free(mtcur->mnt_opts); + } + goto clean_up; + } + + /* If we're trying to mount something specific and this isn't it, + * skip it. Note we must match both the exact text in fstab (ala + * "proc") or a full path from root */ + + if (argc) { + + // Is this what we're looking for? + + if (strcmp(argv[0], mtcur->mnt_fsname) && + strcmp(storage_path, mtcur->mnt_fsname) && + strcmp(argv[0], mtcur->mnt_dir) && + strcmp(storage_path, mtcur->mnt_dir)) continue; + + // Remember this entry. Something later may have overmounted + // it, and we want the _last_ match. + + mtcur = mtnext; + + // If we're mounting all. + + } else { + // Do we need to match a filesystem type? + // TODO: support "-t type1,type2"; "-t notype1,type2" + + if (fstype && strcmp(mtcur->mnt_type, fstype)) continue; + + // Skip noauto and swap anyway. + + if (parse_mount_options(mtcur->mnt_opts, 0) + & (MOUNT_NOAUTO | MOUNT_SWAP)) continue; + + // No, mount -a won't mount anything, + // even user mounts, for mere humans. + + if (nonroot) + bb_error_msg_and_die(must_be_root); + + // Mount this thing. + + if (singlemount(mtcur, 1)) { + /* Count number of failed mounts */ + rc++; + } + } + } + if (ENABLE_FEATURE_CLEAN_UP) endmntent(fstab); + +clean_up: + + if (ENABLE_FEATURE_CLEAN_UP) { + free(storage_path); + free(cmdopts); + } + + return rc; +} diff --git a/util-linux/pivot_root.c b/util-linux/pivot_root.c new file mode 100644 index 000000000..bd02302c7 --- /dev/null +++ b/util-linux/pivot_root.c @@ -0,0 +1,24 @@ +/* vi: set sw=4 ts=4: */ +/* + * pivot_root.c - Change root file system. Based on util-linux 2.10s + * + * busyboxed by Evin Robertson + * pivot_root syscall stubbed by Erik Andersen, so it will compile + * regardless of the kernel being used. + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ +#include "busybox.h" + +extern int pivot_root(const char * new_root,const char * put_old); + +int pivot_root_main(int argc, char **argv) +{ + if (argc != 3) + bb_show_usage(); + + if (pivot_root(argv[1],argv[2]) < 0) + bb_perror_msg_and_die("pivot_root"); + + return EXIT_SUCCESS; +} diff --git a/util-linux/rdate.c b/util-linux/rdate.c new file mode 100644 index 000000000..12105953d --- /dev/null +++ b/util-linux/rdate.c @@ -0,0 +1,86 @@ +/* vi: set sw=4 ts=4: */ +/* + * The Rdate command will ask a time server for the RFC 868 time + * and optionally set the system time. + * + * by Sterling Huxley <sterling@europa.com> + * + * Licensed under GPL v2 or later, see file License for details. +*/ + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <stdlib.h> +#include <unistd.h> +#include <signal.h> + +#include "busybox.h" + + +static const int RFC_868_BIAS = 2208988800UL; + +static void socket_timeout(int sig) +{ + bb_error_msg_and_die("timeout connecting to time server"); +} + +static time_t askremotedate(const char *host) +{ + unsigned long nett; + struct sockaddr_in s_in; + int fd; + + bb_lookup_host(&s_in, host); + s_in.sin_port = bb_lookup_port("time", "tcp", 37); + + /* Add a timeout for dead or inaccessible servers */ + alarm(10); + signal(SIGALRM, socket_timeout); + + fd = xconnect_tcp_v4(&s_in); + + if (safe_read(fd, (void *)&nett, 4) != 4) /* read time from server */ + bb_error_msg_and_die("%s did not send the complete time", host); + close(fd); + + /* convert from network byte order to local byte order. + * RFC 868 time is the number of seconds + * since 00:00 (midnight) 1 January 1900 GMT + * the RFC 868 time 2,208,988,800 corresponds to 00:00 1 Jan 1970 GMT + * Subtract the RFC 868 time to get Linux epoch + */ + + return ntohl(nett) - RFC_868_BIAS; +} + +int rdate_main(int argc, char **argv) +{ + time_t remote_time; + unsigned long flags; + + opt_complementary = "-1"; + flags = getopt32(argc, argv, "sp"); + + remote_time = askremotedate(argv[optind]); + + if ((flags & 2) == 0) { + time_t current_time; + + time(¤t_time); + if (current_time == remote_time) + bb_error_msg("current time matches remote time"); + else + if (stime(&remote_time) < 0) + bb_perror_msg_and_die("cannot set time of day"); + } + + if ((flags & 1) == 0) + printf("%s", ctime(&remote_time)); + + return EXIT_SUCCESS; +} diff --git a/util-linux/readprofile.c b/util-linux/readprofile.c new file mode 100644 index 000000000..dd810f021 --- /dev/null +++ b/util-linux/readprofile.c @@ -0,0 +1,236 @@ +/* vi: set sw=4 ts=4: */ +/* + * readprofile.c - used to read /proc/profile + * + * Copyright (C) 1994,1996 Alessandro Rubini (rubini@ipvvis.unipv.it) + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + */ + +/* + * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL> + * - added Native Language Support + * 1999-09-01 Stephane Eranian <eranian@cello.hpl.hp.com> + * - 64bit clean patch + * 3Feb2001 Andrew Morton <andrewm@uow.edu.au> + * - -M option to write profile multiplier. + * 2001-11-07 Werner Almesberger <wa@almesberger.net> + * - byte order auto-detection and -n option + * 2001-11-09 Werner Almesberger <wa@almesberger.net> + * - skip step size (index 0) + * 2002-03-09 John Levon <moz@compsoc.man.ac.uk> + * - make maplineno do something + * 2002-11-28 Mads Martin Joergensen + + * - also try /boot/System.map-`uname -r` + * 2003-04-09 Werner Almesberger <wa@almesberger.net> + * - fixed off-by eight error and improved heuristics in byte order detection + * 2003-08-12 Nikita Danilov <Nikita@Namesys.COM> + * - added -s option; example of use: + * "readprofile -s -m /boot/System.map-test | grep __d_lookup | sort -n -k3" + * + * Taken from util-linux and adapted for busybox by + * Paul Mundt <lethal@linux-sh.org>. + */ + +#include "busybox.h" +#include <sys/utsname.h> + +#define S_LEN 128 + +/* These are the defaults */ +static const char defaultmap[] = "/boot/System.map"; +static const char defaultpro[] = "/proc/profile"; + +int readprofile_main(int argc, char **argv) +{ + FILE *map; + const char *mapFile, *proFile, *mult = 0; + unsigned long indx = 1; + size_t len; + uint64_t add0 = 0; + unsigned int step; + unsigned int *buf, total, fn_len; + unsigned long long fn_add, next_add; /* current and next address */ + char fn_name[S_LEN], next_name[S_LEN]; /* current and next name */ + char mapline[S_LEN]; + char mode[8]; + int optAll = 0, optInfo = 0, optReset = 0; + int optVerbose = 0, optNative = 0; + int optBins = 0, optSub = 0; + int maplineno = 1; + int header_printed; + +#define next (current^1) + + proFile = defaultpro; + mapFile = defaultmap; + + opt_complementary = "nn:aa:bb:ss:ii:rr:vv"; + getopt32(argc, argv, "M:m:p:nabsirv", + &mult, &mapFile, &proFile, + &optNative, &optAll, &optBins, &optSub, + &optInfo, &optReset, &optVerbose); + + if (optReset || mult) { + int multiplier, fd, to_write; + + /* + * When writing the multiplier, if the length of the write is + * not sizeof(int), the multiplier is not changed + */ + if (mult) { + multiplier = xatoi_u(mult); + to_write = sizeof(int); + } else { + multiplier = 0; + to_write = 1; /* sth different from sizeof(int) */ + } + + fd = xopen(defaultpro, O_WRONLY); + + if (full_write(fd, &multiplier, to_write) != to_write) + bb_perror_msg_and_die("error writing %s", defaultpro); + + close(fd); + return EXIT_SUCCESS; + } + + /* + * Use an fd for the profiling buffer, to skip stdio overhead + */ + len = INT_MAX; + buf = xmalloc_open_read_close(proFile, &len); + if (!optNative) { + int entries = len/sizeof(*buf); + int big = 0, small = 0, i; + unsigned *p; + + for (p = buf+1; p < buf+entries; p++) { + if (*p & ~0U << (sizeof(*buf)*4)) + big++; + if (*p & ((1 << (sizeof(*buf)*4))-1)) + small++; + } + if (big > small) { + bb_error_msg("assuming reversed byte order, " + "use -n to force native byte order"); + for (p = buf; p < buf+entries; p++) + for (i = 0; i < sizeof(*buf)/2; i++) { + unsigned char *b = (unsigned char *) p; + unsigned char tmp; + + tmp = b[i]; + b[i] = b[sizeof(*buf)-i-1]; + b[sizeof(*buf)-i-1] = tmp; + } + } + } + + step = buf[0]; + if (optInfo) { + printf("Sampling_step: %i\n", step); + return EXIT_SUCCESS; + } + + total = 0; + + map = xfopen(mapFile, "r"); + + while (fgets(mapline, S_LEN, map)) { + if (sscanf(mapline, "%llx %s %s", &fn_add, mode, fn_name) != 3) + bb_error_msg_and_die("%s(%i): wrong map line", + mapFile, maplineno); + + if (!strcmp(fn_name, "_stext")) /* only elf works like this */ { + add0 = fn_add; + break; + } + maplineno++; + } + + if (!add0) + bb_error_msg_and_die("can't find \"_stext\" in %s", mapFile); + + /* + * Main loop. + */ + while (fgets(mapline, S_LEN, map)) { + unsigned int this = 0; + + if (sscanf(mapline, "%llx %s %s", &next_add, mode, next_name) != 3) + bb_error_msg_and_die("%s(%i): wrong map line", + mapFile, maplineno); + + header_printed = 0; + + /* ignore any LEADING (before a '[tT]' symbol is found) + Absolute symbols */ + if ((*mode == 'A' || *mode == '?') && total == 0) continue; + if (*mode != 'T' && *mode != 't' && + *mode != 'W' && *mode != 'w') + break; /* only text is profiled */ + + if (indx >= len / sizeof(*buf)) + bb_error_msg_and_die("profile address out of range. " + "Wrong map file?"); + + while (indx < (next_add-add0)/step) { + if (optBins && (buf[indx] || optAll)) { + if (!header_printed) { + printf("%s:\n", fn_name); + header_printed = 1; + } + printf("\t%"PRIx64"\t%u\n", (indx - 1)*step + add0, buf[indx]); + } + this += buf[indx++]; + } + total += this; + + if (optBins) { + if (optVerbose || this > 0) + printf(" total\t\t\t\t%u\n", this); + } else if ((this || optAll) && + (fn_len = next_add-fn_add) != 0) { + if (optVerbose) + printf("%016llx %-40s %6i %8.4f\n", fn_add, + fn_name, this, this/(double)fn_len); + else + printf("%6i %-40s %8.4f\n", + this, fn_name, this/(double)fn_len); + if (optSub) { + unsigned long long scan; + + for (scan = (fn_add-add0)/step + 1; + scan < (next_add-add0)/step; scan++) { + unsigned long long addr; + + addr = (scan - 1)*step + add0; + printf("\t%#llx\t%s+%#llx\t%u\n", + addr, fn_name, addr - fn_add, + buf[scan]); + } + } + } + + fn_add = next_add; + strcpy(fn_name, next_name); + + maplineno++; + } + + /* clock ticks, out of kernel text - probably modules */ + printf("%6i %s\n", buf[len/sizeof(*buf)-1], "*unknown*"); + + /* trailer */ + if (optVerbose) + printf("%016x %-40s %6i %8.4f\n", + 0, "total", total, total/(double)(fn_add-add0)); + else + printf("%6i %-40s %8.4f\n", + total, "total", total/(double)(fn_add-add0)); + + fclose(map); + free(buf); + + return EXIT_SUCCESS; +} diff --git a/util-linux/setarch.c b/util-linux/setarch.c new file mode 100644 index 000000000..d7e1c0917 --- /dev/null +++ b/util-linux/setarch.c @@ -0,0 +1,52 @@ +/* vi: set sw=4 ts=4: */ +/* + * Linux32/linux64 allows for changing uname emulation. + * + * Copyright 2002 Andi Kleen, SuSE Labs. + * + * Licensed under GPL v2 or later, see file License for details. +*/ + +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> +#include <sys/personality.h> + +#include "busybox.h" + +int setarch_main(int ATTRIBUTE_UNUSED argc, char **argv) +{ + int pers = -1; + + /* Figure out what personality we are supposed to switch to ... + * we can be invoked as either: + * argv[0],argv[1] -> "setarch","personality" + * argv[0] -> "personality" + */ +retry: + if (argv[0][5] == '6') /* linux64 */ + pers = PER_LINUX; + else if (argv[0][5] == '3') /* linux32 */ + pers = PER_LINUX32; + else if (pers == -1 && argv[1] != NULL) { + pers = PER_LINUX32; + ++argv; + goto retry; + } + + /* make user actually gave us something to do */ + ++argv; + if (argv[0] == NULL) + bb_show_usage(); + + /* Try to set personality */ + if (personality(pers) >= 0) { + + /* Try to execute the program */ + execvp(argv[0], argv); + } + + bb_perror_msg_and_die("%s", argv[0]); +} diff --git a/util-linux/swaponoff.c b/util-linux/swaponoff.c new file mode 100644 index 000000000..8434d121c --- /dev/null +++ b/util-linux/swaponoff.c @@ -0,0 +1,77 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini swapon/swapoff implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> + * + * Licensed under the GPL version 2, see the file LICENSE in this tarball. + */ + +#include "busybox.h" +#include <mntent.h> +#include <sys/swap.h> + + +static int swap_enable_disable(char *device) +{ + int status; + struct stat st; + + xstat(device, &st); + + /* test for holes */ + if (S_ISREG(st.st_mode)) + if (st.st_blocks * 512 < st.st_size) + bb_error_msg_and_die("swap file has holes"); + + if (applet_name[5] == 'n') + status = swapon(device, 0); + else + status = swapoff(device); + + if (status != 0) { + bb_perror_msg("%s", device); + return 1; + } + + return 0; +} + +static int do_em_all(void) +{ + struct mntent *m; + FILE *f; + int err; + + f = setmntent("/etc/fstab", "r"); + if (f == NULL) + bb_perror_msg_and_die("/etc/fstab"); + + err = 0; + while ((m = getmntent(f)) != NULL) + if (strcmp(m->mnt_type, MNTTYPE_SWAP) == 0) + err += swap_enable_disable(m->mnt_fsname); + + endmntent(f); + + return err; +} + +#define DO_ALL 0x01 + +int swap_on_off_main(int argc, char **argv) +{ + int ret; + + if (argc == 1) + bb_show_usage(); + + ret = getopt32(argc, argv, "a"); + if (ret & DO_ALL) + return do_em_all(); + + ret = 0; + while (*++argv) + ret += swap_enable_disable(*argv); + return ret; +} diff --git a/util-linux/switch_root.c b/util-linux/switch_root.c new file mode 100644 index 000000000..4c23f69da --- /dev/null +++ b/util-linux/switch_root.c @@ -0,0 +1,122 @@ +/* vi: set sw=4 ts=4: */ +/* Copyright 2005 Rob Landley <rob@landley.net> + * + * Switch from rootfs to another filesystem as the root of the mount tree. + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include <sys/vfs.h> + + +// Make up for header deficiencies. + +#ifndef RAMFS_MAGIC +#define RAMFS_MAGIC 0x858458f6 +#endif + +#ifndef TMPFS_MAGIC +#define TMPFS_MAGIC 0x01021994 +#endif + +#ifndef MS_MOVE +#define MS_MOVE 8192 +#endif + +dev_t rootdev; + +// Recursively delete contents of rootfs. + +static void delete_contents(char *directory) +{ + DIR *dir; + struct dirent *d; + struct stat st; + + // Don't descend into other filesystems + if (lstat(directory, &st) || st.st_dev != rootdev) return; + + // Recursively delete the contents of directories. + if (S_ISDIR(st.st_mode)) { + if((dir = opendir(directory))) { + while ((d = readdir(dir))) { + char *newdir=d->d_name; + + // Skip . and .. + if(*newdir=='.' && (!newdir[1] || (newdir[1]=='.' && !newdir[2]))) + continue; + + // Recurse to delete contents + newdir = alloca(strlen(directory) + strlen(d->d_name) + 2); + sprintf(newdir, "%s/%s", directory, d->d_name); + delete_contents(newdir); + } + closedir(dir); + + // Directory should now be empty. Zap it. + rmdir(directory); + } + + // It wasn't a directory. Zap it. + + } else unlink(directory); +} + +int switch_root_main(int argc, char *argv[]) +{ + char *newroot, *console=NULL; + struct stat st1, st2; + struct statfs stfs; + + // Parse args (-c console) + + opt_complementary = "-2"; + getopt32(argc, argv, "c:", &console); + + // Change to new root directory and verify it's a different fs. + + newroot=argv[optind++]; + + if (chdir(newroot) || lstat(".", &st1) || lstat("/", &st2) || + st1.st_dev == st2.st_dev) + { + bb_error_msg_and_die("bad newroot %s", newroot); + } + rootdev=st2.st_dev; + + // Additional sanity checks: we're about to rm -rf /, so be REALLY SURE + // we mean it. (I could make this a CONFIG option, but I would get email + // from all the people who WILL eat their filesystemss.) + + if (lstat("/init", &st1) || !S_ISREG(st1.st_mode) || statfs("/", &stfs) || + (stfs.f_type != RAMFS_MAGIC && stfs.f_type != TMPFS_MAGIC) || + getpid() != 1) + { + bb_error_msg_and_die("not rootfs"); + } + + // Zap everything out of rootdev + + delete_contents("/"); + + // Overmount / with newdir and chroot into it. The chdir is needed to + // recalculate "." and ".." links. + + if (mount(".", "/", NULL, MS_MOVE, NULL) || chroot(".") || chdir("/")) + bb_error_msg_and_die("moving root"); + + // If a new console specified, redirect stdin/stdout/stderr to that. + + if (console) { + close(0); + if(open(console, O_RDWR) < 0) + bb_error_msg_and_die("bad console '%s'", console); + dup2(0, 1); + dup2(0, 2); + } + + // Exec real init. (This is why we must be pid 1.) + execv(argv[optind], argv+optind); + bb_error_msg_and_die("bad init '%s'", argv[optind]); +} diff --git a/util-linux/umount.c b/util-linux/umount.c new file mode 100644 index 000000000..6ba72aed1 --- /dev/null +++ b/util-linux/umount.c @@ -0,0 +1,151 @@ +/* vi: set sw=4 ts=4: */ +/* + * Mini umount implementation for busybox + * + * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org> + * Copyright (C) 2005 by Rob Landley <rob@landley.net> + * + * Licensed under GPL version 2, see file LICENSE in this tarball for details. + */ + +#include "busybox.h" +#include <mntent.h> +#include <getopt.h> + +#define OPTION_STRING "flDnravd" +#define OPT_FORCE 1 +#define OPT_LAZY 2 +#define OPT_DONTFREELOOP 4 +#define OPT_NO_MTAB 8 +#define OPT_REMOUNT 16 +#define OPT_ALL (ENABLE_FEATURE_UMOUNT_ALL ? 32 : 0) + +int umount_main(int argc, char **argv) +{ + int doForce; + char path[2*PATH_MAX]; + struct mntent me; + FILE *fp; + int status = EXIT_SUCCESS; + unsigned opt; + struct mtab_list { + char *dir; + char *device; + struct mtab_list *next; + } *mtl, *m; + + /* Parse any options */ + + opt = getopt32(argc, argv, OPTION_STRING); + + argc -= optind; + argv += optind; + + doForce = MAX((opt & OPT_FORCE), (opt & OPT_LAZY)); + + /* Get a list of mount points from mtab. We read them all in now mostly + * for umount -a (so we don't have to worry about the list changing while + * we iterate over it, or about getting stuck in a loop on the same failing + * entry. Notice that this also naturally reverses the list so that -a + * umounts the most recent entries first. */ + + m = mtl = 0; + + /* If we're umounting all, then m points to the start of the list and + * the argument list should be empty (which will match all). */ + + fp = setmntent(bb_path_mtab_file, "r"); + if (!fp) { + if (opt & OPT_ALL) + bb_error_msg_and_die("cannot open %s", bb_path_mtab_file); + } else { + while (getmntent_r(fp, &me, path, sizeof(path))) { + m = xmalloc(sizeof(struct mtab_list)); + m->next = mtl; + m->device = xstrdup(me.mnt_fsname); + m->dir = xstrdup(me.mnt_dir); + mtl = m; + } + endmntent(fp); + } + + /* If we're not umounting all, we need at least one argument. */ + if (!(opt & OPT_ALL)) { + m = 0; + if (!argc) bb_show_usage(); + } + + // Loop through everything we're supposed to umount, and do so. + for (;;) { + int curstat; + char *zapit = *argv; + + // Do we already know what to umount this time through the loop? + if (m) safe_strncpy(path, m->dir, PATH_MAX); + // For umount -a, end of mtab means time to exit. + else if (opt & OPT_ALL) break; + // Get next command line argument (and look it up in mtab list) + else if (!argc--) break; + else { + argv++; + realpath(zapit, path); + for (m = mtl; m; m = m->next) + if (!strcmp(path, m->dir) || !strcmp(path, m->device)) + break; + } + // If we couldn't find this sucker in /etc/mtab, punt by passing our + // command line argument straight to the umount syscall. Otherwise, + // umount the directory even if we were given the block device. + if (m) zapit = m->dir; + + // Let's ask the thing nicely to unmount. + curstat = umount(zapit); + + // Force the unmount, if necessary. + if (curstat && doForce) { + curstat = umount2(zapit, doForce); + if (curstat) + bb_error_msg("forced umount of %s failed!", zapit); + } + + // If still can't umount, maybe remount read-only? + if (curstat && (opt & OPT_REMOUNT) && errno == EBUSY && m) { + curstat = mount(m->device, zapit, NULL, MS_REMOUNT|MS_RDONLY, NULL); + bb_error_msg(curstat ? "cannot remount %s read-only" : + "%s busy - remounted read-only", m->device); + } + + if (curstat) { + status = EXIT_FAILURE; + bb_perror_msg("cannot umount %s", zapit); + } else { + /* De-allocate the loop device. This ioctl should be ignored on + * any non-loop block devices. */ + if (ENABLE_FEATURE_MOUNT_LOOP && !(opt & OPT_DONTFREELOOP) && m) + del_loop(m->device); + if (ENABLE_FEATURE_MTAB_SUPPORT && !(opt & OPT_NO_MTAB) && m) + erase_mtab(m->dir); + } + + // Find next matching mtab entry for -a or umount /dev + // Note this means that "umount /dev/blah" will unmount all instances + // of /dev/blah, not just the most recent. + while (m && (m = m->next)) + if ((opt & OPT_ALL) || !strcmp(path, m->device)) + break; + } + + // Free mtab list if necessary + + if (ENABLE_FEATURE_CLEAN_UP) { + while (mtl) { + m = mtl->next; + free(mtl->device); + free(mtl->dir); + free(mtl); + mtl = m; + } + } + + return status; +}